Files
mtgodot-poc/extension/src/python_host_node.cpp
T
shenleiandClaude Opus 5.5 5285ca85fd native render: transliterate D3D8 fixed-function pipeline (unit A)
- Texture coordinates: TEXCOORDINDEX generation (camera-space normal /
  position / reflection vector, LOCALVIEWER) and D3DTS_TEXTUREn under
  TEXTURETRANSFORMFLAGS (incl. PROJECTED) now run in native.vert; the
  RecordingDevice CPU uv baking and the sphere-map / multiplicative-shadow
  shader hacks are removed. Godot consumer uses Render3DStageTexcoords.
- Lighting: 8 D3D lights in camera space, 1/(a0+a1d+a2d^2) without clamp,
  spot cone theta/2 phi/2 with falloff, diffuse/ambient/emissive material
  sources, NORMALIZENORMALS, inverse-transpose normal matrix.
- Alpha test compares 8-bit alpha against ALPHAREF with ALPHAFUNC.
- Back-buffer Clear() is recorded in draw order and executed with
  vkCmdClearAttachments; render pass clears to 0xff000000.
- D3DVIEWPORT8 (incl. MinZ/MaxZ) is dynamic per-draw state.
- Missing vertex diffuse is opaque white; alpha blend mirrors SRC/DESTBLEND.
- CScreen::ms_clearDepth / ms_diffuseColor initialised to 40250's
  1.0f / 0xffffffff (were zero; exposed once clears were executed).
- Capture format v8; --screenshot-out FILE.bmp swapchain readback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:54:13 +09:00

670 lines
26 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 "platform/PackBackend.h"
#include "../../native_render/draw_capture.h"
#include <cstdlib>
#include <cstring>
#include <string>
#include <type_traits>
#include <unordered_map>
#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>
#include <godot_cpp/variant/string_name.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_mouse_wheel(int delta) { PythonBoot::UIMouseWheel(delta); }
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(); }
namespace {
Dictionary ui_command_item(const UIRenderCommand &command) {
static const StringName s_kind("kind");
static const StringName s_x1("x1");
static const StringName s_y1("y1");
static const StringName s_x2("x2");
static const StringName s_y2("y2");
static const StringName s_argb("argb");
static const StringName s_end_argb("end_argb");
static const StringName s_clip_x1("clip_x1");
static const StringName s_clip_y1("clip_y1");
static const StringName s_clip_x2("clip_x2");
static const StringName s_clip_y2("clip_y2");
static const StringName s_text("text");
static const StringName s_quad("quad");
static const StringName s_uv("uv");
static const StringName s_blend("blend");
static const StringName s_mask("mask");
static const StringName s_mask_uv("mask_uv");
static const StringName s_behind_3d("behind_3d");
static const StringName s_val_bar("bar");
static const StringName s_val_gradient_bar("gradient_bar");
static const StringName s_val_line("line");
static const StringName s_val_image("image");
static const StringName s_val_text("text");
Dictionary item;
item[s_kind] = command.kind == UIRenderCommand::GradientBar ? s_val_gradient_bar :
(command.kind == UIRenderCommand::Bar ? s_val_bar :
(command.kind == UIRenderCommand::Line ? s_val_line :
(command.kind == UIRenderCommand::Image ? s_val_image : s_val_text)));
item[s_x1] = command.x1;
item[s_y1] = command.y1;
item[s_x2] = command.x2;
item[s_y2] = command.y2;
item[s_argb] = static_cast<int64_t>(command.argb);
if (command.kind == UIRenderCommand::GradientBar)
item[s_end_argb] = static_cast<int64_t>(command.end_argb);
item[s_clip_x1] = command.clip_x1;
item[s_clip_y1] = command.clip_y1;
item[s_clip_x2] = command.clip_x2;
item[s_clip_y2] = command.clip_y2;
if (command.kind == UIRenderCommand::Text || command.kind == UIRenderCommand::Image)
item[s_text] = String::utf8(command.text.c_str());
if (command.quad) {
PackedVector2Array quad;
quad.resize(4);
Vector2 *qptr = quad.ptrw();
for (int i = 0; i < 4; ++i)
qptr[i] = Vector2(command.qx[i], command.qy[i]);
item[s_quad] = quad;
PackedVector2Array uv;
uv.resize(4);
Vector2 *uvptr = uv.ptrw();
uvptr[0] = Vector2(command.su, command.sv);
uvptr[1] = Vector2(command.eu, command.sv);
uvptr[2] = Vector2(command.su, command.ev);
uvptr[3] = Vector2(command.eu, command.ev);
item[s_uv] = uv;
item[s_blend] = command.blend;
if (!command.mask.empty()) {
item[s_mask] = String::utf8(command.mask.c_str());
PackedVector2Array mask_uv;
mask_uv.resize(4);
Vector2 *mptr = mask_uv.ptrw();
for (int i = 0; i < 4; ++i)
mptr[i] = Vector2(command.mu[i], command.mv[i]);
item[s_mask_uv] = mask_uv;
}
}
item[s_behind_3d] = command.behind_3d;
return item;
}
} // namespace
Array Metin2PythonHost::ui_render_commands() {
PythonBoot::UIRender();
static std::uint64_t cached_frame_id = UINT64_MAX;
static Array cached_commands;
const std::uint64_t frame_id = UIRenderFrameId();
if (cached_frame_id == frame_id)
return cached_commands;
const auto &commands = UIRenderCommands();
Array out;
out.resize(static_cast<int64_t>(commands.size()));
for (size_t idx = 0; idx < commands.size(); ++idx)
out[static_cast<int64_t>(idx)] = ui_command_item(commands[idx]);
cached_frame_id = frame_id;
cached_commands = out;
return out;
}
Array Metin2PythonHost::ui_render_commands_batched() {
PythonBoot::UIRender();
static std::uint64_t cached_frame_id = UINT64_MAX;
static Array cached_commands;
const std::uint64_t frame_id = UIRenderFrameId();
if (cached_frame_id == frame_id)
return cached_commands;
const auto &commands = UIRenderCommands();
const auto batchable = [](const UIRenderCommand &c) {
return c.kind == UIRenderCommand::Image && c.quad && c.text.rfind("mem:", 0) == 0 &&
c.mask.empty() && c.blend == 0 && c.x1 >= c.clip_x1 && c.y1 >= c.clip_y1 &&
c.x2 <= c.clip_x2 && c.y2 <= c.clip_y2;
};
Array out;
for (size_t i = 0; i < commands.size();) {
const UIRenderCommand &first = commands[i];
if (!batchable(first)) {
out.push_back(ui_command_item(first));
++i;
continue;
}
size_t end = i + 1;
while (end < commands.size() && batchable(commands[end]) &&
commands[end].text == first.text && commands[end].behind_3d == first.behind_3d)
++end;
if (end == i + 1) {
out.push_back(ui_command_item(first));
i = end;
continue;
}
const int64_t count = static_cast<int64_t>(end - i);
PackedVector2Array points, uvs;
PackedColorArray colors;
PackedInt32Array indices;
points.resize(count * 4);
uvs.resize(count * 4);
colors.resize(count * 4);
indices.resize(count * 6);
Vector2 *point_data = points.ptrw(), *uv_data = uvs.ptrw();
Color *color_data = colors.ptrw();
int32_t *index_data = indices.ptrw();
for (int64_t j = 0; j < count; ++j) {
const UIRenderCommand &c = commands[i + static_cast<size_t>(j)];
const int64_t v = j * 4, t = j * 6;
point_data[v] = Vector2(c.qx[0], c.qy[0]);
point_data[v + 1] = Vector2(c.qx[1], c.qy[1]);
point_data[v + 2] = Vector2(c.qx[3], c.qy[3]);
point_data[v + 3] = Vector2(c.qx[2], c.qy[2]);
uv_data[v] = Vector2(c.su, c.sv);
uv_data[v + 1] = Vector2(c.eu, c.sv);
uv_data[v + 2] = Vector2(c.eu, c.ev);
uv_data[v + 3] = Vector2(c.su, c.ev);
const Color color(((c.argb >> 16) & 255) / 255.0f, ((c.argb >> 8) & 255) / 255.0f,
(c.argb & 255) / 255.0f, ((c.argb >> 24) & 255) / 255.0f);
for (int k = 0; k < 4; ++k)
color_data[v + k] = color;
const int32_t base = static_cast<int32_t>(v);
index_data[t] = base;
index_data[t + 1] = base + 1;
index_data[t + 2] = base + 2;
index_data[t + 3] = base;
index_data[t + 4] = base + 2;
index_data[t + 5] = base + 3;
}
Dictionary item;
item["kind"] = StringName("glyph_batch");
item["text"] = String::utf8(first.text.c_str());
item["behind_3d"] = first.behind_3d;
item["points"] = points;
item["uvs"] = uvs;
item["colors"] = colors;
item["indices"] = indices;
item["glyph_count"] = count;
out.push_back(item);
i = end;
}
cached_frame_id = frame_id;
cached_commands = out;
return out;
}
bool Metin2PythonHost::has_3d_draws() { return !Render3DDraws().empty(); }
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);
}
bool read_pack_texture(const std::string &vpath, std::vector<std::uint8_t> &bytes) {
if (vpath.empty() || !mtpack40250::ready())
return false;
std::string norm = vpath;
for (char &ch : norm)
if (ch == '\\')
ch = '/';
std::string stripped = norm;
if (stripped.size() >= 2 && stripped[1] == ':')
stripped = stripped.substr(2);
while (!stripped.empty() && stripped.front() == '/')
stripped.erase(stripped.begin());
auto lower = [](std::string s) {
for (char &ch : s)
if (ch >= 'A' && ch <= 'Z')
ch = static_cast<char>(ch - 'A' + 'a');
return s;
};
for (const std::string &candidate : {
norm, stripped, "d:/" + stripped,
lower(norm), lower(stripped), lower("d:/" + stripped)}) {
if (mtpack40250::read(candidate, bytes) && !bytes.empty())
return true;
}
return false;
}
} // namespace
Array Metin2PythonHost::render3d_draws() {
struct CachedGeometry {
std::uint64_t revision;
std::uint64_t last_used;
Dictionary arrays;
};
static std::unordered_map<std::uint64_t, CachedGeometry> geometry_cache;
static std::uint64_t extraction = 0;
++extraction;
const auto &draws = Render3DDraws();
static bool capture_written = false;
if (!capture_written) {
const char *capture_path = std::getenv("MT_NATIVE_CAPTURE_PATH");
const char *minimum_text = std::getenv("MT_NATIVE_CAPTURE_MIN_DRAWS");
const unsigned long minimum = minimum_text ? std::strtoul(minimum_text, nullptr, 10) : 100UL;
if (capture_path && *capture_path && draws.size() >= minimum) {
capture_written = true;
try {
std::unordered_map<std::string, std::vector<std::uint8_t>> textures;
auto add_texture = [&](const std::string &name) {
if (name.empty() || textures.count(name))
return;
if (name.rfind("mem:", 0) == 0) {
UIMemoryTexture mem_tex;
if (UIRenderMemoryTexture(name, &mem_tex) && mem_tex.width > 0 && mem_tex.height > 0) {
auto mtra = native_draw_capture::encode_raw_argb_as_mtra(
static_cast<std::uint32_t>(mem_tex.width),
static_cast<std::uint32_t>(mem_tex.height),
mem_tex.argb.data());
if (!mtra.empty())
textures.emplace(name, std::move(mtra));
}
return;
}
std::vector<std::uint8_t> bytes;
if (read_pack_texture(name, bytes))
textures.emplace(name, std::move(bytes));
};
for (const Render3DDraw &draw : draws) {
add_texture(draw.texture0);
add_texture(draw.texture1);
}
const auto &ui_commands = UIRenderCommands();
for (const UIRenderCommand &cmd : ui_commands) {
if (cmd.kind == UIRenderCommand::Image) {
add_texture(cmd.text);
add_texture(cmd.mask);
}
}
unsigned ui_w = 0, ui_h = 0;
UIRenderGetSize(&ui_w, &ui_h);
native_draw_capture::write(
capture_path, draws, textures,
ui_w ? ui_w : 960u, ui_h ? ui_h : 640u, ui_commands);
UtilityFunctions::print(
"native draw capture: ", capture_path,
" draws=", static_cast<int64_t>(draws.size()),
" ui_commands=", static_cast<int64_t>(ui_commands.size()),
" textures=", static_cast<int64_t>(textures.size()));
} catch (const std::exception &error) {
UtilityFunctions::printerr("native draw capture failed: ", error.what());
}
}
}
Array out;
out.resize(static_cast<int64_t>(draws.size()));
int64_t draw_index = 0;
for (const Render3DDraw &draw : draws) {
if (draw.clear_flags)
continue; // back-buffer clears are consumed by the native renderer only
Dictionary item;
item["geometry_key"] = static_cast<int64_t>(draw.geometry_key);
item["geometry_revision"] = static_cast<int64_t>(draw.geometry_revision);
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;
// Stage-0 coordinates the D3D8 pipeline generates or transforms depend on per-draw matrices,
// so such draws carry them outside the geometry cache.
const bool stage0_texgen = (draw.texcoord_index[0] & 0xFFFF0000u) != 0 ||
(draw.texcoord_index[0] & 0xFFFFu) != 0 || draw.texture_transform_flags[0] != 0;
if (stage0_texgen)
item["geometry_key"] = static_cast<int64_t>(0);
Dictionary geometry;
if (draw.geometry_key != 0 && !stage0_texgen) {
auto cached = geometry_cache.find(draw.geometry_key);
if (cached != geometry_cache.end() && cached->second.revision == draw.geometry_revision) {
cached->second.last_used = extraction;
geometry = cached->second.arrays;
}
}
if (geometry.is_empty()) {
PackedVector3Array positions;
positions.resize(static_cast<int64_t>(draw.positions.size() / 3));
if constexpr (sizeof(Vector3) == sizeof(float) * 3 && std::is_trivially_copyable_v<Vector3>) {
if (!draw.positions.empty())
std::memcpy(positions.ptrw(), draw.positions.data(), draw.positions.size() * sizeof(float));
} else {
Vector3 *dst = positions.ptrw();
for (int64_t i = 0; i < positions.size(); ++i)
dst[i] = Vector3(draw.positions[i * 3], draw.positions[i * 3 + 1], draw.positions[i * 3 + 2]);
}
geometry["positions"] = positions;
if (!draw.rhw.empty())
geometry["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));
if constexpr (sizeof(Vector3) == sizeof(float) * 3 && std::is_trivially_copyable_v<Vector3>) {
std::memcpy(normals.ptrw(), draw.normals.data(), draw.normals.size() * sizeof(float));
} else {
Vector3 *dst = normals.ptrw();
for (int64_t i = 0; i < normals.size(); ++i)
dst[i] = Vector3(draw.normals[i * 3], draw.normals[i * 3 + 1], draw.normals[i * 3 + 2]);
}
geometry["normals"] = normals;
}
auto uvs = [](const std::vector<float> &values) {
PackedVector2Array out;
out.resize(static_cast<int64_t>(values.size() / 2));
if constexpr (sizeof(Vector2) == sizeof(float) * 2 && std::is_trivially_copyable_v<Vector2>) {
if (!values.empty())
std::memcpy(out.ptrw(), values.data(), values.size() * sizeof(float));
} else {
Vector2 *dst = out.ptrw();
for (int64_t i = 0; i < out.size(); ++i)
dst[i] = Vector2(values[i * 2], values[i * 2 + 1]);
}
return out;
};
if (stage0_texgen) {
std::vector<float> stage_uv;
Render3DStageTexcoords(draw, 0, stage_uv);
geometry["uv0"] = uvs(stage_uv);
} else if (!draw.uv0.empty())
geometry["uv0"] = uvs(draw.uv0);
if (!draw.uv1.empty())
geometry["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]);
geometry["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]);
geometry["indices"] = indices;
if (draw.geometry_key != 0 && !stage0_texgen)
geometry_cache[draw.geometry_key] = {draw.geometry_revision, extraction, geometry};
}
item.merge(geometry);
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);
bool uses_tf = false;
for (int s = 0; s < 2; ++s) {
if (draw.color_op[s] > 1 &&
(((draw.color_arg1[s] & 0xF) == 3) || ((draw.color_arg2[s] & 0xF) == 3)))
uses_tf = true;
if (draw.alpha_op[s] > 1 &&
(((draw.alpha_arg1[s] & 0xF) == 3) || ((draw.alpha_arg2[s] & 0xF) == 3)))
uses_tf = true;
}
item["uses_tf"] = uses_tf;
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);
PackedFloat32Array vp;
vp.resize(4);
vp[0] = draw.viewport[0];
vp[1] = draw.viewport[1];
vp[2] = draw.viewport[2];
vp[3] = draw.viewport[3];
item["viewport"] = vp;
out[draw_index++] = item;
}
out.resize(draw_index);
if (extraction % 120 == 0) {
for (auto it = geometry_cache.begin(); it != geometry_cache.end();) {
if (extraction - it->second.last_used > 120)
it = geometry_cache.erase(it);
else
++it;
}
}
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;
case AudioCommand::FadeInMusic: item["type"] = "fade_in_music"; break;
case AudioCommand::FadeLimitOutMusic: item["type"] = "fade_limit_out_music"; break;
case AudioCommand::SetSoundVolume3D: item["type"] = "set_sound_volume_3d"; 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_mouse_wheel(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(); }
Array Metin2PythonHost::ui_render_commands_batched() { return Array(); }
bool Metin2PythonHost::has_3d_draws() { return false; }
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_mouse_wheel", "delta"), &Metin2PythonHost::ui_mouse_wheel);
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("ui_render_commands_batched"), &Metin2PythonHost::ui_render_commands_batched);
ClassDB::bind_static_method("Metin2PythonHost", D_METHOD("has_3d_draws"), &Metin2PythonHost::has_3d_draws);
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