morph_api/init.lua
2023-08-07 19:27:25 -04:00

369 lines
12 KiB
Lua

morph_api = {
morphs = {},-- table containing morph data
-- the below ones are player data. key is username and value is data
player_scale = {},-- table containing scale data for each player
player = {},-- table containing current morph of player
player_parent = {},-- table containing current morph parent of player
player_properties = {},-- table containing player properties after morphed when not scaled
--smallest_box = {-0.1, 0.0, -0.1, 0.1, 0.2, 0.1}
}
local pads = {}
local plapi = minetest.get_modpath("player_api")
--[[
example of morph definition:
{
-- model defined in player_api
player_model = "example.b3d",
-- model property definition used in set_properties()
properties = {
}
-- one of player_model or properties MUST be set. both may be set
-- properties will override anything set by player_api
-- optional physics override applied upon morphing
physics = [override_table],
-- optional parent morph, properties from the parent are applied to the child
-- morph child properties override parent properties
parent = "Other morph name",
-- whatever you do, do NOT have recursive parents. i dont know what it will do
-- probably crash. so avoid that.
-- optional. privlage required for a player to use a morph pad with this
-- morph. nil means anyone can use it.
use_priv = "VIP_morphs"
-- this can be single priv as a string or priv table
-- optional message shown to user when they don't have use_priv if set.
-- default is "You are not allowed to morph into this."
use_deny_msg = "you must be VIP user to use this"
-- optional. privlage required for a player to see the morph in the morph
-- pad menu and set the morph pad to this morph. nil means anyone can set it.
set_priv = "admin_morphs"
-- this can be single priv as a string or priv table
-- optional custom morph data may be applied for other mods to use.
-- can be any key as long as it does not conflict with the above ones.
_custom_data = [whatever you want],
}
]]
function morph_api.register_morph(name,data)
morph_api.morphs[name] = data
--print(dump(morph_api.morphs))
end
function morph_api.morph(obj,name)
if morph_api.morphs[name] == nil then
return false
end
local player_name = obj:get_player_name()
local parent = false -- this is gonna be the topmost parent
local morph_data = morph_api.morphs[name]
if morph_data.parent then
morph_api.morph(obj,morph_data.parent)
else
parent = name
end
if player_name ~= "" and parent then
morph_api.player_parent[player_name] = parent
end
if player_name ~= "" then
morph_api.player[player_name] = name
end
if morph_data.player_model and plapi then
if player_name == "" then
-- workaround maybe to set non player to a player_api model
obj:set_properties({mesh = morph_data.player_model, visual = "mesh"})
obj:set_properties(player_api.registered_models[morph_data.player_model])
else
--this is a work around because player_api doesnt seem to set these 2 values when setting the model.
local workaround = {}
if player_api.registered_models[morph_data.player_model].collisionbox then
workaround.collisionbox = player_api.registered_models[morph_data.player_model].collisionbox
end
if player_api.registered_models[morph_data.player_model].eye_height then
workaround.eye_height = player_api.registered_models[morph_data.player_model].eye_height
end
player_api.set_model(obj,"Default")
if workaround ~= {} then
obj:set_properties(workaround)
end
player_api.set_model(obj,morph_data.player_model)
end
end
if morph_data.properties then
local props = morph_data.properties
obj:set_properties(props)
end
if morph_data.physics then
local props = morph_data.physics
obj:set_physics_override(props)
end
morph_api.player_properties[player_name] = obj:get_properties()
morph_api.scale(obj,1)
end
function morph_api.scale(obj,amount)
--obj is ObjectRef, anount is float
-- for now will only work on players
if obj == nil then return end
if not obj:is_player() or amount < 0.09 then
return
end
local name = obj:get_player_name()
if not morph_api.player_properties[name] then
return
end
if morph_api.player_scale[name] == amount then
return
end
local f_props = morph_api.player_properties[name]
morph_api.player_scale[name] = amount
local properties = obj:get_properties()
properties.eye_height = math.max(f_props.eye_height*amount,0.1)
properties.stepheight = math.max(f_props.stepheight*amount,0.1)
properties.visual_size = f_props.visual_size*amount
for index, value in ipairs(properties.collisionbox) do
properties.collisionbox[index] = f_props.collisionbox[index]*amount
properties.selectionbox[index] = f_props.collisionbox[index]*amount
end
--set
obj:set_properties(properties)
end
function morph_api.user_allowed(username,morph)
-- returns "" if user IS allowed, returns deny reason if user is denied
if morph == "" or morph == nil then
return "Error: No morph."
end
if morph_api.morphs[morph] == nil then
return "Error: Morph does not exist."
end
if morph_api.morphs[morph].use_priv == nil then
return ""
end
local priv = morph_api.morphs[morph].use_priv
local allow, missing = minetest.check_player_privs(username, priv)
if allow then
return ""
else
return morph_api.morphs[morph].use_deny_msg or "You are not allowed to morph into this."
end
end
function morph_api.place_allowed(username,morph)
-- returns true if user is allowed to place morph. false if denied
if not morph_api.morphs[morph] then return false end
if morph_api.morphs[morph].set_priv == nil then
return true
end
local priv = morph_api.morphs[morph].set_priv
local allow, missing = minetest.check_player_privs(username, priv)
if allow then
return true
else
return false
end
end
local function remove_preview(pos)
local ents = minetest.get_objects_inside_radius(pos,0.1)
for index, value in ipairs(ents) do
if value:get_luaentity()._morph then
value:remove()
end
end
end
local function morph_pad_clicked(pos,clicker)
-- this is run when player clicks a morph pad or morph preview.
-- checks the morph permission data and set morph if allowed.
if minetest.get_node(pos).name ~= "morph_api:morph_pad" then
return
end
local meta = minetest.get_meta(pos)
local name = clicker:get_player_name()
local morph = meta:get_string("morph")
local denymsg = morph_api.user_allowed(name,morph)
if denymsg == "" then
morph_api.morph(clicker,morph)
else
minetest.chat_send_player(name,denymsg)
if denymsg == "Error: Morph does not exist." then
remove_preview(pos)
end
end
end
minetest.register_privilege("morph_admin", {
description = "allowed to break all morph pads",
give_to_singleplayer = true,
give_to_admin = true,
})
minetest.register_chatcommand("morph_dump", {
description = "dumps morph_api data to chat and console",
privs = {morph_admin=true},
func = function (name,_)
print(dump(morph_api))
minetest.chat_send_player(name,dump(morph_api))
end
})
local function entity_interacted(self, clicker)
local pos = self.object:get_pos()
if clicker == nil or not clicker:is_player() then
return
end
if minetest.get_node(pos).name == "morph_api:morph_pad" then
morph_pad_clicked(pos,clicker)
else
self.object:remove()
end
end
minetest.register_entity("morph_api:preview", {
_morph = true, --used for checking if entity is morph
on_activate = function (self, staticdata, dtime_s)
self.object:set_armor_groups({fleshy=1, immortal=1})
local pos = self.object:get_pos()
local meta = minetest.get_meta(pos)
if meta:get_string("morph") ~= "" then
local morph_name = meta:get_string("morph")
morph_api.morph(self.object,morph_name)
self.object:set_properties({infotext = morph_name..
" morph\nRight click or double tap to transform."})
else
self.object:remove()
end
self.object:set_yaw(meta:get_float("rotate"))
end,
on_rightclick = entity_interacted,
on_punch = entity_interacted,
})
local function pad_placed(pos, placer, itemstack, pointed_thing)
local name = placer:get_player_name()
local meta = minetest.get_meta(pos)
meta:set_string("owner",name)
meta:set_string("infotext",name.."'s morph")
local dropdown = ""
pads[name] = pos
-- this is to sort them in the menu
local tkeys = {}
for k in pairs(morph_api.morphs) do table.insert(tkeys, k) end
table.sort(tkeys)
--
for _, key in ipairs(tkeys) do
print(key)
-- only show morph in menu if user is allowed to place it
if morph_api.place_allowed(name,key) then
dropdown = dropdown ..",".. key
end
end
dropdown = string.sub(dropdown,2)
--meta:set_string(key, value)
minetest.show_formspec(placer:get_player_name(), "set_morph",
"formspec_version[4]size[8,4]position[0.5,0.3]label[0.375,0.5;Select morph ok?]"..
"dropdown[0.5,2.0;7,0.5;morph;"..dropdown..";1]"..
"button_exit[0.5,3.0;7,0.5;set;Set]")
end
local function pad_can_dig(pos, player)
local meta = minetest.env:get_meta(pos)
local ownername = meta:get_string("owner")
local playername = player:get_player_name()
if ownername == "" or ownername == nil or playername == ownername
or minetest.get_player_privs(playername).morph_admin then
remove_preview(pos)
return true
end
minetest.chat_send_player(playername, "You may not break this.")
return false
end
minetest.register_node("morph_api:morph_pad", {
description = "Morph Pad",
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {-0.5, -0.5, -0.5, 0.5, -0.25, 0.5},
},
groups = {choppy = 1},
sunlight_propagates = true,
paramtype = "light",
after_place_node = pad_placed,
tiles = {"morph_api_morph_pad.png"},
can_dig = pad_can_dig,
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
morph_pad_clicked(pos,clicker)
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
local name = player:get_player_name()
local pos
if formname ~= "set_morph" then
return
end
if not fields.morph then
return
end
if pads[name] then
pos = pads[name]
else
return
end
if not minetest.get_node(pos).name == "morph_api:morph_pad" then
return
end
local morph = fields.morph
if not morph_api.place_allowed(name,morph) then
print("!!!!! user ".. name.. " attempted to place denied morph "..morph)
return
end
if morph_api.morphs[fields.morph] and fields.set then
if minetest.get_node(pos).name == "morph_api:morph_pad" then
local meta = minetest.get_meta(pos)
meta:set_string("morph",fields.morph)
print(dump(player:get_look_horizontal()))
meta:set_float("rotate", player:get_look_horizontal() - math.rad(180))
meta:set_string("infotext",name.."'s "..fields.morph.." morph")
local ent = minetest.add_entity(pos, "morph_api:preview")
end
pads[name] = nil
end
end)
-- register default morph
if plapi then
morph_api.register_morph("Default",{
player_model = "character.b3d",
physics = {speed = 1, jump = 1, gravity = 1, sneak = true}
})
minetest.register_on_joinplayer(function(ObjectRef, last_login)
local name = ObjectRef:get_player_name()
morph_api.player[name] = "Default"
morph_api.player_parent[name] = "Default"
morph_api.morph(ObjectRef,"Default")
end)
end
minetest.register_on_leaveplayer(function(ObjectRef, timed_out)
local name = ObjectRef:get_player_name()
morph_api.player_scale[name] = nil
morph_api.player[name] = nil
morph_api.player_parent[name] = nil
morph_api.player_properties[name] = nil
end)