某岛

… : "…アッカリ~ン . .. . " .. .
June 5, 2022

Minetest 如何实现游戏内转账

客户端修改

之前发现 Minetest 的引擎代码是和 easyrpg 一样是 cpp,那么也就意味着它也同样有机会可以使用 emscripten 编译到浏览器中运行。
网上搜了一圈,发现还真有人已经做了这件事。。。
https://blog.minetest.net/2022/03/27/March/

那么现在转账命令就非常容易实现了,可以先写在 js 里,然后直接调用对应的 js 方法即可。
不考虑安全问题。。我们先做一个简易且泛用的实作。。用邪恶的 Dr.Eval 大法,执行任何脚本。。。

void escape_EM_ASM(const std::wstring &message) {
	std::wstring m = translate_string(message);
	std::string s(m.length(), 0);
	std::transform(m.begin(), m.end(), s.begin(), [] (wchar_t c) {
		return (char)c;
	});
	MAIN_THREAD_ASYNC_EM_ASM(console.log("Msg: " + UTF8ToString($0)), s.c_str());
	std::string c;
	c = ".EM_ASM ";
	if (s.find(c) != std::string::npos) {
		s = s.substr(s.find(c) + c.size());
		MAIN_THREAD_ASYNC_EM_ASM(eval(UTF8ToString($0)), s.c_str());
		return;
	}
}

注意这里我踩了一个坑。。。https://github.com/paradust7/minetest-wasm/issues/3
简单来说就是最好用这个命令。。MAIN_THREAD_ASYNC_EM_ASM。。。
上面的代码如果换成 EM_ASM,那么发消息的时候用这个还是能 trigger 里面的 js 代码,
但是如果经过一次服务器收到消息的时候进行解析就无效了,而模组里的 minetest.chat_send_player(name, text) API 是会经过一次服务器的。

模组修改

模组第一个要求就是我们需要有一个动作能够 trigger 右击玩家这个事件。。。on_rightclick 什么的。。
搜了一圈发现没有现成的 API。。。

但是有一个现成的 mod 里有这个功能。。。所以我们从那个模组开始入手。。。
首先添加 right_click 的事件,我们创建一个菜单。。。

https://rubenwardy.com/minetest_modding_book/en/players/formspecs.html

minetest.register_on_rightclickplayer(function(player, clicker)
	local s = clicker:get_player_name()
	local t = player:get_player_name()
	local controls = clicker:get_player_control()
	if not(check_distance(clicker, player)) then
		minetest.chat_send_player(s, S("Target too far."))
		return
	end
	local context = get_context(s)
	context.target = t	
	local formspec = {
		"formspec_version[4]",
		"size[4.5,5.25]",
		"label[0.375,0.5;", minetest.formspec_escape("This is " .. t .. "."), "]",
		"button_exit[0.5,1;3.5,0.8;profile;Profile]",
		"button_exit[0.5,2;3.5,0.8;transfer;Transfer]",
		"button_exit[0.5,3;3.5,0.8;follow;Follow]",
		"button_exit[0.5,4;3.5,0.8;cancel;Cancel]"
	}
	minetest.show_formspec(s, "player:interact", table.concat(formspec, ""))
end)

上面的代码里我们开了一个状态 context.target 用来记录当前交互的对象。。

然后再处理菜单的回执即可。。。

minetest.register_on_player_receive_fields(function(player, formname, fields)

    if formname == "player:interact" then
		local s = player:get_player_name()
		local t = get_context(s).target		
		if fields.profile then			
			minetest.chat_send_player(s, ".EM_ASM window.open(\"https://" .. t .. ".test.w3itch.io/zh-CN\", \"new\")")							
		end
		if fields.transfer then			
			minetest.chat_send_player(s, ".EM_ASM alert(feature not implement yet...)")							
		end
		if fields.follow then			
			minetest.chat_send_player(s, ".EM_ASM alert(feature not implement yet...)")							
		end
		return true
    end

end)