-- NB: This mod is intended for use with the Minimal game and expects -- a singlenode mapgen. See README.txt for licensing information. local entity_props = { paniki = { collisionbox = { -0.4, -0.2, -0.4, 0.4, 0.3, 0.4 }, textures = { "mobs_paniki.png" }, mesh = "paniki.b3d", yaw_origin = -math.pi / 2, enable_swimming = false, enable_climbing = false, }, reindeer = { collisionbox = { -0.3, -0.1, -0.3, 0.3, 1.4, 0.3 }, textures = { "mobs_reindeer.png" }, mesh = "reindeer.b3d", yaw_origin = 0, enable_swimming = true, enable_climbing = true, }, } local presets = { ["(Custom)"] = { }, ["(Reset)"] = { "now: set_pos(home)", "now: unlock_velocity()", "now: set_velocity(none)", "now: set_rotation(none)", "now: set_acceleration(none)", }, ["Big square fly"] = { "now: set_pos(vec(8,5,8))", "now: set_yaw(rad90)", "now: set_acceleration_vert()", "now: set_speed(4)", "after 4: add_yaw(rad90)", "after 4: add_yaw(rad90)", "after 4: add_yaw(rad90)", "after 4: set_speed(0)", }, ["Big circle fly"] = { "now: set_pos(vec(0,5,8))", "now: set_yaw(rad90)", "now: set_acceleration_vert()", "now: set_speed(4)", "now: turn_by(rad90,3,4)", "after 12: set_speed(0)", }, ["Big diamond fly"] = { "now: set_pos(vec(0,5,-8))", "now: set_yaw(-rad45)", "now: set_acceleration_vert()", "now: set_speed(4)", "after 2.8: add_yaw(rad90)", "after 2.8: add_yaw(rad90)", "after 2.8: add_yaw(rad90)", "after 2.8: set_speed(0)", }, ["Roundabounce"] = { "now: set_pos(vec(0,2,8))", "now: set_yaw(rad90)", "now: set_acceleration_vert(-9)", "now: set_speed(2.5)", "now: turn_by(rad45,1,8)", "now: set_velocity_vert(4)", "after 1: set_velocity_vert(4)", "after 1: set_velocity_vert(4)", "after 1: set_velocity_vert(4)", "after 1: set_velocity_vert(4)", "after 1: set_velocity_vert(4)", "after 1: set_velocity_vert(4)", "after 1: set_velocity_vert(4)", "after 1: set_speed(0)", }, ["Zig zaggy fun"] = { "now: set_pos(vec(8,5,5))", "now: set_yaw(rad90)", "now: set_acceleration_vert()", "now: set_speed(4)", "after 4: turn_by(rad180,1)", "after 4.5: turn_by(-rad180,1)", "after 4.5: turn_by(rad180,1)", "after 4.5: turn_by(-rad180,1)", "after 4.5: set_speed(0)", }, ["Lots of collisions"] = { "now: set_pos(vec(0,5,0))", "now: set_yaw()", "now: set_acceleration_vert(-9)", "now: unlock_velocity()", "after 1: set_velocity_horz(-8,0)", "after 1: set_velocity_horz(8,0)", "after 1: set_velocity_horz(-8,0)", "after 1: set_velocity_horz(0,-8)", "after 2: set_velocity_horz(12,0)", "after 2: set_velocity_horz(-12,12)", "after 2: set_velocity_horz(12,0)", "after 2: set_velocity_horz(-12,-12)", "after 2: set_velocity_horz(12,0)", "after 2: set_velocity_horz(-12,0)", }, ["Drown in water"] = { "now: set_pos(vec(6,4.5,0))", "now: set_yaw()", "now: set_acceleration_vert()", "now: unlock_velocity()", "now: set_velocity(vec(0,-0.2,0))", "after 15: set_velocity_vert(0.2)", "after 1.5: set_velocity_vert(-0.2)", "after 3: set_velocity_vert(0.2)", "after 15: set_velocity_vert()", }, ["Back and forth"] = { "now: set_pos(vec(-8,2,4))", "now: set_yaw(rad90)", "now: set_acceleration_vert(-9)", "now: set_speed_lateral(0,-2.5)", "after 2: turn_by(rad90,2)", "after 2: set_speed(0)", "after 1.5: set_speed(4)", "after 3.5: set_speed(0)", "after 1.5: set_speed(-2.5)", "now: turn_by(-rad90,2)", "after 4: set_speed(0)", } } local preset_list = { "(Custom)", "(Reset)", "Big square fly", "Big circle fly", "Big diamond fly", "Roundabounce", "Zig zaggy fun", "Lots of collisions", "Drown in water", "Back and forth" } local is_running = false local is_lagging = false local player = nil local avatar = nil local player_huds = nil local script = nil ------------------------------------- local pi = math.pi local format = string.format local fs = minetest.formspec_escape local _ = nil local function is_match( text, glob ) -- use underscore variable to preserve captures _ = { string.match( text, glob ) } return #_ > 0 end local function printf( ... ) minetest.chat_send_all( string.format( ... ) ) end local function pos_to_str( vec, is_int ) return format( is_int and "(%d,%d,%d)" or "(%0.1f,%0.1f,%0.1f)", vec.x, vec.y, vec.z ) end local function rot_to_str( vec ) return format( "(%0.1f,%0.1f,%0.1f)", vec.x / pi * 180, vec.y / pi * 180, vec.z / pi * 180 ) end local function join( list, sep, func ) local str = "" for i, v in ipairs( list ) do local res = func( i, v ) if res ~= nil then str = i > 1 and str .. sep .. res or str .. res end end return str end local function sanitize( buf ) return string.trim( string.gsub( buf, ".", { ["\r"] = "\n", ["\t"] = " ", ["\f"] = " ", ["\b"] = " " } ) ) .. "\n" end local function update_hud( idx, ... ) player:hud_change( player_huds[ idx ], "text", format( ... ) ) end local function create_hud( ) player_huds = { -- hud background player:hud_add( { hud_elem_type = "image", text = "default_cloud.png^[colorize:#000066BB", scale = { x = -15, y = -31 }, position = { x = 0.05, y = 0.25 }, alignment = { x = 1, y = 1 }, } ), player:hud_add( { hud_elem_type = "image", text = "default_cloud.png^[colorize:#006600BB", scale = { x = -15, y = -38 }, position = { x = 0.05, y = 0.56 }, alignment = { x = 1, y = 1 }, } ), player:hud_add( { hud_elem_type = "image", text = "default_cloud.png^[colorize:#006666BB", scale = { x = -40, y = -15 }, position = { x = 0.30, y = 0.70 }, alignment = { x = 1, y = 1 }, } ), -- hud foreground player:hud_add( { -- pos, rot, new_vel, old_vel hud_elem_type = "text", text = "", position = { x = 0.05, y = 0.25 }, scale = { x = -15, y = -31 }, number = 0xFFFFFF, alignment = { x = 1, y = 1 }, offset = { x = 8, y = 8 } } ), player:hud_add( { -- collides_xz, collides_y, is_standing, is_swimming, is_climbing hud_elem_type = "text", text = "", position = { x = 0.05, y = 0.56 }, scale = { x = -15, y = -38 }, number = 0xFFFFFF, alignment = { x = 1, y = 1 }, offset = { x = 8, y = 8 } } ), player:hud_add( { -- touched_objects hud_elem_type = "text", text = "", position = { x = 0.30, y = 0.70 }, scale = { x = -15, y = -15 }, number = 0xFFFFFF, alignment = { x = 1, y = 1 }, offset = { x = 8, y = 8 } } ), player:hud_add( { -- collisions hud_elem_type = "text", text = "", position = { x = 0.50, y = 0.70 }, scale = { x = -15, y = -15 }, number = 0xFFFFFF, alignment = { x = 1, y = 1 }, offset = { x = 0, y = 8 } } ), } end local function get_formspec( preset_name ) local script = presets[ preset_name ] local formspec = "size[10.0,8.0]" .. "real_coordinates[true]" .. format( "textarea[0.5,0.8;9.0,5.8;script;Enter a script for this object:;%s]", join( script, "\n", function ( i, v ) return fs( v ) end ) ) .. "button_exit[7.5,6.9;2.0,0.7;run;Run]" .. "label[0.5,7.2;Preset:]" .. format( "dropdown[1.8,6.9;5.0,0.7;presets;%s;%d", join( preset_list, ",", function ( i, v ) return fs( v ) end ), table.indexof( preset_list, preset_name ) ) return formspec end local function execute( expr ) local func = loadstring( "return obj:" .. expr ) if func then setfenv( func, { obj = avatar, pi = math.pi, inf = math.huge, nan = 0/0, vec = vector.new, home = { x = 0, y = 5, z = 0 }, none = { x = 0, y = 0, z = 0 }, rad360 = 2 * pi, rad180 = pi, rad90 = pi / 2, rad60 = pi / 3, rad45 = pi / 4, rad30 = pi / 6, rad20 = pi / 9, } ) local status, err = pcall( func ) if not status then minetest.chat_send_all( "[Avatar] Execution failed: " .. err ) return false end if err then minetest.chat_send_all( "[Avatar] Result: " .. tostring( err ) ) end return true else minetest.chat_send_all( "[Avatar] Syntax error in expression." ) return false end end local function execute_script( script ) presets[ "(Custom)" ] = script -- save script to presets local lines = { } for i, v in ipairs( script ) do local expr if is_match( v, "^now: (.+)" ) then table.insert( lines, { expr = _[ 1 ] } ) elseif is_match( v, "^after (%d+): (.+)" ) or is_match( v, "^after (%d+%.%d): (.+)" ) then table.insert( lines, { time = tonumber( _[ 1 ] ), expr = _[ 2 ] } ) else minetest.chat_send_all( format( "[Avatar] Invalid statement on line %d.", i ) ) return end end local function next_step( idx ) while lines[ idx ] do local cur_line = lines[ idx ] if cur_line.time then minetest.after( cur_line.time, function ( ) cur_line.time = nil next_step( idx ) end ) return else minetest.chat_send_all( format( "[Avatar] Line %d: %s", idx, cur_line.expr ) ) if not execute( cur_line.expr ) then is_running = false return end end idx = idx + 1 end is_running = false minetest.chat_send_all( "[Avatar] Script completed." ) end is_running = true next_step( 1 ) end ------------------------------------- minetest.register_entity( "extend_entity:avatar",{ hp_max = 1, physical = true, static_save = true, description = "Avatar", collisionbox = { -0.2, -0.2, -0.2, 0.2, 1.2, 0.2 }, visual = "mesh", visual_size = { x = 1, y = 1 }, collide_with_objects = true, collision_checks = { }, immersion_checks = { "default:water" }, on_step = function( self, dtime, pos, rot, new_vel, old_vel, res ) local obj = self.object if avatar == obj then if is_lagging then if math.random( 10 ) == 1 then -- Stall process for 0.3 secs local t = os.clock( ) while os.clock( ) - t <= 0.3 do end end end update_hud( 4, "pos:\n%s\n\nrot:\n%s\n\nnew_vel:\n%s\n\nold_vel:\n%s", pos_to_str( pos ), rot_to_str( rot ), pos_to_str( new_vel ), pos_to_str( old_vel ) ) update_hud( 5, "is_standing:\n%s\n\nis_swimming:\n%s\n\nis_climbing:\n%s\n\ncollides_xz\n%s\n\ncollides_y\n%s", tostring( res.is_standing ), tostring( res.is_swimming ), tostring( res.is_climbing ), tostring( res.collides_xz ), tostring( res.collides_y ) ) update_hud( 6, "touched_objects:\n%s", join( res.touched_objects, "\n", function ( i, v ) return ( v and "player" or "entity" ) .. " @ " .. pos_to_str( v:get_pos( ) ) end ) ) update_hud( 7, "collisions:\n%s", join( res.collisions, "\n", function ( i, v ) return minetest.get_node( v ).name .. " @ " .. pos_to_str( v, true ) end ) ) end print( string.format( "[%d] pos=%s rot=%s new_vel=%s old_vel=%s, col_xz=%s, col_y=%s", self.id, pos_to_str( pos ), rot_to_str( rot ), pos_to_str( new_vel ), pos_to_str( old_vel ), tostring( res.collides_xz ), tostring( res.collides_y ) ) ) end, on_activate = function ( self, staticdata, dtime, id ) printf( "[Avatar] on_activate( ): dtime=%0.1f, id=%d", dtime, id ) self.id = id end, on_deactivate = function ( self, id ) printf( "[Avatar] on_deactivate( ): id=%d", id ) if avatar == self.object then for i, v in ipairs( player_huds ) do player:hud_remove( v ) player_huds = nil end avatar = nil printf( "[Avatar] Deselected object id " .. self.id .. "." ) end end, on_punch = function ( self ) avatar = self.object printf( "[Avatar] Selected object id " .. self.id .. "." ) end, get_staticdata = function ( self ) printf( "[Avatar] get_staticdata( )" ) end, } ) minetest.register_chatcommand( "add", { func = function( name, param ) player = minetest.get_player_by_name( name ) avatar = minetest.add_entity( { x = 0, y = 5, z = 0 }, "extend_entity:avatar" ) avatar:set_properties( entity_props[ param ] or entity_props.reindeer ) if not player_huds then create_hud( ) end return true, "[Avatar] Selected object id " .. avatar:get_luaentity( ).id .. "." end } ) minetest.register_chatcommand( "sel", { func = function( name, param ) local id = tonumber( param ) if minetest.luaentities[ id ] then avatar = minetest.luaentities[ id ].object if not player_huds then create_hud( ) end return true, "[Avatar] Selected object id " .. id .. "." end end } ) minetest.register_chatcommand( "cmd", { func = function( name, param ) if avatar then if is_running then return false, "[Avatar] The script is still running." elseif param == "" then minetest.show_formspec( name, "extend_entity:editor", get_formspec( preset_list[ 1 ] ) ) return true else return execute( param ) end else return false, "[Avatar] No object selected." end end } ) minetest.register_chatcommand( "lag", { func = function( name, param ) is_lagging = not is_lagging return true, "[Avatar] Randomly induced lag " .. ( is_lagging and "enabled" or "disabled" ) .. "." end } ) minetest.register_on_player_receive_fields( function( player, formname, fields ) local name = player:get_player_name( ) if formname ~= "extend_entity:editor" then return end if fields.run then execute_script( string.split( sanitize( fields.script ), "\n" ) ) elseif fields.presets then local idx = table.indexof( preset_list, fields.presets ) minetest.show_formspec( name, "extend_entity:editor", get_formspec( preset_list[ idx ] ) ) end end ) local function construct_spawn( ) for y = 0, 4 do for x = -20 - y, 20 + y do for z = -20 - y, 20 + y do minetest.set_node( { x = x, y = 1 - y, z = z}, { name = "default:cobble" } ) end end end for y = 2, 5 do for x = -10, 10 do minetest.set_node( { x = x, y = y, z = -10 }, { name = "default:cobble" } ) minetest.set_node( { x = x, y = y, z = 10 }, { name = "default:cobble" } ) end for z = -9, 9 do minetest.set_node( { x = -10, y = y, z = -z }, {name = "default:cobble" } ) minetest.set_node( { x = 10, y = y, z = z }, { name = "default:cobble" } ) end minetest.set_node( { x = 0, y = y, z = 9 }, { name = "default:ladder", param2 = 4 } ) end for y = 2, 3 do for z = -2, 2 do for x = 4, 8 do local is_cobble = math.abs( z ) == 2 or x % 4 == 0 minetest.set_node( { x = x, y = y, z = z }, { name = is_cobble and "default:cobble" or "default:water_source" } ) minetest.set_node( { x = -x, y = y, z = z }, { name = is_cobble and "default:cobble" or "default:lava_source" } ) end end end for x = -2, 2 do minetest.set_node( { x = x, y = 2, z = 0 }, { name = "default:ladder", param2 = 1 } ) end end minetest.register_on_joinplayer( function( ) minetest.after( 0.5, construct_spawn ) end ) minetest.register_on_leaveplayer( function ( ) if avatar then avatar_obj:remove( ) end end )