某島

… : "…アッカリ~ン . .. . " .. .
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)