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)