Replace cmer_horse with whinny mod @ v1.0...
Release: https://github.com/AntumMT/mod-whinny/releases/tag/v1.0master
|
@ -94,7 +94,6 @@ The game includes the mods from the default [minetest_game](https://github.com/m
|
|||
* [dungeon_master][mobs_monster] ([MIT][lic.dungeon_master] -- version: [00c890f Git][ver.dungeon_master] *2021-05-20*
|
||||
* [folks][] ([GPL][lic.gpl3.0]) -- version: [0.2.0][ver.folks] *2021-02-23*
|
||||
* [ghost][creatures] ([Zlib][lic.creatures] / [CC BY-SA][lic.ccbysa3.0]) -- version: [c09aacb Git][ver.ghost] *2021-05-19*
|
||||
* [horse][] ([MIT][lic.horse]) -- version: [3240dde Git][ver.horse] *2021-05-19*
|
||||
* [kitten][mobs_animal] ([MIT][lic.mobs_kitten]) -- version: [34fac03 Git][ver.mobs_kitten] *2021-05-20*
|
||||
* [mese_monster][mobs_monster] ([MIT][lic.mese_monster]) -- version: [cd02839 Git][ver.mese_monster] *2021-05-20*
|
||||
* [oerkki][creatures] ([Zlib][lic.creatures] / [CC BY-SA][lic.ccbysa3.0]) -- version: [70410fe Git][ver.oerkki] *2021-05-05*
|
||||
|
@ -107,6 +106,7 @@ The game includes the mods from the default [minetest_game](https://github.com/m
|
|||
* [stone_monster][mobs_monster] ([MIT][lic.stone_monster]) -- version: [fa52f6f Git][ver.stone_monster] *2021-05-20*
|
||||
* [tree_monster][mobs_monster] ([MIT][lic.tree_monster]) -- version: [f2977ff Git][ver.tree_monster] *2021-05-20*
|
||||
* velociraptor ([LGPL][lic.lgpl2.1] / [CC BY-SA][lic.ccbysa3.0]) -- forked from *mobs_farlands* (part of [farlands][] sub-game)
|
||||
* [whinny][] ([MIT][lic.whinny]) -- version: [1.0][ver.whinny] *2021-05-21*
|
||||
* [zombie][creatures] ([Zlib][lic.creatures] / [CC BY-SA][lic.ccbysa3.0]) -- version: [4edb18e Git][ver.zombie] *2021-05-05*
|
||||
* player/action/
|
||||
* [hbsprint][] ([LGPL][lic.lgpl2.1]) -- version: [f566d0f Git][ver.hbsprint] *2021-02-18* ([patched][patch.hbsprint])
|
||||
|
@ -310,7 +310,6 @@ The game includes the mods from the default [minetest_game](https://github.com/m
|
|||
[home_gui]: http://cornernote.github.io/minetest-home_gui/
|
||||
[home_workshop]: https://gitlab.com/VanessaE/home_workshop_modpack
|
||||
[homedecor]: https://forum.minetest.net/viewtopic.php?t=2041
|
||||
[horse]: https://github.com/AntumMT/mod-horse
|
||||
[hot_air_balloons]: https://forum.minetest.net/viewtopic.php?t=22367
|
||||
[hovercraft]: https://forum.minetest.net/viewtopic.php?t=6722
|
||||
[hudbars]: https://forum.minetest.net/viewtopic.php?t=11153
|
||||
|
@ -380,6 +379,7 @@ The game includes the mods from the default [minetest_game](https://github.com/m
|
|||
[wardrobe]: https://github.com/AntumMT/mod-wardrobe
|
||||
[wardrobe.old]: https://forum.minetest.net/viewtopic.php?t=9680
|
||||
[weather]: https://forum.minetest.net/viewtopic.php?t=5245
|
||||
[whinny]: https://github.com/AntumMT/mod-whinny
|
||||
[whitelist]: https://forum.minetest.net/viewtopic.php?t=8434
|
||||
[windmill]: https://forum.minetest.net/viewtopic.php?id=7440
|
||||
[workbench]: https://forum.minetest.net/viewtopic.php?t=14085
|
||||
|
@ -418,7 +418,6 @@ The game includes the mods from the default [minetest_game](https://github.com/m
|
|||
[lic.fort_spikes]: mods/buildings/fort_spikes/LICENSE
|
||||
[lic.headanim]: mods/player/visuals/headanim/LICENSE
|
||||
[lic.home_gui]: mods/ui/home_gui/LICENSE
|
||||
[lic.horse]: mods/mobiles/horse/LICENSE.txt
|
||||
[lic.hidename]: mods/antum/hidename/LICENSE.txt
|
||||
[lic.homedecor]: mods/modpacks/homedecor/LICENSE
|
||||
[lic.hovercraft]: mods/transport/hovercraft/LICENSE.txt
|
||||
|
@ -459,6 +458,7 @@ The game includes the mods from the default [minetest_game](https://github.com/m
|
|||
[lic.waffles]: mods/furniture/waffles/LICENSE.txt
|
||||
[lic.walking_light]: mods/lighting/walking_light/README.md
|
||||
[lic.wardrobe]: mods/player/visuals/wardrobe/LICENSE.txt
|
||||
[lic.whinny]: mods/mobiles/whinny/LICENSE.txt
|
||||
[lic.windmill]: mods/buildings/windmill/README.md
|
||||
|
||||
[lic.cc0]: doc/licenses/CC0.txt
|
||||
|
@ -552,7 +552,6 @@ The game includes the mods from the default [minetest_game](https://github.com/m
|
|||
[ver.home_gui]: https://github.com/cornernote/minetest-home_gui/tree/a291a09
|
||||
[ver.home_workshop]: https://gitlab.com/VanessaE/home_workshop_modpack/tree/03325e8
|
||||
[ver.homedecor]: https://gitlab.com/VanessaE/homedecor_modpack/tree/f1dc68a
|
||||
[ver.horse]: https://github.com/AntumMT/mod-horse/tree/3240dde
|
||||
[ver.hot_air_balloons]: https://notabug.org/NetherEran/hot_air_balloons/src/39a3572ad1bf7fd800525d68b128981e1b2c37d1
|
||||
[ver.hovercraft]: https://github.com/stujones11/hovercraft/tree/4d50e68
|
||||
[ver.hudbars]: http://repo.or.cz/minetest_hudbars.git/tree/0684bac
|
||||
|
@ -620,6 +619,7 @@ The game includes the mods from the default [minetest_game](https://github.com/m
|
|||
[ver.waffles]: https://github.com/GreenXenith/waffles/tree/15bcdce
|
||||
[ver.walking_light]: https://github.com/petermaloney/walking_light/tree/766ef0f
|
||||
[ver.wardrobe]: https://github.com/AntumMT/mod-wardrobe/releases/tag/v1.3
|
||||
[ver.whinny]: https://github.com/AntumMT/mod-whinny/releases/tag/v1.0
|
||||
[ver.whitelist]: https://github.com/AntumMT/mod-whitelist/tree/b813b19
|
||||
[ver.windmill]: https://github.com/Sokomine/windmill/tree/47b029d
|
||||
[ver.workbench]: https://github.com/minetest-mods/workbench/tree/bd14f59
|
||||
|
|
|
@ -2018,22 +2018,38 @@ coloredwood_enable_stairsplus = true
|
|||
#sneeker.spawn_maxlight = 5
|
||||
|
||||
|
||||
# *** horse ***
|
||||
# *** whinny ***
|
||||
|
||||
## Entity lifespan.
|
||||
# type: int
|
||||
# default: 300
|
||||
#horse.lifetime = 300
|
||||
## If enabled, messages all players when spawned.
|
||||
# type: bool
|
||||
# default: false
|
||||
#display_mob_spawn = false
|
||||
|
||||
## Spawn interval in seconds.
|
||||
# type: int
|
||||
# default: 600 (10 minutes)
|
||||
#horse.spawn_interval = 600
|
||||
## If disabled, whinny will attack players.
|
||||
# type: bool
|
||||
# default: true
|
||||
#whinny.peaceful_only = true
|
||||
|
||||
## Chance of spawn at interval.
|
||||
## Inverted chance that entity will spawn.
|
||||
# type: int
|
||||
# default: 9000
|
||||
#horse.spawn_chance = 9000
|
||||
# min: 0
|
||||
# default: 50000
|
||||
#whinny.spawn_chance = 50000
|
||||
|
||||
## Minimum height at which entity can spawn.
|
||||
# type: int
|
||||
# default: -500
|
||||
#whinny.spawn_height_min = -500
|
||||
|
||||
## Maximum height at which entity can spawn.
|
||||
# type: int
|
||||
# default: 500
|
||||
#whinny.spawn_height_max = 500
|
||||
|
||||
## Use mouse to control direction instead of a/d keys.
|
||||
# type: bool
|
||||
# default: true
|
||||
#whinny.enable_mouse_ctrl = true
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
|
||||
next
|
||||
- fixed apple eating sound so only nearby players hear it
|
|
@ -1,33 +0,0 @@
|
|||
## Horse Mob for [Minetest](http://minetest.net/)
|
||||
|
||||
---
|
||||
### Description:
|
||||
|
||||
A ridable horse forked from [KPGMobs][kpgmobs] originally by PilzAdam & KrupnovPavel. It has been ported to use the [Creatures MOB-Framework API][git.creatures].
|
||||
|
||||
---
|
||||
### Licensing:
|
||||
|
||||
- Code & models: [MIT](LICENSE.txt) (models by Krupnov Pavel)
|
||||
- Sounds: see [sources.txt](sounds/sources.txt)
|
||||
|
||||
---
|
||||
### Dependencies:
|
||||
|
||||
- Required:
|
||||
- [default][git.default]
|
||||
- [creatures][git.creatures]
|
||||
- Optional:
|
||||
- none
|
||||
|
||||
---
|
||||
### Links:
|
||||
|
||||
- [Original KPGMobs forum thread][kpgmobs]
|
||||
- [Git repo](https://github.com/AntumMT/mod-horse)
|
||||
|
||||
|
||||
[kpgmobs]: https://forum.minetest.net/viewtopic.php?t=8798
|
||||
|
||||
[git.creatures]: https://github.com/AntumMT/mod-creatures
|
||||
[git.default]: https://github.com/minetest/minetest_game/tree/master/mods/default
|
|
@ -1,5 +0,0 @@
|
|||
|
||||
TODO:
|
||||
- fix walking so player can control when riding
|
||||
- prevent owned horses from jumping fences to escape corrals
|
||||
- don't let horses "swim" in air
|
|
@ -1,391 +0,0 @@
|
|||
|
||||
cmer_horse = {}
|
||||
cmer_horse.modname = core.get_current_modname()
|
||||
cmer_horse.modpath = core.get_modpath(cmer_horse.modname)
|
||||
|
||||
dofile(cmer_horse.modpath .. "/settings.lua")
|
||||
|
||||
|
||||
--HORSE go go goooo :)
|
||||
local horse = {
|
||||
physical = true,
|
||||
collisionbox = {-0.4, -0.01, -0.4, 0.4, 1, 0.4},
|
||||
visual = "mesh",
|
||||
stepheight = 1.1,
|
||||
visual_size = {x=1,y=1},
|
||||
mesh = "mobs_horseh1.x",
|
||||
textures = {"mobs_horseh1.png"},
|
||||
|
||||
driver = nil,
|
||||
v = 0,
|
||||
}
|
||||
|
||||
|
||||
local function is_ground(pos)
|
||||
local nn = minetest.get_node(pos).name
|
||||
return minetest.get_item_group(nn, "crumbly") ~= 0
|
||||
end
|
||||
|
||||
local function get_sign(i)
|
||||
-- FIXME:
|
||||
if i == nil then i = 0 end
|
||||
|
||||
if i == 0 then
|
||||
return 0
|
||||
else
|
||||
return i/math.abs(i)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_velocity(v, yaw, y)
|
||||
local x = math.cos(yaw)*v
|
||||
local z = math.sin(yaw)*v
|
||||
return {x=x, y=y, z=z}
|
||||
end
|
||||
|
||||
local function get_v(v)
|
||||
return math.sqrt(v.x^2+v.z^2)
|
||||
end
|
||||
|
||||
|
||||
function horse:on_rightclick(clicker)
|
||||
if not clicker or not clicker:is_player() then
|
||||
return
|
||||
end
|
||||
|
||||
local pname = clicker:get_player_name()
|
||||
|
||||
if not self.owner then
|
||||
local wielded = clicker:get_wielded_item()
|
||||
-- FIXME: should be done with left click/punch
|
||||
if wielded and wielded:get_name() == "default:apple" then
|
||||
wielded:set_count(wielded:get_count()-1)
|
||||
clicker:set_wielded_item(wielded)
|
||||
core.sound_play("creatures_apple_bite", {object=self.object})
|
||||
|
||||
if self._appetite == nil then
|
||||
self._appetite = 1
|
||||
else
|
||||
self._appetite = self._appetite + 1
|
||||
|
||||
if self._appetite == 10 then
|
||||
self.owner = pname
|
||||
self._appetite = nil
|
||||
core.chat_send_player(pname, "You now own this horse!")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
core.chat_send_player(pname, "This horse is still hungry. Keep feeding it.")
|
||||
return
|
||||
else
|
||||
-- can't ride wild horses
|
||||
core.chat_send_player(pname, "This horse is too wild to ride. Try feeding it some apples.")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- only owner can ride
|
||||
if self.owner and pname ~= self.owner then
|
||||
core.chat_send_player(pname, "This horse is owned by " .. self.owner)
|
||||
return
|
||||
end
|
||||
|
||||
if self.driver and clicker == self.driver then
|
||||
self.driver = nil
|
||||
clicker:set_detach()
|
||||
elseif not self.driver then
|
||||
self.driver = clicker
|
||||
clicker:set_attach(self.object, "", {x=0,y=5,z=0}, {x=0,y=0,z=0})
|
||||
self.object:set_yaw(clicker:get_look_horizontal())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function horse:on_activate(staticdata, dtime_s)
|
||||
--self.object:set_armor_groups({immortal=1})
|
||||
if staticdata then
|
||||
self.v = tonumber(staticdata)
|
||||
end
|
||||
end
|
||||
|
||||
function horse:get_staticdata()
|
||||
return tostring(self.v)
|
||||
end
|
||||
|
||||
function horse:on_punch(puncher, time_from_last_punch, tool_capabilities, dir, damage)
|
||||
if puncher and puncher:is_player() then
|
||||
local pname = puncher:get_player_name()
|
||||
local owner = self.owner
|
||||
|
||||
-- don't allow owned horses to be killed or owned by other players
|
||||
if owner and pname ~= owner then
|
||||
core.chat_send_player(pname, "This horse is owned by " .. owner)
|
||||
return true
|
||||
end
|
||||
|
||||
local wielded = puncher:get_wielded_item()
|
||||
if wielded then
|
||||
local wname = wielded:get_name()
|
||||
local idx = wname:find(":")
|
||||
|
||||
-- can be tamed with any item named "lasso"
|
||||
if wname and idx and wname:sub(idx+1) == "lasso" then
|
||||
if not owner then
|
||||
core.chat_send_player(pname, "This horse is too wild to tame. Try feeding it some apples.")
|
||||
return true
|
||||
else
|
||||
local pinv = puncher:get_inventory()
|
||||
if not pinv:room_for_item("main", self.name .. "_spawn_egg") then
|
||||
core.chat_send_player(pname, "You don't have enought room in your inventory.")
|
||||
else
|
||||
self.object:remove()
|
||||
puncher:get_inventory():add_item("main", self.name .. "_spawn_egg")
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function horse:on_step(dtime)
|
||||
if not self.driver then return false end
|
||||
|
||||
-- FIXME: let owners control horse
|
||||
self.object:set_velocity({x=0, y=0, z=0})
|
||||
self.object:set_animation()
|
||||
|
||||
--[[
|
||||
self.v = get_v(self.object:get_velocity())*get_sign(self.v)
|
||||
if self.driver then
|
||||
local ctrl = self.driver:get_player_control()
|
||||
if ctrl.up then
|
||||
self.v = self.v+2
|
||||
end
|
||||
if ctrl.down then
|
||||
self.v = self.v-0.1
|
||||
end
|
||||
if ctrl.left then
|
||||
self.object:set_yaw(self.object:get_yaw()+math.pi/120+dtime*math.pi/120)
|
||||
end
|
||||
if ctrl.right then
|
||||
self.object:set_yaw(self.object:get_yaw()-math.pi/120-dtime*math.pi/120)
|
||||
end
|
||||
if ctrl.jump then
|
||||
local p = self.object:get_pos()
|
||||
p.y = p.y-0.5
|
||||
if is_ground(p) then
|
||||
local pos = self.object:get_pos()
|
||||
pos.y = math.floor(pos.y)+4
|
||||
self.object:set_pos(pos)
|
||||
self.object:set_velocity(get_velocity(self.v, self.object:get_yaw(), 0))
|
||||
end
|
||||
end
|
||||
end
|
||||
local s = get_sign(self.v)
|
||||
self.v = self.v - 0.02*s
|
||||
if s ~= get_sign(self.v) then
|
||||
self.object:set_velocity({x=0, y=0, z=0})
|
||||
self.v = 0
|
||||
return
|
||||
end
|
||||
if math.abs(self.v) > 4.5 then
|
||||
self.v = 4.5*get_sign(self.v)
|
||||
end
|
||||
|
||||
local p = self.object:get_pos()
|
||||
p.y = p.y-0.5
|
||||
if not is_ground(p) then
|
||||
if minetest.registered_nodes[minetest.get_node(p).name].walkable then
|
||||
self.v = 0
|
||||
end
|
||||
self.object:set_acceleration({x=0, y=-10, z=0})
|
||||
self.object:set_velocity(get_velocity(self.v, self.object:get_yaw(), self.object:get_velocity().y))
|
||||
else
|
||||
p.y = p.y+1
|
||||
if is_ground(p) then
|
||||
self.object:set_acceleration({x=0, y=3, z=0})
|
||||
local y = self.object:get_velocity().y
|
||||
if y > 2 then
|
||||
y = 2
|
||||
end
|
||||
if y < 0 then
|
||||
self.object:set_acceleration({x=0, y=10, z=0})
|
||||
end
|
||||
self.object:set_velocity(get_velocity(self.v, self.object:get_yaw(), y))
|
||||
else
|
||||
self.object:set_acceleration({x=0, y=0, z=0})
|
||||
if math.abs(self.object:get_velocity().y) < 1 then
|
||||
local pos = self.object:get_pos()
|
||||
pos.y = math.floor(pos.y)+0.5
|
||||
self.object:set_pos(pos)
|
||||
self.object:set_velocity(get_velocity(self.v, self.object:get_yaw(), 0))
|
||||
else
|
||||
self.object:set_velocity(get_velocity(self.v, self.object:get_yaw(), self.object:get_velocity().y))
|
||||
end
|
||||
end
|
||||
end
|
||||
]]
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
local likes = {"default:apple"}
|
||||
if core.global_exists("farming") then
|
||||
table.insert(likes, "farming:wheat")
|
||||
end
|
||||
]]
|
||||
|
||||
local drops = {}
|
||||
if core.global_exists("mobs") then
|
||||
table.insert(drops, {"mobs:meat_raw", {min=2, max=3}, 1.0})
|
||||
table.insert(drops, {"mobs:leather", 1, 1.0})
|
||||
end
|
||||
|
||||
|
||||
local sounds = {
|
||||
neigh = {
|
||||
name = "creatures_horse_neigh_01",
|
||||
gain = 1.0,
|
||||
},
|
||||
snort1 = {
|
||||
name = "creatures_horse_snort_01",
|
||||
gain = 1.0,
|
||||
},
|
||||
snort2 = {
|
||||
name = "creatures_horse_snort_02",
|
||||
gain = 1.0,
|
||||
},
|
||||
distress = {
|
||||
name = "creatures_horse_neigh_02",
|
||||
gain = 1.0,
|
||||
},
|
||||
}
|
||||
|
||||
-- FIXME:
|
||||
-- - mounted horse movement is incorrect
|
||||
local base_def = {
|
||||
ownable = true,
|
||||
stats = {
|
||||
hp = 16,
|
||||
hostile = false,
|
||||
lifetime = cmer_horse.lifetime,
|
||||
can_jump = 1, -- FIXME: should only not be able to jump over certain nodes for coralling
|
||||
can_panic = true,
|
||||
has_kockback = true,
|
||||
},
|
||||
modes = {
|
||||
idle = {
|
||||
chance = 0.3,
|
||||
},
|
||||
walk = {chance=0.7, moving_speed=1,},
|
||||
},
|
||||
model = {
|
||||
mesh = "mobs_horse.x",
|
||||
collisionbox = {-0.4, -0.01, -0.4, 0.4, 1, 0.4},
|
||||
rotation = -90.0,
|
||||
animations = {
|
||||
idle = {
|
||||
start = 25,
|
||||
stop = 75,
|
||||
speed = 15,
|
||||
},
|
||||
walk = {start=75, stop=100, speed=15,},
|
||||
},
|
||||
},
|
||||
sounds = {
|
||||
on_damage = sounds.distress,
|
||||
on_death = sounds.snort2,
|
||||
random = {
|
||||
idle = sounds.snort1,
|
||||
follow = sounds.neigh,
|
||||
}
|
||||
},
|
||||
drops = drops,
|
||||
spawning = {
|
||||
abm_nodes = {
|
||||
spawn_on = {"default:dirt_with_grass"},
|
||||
neighbors = {},
|
||||
},
|
||||
abm_interval = cmer_horse.spawn_interval,
|
||||
abm_chance = cmer_horse.spawn_chance,
|
||||
max_number = 1,
|
||||
light = {min=8, max=20},
|
||||
height_limit = {min=-50, max=31000},
|
||||
},
|
||||
on_rightclick = function(self, clicker)
|
||||
return horse.on_rightclick(self, clicker)
|
||||
end,
|
||||
on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
|
||||
return horse.on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
|
||||
end,
|
||||
on_step = function(self, dtime)
|
||||
return horse.on_step(self, dtime)
|
||||
end,
|
||||
on_activate = function(self, staticdata)
|
||||
return horse.on_activate(self, staticdata)
|
||||
end,
|
||||
get_staticdata = function(self)
|
||||
return horse.get_staticdata(self)
|
||||
end,
|
||||
}
|
||||
|
||||
|
||||
local horses = {
|
||||
{
|
||||
name = "horse_brown",
|
||||
description = "Brown Horse",
|
||||
textures = {"mobs_horse.png"},
|
||||
inventory_image = "mobs_horse_brown_inv.png",
|
||||
},
|
||||
{
|
||||
name = "horse_white",
|
||||
description = "White Horse",
|
||||
textures = {"mobs_horsepeg.png"},
|
||||
inventory_image = "mobs_horse_white_inv.png",
|
||||
},
|
||||
{
|
||||
name = "horse_black",
|
||||
description = "Black Horse",
|
||||
textures = {"mobs_horseara.png"},
|
||||
inventory_image = "mobs_horse_black_inv.png",
|
||||
},
|
||||
}
|
||||
|
||||
for _, horse in ipairs(horses) do
|
||||
local def = table.copy(base_def)
|
||||
def.name = "creatures:" .. horse.name
|
||||
def.model.textures = horse.textures
|
||||
|
||||
if not core.global_exists("asm") then
|
||||
def.spawning.spawn_egg = {}
|
||||
def.spawning.spawn_egg.description = horse.description
|
||||
def.spawning.spawn_egg.texture = horse.inventory_image
|
||||
else
|
||||
asm.addEgg({
|
||||
name=horse.name,
|
||||
title=horse.description .. " Spawn Egg",
|
||||
inventory_image = horse.inventory_image,
|
||||
spawn = def.name,
|
||||
})
|
||||
|
||||
core.register_alias(def.name .. "_spawn_egg", "spawneggs:" .. horse.name)
|
||||
end
|
||||
|
||||
cmer.register_mob(def)
|
||||
end
|
||||
|
||||
if not core.global_exists("mobs") then
|
||||
cmer.register_alias("mob_horse:horse", "creatures:horse_brown")
|
||||
cmer.register_alias("mobs:horse", "creatures:horse_brown")
|
||||
end
|
||||
|
||||
if core.settings:get_bool("log_mods", false) then
|
||||
core.log("action", "horse loaded")
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
name = cmer_horse
|
||||
description = A ridable horse for Revived Creatures mob engine based on kpgmobs.
|
||||
depends = cmer, default
|
||||
optional_depends = mobs, asm_spawneggs
|
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 651 B |
Before Width: | Height: | Size: 899 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 851 B |
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,24 +0,0 @@
|
|||
|
||||
--- Entity lifespan.
|
||||
--
|
||||
-- @setting horse.lifetime
|
||||
-- @settype int
|
||||
-- @default 300
|
||||
cmer_horse.lifetime = tonumber(core.settings:get("horse.lifetime")) or 300
|
||||
|
||||
--- Spawn rate frequency.
|
||||
--
|
||||
--
|
||||
-- @setting horse.spawn_interval
|
||||
-- @settype int
|
||||
-- @default 600 (10 minutes)
|
||||
-- @see [ABM definition](http://minetest.gitlab.io/minetest/definition-tables.html#abm-activeblockmodifier-definition)
|
||||
cmer_horse.spawn_interval = tonumber(core.settings:get("horse.spawn_interval")) or 10 * 60
|
||||
|
||||
--- Chance of spawn at interval.
|
||||
--
|
||||
-- @setting horse.spawn_chance
|
||||
-- @settype int
|
||||
-- @default 9000
|
||||
-- @see [ABM definition](http://minetest.gitlab.io/minetest/definition-tables.html#abm-activeblockmodifier-definition)
|
||||
cmer_horse.spawn_chance = tonumber(core.settings:get("horse.spawn_chance")) or 9000
|
|
@ -1,18 +0,0 @@
|
|||
|
||||
# Entity lifespan.
|
||||
#
|
||||
# type: int
|
||||
# default: 300
|
||||
horse.lifetime (Lifespan) int 300 1
|
||||
|
||||
# Spawn interval in seconds.
|
||||
#
|
||||
# type: int
|
||||
# default: 600 (10 minutes)
|
||||
horse.spawn_interval (Spawn interval) int 600 1
|
||||
|
||||
# Chance of spawn at interval.
|
||||
#
|
||||
# type: int
|
||||
# default: 9000
|
||||
horse.spawn_chance (Spawn chance) int 9000 1
|
Before Width: | Height: | Size: 505 B |
Before Width: | Height: | Size: 692 B |
Before Width: | Height: | Size: 411 B |
Before Width: | Height: | Size: 426 B |
|
@ -0,0 +1,14 @@
|
|||
|
||||
1.0
|
||||
- forked from original whinny mod
|
||||
- updated for 0.4.16/5.x Minetest
|
||||
- renamed horses
|
||||
- added settings for min/max spawn height & spawn chance
|
||||
- changed setting "only_peaceful_whinny" to "whinny.peaceful_only"
|
||||
- player faces same direction as horse after mounting
|
||||
- added setting to use mouse/look direction to control left/right turning (enabled by default)
|
||||
- horse can follow multiple items
|
||||
- added sounds for when damaged
|
||||
- added sounds for when in "walk" & "stand" states
|
||||
- added galloping sounds while riding
|
||||
- mobs:lasso can be used to pick up tamed horses
|
|
@ -1,8 +1,7 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2021 Jordan Irwin (AntumDeluge)
|
||||
|
||||
Copyright © 2014 Krupnov Pavel
|
||||
Copyright © 2021 Jordan Irwin (AntumDeluge)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
|
@ -0,0 +1,24 @@
|
|||
## Horses for Minetest
|
||||
|
||||
### Description:
|
||||
|
||||
A continuation of the [whinny][forum] mod for [Minetest], that adds tameable & rideable horses.
|
||||
|
||||
![screenshot](screenshot.png)
|
||||
|
||||
### Licensing:
|
||||
|
||||
- Code: [MIT](LICENSE.txt)
|
||||
- Models & textures by sparky: CC0
|
||||
- Sounds: see [sounds/sources.txt](sounds/sources.txt)
|
||||
|
||||
### Links:
|
||||
|
||||
- [Forum][forum]
|
||||
- [Git repo](https://github.com/AntumMT/mod-whinny)
|
||||
- [Changelog](CHANGES.txt)
|
||||
- [TODO](TODO.txt)
|
||||
|
||||
|
||||
[Minetest]: http://minetest.net/
|
||||
[forum]: https://forum.minetest.net/viewtopic.php?t=17170
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
TODO:
|
||||
- fix drops
|
||||
- add localization support
|
||||
- fix player position on horse
|
||||
- make turning gradual when using mouse control
|
|
@ -0,0 +1,904 @@
|
|||
|
||||
local yaw = {}
|
||||
|
||||
function whinny:register_mob(name, def)
|
||||
core.register_entity(name, {
|
||||
name = name,
|
||||
hp_min = def.hp_min,
|
||||
hp_max = def.hp_max,
|
||||
appetite = def.appetite,
|
||||
physical = true,
|
||||
collisionbox = def.collisionbox,
|
||||
visual = def.visual,
|
||||
visual_size = def.visual_size,
|
||||
mesh = def.mesh,
|
||||
makes_footstep_sound = def.makes_footstep_sound,
|
||||
view_range = def.view_range,
|
||||
walk_velocity = def.walk_velocity,
|
||||
run_velocity = def.run_velocity,
|
||||
damage = def.damage,
|
||||
light_damage = def.light_damage,
|
||||
water_damage = def.water_damage,
|
||||
lava_damage = def.lava_damage,
|
||||
disable_fall_damage = def.disable_fall_damage,
|
||||
drops = def.drops,
|
||||
armor = def.armor,
|
||||
drawtype = def.drawtype,
|
||||
on_rightclick = def.on_rightclick,
|
||||
type = def.type,
|
||||
attack_type = def.attack_type,
|
||||
arrow = def.arrow,
|
||||
shoot_interval = def.shoot_interval,
|
||||
sounds = def.sounds,
|
||||
animation = def.animation,
|
||||
follow = def.follow,
|
||||
jump = def.jump or true,
|
||||
exp_min = def.exp_min or 0,
|
||||
exp_max = def.exp_max or 0,
|
||||
walk_chance = def.walk_chance or 50,
|
||||
attacks_monsters = def.attacks_monsters or false,
|
||||
group_attack = def.group_attack or false,
|
||||
step = def.step or 0,
|
||||
fov = def.fov or 120,
|
||||
passive = def.passive or false,
|
||||
|
||||
stimer = 0,
|
||||
timer = 0,
|
||||
env_damage_timer = 0, -- only if state = "attack"
|
||||
attack = {player=nil, dist=nil},
|
||||
state = "stand",
|
||||
v_start = false,
|
||||
old_y = nil,
|
||||
lifetimer = 600,
|
||||
tamed = false,
|
||||
|
||||
do_attack = function(self, player, dist)
|
||||
if self.state ~= "attack" then
|
||||
if self.sounds.war_cry then
|
||||
if math.random(0,100) < 90 then
|
||||
core.sound_play(self.sounds.war_cry,{ object = self.object })
|
||||
end
|
||||
end
|
||||
|
||||
self.state = "attack"
|
||||
self.attack.player = player
|
||||
self.attack.dist = dist
|
||||
end
|
||||
end,
|
||||
|
||||
set_velocity = function(self, v)
|
||||
local yaw = self.object:get_yaw()
|
||||
if yaw then
|
||||
if self.drawtype == "side" then
|
||||
yaw = yaw+(math.pi/2)
|
||||
end
|
||||
|
||||
local x = math.sin(yaw) * -v
|
||||
local y = self.object:get_velocity().y
|
||||
local z = math.cos(yaw) * v
|
||||
|
||||
for _, coord in ipairs({x, y, z}) do
|
||||
if core.is_nan(coord) then
|
||||
coord = 0
|
||||
end
|
||||
end
|
||||
|
||||
self.object:set_velocity({x=x, y=y, z=z})
|
||||
end
|
||||
end,
|
||||
|
||||
get_velocity = function(self)
|
||||
local v = self.object:get_velocity()
|
||||
if v then
|
||||
return (v.x^2 + v.z^2)^(0.5)
|
||||
end
|
||||
|
||||
return 0.0
|
||||
end,
|
||||
|
||||
in_fov = function(self,pos)
|
||||
-- checks if POS is in self's FOV
|
||||
local yaw = self.object:get_yaw()
|
||||
if self.drawtype == "side" then
|
||||
yaw = yaw+(math.pi/2)
|
||||
end
|
||||
|
||||
local vx = math.sin(yaw)
|
||||
local vz = math.cos(yaw)
|
||||
local ds = math.sqrt(vx^2 + vz^2)
|
||||
local ps = math.sqrt(pos.x^2 + pos.z^2)
|
||||
local d = { x = vx / ds, z = vz / ds }
|
||||
local p = { x = pos.x / ps, z = pos.z / ps }
|
||||
|
||||
local an = ( d.x * p.x ) + ( d.z * p.z )
|
||||
|
||||
local a = math.deg( math.acos( an ) )
|
||||
|
||||
if a > ( self.fov / 2 ) then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end,
|
||||
|
||||
set_animation = function(self, type)
|
||||
if not self.animation then return end
|
||||
|
||||
if not self.animation.current then
|
||||
self.animation.current = ""
|
||||
end
|
||||
|
||||
if type == "stand" and self.animation.current ~= "stand" then
|
||||
if self.animation.stand_start and self.animation.stand_end and
|
||||
self.animation.speed_normal then
|
||||
self.object:set_animation(
|
||||
{x=self.animation.stand_start,y=self.animation.stand_end},
|
||||
self.animation.speed_normal,
|
||||
0
|
||||
)
|
||||
self.animation.current = "stand"
|
||||
end
|
||||
elseif type == "walk" and self.animation.current ~= "walk" then
|
||||
if self.animation.walk_start and self.animation.walk_end and
|
||||
self.animation.speed_normal then
|
||||
self.object:set_animation(
|
||||
{x=self.animation.walk_start,y=self.animation.walk_end},
|
||||
self.animation.speed_normal,
|
||||
0
|
||||
)
|
||||
self.animation.current = "walk"
|
||||
end
|
||||
elseif type == "run" and self.animation.current ~= "run" then
|
||||
if self.animation.run_start and self.animation.run_end and
|
||||
self.animation.speed_run then
|
||||
self.object:set_animation(
|
||||
{x=self.animation.run_start,y=self.animation.run_end},
|
||||
self.animation.speed_run,
|
||||
0
|
||||
)
|
||||
self.animation.current = "run"
|
||||
end
|
||||
elseif type == "punch" and self.animation.current ~= "punch" then
|
||||
if self.animation.punch_start and self.animation.punch_end and
|
||||
self.animation.speed_normal then
|
||||
self.object:set_animation(
|
||||
{x=self.animation.punch_start,y=self.animation.punch_end},
|
||||
self.animation.speed_normal,
|
||||
0
|
||||
)
|
||||
self.animation.current = "punch"
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
on_step = function(self, dtime)
|
||||
if self.type == "monster" and whinny.peaceful_only then
|
||||
self.object:remove()
|
||||
end
|
||||
|
||||
self.lifetimer = self.lifetimer - dtime
|
||||
-- RJK:
|
||||
--if self.lifetimer <= 0 and not self.tamed and self.type ~= "npc" then
|
||||
if self.lifetimer <= 0 and not self.tamed and self.name ~= "whinny:ent" then
|
||||
local player_count = 0
|
||||
for _,obj in ipairs(core.get_objects_inside_radius(self.object:get_pos(), 10)) do
|
||||
if obj:is_player() then
|
||||
player_count = player_count+1
|
||||
end
|
||||
end
|
||||
|
||||
if player_count == 0 and self.state ~= "attack" then
|
||||
core.log("action","lifetimer expired, removed mob "..self.name)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if self.object:get_velocity().y > 0.1 then
|
||||
local yaw = self.object:get_yaw()
|
||||
if self.drawtype == "side" then
|
||||
yaw = yaw+(math.pi/2)
|
||||
end
|
||||
|
||||
local x = math.sin(yaw) * -2
|
||||
local z = math.cos(yaw) * 2
|
||||
|
||||
if core.get_item_group(core.get_node(self.object:get_pos()).name, "water") ~= 0 then
|
||||
self.object:set_acceleration({x = x, y = 1.5, z = z})
|
||||
else
|
||||
self.object:set_acceleration({x = x, y = -14.5, z = z})
|
||||
end
|
||||
else
|
||||
if core.get_item_group(core.get_node(self.object:get_pos()).name, "water") ~= 0 then
|
||||
self.object:set_acceleration({x = 0, y = 1.5, z = 0})
|
||||
else
|
||||
self.object:set_acceleration({x = 0, y = -14.5, z = 0})
|
||||
end
|
||||
end
|
||||
|
||||
if self.disable_fall_damage and self.object:get_velocity().y == 0 then
|
||||
if not self.old_y then
|
||||
self.old_y = self.object:get_pos().y
|
||||
else
|
||||
local d = self.old_y - self.object:get_pos().y
|
||||
if d > 5 then
|
||||
local damage = d-5
|
||||
self.object:set_hp(self.object:get_hp()-damage)
|
||||
if self.object:get_hp() == 0 then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
|
||||
self.old_y = self.object:get_pos().y
|
||||
end
|
||||
end
|
||||
|
||||
self.timer = self.timer+dtime
|
||||
if self.state ~= "attack" then
|
||||
if self.timer < 1 then return end
|
||||
|
||||
self.timer = 0
|
||||
end
|
||||
|
||||
if self.sounds and self.sounds.random and math.random(1, 100) <= 1 then
|
||||
local to_play = self.sounds.random[self.state]
|
||||
if to_play then
|
||||
core.sound_play(to_play.name, {object=self.object, to_play.gain})
|
||||
end
|
||||
end
|
||||
|
||||
local do_env_damage = function(self)
|
||||
local pos = self.object:get_pos()
|
||||
local n = core.get_node(pos)
|
||||
|
||||
if self.light_damage and self.light_damage ~= 0 and pos.y>0 and
|
||||
core.get_node_light(pos) and core.get_node_light(pos) > 4 and
|
||||
core.get_timeofday() > 0.2 and core.get_timeofday() < 0.8 then
|
||||
self.object:set_hp(self.object:get_hp()-self.light_damage)
|
||||
if self.object:get_hp() == 0 then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
|
||||
if self.water_damage and self.water_damage ~= 0 and
|
||||
core.get_item_group(n.name, "water") ~= 0 then
|
||||
self.object:set_hp(self.object:get_hp()-self.water_damage)
|
||||
if self.object:get_hp() == 0 then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
|
||||
if self.lava_damage and self.lava_damage ~= 0 and
|
||||
core.get_item_group(n.name, "lava") ~= 0 then
|
||||
self.object:set_hp(self.object:get_hp()-self.lava_damage)
|
||||
if self.object:get_hp() == 0 then
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
end -- FIXME: missplaced end???
|
||||
|
||||
self.env_damage_timer = self.env_damage_timer + dtime
|
||||
if self.state == "attack" and self.env_damage_timer > 1 then
|
||||
self.env_damage_timer = 0
|
||||
do_env_damage(self)
|
||||
elseif self.state ~= "attack" then
|
||||
do_env_damage(self)
|
||||
end
|
||||
|
||||
-- FIND SOMEONE TO ATTACK
|
||||
if ( self.type == "monster" or self.type == "barbarian" ) and
|
||||
whinny.enable_damage and self.state ~= "attack" then
|
||||
|
||||
local s = self.object:get_pos()
|
||||
local inradius = core.get_objects_inside_radius(s,self.view_range)
|
||||
local player = nil
|
||||
local type = nil
|
||||
for _,oir in ipairs(inradius) do
|
||||
if oir:is_player() then
|
||||
player = oir
|
||||
type = "player"
|
||||
else
|
||||
local obj = oir:get_luaentity()
|
||||
if obj then
|
||||
player = obj.object
|
||||
type = obj.type
|
||||
end
|
||||
end
|
||||
|
||||
if type == "player" or type == "npc" then
|
||||
local s = self.object:get_pos()
|
||||
local p = player:get_pos()
|
||||
local sp = s
|
||||
p.y = p.y + 1
|
||||
sp.y = sp.y + 1 -- aim higher to make looking up hills more realistic
|
||||
local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
|
||||
if dist < self.view_range and self.in_fov(self,p) then
|
||||
if core.line_of_sight(sp,p,2) == true then
|
||||
self.do_attack(self,player,dist)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- NPC FIND A MONSTER TO ATTACK
|
||||
if self.type == "npc" and self.attacks_monsters and self.state ~= "attack" then
|
||||
local s = self.object:get_pos()
|
||||
local inradius = core.get_objects_inside_radius(s,self.view_range)
|
||||
for _, oir in pairs(inradius) do
|
||||
local obj = oir:get_luaentity()
|
||||
if obj then
|
||||
if obj.type == "monster" or obj.type == "barbarian" then
|
||||
-- attack monster
|
||||
local p = obj.object:get_pos()
|
||||
local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
|
||||
print("attack monster at "..core.pos_to_string(obj.object:get_pos()))
|
||||
self.do_attack(self,obj.object,dist)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local follow = false
|
||||
local follow_multiple = type(self.follow) == "table"
|
||||
|
||||
if follow_multiple then
|
||||
follow = #self.follow > 0
|
||||
else
|
||||
follow = self.follow ~= ""
|
||||
end
|
||||
|
||||
if follow and not self.following then
|
||||
for _, player in pairs(core.get_connected_players()) do
|
||||
local s = self.object:get_pos()
|
||||
local p = player:get_pos()
|
||||
|
||||
if s and p then
|
||||
local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
|
||||
if self.view_range and dist < self.view_range then
|
||||
self.following = player
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.following and self.following:is_player() then
|
||||
local wielded = self.following:get_wielded_item():get_name()
|
||||
local likes_wielded = false
|
||||
|
||||
if follow_multiple then
|
||||
for _, i in ipairs(self.follow) do
|
||||
if i == wielded then
|
||||
likes_wielded = true
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
likes_wielded = wielded == self.follow
|
||||
end
|
||||
|
||||
if not likes_wielded then
|
||||
self.following = nil
|
||||
else
|
||||
local s = self.object:get_pos()
|
||||
local p = self.following:get_pos()
|
||||
local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
|
||||
if dist > self.view_range then
|
||||
self.following = nil
|
||||
self.v_start = false
|
||||
else
|
||||
local vec = {x=p.x-s.x, y=p.y-s.y, z=p.z-s.z}
|
||||
local yaw = math.atan(vec.z/vec.x)+math.pi/2
|
||||
if self.drawtype == "side" then
|
||||
yaw = yaw+(math.pi/2)
|
||||
end
|
||||
if p.x > s.x then
|
||||
yaw = yaw+math.pi
|
||||
end
|
||||
self.object:set_yaw(yaw)
|
||||
if dist > 2 then
|
||||
if not self.v_start then
|
||||
self.v_start = true
|
||||
self.set_velocity(self, self.walk_velocity)
|
||||
else
|
||||
if self.jump and self.get_velocity(self) <= 1.5 and self.object:get_velocity().y == 0 then
|
||||
local v = self.object:get_velocity()
|
||||
v.y = 6
|
||||
self.object:set_velocity(v)
|
||||
end
|
||||
self.set_velocity(self, self.walk_velocity)
|
||||
end
|
||||
self:set_animation("walk")
|
||||
else
|
||||
self.v_start = false
|
||||
self.set_velocity(self, 0)
|
||||
self:set_animation("stand")
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.state == "stand" then
|
||||
-- randomly turn
|
||||
if math.random(1, 4) == 1 then
|
||||
-- if there is a player nearby look at them
|
||||
local lp = nil
|
||||
local s = self.object:get_pos()
|
||||
if self.type == "npc" then
|
||||
local o = core.get_objects_inside_radius(self.object:get_pos(), 3)
|
||||
|
||||
local yaw = 0
|
||||
for _,o in ipairs(o) do
|
||||
if o:is_player() then
|
||||
lp = o:get_pos()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if lp ~= nil then
|
||||
local vec = {x=lp.x-s.x, y=lp.y-s.y, z=lp.z-s.z}
|
||||
yaw = math.atan(vec.z/vec.x)+math.pi/2
|
||||
if self.drawtype == "side" then
|
||||
yaw = yaw+(math.pi/2)
|
||||
end
|
||||
|
||||
if lp.x > s.x then
|
||||
yaw = yaw+math.pi
|
||||
end
|
||||
else
|
||||
yaw = self.object:get_yaw()
|
||||
if yaw then
|
||||
yaw = yaw + ((math.random(0,360)-180)/180*math.pi)
|
||||
end
|
||||
end
|
||||
|
||||
self.object:set_yaw(yaw)
|
||||
end
|
||||
|
||||
self.set_velocity(self, 0)
|
||||
self.set_animation(self, "stand")
|
||||
if math.random(1, 100) <= self.walk_chance then
|
||||
self.set_velocity(self, self.walk_velocity)
|
||||
self.state = "walk"
|
||||
self.set_animation(self, "walk")
|
||||
end
|
||||
elseif self.state == "walk" then
|
||||
if math.random(1, 100) <= 30 then
|
||||
local yaw = self.object:get_yaw()
|
||||
if yaw then
|
||||
self.object:set_yaw(yaw + ((math.random(0,360)-180)/180*math.pi))
|
||||
end
|
||||
end
|
||||
|
||||
local vel = self.object:get_velocity()
|
||||
if self.jump and self.get_velocity(self) <= 0.5 and vel and vel.y == 0 then
|
||||
local v = self.object:get_velocity()
|
||||
v.y = 6
|
||||
self.object:set_velocity(v)
|
||||
end
|
||||
|
||||
self:set_animation("walk")
|
||||
self.set_velocity(self, self.walk_velocity)
|
||||
|
||||
if math.random(1, 100) <= 30 then
|
||||
self.set_velocity(self, 0)
|
||||
self.state = "stand"
|
||||
self:set_animation("stand")
|
||||
end
|
||||
elseif self.state == "attack" and self.attack_type == "dogfight" then
|
||||
if not self.attack.player or not self.attack.player:get_pos() then
|
||||
print("stop attacking")
|
||||
self.state = "stand"
|
||||
self:set_animation("stand")
|
||||
return
|
||||
end
|
||||
|
||||
local s = self.object:get_pos()
|
||||
local p = self.attack.player:get_pos()
|
||||
local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
|
||||
|
||||
if dist > self.view_range or self.attack.player:get_hp() <= 0 then
|
||||
self.state = "stand"
|
||||
self.v_start = false
|
||||
self.set_velocity(self, 0)
|
||||
self.attack = {player=nil, dist=nil}
|
||||
self:set_animation("stand")
|
||||
return
|
||||
else
|
||||
self.attack.dist = dist
|
||||
end
|
||||
|
||||
local vec = {x=p.x-s.x, y=p.y-s.y, z=p.z-s.z}
|
||||
local yaw = math.atan(vec.z/vec.x)+math.pi/2
|
||||
if self.drawtype == "side" then
|
||||
yaw = yaw+(math.pi/2)
|
||||
end
|
||||
|
||||
if p.x > s.x then
|
||||
yaw = yaw+math.pi
|
||||
end
|
||||
|
||||
self.object:set_yaw(yaw)
|
||||
if self.attack.dist > 2 then
|
||||
if not self.v_start then
|
||||
self.v_start = true
|
||||
self.set_velocity(self, self.run_velocity)
|
||||
else
|
||||
if self.jump and self.get_velocity(self) <= 0.5 and self.object:get_velocity().y == 0 then
|
||||
local v = self.object:get_velocity()
|
||||
v.y = 6
|
||||
self.object:set_velocity(v)
|
||||
end
|
||||
|
||||
self.set_velocity(self, self.run_velocity)
|
||||
end
|
||||
|
||||
self:set_animation("run")
|
||||
else
|
||||
self.set_velocity(self, 0)
|
||||
self:set_animation("punch")
|
||||
self.v_start = false
|
||||
|
||||
if self.timer > 1 then
|
||||
self.timer = 0
|
||||
local p2 = p
|
||||
local s2 = s
|
||||
p2.y = p2.y + 1.5
|
||||
s2.y = s2.y + 1.5
|
||||
|
||||
if core.line_of_sight(p2,s2) == true then
|
||||
if self.sounds and self.sounds.attack then
|
||||
core.sound_play(self.sounds.attack, {object = self.object})
|
||||
end
|
||||
|
||||
self.attack.player:punch(self.object, 1.0,
|
||||
{
|
||||
full_punch_interval = 1.0,
|
||||
damage_groups = {fleshy=self.damage},
|
||||
},
|
||||
vec
|
||||
)
|
||||
|
||||
if math.random(0,3) == 3 and self.attack.player:is_player() then
|
||||
local snum = math.random(1,4)
|
||||
core.sound_play("default_hurt"..tostring(snum),
|
||||
{
|
||||
object = self.attack.player,
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
if self.attack.player:get_hp() <= 0 then
|
||||
self.state = "stand"
|
||||
self:set_animation("stand")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif self.state == "attack" and self.attack_type == "shoot" then
|
||||
if not self.attack.player or not self.attack.player:is_player() then
|
||||
self.state = "stand"
|
||||
self:set_animation("stand")
|
||||
return
|
||||
end
|
||||
|
||||
local s = self.object:get_pos()
|
||||
local p = self.attack.player:get_pos()
|
||||
p.y = p.y - .5
|
||||
s.y = s.y + .5
|
||||
local dist = ((p.x-s.x)^2 + (p.y-s.y)^2 + (p.z-s.z)^2)^0.5
|
||||
|
||||
if dist > self.view_range or self.attack.player:get_hp() <= 0 then
|
||||
self.state = "stand"
|
||||
self.v_start = false
|
||||
self.set_velocity(self, 0)
|
||||
|
||||
if self.type ~= "npc" then
|
||||
self.attack = {player=nil, dist=nil}
|
||||
end
|
||||
|
||||
self:set_animation("stand")
|
||||
return
|
||||
else
|
||||
self.attack.dist = dist
|
||||
end
|
||||
|
||||
local vec = {x=p.x-s.x, y=p.y-s.y, z=p.z-s.z}
|
||||
local yaw = math.atan(vec.z/vec.x)+math.pi/2
|
||||
|
||||
if self.drawtype == "side" then
|
||||
yaw = yaw+(math.pi/2)
|
||||
end
|
||||
|
||||
if p.x > s.x then
|
||||
yaw = yaw+math.pi
|
||||
end
|
||||
|
||||
self.object:set_yaw(yaw)
|
||||
self.set_velocity(self, 0)
|
||||
|
||||
if self.timer > self.shoot_interval and math.random(1, 100) <= 60 then
|
||||
self.timer = 0
|
||||
|
||||
self:set_animation("punch")
|
||||
|
||||
if self.sounds and self.sounds.attack then
|
||||
core.sound_play(self.sounds.attack, {object = self.object})
|
||||
end
|
||||
|
||||
local p = self.object:get_pos()
|
||||
p.y = p.y + (self.collisionbox[2]+self.collisionbox[5])/2
|
||||
local obj = core.add_entity(p, self.arrow)
|
||||
local amount = (vec.x^2+vec.y^2+vec.z^2)^0.5
|
||||
local v = obj:get_luaentity().velocity
|
||||
vec.y = vec.y+1
|
||||
vec.x = vec.x*v/amount
|
||||
vec.y = vec.y*v/amount
|
||||
vec.z = vec.z*v/amount
|
||||
obj:set_velocity(vec)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
on_activate = function(self, staticdata, dtime_s)
|
||||
-- reset HP
|
||||
local pos = self.object:get_pos()
|
||||
local distance_rating = ( ( get_distance({x=0,y=0,z=0},pos) ) / 20000 )
|
||||
local newHP = self.hp_min + math.floor( self.hp_max * distance_rating )
|
||||
self.object:set_hp( newHP )
|
||||
|
||||
self.object:set_armor_groups({fleshy=self.armor})
|
||||
self.object:set_acceleration({x=0, y=-10, z=0})
|
||||
self.state = "stand"
|
||||
self.object:set_velocity({x=0, y=self.object:get_velocity().y, z=0})
|
||||
self.object:set_yaw(math.random(1, 360)/180*math.pi)
|
||||
|
||||
if self.type == "monster" and whinny.peaceful_only then
|
||||
self.object:remove()
|
||||
end
|
||||
|
||||
if self.type ~= "npc" then
|
||||
self.lifetimer = 600 - dtime_s
|
||||
end
|
||||
|
||||
if staticdata then
|
||||
local tmp = core.deserialize(staticdata)
|
||||
if tmp and tmp.lifetimer then
|
||||
self.lifetimer = tmp.lifetimer - dtime_s
|
||||
end
|
||||
if tmp and tmp.tamed then
|
||||
self.tamed = tmp.tamed
|
||||
end
|
||||
--[[if tmp and tmp.textures then
|
||||
self.object:set_properties(tmp.textures)
|
||||
end]]
|
||||
end
|
||||
|
||||
if self.lifetimer <= 0 and not self.tamed and self.type ~= "npc" then
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
|
||||
get_staticdata = function(self)
|
||||
local tmp = {
|
||||
lifetimer = self.lifetimer,
|
||||
tamed = self.tamed,
|
||||
textures = def.available_textures["texture_"..math.random(1,def.available_textures["total"])],
|
||||
}
|
||||
|
||||
self.object:set_properties(tmp)
|
||||
return core.serialize(tmp)
|
||||
end,
|
||||
|
||||
on_punch = function(self, hitter)
|
||||
local weapon = hitter:get_wielded_item()
|
||||
if weapon:get_definition().tool_capabilities ~= nil then
|
||||
local wear = ( weapon:get_definition().tool_capabilities.full_punch_interval / 75 ) * 9000
|
||||
weapon:add_wear(wear)
|
||||
hitter:set_wielded_item(weapon)
|
||||
end
|
||||
|
||||
if weapon:get_definition().sounds ~= nil then
|
||||
local s = math.random(0,#weapon:get_definition().sounds)
|
||||
core.sound_play(weapon:get_definition().sounds[s],
|
||||
{
|
||||
object = hitter,
|
||||
}
|
||||
)
|
||||
else
|
||||
core.sound_play("player_damage",
|
||||
{
|
||||
object = hitter,
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
if self.sounds and self.sounds.on_damage then
|
||||
core.sound_play(self.sounds.on_damage.name,
|
||||
{object=self.object, self.sounds.on_damage.gain})
|
||||
end
|
||||
|
||||
-- FIXME: drops not working because HP never <= 0
|
||||
if self.object:get_hp() <= 0 then
|
||||
if hitter and hitter:is_player() and hitter:get_inventory() then
|
||||
for _, drop in ipairs(self.drops) do
|
||||
if math.random(1, drop.chance) == 1 then
|
||||
hitter:get_inventory():add_item("main",
|
||||
ItemStack(drop.name.." "..math.random(drop.min, drop.max)))
|
||||
end
|
||||
end
|
||||
|
||||
-- FIXME: doesn't work
|
||||
if self.sounds.on_death ~= nil then
|
||||
core.sound_play(self.sounds.on_death.name,
|
||||
{object=self.object, self.sounds.on_death.gain})
|
||||
end
|
||||
|
||||
if core.get_modpath("skills") and core.get_modpath("experience") then
|
||||
-- DROP experience
|
||||
local distance_rating = ( ( get_distance({x=0,y=0,z=0},pos) ) / ( skills.get_player_level(hitter:get_player_name()).level * 1000 ) )
|
||||
local emax = math.floor( self.exp_min + ( distance_rating * self.exp_max ) )
|
||||
local expGained = math.random(self.exp_min, emax)
|
||||
skills.add_exp(hitter:get_player_name(),expGained)
|
||||
local expStack = experience.exp_to_items(expGained)
|
||||
|
||||
for _,stack in ipairs(expStack) do
|
||||
default.drop_item(pos,stack)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self.passive == false then
|
||||
self.do_attack(self,hitter,1)
|
||||
-- alert other NPCs to the attack
|
||||
local inradius = core.get_objects_inside_radius(hitter:get_pos(),5)
|
||||
|
||||
for _, oir in pairs(inradius) do
|
||||
local obj = oir:get_luaentity()
|
||||
if obj then
|
||||
if obj.group_attack == true and obj.state ~= "attack" then
|
||||
obj.do_attack(obj,hitter,1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
whinny.spawning_whinny = {}
|
||||
|
||||
function whinny:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, min_height, max_height, spawn_func)
|
||||
whinny.spawning_whinny[name] = true
|
||||
|
||||
-- RJK: Add this:
|
||||
if name == "whinny:rohan_guard" or
|
||||
name == "whinny:gondor_guard" or
|
||||
name == "whinny:dunlending" or
|
||||
name == "whinny:hobbit" or
|
||||
name == "whinny:orc" or
|
||||
name == "whinny:half_troll" or
|
||||
name == "whinny:troll" or
|
||||
name == "whinny:dwarf" or
|
||||
name == "whinny:dead_men" then
|
||||
chance = chance * 1
|
||||
end
|
||||
|
||||
core.register_abm({
|
||||
nodenames = nodes,
|
||||
--neighbors = {"air"},
|
||||
interval = 30,
|
||||
chance = chance,
|
||||
action = function(pos, node, _, active_object_count_wider)
|
||||
if active_object_count_wider > active_object_count then return end
|
||||
if not whinny.spawning_whinny[name] then return end
|
||||
|
||||
--[[ don't spawn inside of blocks
|
||||
local p2 = pos
|
||||
p2.y = p2.y + 1
|
||||
local p3 = p2
|
||||
p3.y = p3.y + 1
|
||||
if core.registered_nodes[core.get_node(p2).name].walkable == false or core.registered_nodes[core.get_node(p3).name].walkable == false then
|
||||
return
|
||||
end]]
|
||||
|
||||
pos.y = pos.y+1
|
||||
|
||||
if not core.get_node_light(pos) then return end
|
||||
if core.get_node_light(pos) > max_light then return end
|
||||
if core.get_node_light(pos) < min_light then return end
|
||||
if pos.y > max_height then return end
|
||||
if pos.y < min_height then return end
|
||||
|
||||
local get_node_pos
|
||||
local get_node_pos_name
|
||||
local registered_node
|
||||
|
||||
get_node_pos = core.get_node(pos)
|
||||
if get_node_pos == nil then return end
|
||||
|
||||
get_node_pos_name = get_node_pos.name
|
||||
if get_node_pos_name == nil then return end
|
||||
|
||||
registered_node = core.registered_nodes [get_node_pos_name]
|
||||
if registered_node == nil then return end
|
||||
|
||||
if registered_node.walkable == true or registered_node.walkable == nil then return end
|
||||
|
||||
pos.y = pos.y+1
|
||||
|
||||
get_node_pos = core.get_node(pos)
|
||||
if get_node_pos == nil then return end
|
||||
|
||||
get_node_pos_name = get_node_pos.name
|
||||
if get_node_pos_name == nil then return end
|
||||
|
||||
registered_node = core.registered_nodes [get_node_pos_name]
|
||||
if registered_node == nil then return end
|
||||
|
||||
if registered_node.walkable == true or registered_node.walkable == nil then return end
|
||||
|
||||
if spawn_func and not spawn_func(pos, node) then return end
|
||||
|
||||
if whinny.display_spawn then
|
||||
core.chat_send_all("[whinny] Add "..name.." at "..core.pos_to_string(pos))
|
||||
end
|
||||
|
||||
local mob = core.add_entity(pos, name)
|
||||
|
||||
-- setup the hp, armor, drops, etc... for this specific mob
|
||||
local distance_rating = ( ( get_distance({x=0,y=0,z=0},pos) ) / 15000 )
|
||||
if mob then
|
||||
mob = mob:get_luaentity()
|
||||
local newHP = mob.hp_min + math.floor( mob.hp_max * distance_rating )
|
||||
mob.object:set_hp( newHP )
|
||||
end
|
||||
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function whinny:register_arrow(name, def)
|
||||
core.register_entity(name, {
|
||||
physical = false,
|
||||
visual = def.visual,
|
||||
visual_size = def.visual_size,
|
||||
textures = def.textures,
|
||||
velocity = def.velocity,
|
||||
hit_player = def.hit_player,
|
||||
hit_node = def.hit_node,
|
||||
|
||||
on_step = function(self, dtime)
|
||||
local pos = self.object:get_pos()
|
||||
if core.get_node(self.object:get_pos()).name ~= "air" then
|
||||
self.hit_node(self, pos, node)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
pos.y = pos.y-1
|
||||
for _,player in pairs(core.get_objects_inside_radius(pos, 1)) do
|
||||
if player:is_player() then
|
||||
self.hit_player(self, player)
|
||||
self.object:remove()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function get_distance(pos1,pos2)
|
||||
if ( pos1 ~= nil and pos2 ~= nil ) then
|
||||
return math.abs(math.floor(math.sqrt( (pos1.x - pos2.x)^2 + (pos1.z - pos2.z)^2 )))
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
|
@ -0,0 +1,562 @@
|
|||
|
||||
local rot_compensate = 4.7
|
||||
|
||||
-- name, fill value
|
||||
local fill_values = {
|
||||
["default:apple"] = 2,
|
||||
["farming:wheat"] = 1,
|
||||
["farming:barley"] = 3,
|
||||
["farming:oat"] = 5,
|
||||
["farming:carrot"] = 4,
|
||||
["farming:carrot_gold"] = 5,
|
||||
["farming:cucumber"] = 2,
|
||||
}
|
||||
|
||||
local horse_likes = {}
|
||||
for iname, fill in pairs(fill_values) do
|
||||
if core.registered_items[iname] then
|
||||
table.insert(horse_likes, iname)
|
||||
else
|
||||
whinny.log("warning", "\"" .. iname
|
||||
.. "\" is not a registered item, removing it from items that horse will follow")
|
||||
end
|
||||
end
|
||||
|
||||
local sounds = {
|
||||
neigh = {
|
||||
name = "whinny_horse_neigh_01",
|
||||
gain = 1.0,
|
||||
},
|
||||
snort1 = {
|
||||
name = "whinny_horse_snort_01",
|
||||
gain = 1.0,
|
||||
},
|
||||
snort2 = {
|
||||
name = "whinny_horse_snort_02",
|
||||
gain = 1.0,
|
||||
},
|
||||
distress = {
|
||||
name = "whinny_horse_neigh_02",
|
||||
gain = 1.0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
-- galloping sounds
|
||||
local function handle_is_playing(handle)
|
||||
return handle > -1
|
||||
end
|
||||
|
||||
|
||||
local function is_ground(pos)
|
||||
local nn = core.get_node(pos).name
|
||||
return core.get_item_group(nn, "crumbly") ~= 0 or
|
||||
core.get_item_group(nn, "cracky") ~= 0 or
|
||||
core.get_item_group(nn, "choppy") ~= 0 or
|
||||
core.get_item_group(nn, "snappy") ~= 0
|
||||
end
|
||||
|
||||
local function get_sign(i)
|
||||
if i == 0 then
|
||||
return 0
|
||||
else
|
||||
return i/math.abs(i)
|
||||
end
|
||||
end
|
||||
|
||||
local function get_velocity(speed, yaw, y)
|
||||
local x = math.cos(yaw)*speed
|
||||
local z = math.sin(yaw)*speed
|
||||
return {x=x, y=y, z=z}
|
||||
end
|
||||
|
||||
local function get_speed(velocity)
|
||||
return math.sqrt(velocity.x^2+velocity.z^2)
|
||||
end
|
||||
|
||||
-- ===================================================================
|
||||
|
||||
local horse_drops = {}
|
||||
if core.registered_items["mobs:meat_raw"] then
|
||||
table.insert(horse_drops, {name="mobs:meat_raw", chance=1, min=2, max=3})
|
||||
end
|
||||
|
||||
local function register_wildhorse(color)
|
||||
whinny:register_mob("whinny:horse_" .. color, {
|
||||
type = "animal",
|
||||
hp_min = 10,
|
||||
hp_max = 10,
|
||||
appetite = 40,
|
||||
collisionbox = {-.5, -0.01, -.5, .5, 1.4, .5},
|
||||
available_textures = {
|
||||
total = 1,
|
||||
texture_1 = {"whinny_horse_" .. color .. "_mesh.png"},
|
||||
},
|
||||
visual = "mesh",
|
||||
drops = horse_drops,
|
||||
mesh = "horsemob.x",
|
||||
makes_footstep_sound = true,
|
||||
walk_velocity = 1,
|
||||
armor = 100,
|
||||
drawtype = "front",
|
||||
water_damage = 1,
|
||||
lava_damage = 5,
|
||||
light_damage = 0,
|
||||
sounds = {
|
||||
on_damage = sounds.distress,
|
||||
on_death = sounds.snort2,
|
||||
random = {
|
||||
stand = sounds.snort1,
|
||||
walk = sounds.neigh,
|
||||
}
|
||||
},
|
||||
animation = {
|
||||
speed_normal = 20,
|
||||
stand_start = 300,
|
||||
stand_end = 460,
|
||||
walk_start = 10,
|
||||
walk_end = 60
|
||||
},
|
||||
follow = horse_likes,
|
||||
view_range = 5,
|
||||
on_rightclick = function(self, clicker)
|
||||
local pname = clicker:get_player_name()
|
||||
local item = clicker:get_wielded_item()
|
||||
local item_name = item:get_name()
|
||||
local wants = false
|
||||
|
||||
if type(horse_likes) == "table" then
|
||||
for _, i in ipairs(horse_likes) do
|
||||
if i == item_name then
|
||||
wants = true
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
wants = item_name == horse_likes
|
||||
end
|
||||
|
||||
if wants then
|
||||
local fills = fill_values[item_name]
|
||||
if not fills then fills = 1 end
|
||||
|
||||
self.appetite = self.appetite - fills
|
||||
core.sound_play("whinny_apple_bite", {object=self.object})
|
||||
|
||||
if not whinny.creative then
|
||||
item:take_item()
|
||||
clicker:set_wielded_item(item)
|
||||
end
|
||||
|
||||
if self.appetite <= 0 then
|
||||
|
||||
-- replace with tamed horse
|
||||
core.add_entity(self.object:get_pos(),
|
||||
"whinny:horse_" .. color .. "_tame",
|
||||
core.serialize({owner=pname}))
|
||||
self.object:remove()
|
||||
|
||||
core.chat_send_player(pname, "This horse is now tame!")
|
||||
return
|
||||
else
|
||||
core.chat_send_player(pname, "This horse is still hungry. Keep feeding it.")
|
||||
end
|
||||
else
|
||||
-- can't ride wild horses
|
||||
core.chat_send_player(pname, "This horse is too wild to ride. Try feeding it.")
|
||||
end
|
||||
end,
|
||||
|
||||
jump = true,
|
||||
step = 1,
|
||||
passive = true,
|
||||
})
|
||||
end
|
||||
|
||||
-- ===================================================================
|
||||
|
||||
local function register_basehorse(name, craftitem, horse)
|
||||
if craftitem ~= nil then
|
||||
function craftitem.on_place(itemstack, placer, pointed_thing)
|
||||
if pointed_thing.above then
|
||||
core.add_entity(pointed_thing.above, name, core.serialize({owner=placer:get_player_name()}))
|
||||
|
||||
if not whinny.creative then
|
||||
itemstack:take_item()
|
||||
end
|
||||
end
|
||||
|
||||
return itemstack
|
||||
end
|
||||
|
||||
core.register_craftitem(name, craftitem)
|
||||
end
|
||||
|
||||
function horse:set_animation(type)
|
||||
if type == self.animation_current then return end
|
||||
|
||||
if type == self.animation.mode_stand then
|
||||
if self.animation.stand_start and self.animation.stand_end and self.animation.speed_normal then
|
||||
self.object:set_animation(
|
||||
{x=self.animation.stand_start,y=self.animation.stand_end},
|
||||
self.animation.speed_normal * 0.6,
|
||||
0
|
||||
)
|
||||
self.animation_current = self.animation.mode_stand
|
||||
end
|
||||
elseif type == self.animation.mode_walk then
|
||||
if self.animation.walk_start and self.animation.walk_end and self.animation.speed_normal then
|
||||
self.object:set_animation(
|
||||
{x=self.animation.walk_start,y=self.animation.walk_end},
|
||||
self.animation.speed_normal * 3,
|
||||
0
|
||||
)
|
||||
self.animation_current = self.animation.mode_walk
|
||||
end
|
||||
elseif type == self.animation.mode_gallop then
|
||||
if self.animation.gallop_start and self.animation.gallop_end and self.animation.speed_normal then
|
||||
self.object:set_animation(
|
||||
{x=self.animation.gallop_start,y=self.animation.gallop_end},
|
||||
self.animation.speed_normal * 2,
|
||||
0
|
||||
)
|
||||
self.animation_current = self.animation.mode_gallop
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- sounds played while riding
|
||||
horse.gallop_handle_1 = -1
|
||||
horse.gallop_handle_2 = -1
|
||||
|
||||
function horse:stop_gallop()
|
||||
if self.gallop_handle_1 > -1 then
|
||||
core.sound_stop(self.gallop_handle_1)
|
||||
self.gallop_handle_1 = -1
|
||||
end
|
||||
|
||||
if self.gallop_handle_2 > -1 then
|
||||
core.sound_stop(self.gallop_handle_2)
|
||||
self.gallop_handle_2 = -1
|
||||
end
|
||||
|
||||
return self.gallop_handle_1 < 0 and self.gallop_handle_2 < 0
|
||||
end
|
||||
|
||||
function horse:start_gallop(stage)
|
||||
local to_play = "whinny_gallop_0" .. tostring(stage)
|
||||
if stage == 1 and self.gallop_handle_1 < 0 then
|
||||
self.gallop_handle_1 = core.sound_play(to_play, {object=self.object, loop=true})
|
||||
return self.gallop_handle_1 >= 0
|
||||
elseif stage == 2 and self.gallop_handle_2 < 0 then
|
||||
self.gallop_handle_2 = core.sound_play(to_play, {object=self.object, loop=true})
|
||||
return self.gallop_handle_2 >= 0
|
||||
end
|
||||
end
|
||||
|
||||
function horse:on_step(dtime)
|
||||
local p = self.object:get_pos()
|
||||
p.y = p.y - 0.1
|
||||
local on_ground = is_ground(p)
|
||||
local inside_block = self.object:get_pos()
|
||||
inside_block.y = inside_block.y + 1
|
||||
|
||||
self.speed = get_speed(self.object:get_velocity())*get_sign(self.speed)
|
||||
|
||||
if self.driver then
|
||||
-- galloping sounds
|
||||
if self.speed > 5 then
|
||||
if handle_is_playing(self.gallop_handle_1) then self:stop_gallop() end
|
||||
|
||||
if not handle_is_playing(self.gallop_handle_2) then
|
||||
if not self:start_gallop(2) then
|
||||
whinny.log("warning", "Failed to start gallop stage 2 sound")
|
||||
end
|
||||
end
|
||||
elseif self.speed > 1 then
|
||||
if handle_is_playing(self.gallop_handle_2) then self:stop_gallop() end
|
||||
|
||||
if not handle_is_playing(self.gallop_handle_1) then
|
||||
if not self:start_gallop(1) then
|
||||
whinny.log("warning", "Failed to start gallop stage 1 sound")
|
||||
end
|
||||
end
|
||||
else
|
||||
if handle_is_playing(self.gallop_handle_1) or handle_is_playing(self.gallop_handle_2) then
|
||||
if not self:stop_gallop() then
|
||||
whinny.log("warning", "Failed to stop gallop sounds")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- driver controls
|
||||
local ctrl = self.driver:get_player_control()
|
||||
|
||||
-- rotation (the faster we go, the less we rotate)
|
||||
if whinny.enable_mouse_ctrl then
|
||||
-- FIXME: turning should be gradual
|
||||
local driver_look = self.driver:get_look_horizontal()
|
||||
if driver_look then
|
||||
self.object:set_yaw(driver_look - rot_compensate)
|
||||
end
|
||||
else
|
||||
if ctrl.left then
|
||||
self.object:set_yaw(self.object:get_yaw()+2*(1.5-math.abs(self.speed/self.max_speed))*math.pi/90 +dtime*math.pi/90)
|
||||
end
|
||||
if ctrl.right then
|
||||
self.object:set_yaw(self.object:get_yaw()-2*(1.5-math.abs(self.speed/self.max_speed))*math.pi/90 -dtime*math.pi/90)
|
||||
end
|
||||
end
|
||||
|
||||
-- jumping (only if on ground)
|
||||
if ctrl.jump then
|
||||
if on_ground then
|
||||
local v = self.object:get_velocity()
|
||||
local vel = 3
|
||||
--v.y = (self.jump_speed or 3)
|
||||
v.y = vel
|
||||
self.object:set_velocity(v)
|
||||
end
|
||||
end
|
||||
|
||||
-- forwards/backwards
|
||||
if ctrl.up then
|
||||
self.speed = self.speed + self.forward_boost
|
||||
elseif ctrl.down then
|
||||
if self.speed >= -2.9 then
|
||||
self.speed = self.speed - self.forward_boost
|
||||
end
|
||||
elseif self.speed < 3 then
|
||||
--decay speed if not going fast
|
||||
self.speed = self.speed * 0.85
|
||||
end
|
||||
else
|
||||
if math.abs(self.speed) < 1 then
|
||||
self.speed = 0
|
||||
else
|
||||
self.speed = self.speed * 0.90
|
||||
end
|
||||
|
||||
if self.sounds and self.sounds.random and math.random(1, 100) <= 1 then
|
||||
local to_play = self.sounds.random.stand
|
||||
if to_play then
|
||||
core.sound_play(to_play.name, {object=self.object, to_play.gain})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if math.abs(self.speed) < 1 then
|
||||
self.object:set_velocity({x=0,y=0,z=0})
|
||||
self:set_animation(self.animation.mode_stand)
|
||||
elseif self.speed > 5 then
|
||||
self:set_animation(self.animation.mode_gallop)
|
||||
else
|
||||
self:set_animation(self.animation.mode_walk)
|
||||
end
|
||||
|
||||
-- make sure we don't go past the limit
|
||||
if core.get_item_group(core.get_node(inside_block).name, "water") ~= 0 then
|
||||
if math.abs(self.speed) > self.max_speed * .2 then
|
||||
self.speed = self.max_speed*get_sign(self.speed)*.2
|
||||
end
|
||||
else
|
||||
if math.abs(self.speed) > self.max_speed then
|
||||
self.speed = self.max_speed*get_sign(self.speed)
|
||||
end
|
||||
end
|
||||
|
||||
local p = self.object:get_pos()
|
||||
p.y = p.y+1
|
||||
if not is_ground(p) then
|
||||
if core.registered_nodes[core.get_node(p).name].walkable then
|
||||
self.speed = 0
|
||||
end
|
||||
self.object:set_acceleration({x=0, y=-10, z=0})
|
||||
self.object:set_velocity(get_velocity(self.speed, self.object:get_yaw(), self.object:get_velocity().y))
|
||||
else
|
||||
self.object:set_acceleration({x=0, y=0, z=0})
|
||||
-- falling
|
||||
if math.abs(self.object:get_velocity().y) < 1 then
|
||||
local pos = self.object:get_pos()
|
||||
pos.y = math.floor(pos.y)+0.5
|
||||
self.object:set_pos(pos)
|
||||
self.object:set_velocity(get_velocity(self.speed, self.object:get_yaw(), 0))
|
||||
else
|
||||
self.object:set_velocity(get_velocity(self.speed, self.object:get_yaw(), self.object:get_velocity().y))
|
||||
end
|
||||
end
|
||||
|
||||
if self.object:get_velocity().y > 0.1 then
|
||||
local yaw = self.object:get_yaw()
|
||||
if self.drawtype == "side" then
|
||||
yaw = yaw+(math.pi/2)
|
||||
end
|
||||
local x = math.sin(yaw) * -2
|
||||
local z = math.cos(yaw) * 2
|
||||
if core.get_item_group(core.get_node(inside_block).name, "water") ~= 0 then
|
||||
self.object:set_acceleration({x = x, y = .1, z = z})
|
||||
else
|
||||
self.object:set_acceleration({x = x, y = -10, z = z})
|
||||
end
|
||||
else
|
||||
if core.get_item_group(core.get_node(inside_block).name, "water") ~= 0 then
|
||||
self.object:set_acceleration({x = 0, y = .1, z = 0})
|
||||
else
|
||||
self.object:set_acceleration({x = 0, y = -10, z = 0})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function horse:on_rightclick(clicker)
|
||||
if not clicker or not clicker:is_player() then return end
|
||||
|
||||
if self.driver and clicker == self.driver then
|
||||
self.driver = nil
|
||||
clicker:set_detach()
|
||||
clicker:set_eye_offset({x=0, y=0, z=0}, {x=0, y=0, z=0})
|
||||
|
||||
-- stop galloping sounds
|
||||
if handle_is_playing(self.gallop_handle_1) or handle_is_playing(self.gallop_handle_2) then
|
||||
if not self:stop_gallop() then
|
||||
whinny.log("warning", "Failed to stop gallop sounds")
|
||||
end
|
||||
end
|
||||
elseif not self.driver then
|
||||
local pname = clicker:get_player_name()
|
||||
|
||||
local wielded = clicker:get_wielded_item():get_name()
|
||||
-- FIXME: use other items if "mobs:lasso" not available (or any item named "lasso")
|
||||
if wielded == "mobs:lasso" then
|
||||
if self.owner and self.owner ~= pname then
|
||||
core.chat_send_player(pname, "You cannot take " .. self.owner .. "'s horse")
|
||||
else
|
||||
local inv = clicker:get_inventory()
|
||||
local stack = ItemStack(self.name)
|
||||
if not inv:room_for_item("main", stack) then
|
||||
core.chat_send_player(pname, "You do not have room in your inventory")
|
||||
else
|
||||
inv:add_item("main", stack)
|
||||
self.object:remove()
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if self.owner and self.owner ~= pname then
|
||||
core.chat_send_player(pname, "You cannot ride " .. self.owner .. "'s horse")
|
||||
return true
|
||||
end
|
||||
|
||||
self.driver = clicker
|
||||
clicker:set_attach(self.object, "", {x=0,y=18,z=0}, {x=0,y=90,z=0})
|
||||
clicker:set_eye_offset({x=0, y=8, z=0}, {x=0, y=0, z=0})
|
||||
-- face same direction as horse
|
||||
clicker:set_look_horizontal(self.object:get_yaw() + rot_compensate) -- FIXME: no idea why I need to add compensation
|
||||
end
|
||||
end
|
||||
|
||||
function horse:on_activate(staticdata, dtime_s)
|
||||
self.object:set_armor_groups({fleshy=100})
|
||||
if staticdata then
|
||||
local data = core.deserialize(staticdata)
|
||||
if data then
|
||||
self.speed = data.speed
|
||||
self.owner = data.owner
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function horse:get_staticdata()
|
||||
local data = {
|
||||
speed = self.speed,
|
||||
owner = self.owner,
|
||||
}
|
||||
|
||||
return core.serialize(data)
|
||||
end
|
||||
|
||||
function horse:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
|
||||
if puncher:is_player() then
|
||||
local pname = puncher:get_player_name()
|
||||
|
||||
-- don't allow owned horses to be killed or owned by other players
|
||||
if self.owner and pname ~= self.owner then
|
||||
core.chat_send_player(pname, "Don't kill " .. self.owner .. "'s horse!!!")
|
||||
return true
|
||||
end
|
||||
|
||||
-- don't damage your own horse while mounted
|
||||
-- FIXME: horse still flashes
|
||||
if self.driver then return true end
|
||||
end
|
||||
|
||||
core.sound_play("player_damage", {object=self.object,})
|
||||
if self.sounds and self.sounds.on_damage then
|
||||
core.sound_play(self.sounds.on_damage.name,
|
||||
{object=self.object, self.sounds.on_damage.gain})
|
||||
end
|
||||
end
|
||||
|
||||
core.register_entity(name, horse)
|
||||
end
|
||||
|
||||
local function register_tamehorse(color, description)
|
||||
register_basehorse("whinny:horse_" .. color .. "_tame",
|
||||
{
|
||||
description = description,
|
||||
inventory_image = "whinny_horse_" .. color .. "_inv.png",
|
||||
},
|
||||
{
|
||||
physical = true,
|
||||
collisionbox = {-.5, -0.01, -.5, .5, 1.4, .5},
|
||||
visual = "mesh",
|
||||
stepheight = 1.1,
|
||||
visual_size = {x=1, y=1},
|
||||
mesh = "horse.x",
|
||||
textures = {"whinny_horse_" .. color .. "_mesh.png"},
|
||||
sounds = {
|
||||
on_damage = sounds.distress,
|
||||
on_death = sounds.snort2,
|
||||
random = {
|
||||
stand = sounds.snort1,
|
||||
walk = sounds.neigh,
|
||||
}
|
||||
},
|
||||
animation = {
|
||||
speed_normal = 20,
|
||||
stand_start = 300,
|
||||
stand_end = 460,
|
||||
walk_start = 10,
|
||||
walk_end = 59,
|
||||
gallop_start = 70,
|
||||
gallop_end = 119,
|
||||
mode_stand = 1,
|
||||
mode_walk = 2,
|
||||
mode_gallop = 3,
|
||||
},
|
||||
animation_current = 0,
|
||||
max_speed = 7,
|
||||
forward_boost = .2,
|
||||
jump_boost = 4,
|
||||
speed = 0,
|
||||
driver = nil,
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
local spawn_nodes = {
|
||||
"default:dirt_with_grass",
|
||||
"default:dirt_with_dry_grass",
|
||||
}
|
||||
|
||||
for color, name in pairs({["brown"]="Brown Horse", ["white"]="White Horse", ["black"]="Black Horse",}) do
|
||||
register_tamehorse(color, name)
|
||||
register_wildhorse(color)
|
||||
|
||||
whinny:register_spawn("whinny:horse_" .. color, spawn_nodes, 20, 6,
|
||||
whinny.spawn_chance, 1, whinny.spawn_height_min, whinny.spawn_height_max)
|
||||
|
||||
-- to simplify item handling
|
||||
core.register_alias("whinny:horse_" .. color, "whinny:horse_" .. color .. "_tame")
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
whinny = {}
|
||||
whinny.modname = core.get_current_modname()
|
||||
whinny.modpath = core.get_modpath(whinny.modname)
|
||||
|
||||
whinny.log = function(lvl, msg)
|
||||
if not msg then
|
||||
msg = lvl
|
||||
lvl = nil
|
||||
end
|
||||
|
||||
msg = "[" .. whinny.modname .. "] " .. msg
|
||||
|
||||
if not lvl then
|
||||
core.log(msg)
|
||||
else
|
||||
core.log(lvl, msg)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local scripts = {
|
||||
"settings",
|
||||
"api",
|
||||
"horse",
|
||||
}
|
||||
|
||||
for _, script in ipairs(scripts) do
|
||||
dofile(whinny.modpath .. "/" .. script .. ".lua")
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
name = whinny
|
||||
version = 1.0
|
||||
depends = default
|
||||
optional_depends = mobs, farming
|
After Width: | Height: | Size: 674 KiB |
|
@ -0,0 +1,24 @@
|
|||
|
||||
-- common settings
|
||||
|
||||
whinny.creative = core.settings:get_bool("creative_mode", false)
|
||||
|
||||
whinny.enable_damage = core.settings:get_bool("enable_damage", true)
|
||||
|
||||
|
||||
-- mobs_redo settings
|
||||
|
||||
whinny.display_spawn = core.settings:get_bool("display_mob_spawn", false) -- deprecated?
|
||||
|
||||
|
||||
-- unique settings
|
||||
|
||||
whinny.peaceful_only = core.settings:get_bool("whinny.peaceful_only", true)
|
||||
|
||||
whinny.spawn_chance = tonumber(core.settings:get("whinny.spawn_chance") or 50000)
|
||||
|
||||
whinny.spawn_height_min = tonumber(core.settings:get("whinny.spawn_height_min") or -500)
|
||||
|
||||
whinny.spawn_height_max = tonumber(core.settings:get("whinny.spawn_height_max") or 500)
|
||||
|
||||
whinny.enable_mouse_ctrl = core.settings:get_bool("whinny.enable_mouse_ctrl", true)
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
# If enabled, messages all players when spawned.
|
||||
display_mob_spawn (Display spawn) bool false
|
||||
|
||||
# If disabled, whinny will attack players.
|
||||
whinny.peaceful_only (Only peaceful) bool true
|
||||
|
||||
# Inverted chance that entity will spawn.
|
||||
whinny.spawn_chance (Spawn chance) int 50000 0
|
||||
|
||||
# Minimum height at which entity can spawn.
|
||||
whinny.spawn_height_min (Spawn min height) int -500
|
||||
|
||||
# Maximum height at which entity can spawn.
|
||||
whinny.spawn_height_max (Spawn max height) int 500
|
||||
|
||||
# Use mouse to control direction instead of a/d keys.
|
||||
whinny.enable_mouse_ctrl (Turn with mouse) bool true
|
|
@ -1,25 +1,35 @@
|
|||
|
||||
- creatures_apple_bite:
|
||||
- whinny_apple_bite:
|
||||
- author: sonicmariobrotha
|
||||
- license: CC0
|
||||
- link: https://freesound.org/people/sonicmariobrotha/sounds/333825/
|
||||
|
||||
- creatures_horse_neigh_01:
|
||||
- whinny_gallop_01:
|
||||
- author: Alan McKinney (alanmcki)
|
||||
- license: CC BY 3.0
|
||||
- link: https://freesound.org/people/alanmcki/sounds/403026/
|
||||
|
||||
- whinny_gallop_02:
|
||||
- author: Alan McKinney (alanmcki)
|
||||
- license: CC BY 3.0
|
||||
- link: https://freesound.org/people/alanmcki/sounds/403025/
|
||||
|
||||
- whinny_horse_neigh_01:
|
||||
- author: GoodListener
|
||||
- license: CC BY 3.0
|
||||
- link: https://freesound.org/people/GoodListener/sounds/322443/
|
||||
|
||||
- creatures_horse_neigh_02:
|
||||
- whinny_horse_neigh_02:
|
||||
- author: foxen10
|
||||
- license: CC0
|
||||
- link: https://freesound.org/people/foxen10/sounds/149024/
|
||||
|
||||
- creatures_horse_snort_01:
|
||||
- whinny_horse_snort_01:
|
||||
- author: madklown
|
||||
- license: CC0
|
||||
- link: https://freesound.org/people/madklown/sounds/184503/
|
||||
|
||||
- creatures_horse_snort_02:
|
||||
- whinny_horse_snort_02:
|
||||
- author: 0_ciz
|
||||
- license: CC0
|
||||
- link: https://freesound.org/people/o_ciz/sounds/475480/
|
After Width: | Height: | Size: 656 B |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 795 B After Width: | Height: | Size: 795 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 660 B |
After Width: | Height: | Size: 1.8 KiB |
|
@ -139,27 +139,6 @@ ghost.spawn_interval (Spawn interval) int 40 1
|
|||
ghost.spawn_chance (Spawn chance) int 7300 1
|
||||
|
||||
|
||||
[**horse]
|
||||
|
||||
# Entity lifespan.
|
||||
#
|
||||
# type: int
|
||||
# default: 300
|
||||
horse.lifetime (Lifespan) int 300 1
|
||||
|
||||
# Spawn interval in seconds.
|
||||
#
|
||||
# type: int
|
||||
# default: 600 (10 minutes)
|
||||
horse.spawn_interval (Spawn interval) int 600 1
|
||||
|
||||
# Chance of spawn at interval.
|
||||
#
|
||||
# type: int
|
||||
# default: 9000
|
||||
horse.spawn_chance (Spawn chance) int 9000 1
|
||||
|
||||
|
||||
[**kitten]
|
||||
|
||||
# Spawn interval in seconds.
|
||||
|
@ -298,6 +277,27 @@ mobs.velociraptor_max_height (Velociraptor max spawn height) int 31000
|
|||
mobs.velociraptor_spawn_time (Velociraptor spawn time) enum any day,night,any
|
||||
|
||||
|
||||
[**whinny]
|
||||
|
||||
# If enabled, messages all players when spawned.
|
||||
display_mob_spawn (Display spawn) bool false
|
||||
|
||||
# If disabled, whinny will attack players.
|
||||
whinny.peaceful_only (Only peaceful) bool true
|
||||
|
||||
# Inverted chance that entity will spawn.
|
||||
whinny.spawn_chance (Spawn chance) int 50000 0
|
||||
|
||||
# Minimum height at which entity can spawn.
|
||||
whinny.spawn_height_min (Spawn min height) int -500
|
||||
|
||||
# Maximum height at which entity can spawn.
|
||||
whinny.spawn_height_max (Spawn max height) int 500
|
||||
|
||||
# Use mouse to control direction instead of a/d keys.
|
||||
whinny.enable_mouse_ctrl (Turn with mouse) bool true
|
||||
|
||||
|
||||
[*airtanks]
|
||||
|
||||
# Number of uses for steel air tanks.
|
||||
|
|