- Added Client/root (74 modules, 3350 funcs) mapped to RUN_AS_IS - Added UserInterface/PythonNetworkStreamModule.cpp.json mapped to PORTED - Implemented AudioCommands queue bridging MilesLib SoundManager to Godot AudioDriver - Implemented AudioDriver node for BGM crossfading, 2D UI SFX and 3D spatial sounds - Implemented core platform stubs in PythonApplication, GrpScreen, and PythonGraphic - Passed all 12 port tests (port_gate macos) and python_game_render_test
352 lines
14 KiB
C++
352 lines
14 KiB
C++
#include "python_host_node.h"
|
|
|
|
#include <godot_cpp/core/class_db.hpp>
|
|
#include <godot_cpp/variant/utility_functions.hpp>
|
|
|
|
#include "python_stdlib.h"
|
|
|
|
#ifdef MTGODOT_HAVE_PYTHON
|
|
// Standard types only — PythonBoot.h keeps <Python-2.7/*> and the Win32 shims out of this translation
|
|
// unit, which has to stay compilable next to godot-cpp.
|
|
#include "platform/ScriptLib/PythonBoot.h"
|
|
#include "platform/EterLib/UIRenderCommands.h"
|
|
#include "platform/EterLib/RenderCommands3D.h"
|
|
#include "platform/MilesLib/AudioCommands.h"
|
|
#include <string>
|
|
#endif
|
|
|
|
#include <godot_cpp/classes/image.hpp>
|
|
#include <godot_cpp/variant/dictionary.hpp>
|
|
#include <godot_cpp/variant/packed_byte_array.hpp>
|
|
#include <godot_cpp/variant/packed_color_array.hpp>
|
|
#include <godot_cpp/variant/packed_float32_array.hpp>
|
|
#include <godot_cpp/variant/packed_int32_array.hpp>
|
|
#include <godot_cpp/variant/packed_vector2_array.hpp>
|
|
#include <godot_cpp/variant/packed_vector3_array.hpp>
|
|
|
|
using namespace godot;
|
|
|
|
namespace mtgodot {
|
|
|
|
#ifdef MTGODOT_HAVE_PYTHON
|
|
|
|
String Metin2PythonHost::start(const String &stdlib_path) {
|
|
String path = stdlib_path;
|
|
if (path.is_empty()) {
|
|
path = Metin2Python::stdlib_path(false);
|
|
if (path.is_empty()) {
|
|
return String("python27.zip unavailable: ") + Metin2Python::last_error();
|
|
}
|
|
}
|
|
std::string error;
|
|
if (!PythonBoot::Start(path.utf8().get_data(), &error)) {
|
|
return String::utf8(error.c_str());
|
|
}
|
|
return String();
|
|
}
|
|
|
|
bool Metin2PythonHost::is_running() {
|
|
return PythonBoot::IsRunning();
|
|
}
|
|
|
|
String Metin2PythonHost::run_main_script(const String &command_line) {
|
|
std::string error;
|
|
if (!PythonBoot::RunMainScript(command_line.utf8().get_data(), &error)) {
|
|
return String::utf8(error.c_str());
|
|
}
|
|
return String();
|
|
}
|
|
|
|
String Metin2PythonHost::run_line(const String &source) {
|
|
std::string error;
|
|
if (!PythonBoot::RunLine(source.utf8().get_data(), &error)) {
|
|
return String::utf8(error.c_str());
|
|
}
|
|
return String();
|
|
}
|
|
|
|
// str(expression) from __main__, or "" when it raised (the error goes to the log like run_line's).
|
|
String Metin2PythonHost::evaluate(const String &expression) {
|
|
std::string result, error;
|
|
if (!PythonBoot::Evaluate(expression.utf8().get_data(), &result, &error)) {
|
|
UtilityFunctions::push_error(String::utf8(error.c_str()));
|
|
return String();
|
|
}
|
|
return String::utf8(result.c_str());
|
|
}
|
|
|
|
bool Metin2PythonHost::is_app_looping() { return PythonBoot::IsAppLooping(); }
|
|
|
|
void Metin2PythonHost::set_ui_size(int width, int height) { PythonBoot::SetUISize(width, height); }
|
|
void Metin2PythonHost::ui_mouse_move(int x, int y) { PythonBoot::UIMouseMove(x, y); }
|
|
void Metin2PythonHost::ui_mouse_button(int button, bool pressed, int x, int y) {
|
|
PythonBoot::UIMouseButton(button, pressed, x, y);
|
|
}
|
|
void Metin2PythonHost::ui_key(int key, bool pressed) { PythonBoot::UIKey(key, pressed); }
|
|
void Metin2PythonHost::ui_char(int codepoint) { PythonBoot::UIChar(unsigned(codepoint)); }
|
|
void Metin2PythonHost::ui_ime_key(int vkey) { PythonBoot::UIIMEKeyDown(vkey); }
|
|
void Metin2PythonHost::ui_update() { PythonBoot::UIUpdate(); }
|
|
Array Metin2PythonHost::ui_render_commands() {
|
|
PythonBoot::UIRender();
|
|
Array out;
|
|
for (const auto &command : UIRenderCommands()) {
|
|
Dictionary item;
|
|
item["kind"] = command.kind == UIRenderCommand::Bar ? "bar" :
|
|
(command.kind == UIRenderCommand::Line ? "line" :
|
|
(command.kind == UIRenderCommand::Image ? "image" : "text"));
|
|
item["x1"] = command.x1;
|
|
item["y1"] = command.y1;
|
|
item["x2"] = command.x2;
|
|
item["y2"] = command.y2;
|
|
item["argb"] = static_cast<int64_t>(command.argb);
|
|
item["clip_x1"] = command.clip_x1;
|
|
item["clip_y1"] = command.clip_y1;
|
|
item["clip_x2"] = command.clip_x2;
|
|
item["clip_y2"] = command.clip_y2;
|
|
if (command.kind == UIRenderCommand::Text || command.kind == UIRenderCommand::Image)
|
|
item["text"] = String::utf8(command.text.c_str());
|
|
if (command.quad) {
|
|
PackedVector2Array quad;
|
|
for (int i = 0; i < 4; ++i)
|
|
quad.push_back(Vector2(command.qx[i], command.qy[i]));
|
|
item["quad"] = quad;
|
|
item["uv"] = PackedVector2Array({Vector2(command.su, command.sv), Vector2(command.eu, command.sv),
|
|
Vector2(command.su, command.ev), Vector2(command.eu, command.ev)});
|
|
item["blend"] = command.blend;
|
|
if (!command.mask.empty()) {
|
|
item["mask"] = String::utf8(command.mask.c_str());
|
|
PackedVector2Array mask_uv;
|
|
for (int i = 0; i < 4; ++i)
|
|
mask_uv.push_back(Vector2(command.mu[i], command.mv[i]));
|
|
item["mask_uv"] = mask_uv;
|
|
}
|
|
}
|
|
out.push_back(item);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
Ref<Image> Metin2PythonHost::memory_texture(const String &name, int64_t known_revision) {
|
|
UIMemoryTexture texture;
|
|
if (!UIRenderMemoryTexture(name.utf8().get_data(), &texture) || texture.width <= 0 || texture.height <= 0)
|
|
return Ref<Image>();
|
|
if (known_revision >= 0 && texture.revision == static_cast<std::uint32_t>(known_revision))
|
|
return Ref<Image>();
|
|
PackedByteArray rgba;
|
|
rgba.resize(static_cast<int64_t>(texture.argb.size()) * 4);
|
|
uint8_t *out = rgba.ptrw();
|
|
for (std::uint32_t argb : texture.argb) {
|
|
*out++ = static_cast<uint8_t>(argb >> 16);
|
|
*out++ = static_cast<uint8_t>(argb >> 8);
|
|
*out++ = static_cast<uint8_t>(argb);
|
|
*out++ = static_cast<uint8_t>(argb >> 24);
|
|
}
|
|
return Image::create_from_data(texture.width, texture.height, false, Image::FORMAT_RGBA8, rgba);
|
|
}
|
|
|
|
namespace {
|
|
|
|
PackedFloat32Array floats(const float *values, int count) {
|
|
PackedFloat32Array out;
|
|
out.resize(count);
|
|
for (int i = 0; i < count; ++i)
|
|
out[i] = values[i];
|
|
return out;
|
|
}
|
|
|
|
Color argb_color(std::uint32_t argb) {
|
|
return Color(((argb >> 16) & 255) / 255.0f, ((argb >> 8) & 255) / 255.0f, (argb & 255) / 255.0f,
|
|
((argb >> 24) & 255) / 255.0f);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Array Metin2PythonHost::render3d_draws() {
|
|
Array out;
|
|
for (const Render3DDraw &draw : Render3DDraws()) {
|
|
Dictionary item;
|
|
item["world"] = floats(draw.world, 16);
|
|
item["view"] = floats(draw.view, 16);
|
|
item["proj"] = floats(draw.proj, 16);
|
|
item["texture0"] = String::utf8(draw.texture0.c_str());
|
|
item["texture1"] = String::utf8(draw.texture1.c_str());
|
|
item["pretransformed"] = draw.pretransformed;
|
|
item["lines"] = draw.lines;
|
|
|
|
PackedVector3Array positions;
|
|
positions.resize(static_cast<int64_t>(draw.positions.size() / 3));
|
|
for (int64_t i = 0; i < positions.size(); ++i)
|
|
positions[i] = Vector3(draw.positions[i * 3], draw.positions[i * 3 + 1], draw.positions[i * 3 + 2]);
|
|
item["positions"] = positions;
|
|
if (!draw.rhw.empty())
|
|
item["rhw"] = floats(draw.rhw.data(), static_cast<int>(draw.rhw.size()));
|
|
if (!draw.normals.empty()) {
|
|
PackedVector3Array normals;
|
|
normals.resize(static_cast<int64_t>(draw.normals.size() / 3));
|
|
for (int64_t i = 0; i < normals.size(); ++i)
|
|
normals[i] = Vector3(draw.normals[i * 3], draw.normals[i * 3 + 1], draw.normals[i * 3 + 2]);
|
|
item["normals"] = normals;
|
|
}
|
|
auto uvs = [](const std::vector<float> &values) {
|
|
PackedVector2Array out;
|
|
out.resize(static_cast<int64_t>(values.size() / 2));
|
|
for (int64_t i = 0; i < out.size(); ++i)
|
|
out[i] = Vector2(values[i * 2], values[i * 2 + 1]);
|
|
return out;
|
|
};
|
|
if (!draw.uv0.empty())
|
|
item["uv0"] = uvs(draw.uv0);
|
|
if (!draw.uv1.empty())
|
|
item["uv1"] = uvs(draw.uv1);
|
|
if (!draw.diffuse.empty()) {
|
|
PackedColorArray colors;
|
|
colors.resize(static_cast<int64_t>(draw.diffuse.size()));
|
|
for (int64_t i = 0; i < colors.size(); ++i)
|
|
colors[i] = argb_color(draw.diffuse[i]);
|
|
item["diffuse"] = colors;
|
|
}
|
|
PackedInt32Array indices;
|
|
indices.resize(static_cast<int64_t>(draw.indices.size()));
|
|
for (int64_t i = 0; i < indices.size(); ++i)
|
|
indices[i] = static_cast<int32_t>(draw.indices[i]);
|
|
item["indices"] = indices;
|
|
|
|
item["alpha_blend"] = static_cast<int64_t>(draw.alpha_blend);
|
|
item["src_blend"] = static_cast<int64_t>(draw.src_blend);
|
|
item["dest_blend"] = static_cast<int64_t>(draw.dest_blend);
|
|
item["alpha_test"] = static_cast<int64_t>(draw.alpha_test);
|
|
item["alpha_ref"] = static_cast<int64_t>(draw.alpha_ref);
|
|
item["alpha_func"] = static_cast<int64_t>(draw.alpha_func);
|
|
item["cull_mode"] = static_cast<int64_t>(draw.cull_mode);
|
|
item["z_enable"] = static_cast<int64_t>(draw.z_enable);
|
|
item["z_write"] = static_cast<int64_t>(draw.z_write);
|
|
item["z_func"] = static_cast<int64_t>(draw.z_func);
|
|
item["lighting"] = static_cast<int64_t>(draw.lighting);
|
|
item["texture_factor"] = static_cast<int64_t>(draw.texture_factor);
|
|
item["fog_enable"] = static_cast<int64_t>(draw.fog_enable);
|
|
item["color_op"] = static_cast<int64_t>(draw.color_op[0]);
|
|
item["alpha_op"] = static_cast<int64_t>(draw.alpha_op[0]);
|
|
item["material_diffuse"] = Color(draw.material_diffuse[0], draw.material_diffuse[1],
|
|
draw.material_diffuse[2], draw.material_diffuse[3]);
|
|
item["material_ambient"] = Color(draw.material_ambient[0], draw.material_ambient[1],
|
|
draw.material_ambient[2], draw.material_ambient[3]);
|
|
item["material_emissive"] = Color(draw.material_emissive[0], draw.material_emissive[1],
|
|
draw.material_emissive[2], draw.material_emissive[3]);
|
|
item["light0"] = draw.light0;
|
|
item["light0_direction"] = Vector3(draw.light0_direction[0], draw.light0_direction[1],
|
|
draw.light0_direction[2]);
|
|
item["light0_diffuse"] = Color(draw.light0_diffuse[0], draw.light0_diffuse[1], draw.light0_diffuse[2],
|
|
draw.light0_diffuse[3]);
|
|
item["light0_ambient"] = Color(draw.light0_ambient[0], draw.light0_ambient[1], draw.light0_ambient[2],
|
|
draw.light0_ambient[3]);
|
|
item["ambient"] = argb_color(draw.ambient);
|
|
out.push_back(item);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
Array Metin2PythonHost::audio_commands() {
|
|
Array out;
|
|
for (const auto &cmd : DrainAudioCommands()) {
|
|
Dictionary item;
|
|
switch (cmd.type) {
|
|
case AudioCommand::PlaySound2D: item["type"] = "play_sound_2d"; break;
|
|
case AudioCommand::PlaySound3D: item["type"] = "play_sound_3d"; break;
|
|
case AudioCommand::StopSound3D: item["type"] = "stop_sound_3d"; break;
|
|
case AudioCommand::StopAllSound3D: item["type"] = "stop_all_sound_3d"; break;
|
|
case AudioCommand::PlayMusic: item["type"] = "play_music"; break;
|
|
case AudioCommand::FadeOutMusic: item["type"] = "fade_out_music"; break;
|
|
case AudioCommand::FadeOutAllMusic: item["type"] = "fade_out_all_music"; break;
|
|
case AudioCommand::SetSoundVolume: item["type"] = "set_sound_volume"; break;
|
|
case AudioCommand::SetMusicVolume: item["type"] = "set_music_volume"; break;
|
|
default: continue;
|
|
}
|
|
item["filename"] = String::utf8(cmd.filename.c_str());
|
|
item["x"] = cmd.x;
|
|
item["y"] = cmd.y;
|
|
item["z"] = cmd.z;
|
|
item["volume"] = cmd.volume;
|
|
item["speed"] = cmd.speed;
|
|
item["play_count"] = cmd.play_count;
|
|
item["id"] = cmd.id;
|
|
out.append(item);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
String Metin2PythonHost::current_map_name() {
|
|
return String::utf8(PythonBoot::CurrentMapName().c_str());
|
|
}
|
|
|
|
void Metin2PythonHost::stop() {
|
|
PythonBoot::Stop();
|
|
}
|
|
|
|
#else
|
|
|
|
// Built without -DMTGODOT_EMBED_PYTHON=ON: the class is still registered so GDScript can tell "no
|
|
// interpreter in this build" apart from "the interpreter failed to start".
|
|
String Metin2PythonHost::start(const String &) {
|
|
return String("this build has no embedded Python (-DMTGODOT_EMBED_PYTHON=ON)");
|
|
}
|
|
|
|
bool Metin2PythonHost::is_running() {
|
|
return false;
|
|
}
|
|
|
|
String Metin2PythonHost::run_main_script(const String &) {
|
|
return String("this build has no embedded Python (-DMTGODOT_EMBED_PYTHON=ON)");
|
|
}
|
|
|
|
String Metin2PythonHost::run_line(const String &) {
|
|
return String("this build has no embedded Python (-DMTGODOT_EMBED_PYTHON=ON)");
|
|
}
|
|
|
|
String Metin2PythonHost::evaluate(const String &) { return String(); }
|
|
|
|
bool Metin2PythonHost::is_app_looping() { return false; }
|
|
void Metin2PythonHost::set_ui_size(int, int) {}
|
|
void Metin2PythonHost::ui_mouse_move(int, int) {}
|
|
void Metin2PythonHost::ui_mouse_button(int, bool, int, int) {}
|
|
void Metin2PythonHost::ui_key(int, bool) {}
|
|
void Metin2PythonHost::ui_char(int) {}
|
|
void Metin2PythonHost::ui_ime_key(int) {}
|
|
void Metin2PythonHost::ui_update() {}
|
|
Array Metin2PythonHost::ui_render_commands() { return Array(); }
|
|
Ref<Image> Metin2PythonHost::memory_texture(const String &, int64_t) { return Ref<Image>(); }
|
|
Array Metin2PythonHost::render3d_draws() { return Array(); }
|
|
Array Metin2PythonHost::audio_commands() { return Array(); }
|
|
String Metin2PythonHost::current_map_name() { return String(); }
|
|
|
|
void Metin2PythonHost::stop() {
|
|
}
|
|
|
|
#endif
|
|
|
|
void Metin2PythonHost::_bind_methods() {
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("start", "stdlib_path"),
|
|
&Metin2PythonHost::start, DEFVAL(String()));
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("is_running"), &Metin2PythonHost::is_running);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("evaluate", "expression"), &Metin2PythonHost::evaluate);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("run_main_script", "command_line"),
|
|
&Metin2PythonHost::run_main_script, DEFVAL(String()));
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("run_line", "source"), &Metin2PythonHost::run_line);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("is_app_looping"), &Metin2PythonHost::is_app_looping);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("set_ui_size", "width", "height"), &Metin2PythonHost::set_ui_size);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_mouse_move", "x", "y"), &Metin2PythonHost::ui_mouse_move);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_mouse_button", "button", "pressed", "x", "y"), &Metin2PythonHost::ui_mouse_button);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_key", "key", "pressed"), &Metin2PythonHost::ui_key);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_char", "codepoint"), &Metin2PythonHost::ui_char);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_ime_key", "vkey"), &Metin2PythonHost::ui_ime_key);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_update"), &Metin2PythonHost::ui_update);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("ui_render_commands"), &Metin2PythonHost::ui_render_commands);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("memory_texture", "name", "known_revision"),
|
|
&Metin2PythonHost::memory_texture, DEFVAL(-1));
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("render3d_draws"), &Metin2PythonHost::render3d_draws);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("audio_commands"), &Metin2PythonHost::audio_commands);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("current_map_name"), &Metin2PythonHost::current_map_name);
|
|
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("stop"), &Metin2PythonHost::stop);
|
|
}
|
|
|
|
} // namespace mtgodot
|