OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-rocca / experiments / minetest-xp.py

445 lines · python ref:
#!/usr/bin/env python3

# Experiment to communicate with Minetest via Miney.
#
# Requirements:
#
# - Minetest, version 5.5.0
# - Miney https://miney.readthedocs.io/en/latest/index.html
# - luacmd https://github.com/prestidigitator/minetest-mod-luacmd
# - inspect https://github.com/kikito/inspect.lua
#
# Before launching minetest you might need to add
#
#      secure.trusted_mods = mineysocket, luacmd
#
# as well as
#
#      secure.enable_security = false
#
#    in your minetest.conf
#
# Usage:
#
# 1. Run minetest
# 2. Create game with mineysocket and optionally luacmd enabled
# 3. Run that script

import copy
import time
import miney

# Delay between each test (in second)
test_delay = 1.0


def log_and_wait(msg: str):
    print("============ {} ============".format(msg))
    time.sleep(test_delay)


####################
# Miney API Basics #
####################

# Connect Minetest
log_and_wait("Connect to minetest")
mt = miney.Minetest()
print("mt = {}".format(mt))

# We set the time to mid-day
log_and_wait("Set time to mid-day")
print("mt.time_of_day = {}".format(mt.time_of_day))
mt.time_of_day = 0.5
print("mt.time_of_day = {}".format(mt.time_of_day))

# Get Lua interpreter
log_and_wait("Get lua interpreter")
lua = miney.Lua(mt)
print("lua = {}".format(lua))

##########################
# Lua Interpreter Basics #
##########################

# Convert Python data into lua string.
#
# List of supported Python constructs (so far):
# - list
# - dict
#
# List of unsupported Python constructs (so far):
# - set
# - tuple
log_and_wait("Convert python data to lua")
data_list = ["123", "abc"]
data_list_dumps = lua.dumps(data_list)
print("python list = {}".format(data_list))
print("lua list dumps = {}".format(data_list_dumps))
data_dict = {"123": 123, "abc": 234}
data_dict_dumps = lua.dumps(data_dict)
print("python dict = {}".format(data_dict))
print("lua dict dumps = {}".format(data_dict_dumps))

# Helpers to print and run lua code
def lua_prt(code: str):
    print('Run lua code:\n"""\n{}\n"""'.format(code.strip("\n")))


def lua_prt_run(code: str):
    lua_prt(code)
    return lua.run(code)


# Run simple lua code
log_and_wait("Invoke minetest lua interpreter to calculate 1+2")
simple_lua_result = lua_prt_run("return 1 + 2")
print("simple_lua_result = {}".format(simple_lua_result))

# Return nil (should get None in Python)
log_and_wait("Return nil")
nil_result = lua_prt_run("return nil")
print("nil_result = {}".format(nil_result))

# Persistance
log_and_wait("Check persistance of lua interpreter")
lua.run("alpha = 10")
alpha = lua_prt_run("return alpha")
print("alpha = {}".format(alpha))

# Check if luacmd amd miney are pointing to the same interpreter.  You must enter
#
# /lua beta = 20
#
# in minetest before testing this.  If it return 20, then it should
# mean that luacmd points to the same lua interpreter as miney.
#
# Actually it does not!!! What is that supposed to mean?
log_and_wait("Check luacmd and miney points to same lua interpreter")
beta = lua_prt_run("return beta")
print("beta = {}".format(beta))

# # Include library (better use request_insecure_environment below)
# log_and_wait("Include lua library")
# inspect = lua_prt_run("inspect = require 'inspect'")
# print("inspect = {}".format(inspect))

# Include library by requesting insecure environment
log_and_wait("Include and test lua library (requesting insecure environment)")
test_inspect = """
ie = minetest.request_insecure_environment()
inspect = ie.require("inspect")
return inspect({1, 2, 3, 4})
"""
inspect_result = lua_prt_run(test_inspect)
print("inspect_result = {}".format(inspect_result))

# Inspect nil
log_and_wait("Inspect nil")
inspect_nil_result = lua_prt_run("return inspect(nil)")
print("inspect_nil_result = {}".format(inspect_nil_result))

# More inspect combined with loop and string concatenation.
log_and_wait("Inspect combined with loop and string concatenation")
test_inspect_comb = """
local t = {1, 2, 3, 4}
local msg = ''
for i, o in pairs(t) do
   msg = msg .. inspect(i) .. ':' .. inspect(o) .. ';'
end
return msg
"""
inspect_comb_result = lua_prt_run(test_inspect_comb)
print("inspect_comb_result = {}".format(inspect_comb_result))

# Chat
log_and_wait("Send chat to minetest")
chat_result = lua_prt_run('return minetest.chat_send_all("Hello Minetest")')
print("chat_result = {}".format(chat_result))

# Get registered nodes.  Miney cannot serialize minetest player, thus
# we return its string representation.
log_and_wait("Get registered nodes")
registered_nodes = lua_prt_run("return inspect(minetest.registered_nodes)")
print("registered_nodes = {}".format(registered_nodes))

###################################
# Player Action/Perception Basics #
###################################

# Testing player
player_name = "singleplayer"

# Check that the player exists
log_and_wait("Check that player exists")
player_exists_result = lua_prt_run(
    'return minetest.player_exists("{}")'.format(player_name)
)
print("player_exists = {}".format(player_exists_result))

# Create lua runner helper to run player methods
def player_lua_prt_run(code: str, player: str = "singleplayer"):
    get_player_code = 'local player = minetest.get_player_by_name("{}")'.format(player)
    full_code = get_player_code + "\n" + code.strip("\n")
    return lua_prt_run(full_code)


# Get the main player.  Miney cannot serialize minetest player, thus
# we return its string representation.
log_and_wait("Get player")
player_result = player_lua_prt_run("return inspect(player)")
print("player_result = {}".format(player_result))

# Get the name of the player (given its name, hehe)
log_and_wait("Get player name")
player_get_name_result = player_lua_prt_run("return player:get_player_name()")
print("player_get_name_result = {}".format(player_get_name_result))

# Get player's properties.  Miney cannot serialize minetest inventory,
# thus we return its string representation.
log_and_wait("Get player properties")
player_properties_result = player_lua_prt_run("return inspect(player:get_properties())")
print("player_properties_result = {}".format(player_properties_result))

# Get player position
log_and_wait("Get player position")
player_pos = player_lua_prt_run("return player:get_pos()")
print("player_pos = {}".format(player_pos))

# Get player's surrounding objects (likely only itself).  Miney cannot
# serialize minetest objects, thus we return their string
# representation.
log_and_wait("Get surrounding objects")
surrounding_objects_result = player_lua_prt_run(
    "return inspect(minetest.get_objects_inside_radius(player:get_pos(), 1.0))"
)
print("surrounding_objects_result = {}".format(surrounding_objects_result))

# Get the block (called node in minetest) at player's position
log_and_wait("Get node at player's position")
player_node_result = player_lua_prt_run("return minetest.get_node(player:get_pos())")
print("player_node_result = {}".format(player_node_result))

# Get metadata of node at player's position.  TODO: seems to return
# the player object!!!
log_and_wait("Get node metadata at player's position")
player_meta_result = player_lua_prt_run(
    "return inspect(minetest.get_meta(player:get_pos()))"
)
print("player_meta_result = {}".format(player_meta_result))

# Get player's nearest node of type air.
log_and_wait("Get player's nearest node of type air")
player_near_air_node_result = player_lua_prt_run(
    "return minetest.find_node_near(player:get_pos(), 1.0, 'air')"
)
print("player_near_air_node_result = {}".format(player_near_air_node_result))

# Get player's nearest node of type dry dirt.
log_and_wait("Get player's nearest node of type dry dirt")
player_near_dry_dirt_node_result = player_lua_prt_run(
    "return minetest.find_node_near(player:get_pos(), 10.0, 'default:dry_dirt')"
)
print("player_near_dry_dirt_node_result = {}".format(player_near_dry_dirt_node_result))

# Get player's nearest node of type desert sand.
log_and_wait("Get player's nearest node of type desert sand")
player_near_desert_sand_node_result = player_lua_prt_run(
    "return minetest.find_node_near(player:get_pos(), 10.0, 'default:desert_sand')"
)
print(
    "player_near_desert_sand_node_result = {}".format(
        player_near_desert_sand_node_result
    )
)

# Get player's nearest node of type dry dirt or desert sand.
log_and_wait("Get player's nearest node of type desert sand")
ddds = "{'default:dry_dirt', 'default:desert_sand'}"
player_near_node_result = player_lua_prt_run(
    "return minetest.find_node_near(player:get_pos(), 10.0, {})".format(ddds)
)
print("player_near_node_result = {}".format(player_near_node_result))

# Get player's nearest nodes under air.
log_and_wait("Get player's surrounding nodes")
player_lower_pos = {k: v - 5.0 for k, v in player_pos.items()}
player_upper_pos = {k: v + 5.0 for k, v in player_pos.items()}
lp = lua.dumps(player_lower_pos)
up = lua.dumps(player_upper_pos)
player_surrounding_nodes_result = player_lua_prt_run(
    "return minetest.find_nodes_in_area_under_air({}, {}, {})".format(lp, up, ddds)
)
print("player_surrounding_nodes_result = {}".format(player_surrounding_nodes_result))

# Get node drops.  Returns list of itemstrings that are dropped by
# `node` when dug with the item `toolname` (not limited to tools)
log_and_wait("Get node drops")
node_drops_result = lua_prt_run("return minetest.get_node_drops({}, nil)".format(ddds))
print("node_drops_result = {}".format(node_drops_result))

# Get node associated to near node
log_and_wait("Get node associated to near node")
associated_node = lua_prt_run(
    "return minetest.get_node({})".format(lua.dumps(player_near_node_result))
)
print("associated_node = {}".format(associated_node))

# Dig
log_and_wait("Dig")
player_dig_result = player_lua_prt_run(
    "return minetest.node_dig({}, {}, player)".format(
        lua.dumps(player_near_node_result), lua.dumps(associated_node)
    )
)
print("player_dig_result = {}".format(player_dig_result))

# Get player's inventory.  Miney cannot serialize minetest inventory,
# thus we return its string representation.
log_and_wait("Get player's inventory")
player_inventory_result = player_lua_prt_run(
    "return inspect(player:get_inventory():get_lists())"
)
print("player_inventory_result = {}".format(player_inventory_result))

# Get player's main inventory.  Miney cannot serialize minetest
# inventory, thus we return its string representation.
log_and_wait("Get player's main inventory")
player_main_inventory_result = player_lua_prt_run(
    "return inspect(player:get_inventory():get_list('main'))"
)
print("player_main_inventory_result = {}".format(player_main_inventory_result))

# Get player's first itemstack of its main inventory.
stack_index = 1
log_and_wait("Get player's first itemstack of main inventory")
player_first_itemstack_main_inventory_result = player_lua_prt_run(
    "return player:get_inventory():get_stack('main', {}):to_table()".format(stack_index)
)
print(
    "player_first_itemstack_main_inventory_result = {}".format(
        player_first_itemstack_main_inventory_result
    )
)

# Get player's first itemstack count of its main inventory.
log_and_wait("Get player's first itemstack count of main inventory")
player_first_itemstack_count_main_inventory_result = player_lua_prt_run(
    "return player:get_inventory():get_stack('main', {}):get_count()".format(
        stack_index
    )
)
print(
    "player_first_itemstack_count_main_inventory_result = {}".format(
        player_first_itemstack_count_main_inventory_result
    )
)

# Another attempt at retrieving player's inventory
log_and_wait("Get inventory formspec")
player_inventory_formspec_result = player_lua_prt_run(
    "return player:get_inventory_formspec()"
)
print("player_inventory_formspec_result = {}".format(player_inventory_formspec_result))

# Drop item from player inventory
log_and_wait("Drop first item from inventory")
itemstack = lua.dumps(player_first_itemstack_main_inventory_result)
drop_item_lua_code = """
local pl_inv = player:get_inventory()
local pl_stack = pl_inv:get_stack('main', {0})
local pl_stack_count = pl_stack:get_count()
local pl_leftover_stack = minetest.item_drop(pl_stack, player, {1})
pl_inv:set_stack('main', {0}, pl_leftover_stack)
return pl_leftover_stack:to_table()
-- TODO: minetest.item_drop actually drops the entire stack.
-- Below is a (failed to far) attempt to only drop one item of the stack.
--
-- local is_pl_stack_cleared = pl_stack:set_count(pl_stack_count - 1)
-- pl_inv:set_stack('main', {1}, pl_stack)
-- pl_inv:set_stack('main', {1}, pl_stack)
-- return pl_stack:to_table()
""".format(
    stack_index, lua.dumps(player_pos)
)
leftover_itemstack = player_lua_prt_run(drop_item_lua_code)
print("leftover_itemstack = {}".format(leftover_itemstack))

# TODO: minetest.item_eat, maybe

# Move abruptly player to a position.  Setting the continuous argument
# of move_to to true does not work for players (as explained in
# https://github.com/minetest/minetest/blob/master/doc/lua_api.txt).
log_and_wait("Move abruptly player to new position")
player_shifted_pos = copy.copy(player_pos)
player_shifted_pos["x"] += 1
print("player_shifted_pos = {}".format(player_shifted_pos))
move_player_to = "return player:move_to({})".format(lua.dumps(player_shifted_pos))
move_player_to_result = player_lua_prt_run(move_player_to)
print("move_player_to_result = {}".format(move_player_to_result))

# Come back smoothly to its original position.  TODO: use player_api
# to simulate walking.
log_and_wait("Move smoothly player to back to previous position")
player_move_back_result = player_lua_prt_run(
    "return player:add_velocity({x=-6.5, y=0, z=0})"
)
print("player_move_back_result = {}".format(player_move_back_result))

# Go slowly to the previous position
log_and_wait("Move smoothly player to back to previous position")
player_move_back_result = player_lua_prt_run(
    "return player:add_velocity({x=3.0, y=0, z=0})"
)
print("player_move_back_result = {}".format(player_move_back_result))

# Go very fast in the same direction
log_and_wait("Move smoothly player to back to previous position")
player_move_back_result = player_lua_prt_run(
    "return player:add_velocity({x=20.0, y=0, z=0})"
)
print("player_move_back_result = {}".format(player_move_back_result))

# Go to another direction
log_and_wait("Move smoothly player to back to previous position")
player_move_back_result = player_lua_prt_run(
    "return player:add_velocity({x=0, y=0, z=6.5})"
)
print("player_move_back_result = {}".format(player_move_back_result))

# Get player horizontal look angle
log_and_wait("Get player look horizontal angle")
player_look_horizontal_angle = player_lua_prt_run("return player:get_look_horizontal()")
print("player_look_horizontal_angle = {}".format(player_look_horizontal_angle))

# Set player horizontal look angle (turn, though not smoothly)
log_and_wait("Set player look horizontal angle (+0.1)")
new_player_look_horizontal_angle = player_look_horizontal_angle + 0.1
set_player_look_horizontal_result = player_lua_prt_run(
    "return player:set_look_horizontal({})".format(new_player_look_horizontal_angle)
)
print(
    "set_player_look_horizontal_result = {}".format(set_player_look_horizontal_result)
)

# TODO
# * `minetest.place_node(pos, node)`
#     * Place node with the same effects that a player would cause
# * `minetest.punch_node(pos)`
#     * Punch node with the same effects that a player would cause

# Jump
log_and_wait("Jump")
player_jump_result = player_lua_prt_run("return player:add_velocity({x=0, y=6.5, z=0})")
print("player_jump_result = {}".format(player_jump_result))

# Jump higher
log_and_wait("Jump higher")
player_jump_result = player_lua_prt_run("return player:add_velocity({x=0, y=13, z=0})")
print("player_jump_result = {}".format(player_jump_result))

# Player control
log_and_wait("Get player control")
player_control_result = player_lua_prt_run("return player:get_player_control()")
print("player_control_result = {}".format(player_control_result))