feat: complete native client rendering parity updates
This commit is contained in:
@@ -0,0 +1,732 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "UIRenderCommands.h"
|
||||
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
#include "platform/ScriptLib/PythonBoot.h"
|
||||
#endif
|
||||
|
||||
class TouchController {
|
||||
public:
|
||||
struct ButtonDef {
|
||||
int id; // 1 = ATK, 2 = S1, 3 = S2, 4 = S3, 5 = POT, 6 = PICK
|
||||
// 99 = MENU (toggle drawer)
|
||||
// 10 = BAG (DIK_I), 11 = CHAR (DIK_C), 12 = SKILL (DIK_V), 13 = QUEST (DIK_N), 14 = COMM (DIK_M), 15 = SET (DIK_ESCAPE)
|
||||
int dik;
|
||||
float x, y, radius;
|
||||
const char* label;
|
||||
uint32_t color_idle;
|
||||
uint32_t color_pressed;
|
||||
bool pressed = false;
|
||||
int64_t finger_id = -1;
|
||||
};
|
||||
|
||||
TouchController() {
|
||||
init_buttons();
|
||||
}
|
||||
|
||||
void set_enabled(bool enabled) { enabled_ = enabled; }
|
||||
bool is_enabled() const { return enabled_; }
|
||||
|
||||
void update_screen_size(int width, int height) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
screen_w_ = width;
|
||||
screen_h_ = height;
|
||||
// Position joystick on lower-left
|
||||
joystick_base_x_ = std::max(90.0f, float(width) * 0.12f);
|
||||
joystick_base_y_ = float(height) - std::max(90.0f, float(height) * 0.22f);
|
||||
if (!joystick_active_) {
|
||||
joystick_knob_x_ = joystick_base_x_;
|
||||
joystick_knob_y_ = joystick_base_y_;
|
||||
}
|
||||
layout_buttons();
|
||||
}
|
||||
|
||||
// Called once per frame to maintain continuous analog movement
|
||||
void update() {
|
||||
if (!enabled_) return;
|
||||
if (joystick_active_) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::SetMoveDirection(move_angle_, true);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Handles finger touch down (norm_x, norm_y in 0.0 .. 1.0)
|
||||
bool on_finger_down(int64_t finger_id, float norm_x, float norm_y) {
|
||||
if (!enabled_) return false;
|
||||
const float px = norm_x * float(screen_w_);
|
||||
const float py = norm_y * float(screen_h_);
|
||||
|
||||
// 1. Check HUD buttons (Action cluster & Drawer menu)
|
||||
int btn_idx = find_button(px, py);
|
||||
if (btn_idx >= 0) {
|
||||
auto& btn = buttons_[btn_idx];
|
||||
if (btn.id == 99) { // MENU toggle button
|
||||
drawer_open_ = !drawer_open_;
|
||||
layout_buttons();
|
||||
return true;
|
||||
}
|
||||
btn.pressed = true;
|
||||
btn.finger_id = finger_id;
|
||||
trigger_button(btn.dik, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Intelligent UI Hit-Testing: check if touch lands inside an open in-game UI window
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (PythonBoot::IsPointInsideActiveUI(int(px), int(py))) {
|
||||
ui_touch_finger_id_ = finger_id;
|
||||
PythonBoot::UIMouseMove(int(px), int(py));
|
||||
PythonBoot::UIMouseButton(1, true, int(px), int(py));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 3. Left side: virtual joystick (lower-left quadrant)
|
||||
if (norm_x < 0.40f && norm_y > 0.35f && !joystick_active_) {
|
||||
joystick_active_ = true;
|
||||
joystick_finger_id_ = finger_id;
|
||||
joystick_base_x_ = px;
|
||||
joystick_base_y_ = py;
|
||||
joystick_knob_x_ = px;
|
||||
joystick_knob_y_ = py;
|
||||
update_joystick_motion(px, py);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. 3D Game World: camera rotation, pinch zoom, or tap to target
|
||||
if (camera_finger_id_ < 0) {
|
||||
camera_finger_id_ = finger_id;
|
||||
camera_last_x_ = px;
|
||||
camera_last_y_ = py;
|
||||
camera_start_x_ = px;
|
||||
camera_start_y_ = py;
|
||||
camera_dragged_ = false;
|
||||
camera_down_time_ = get_time_sec();
|
||||
return true;
|
||||
} else if (pinch_finger2_ < 0) {
|
||||
pinch_finger1_ = camera_finger_id_;
|
||||
pinch_finger2_ = finger_id;
|
||||
pinch_last_dist_ = std::hypot(px - camera_last_x_, py - camera_last_y_);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handles finger motion
|
||||
bool on_finger_motion(int64_t finger_id, float norm_x, float norm_y) {
|
||||
if (!enabled_) return false;
|
||||
const float px = norm_x * float(screen_w_);
|
||||
const float py = norm_y * float(screen_h_);
|
||||
|
||||
// 1. UI Touch dragging (e.g. dragging item in inventory or scrollbar)
|
||||
if (ui_touch_finger_id_ == finger_id) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::UIMouseMove(int(px), int(py));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Virtual Joystick finger
|
||||
if (joystick_active_ && finger_id == joystick_finger_id_) {
|
||||
update_joystick_motion(px, py);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Button drag tracking (check if finger slid off)
|
||||
for (auto& btn : buttons_) {
|
||||
if (btn.finger_id == finger_id) {
|
||||
const float dist = std::hypot(px - btn.x, py - btn.y);
|
||||
if (dist > btn.radius * 1.5f && btn.pressed) {
|
||||
btn.pressed = false;
|
||||
trigger_button(btn.dik, false);
|
||||
} else if (dist <= btn.radius * 1.5f && !btn.pressed) {
|
||||
btn.pressed = true;
|
||||
trigger_button(btn.dik, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Two-finger pinch zoom
|
||||
if (pinch_finger1_ >= 0 && pinch_finger2_ >= 0 &&
|
||||
(finger_id == pinch_finger1_ || finger_id == pinch_finger2_)) {
|
||||
const float cur_dist = std::hypot(px - camera_last_x_, py - camera_last_y_);
|
||||
if (pinch_last_dist_ > 1.0f && cur_dist > 1.0f) {
|
||||
const float delta_d = cur_dist - pinch_last_dist_;
|
||||
if (std::abs(delta_d) > 2.0f) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::UIMouseWheel(int(delta_d * 8.0f));
|
||||
#endif
|
||||
pinch_last_dist_ = cur_dist;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5. Single-finger camera drag
|
||||
if (finger_id == camera_finger_id_) {
|
||||
const float total_dist = std::hypot(px - camera_start_x_, py - camera_start_y_);
|
||||
if (total_dist > 6.0f) {
|
||||
if (!camera_dragged_) {
|
||||
camera_dragged_ = true;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::CameraBeginDrag(int(camera_start_x_), int(camera_start_y_));
|
||||
#endif
|
||||
}
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::CameraDrag(int(px), int(py));
|
||||
#endif
|
||||
}
|
||||
camera_last_x_ = px;
|
||||
camera_last_y_ = py;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handles finger touch up
|
||||
bool on_finger_up(int64_t finger_id, float norm_x, float norm_y) {
|
||||
if (!enabled_) return false;
|
||||
const float px = norm_x * float(screen_w_);
|
||||
const float py = norm_y * float(screen_h_);
|
||||
|
||||
// 1. UI Touch release (e.g. dropped item in inventory or clicked button)
|
||||
if (ui_touch_finger_id_ == finger_id) {
|
||||
ui_touch_finger_id_ = -1;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::UIMouseButton(1, false, int(px), int(py));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// 2. Joystick release
|
||||
if (joystick_active_ && finger_id == joystick_finger_id_) {
|
||||
joystick_active_ = false;
|
||||
joystick_finger_id_ = -1;
|
||||
joystick_knob_x_ = joystick_base_x_;
|
||||
joystick_knob_y_ = joystick_base_y_;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::SetMoveDirection(0.0f, false);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Button release
|
||||
for (auto& btn : buttons_) {
|
||||
if (btn.finger_id == finger_id) {
|
||||
if (btn.pressed) {
|
||||
btn.pressed = false;
|
||||
trigger_button(btn.dik, false);
|
||||
}
|
||||
btn.finger_id = -1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Pinch end
|
||||
if (finger_id == pinch_finger1_ || finger_id == pinch_finger2_) {
|
||||
pinch_finger1_ = -1;
|
||||
pinch_finger2_ = -1;
|
||||
pinch_last_dist_ = 0.0f;
|
||||
if (finger_id == camera_finger_id_) camera_finger_id_ = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5. Camera finger release
|
||||
if (finger_id == camera_finger_id_) {
|
||||
camera_finger_id_ = -1;
|
||||
if (camera_dragged_) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::CameraEndDrag();
|
||||
#endif
|
||||
} else if ((get_time_sec() - camera_down_time_) < 0.35) {
|
||||
// Short tap on 3D world: select target (mob, NPC, ground)
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::UIMouseMove(int(px), int(py));
|
||||
PythonBoot::UIMouseButton(1, true, int(px), int(py));
|
||||
PythonBoot::UIMouseButton(1, false, int(px), int(py));
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Render virtual joystick, buttons, drawer menu, and player status bar
|
||||
void append_ui_commands(std::vector<UIRenderCommand>& commands) const {
|
||||
if (!enabled_) return;
|
||||
|
||||
const float W = float(screen_w_);
|
||||
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
// --- 1. Top-Left Player Status Bar (Mobile HUD) ---
|
||||
const auto status = PythonBoot::GetPlayerStatusInfo();
|
||||
if (status.max_hp > 0) {
|
||||
const float bar_x = 20.0f;
|
||||
const float bar_y = 16.0f;
|
||||
const float bar_w = 205.0f;
|
||||
const float bar_h = 44.0f;
|
||||
|
||||
// Background plate
|
||||
draw_rect_bar(commands, bar_x, bar_y, bar_x + bar_w, bar_y + bar_h, 0x90101824);
|
||||
draw_rect_lines(commands, bar_x, bar_y, bar_x + bar_w, bar_y + bar_h, 0x60405870);
|
||||
|
||||
// Avatar circle badge
|
||||
const float av_cx = bar_x + 22.0f;
|
||||
const float av_cy = bar_y + 22.0f;
|
||||
const float av_r = 15.0f;
|
||||
draw_filled_disc(commands, av_cx, av_cy, av_r, 0xB0203040);
|
||||
draw_circle(commands, av_cx, av_cy, av_r, 0xE0FFD700, 16);
|
||||
// "Lv" tick inside avatar
|
||||
draw_line(commands, av_cx - 4.0f, av_cy - 4.0f, av_cx - 4.0f, av_cy + 3.0f, 0xFFFFFFFF);
|
||||
draw_line(commands, av_cx - 4.0f, av_cy + 3.0f, av_cx - 1.0f, av_cy + 3.0f, 0xFFFFFFFF);
|
||||
draw_line(commands, av_cx + 1.0f, av_cy - 4.0f, av_cx + 3.0f, av_cy + 3.0f, 0xFFFFFFFF);
|
||||
draw_line(commands, av_cx + 5.0f, av_cy - 4.0f, av_cx + 3.0f, av_cy + 3.0f, 0xFFFFFFFF);
|
||||
|
||||
// Gauges
|
||||
const float gx1 = bar_x + 44.0f;
|
||||
const float gx2 = bar_x + bar_w - 10.0f;
|
||||
const float gw = gx2 - gx1;
|
||||
|
||||
// HP Gauge
|
||||
const float hp_y1 = bar_y + 10.0f;
|
||||
const float hp_y2 = hp_y1 + 10.0f;
|
||||
const float hp_ratio = std::clamp(float(status.hp) / float(status.max_hp), 0.0f, 1.0f);
|
||||
draw_rect_bar(commands, gx1, hp_y1, gx2, hp_y2, 0x70301010);
|
||||
draw_rect_bar(commands, gx1, hp_y1, gx1 + gw * hp_ratio, hp_y2, 0xE5D82424);
|
||||
draw_line(commands, gx1, hp_y1, gx1 + gw * hp_ratio, hp_y1, 0x60FFFFFF); // Gloss line
|
||||
draw_rect_lines(commands, gx1, hp_y1, gx2, hp_y2, 0x80502020);
|
||||
|
||||
// MP Gauge
|
||||
const float mp_y1 = bar_y + 24.0f;
|
||||
const float mp_y2 = mp_y1 + 8.0f;
|
||||
const float mp_ratio = status.max_sp > 0 ? std::clamp(float(status.sp) / float(status.max_sp), 0.0f, 1.0f) : 0.0f;
|
||||
draw_rect_bar(commands, gx1, mp_y1, gx2, mp_y2, 0x70102038);
|
||||
draw_rect_bar(commands, gx1, mp_y1, gx1 + gw * mp_ratio, mp_y2, 0xE52068E0);
|
||||
draw_line(commands, gx1, mp_y1, gx1 + gw * mp_ratio, mp_y1, 0x60FFFFFF); // Gloss line
|
||||
draw_rect_lines(commands, gx1, mp_y1, gx2, mp_y2, 0x80204060);
|
||||
|
||||
// EXP bar (bottom trim line)
|
||||
if (status.max_exp > 0) {
|
||||
const float exp_ratio = std::clamp(float(status.exp) / float(status.max_exp), 0.0f, 1.0f);
|
||||
draw_rect_bar(commands, bar_x, bar_y + bar_h - 2.0f, bar_x + bar_w * exp_ratio, bar_y + bar_h, 0xD0E0B020);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// --- 2. Top-Right Drawer Menu Bar ---
|
||||
if (drawer_open_) {
|
||||
// Background capsule tray behind drawer buttons
|
||||
draw_rect_bar(commands, W - 468.0f, 16.0f, W - 198.0f, 54.0f, 0x90101824);
|
||||
draw_rect_lines(commands, W - 468.0f, 16.0f, W - 198.0f, 54.0f, 0x60506880);
|
||||
}
|
||||
|
||||
// --- 3. Buttons Rendering (Combat wheel & Drawer items) ---
|
||||
for (const auto& btn : buttons_) {
|
||||
if (btn.id >= 10 && btn.id <= 15 && !drawer_open_)
|
||||
continue;
|
||||
|
||||
const uint32_t col = btn.pressed ? btn.color_pressed : btn.color_idle;
|
||||
draw_filled_disc(commands, btn.x, btn.y, btn.radius, col);
|
||||
draw_circle(commands, btn.x, btn.y, btn.radius * 0.85f, btn.pressed ? 0xFFFFFFFF : 0x70FFFFFF, 16);
|
||||
|
||||
const uint32_t icon_col = btn.pressed ? 0xFFFFFFFF : 0xDDFFFFFF;
|
||||
const float r = btn.radius;
|
||||
|
||||
switch (btn.id) {
|
||||
case 1: { // ATK: crossed swords
|
||||
const float s = r * 0.35f;
|
||||
draw_line(commands, btn.x - s, btn.y - s, btn.x + s, btn.y + s, icon_col);
|
||||
draw_line(commands, btn.x + s, btn.y - s, btn.x - s, btn.y + s, icon_col);
|
||||
const float g = s * 0.35f;
|
||||
draw_line(commands, btn.x - s*0.4f - g, btn.y - s*0.4f + g, btn.x - s*0.4f + g, btn.y - s*0.4f - g, icon_col);
|
||||
draw_line(commands, btn.x + s*0.4f - g, btn.y - s*0.4f - g, btn.x + s*0.4f + g, btn.y - s*0.4f + g, icon_col);
|
||||
break;
|
||||
}
|
||||
case 2: { // S1: I
|
||||
const float h = r * 0.35f;
|
||||
draw_line(commands, btn.x, btn.y - h, btn.x, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x - 4.0f, btn.y - h, btn.x + 4.0f, btn.y - h, icon_col);
|
||||
draw_line(commands, btn.x - 4.0f, btn.y + h, btn.x + 4.0f, btn.y + h, icon_col);
|
||||
break;
|
||||
}
|
||||
case 3: { // S2: II
|
||||
const float h = r * 0.35f;
|
||||
draw_line(commands, btn.x - 4.0f, btn.y - h, btn.x - 4.0f, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x + 4.0f, btn.y - h, btn.x + 4.0f, btn.y + h, icon_col);
|
||||
break;
|
||||
}
|
||||
case 4: { // S3: III
|
||||
const float h = r * 0.35f;
|
||||
draw_line(commands, btn.x - 6.0f, btn.y - h, btn.x - 6.0f, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x, btn.y - h, btn.x, btn.y + h, icon_col);
|
||||
draw_line(commands, btn.x + 6.0f, btn.y - h, btn.x + 6.0f, btn.y + h, icon_col);
|
||||
break;
|
||||
}
|
||||
case 5: { // POT: +
|
||||
const float p = r * 0.4f;
|
||||
draw_line(commands, btn.x - p, btn.y, btn.x + p, btn.y, icon_col);
|
||||
draw_line(commands, btn.x, btn.y - p, btn.x, btn.y + p, icon_col);
|
||||
break;
|
||||
}
|
||||
case 6: { // PICK: Downward arrow
|
||||
const float a = r * 0.35f;
|
||||
draw_line(commands, btn.x - a, btn.y - a * 0.3f, btn.x, btn.y + a * 0.6f, icon_col);
|
||||
draw_line(commands, btn.x + a, btn.y - a * 0.3f, btn.x, btn.y + a * 0.6f, icon_col);
|
||||
draw_line(commands, btn.x, btn.y - a * 0.7f, btn.x, btn.y + a * 0.6f, icon_col);
|
||||
break;
|
||||
}
|
||||
case 99: { // MENU: ☰ hamburger icon
|
||||
draw_line(commands, btn.x - 7.0f, btn.y - 5.0f, btn.x + 7.0f, btn.y - 5.0f, icon_col);
|
||||
draw_line(commands, btn.x - 7.0f, btn.y, btn.x + 7.0f, btn.y, icon_col);
|
||||
draw_line(commands, btn.x - 7.0f, btn.y + 5.0f, btn.x + 7.0f, btn.y + 5.0f, icon_col);
|
||||
break;
|
||||
}
|
||||
case 10: { // BAG: Backpack
|
||||
draw_rect_lines(commands, btn.x - 6.0f, btn.y - 4.0f, btn.x + 6.0f, btn.y + 6.0f, icon_col);
|
||||
draw_line(commands, btn.x - 3.0f, btn.y - 4.0f, btn.x, btn.y - 7.0f, icon_col);
|
||||
draw_line(commands, btn.x, btn.y - 7.0f, btn.x + 3.0f, btn.y - 4.0f, icon_col);
|
||||
draw_line(commands, btn.x - 6.0f, btn.y, btn.x + 6.0f, btn.y, icon_col);
|
||||
break;
|
||||
}
|
||||
case 11: { // CHAR: Head + Shoulders
|
||||
draw_circle(commands, btn.x, btn.y - 3.0f, 4.0f, icon_col, 12);
|
||||
draw_line(commands, btn.x - 6.0f, btn.y + 6.0f, btn.x - 3.0f, btn.y + 2.0f, icon_col);
|
||||
draw_line(commands, btn.x - 3.0f, btn.y + 2.0f, btn.x + 3.0f, btn.y + 2.0f, icon_col);
|
||||
draw_line(commands, btn.x + 3.0f, btn.y + 2.0f, btn.x + 6.0f, btn.y + 6.0f, icon_col);
|
||||
break;
|
||||
}
|
||||
case 12: { // SKILL: Lightning
|
||||
draw_line(commands, btn.x + 2.0f, btn.y - 7.0f, btn.x - 3.0f, btn.y - 1.0f, icon_col);
|
||||
draw_line(commands, btn.x - 3.0f, btn.y - 1.0f, btn.x + 1.0f, btn.y - 1.0f, icon_col);
|
||||
draw_line(commands, btn.x + 1.0f, btn.y - 1.0f, btn.x - 2.0f, btn.y + 7.0f, icon_col);
|
||||
break;
|
||||
}
|
||||
case 13: { // QUEST: Scroll
|
||||
draw_rect_lines(commands, btn.x - 5.0f, btn.y - 6.0f, btn.x + 5.0f, btn.y + 6.0f, icon_col);
|
||||
draw_line(commands, btn.x - 3.0f, btn.y - 2.0f, btn.x + 3.0f, btn.y - 2.0f, icon_col);
|
||||
draw_line(commands, btn.x - 3.0f, btn.y + 2.0f, btn.x + 1.0f, btn.y + 2.0f, icon_col);
|
||||
break;
|
||||
}
|
||||
case 14: { // COMM: Chat bubble
|
||||
draw_rect_lines(commands, btn.x - 6.0f, btn.y - 5.0f, btn.x + 6.0f, btn.y + 3.0f, icon_col);
|
||||
draw_line(commands, btn.x - 3.0f, btn.y + 3.0f, btn.x - 5.0f, btn.y + 6.0f, icon_col);
|
||||
draw_line(commands, btn.x - 5.0f, btn.y + 6.0f, btn.x, btn.y + 3.0f, icon_col);
|
||||
break;
|
||||
}
|
||||
case 15: { // SET: Gear / Close
|
||||
draw_circle(commands, btn.x, btn.y, 4.0f, icon_col, 10);
|
||||
draw_line(commands, btn.x - 7.0f, btn.y, btn.x + 7.0f, btn.y, icon_col);
|
||||
draw_line(commands, btn.x, btn.y - 7.0f, btn.x, btn.y + 7.0f, icon_col);
|
||||
draw_line(commands, btn.x - 5.0f, btn.y - 5.0f, btn.x + 5.0f, btn.y + 5.0f, icon_col);
|
||||
draw_line(commands, btn.x - 5.0f, btn.y + 5.0f, btn.x + 5.0f, btn.y - 5.0f, icon_col);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4. Virtual Joystick ---
|
||||
draw_circle(commands, joystick_base_x_, joystick_base_y_, joystick_radius_, 0x8080C0FF, 24);
|
||||
draw_circle(commands, joystick_base_x_, joystick_base_y_, joystick_radius_ * 0.45f, 0x4080C0FF, 16);
|
||||
draw_line(commands, joystick_base_x_ - joystick_radius_, joystick_base_y_,
|
||||
joystick_base_x_ - joystick_radius_ + 8.0f, joystick_base_y_, 0x90FFFFFF);
|
||||
draw_line(commands, joystick_base_x_ + joystick_radius_ - 8.0f, joystick_base_y_,
|
||||
joystick_base_x_ + joystick_radius_, joystick_base_y_, 0x90FFFFFF);
|
||||
draw_line(commands, joystick_base_x_, joystick_base_y_ - joystick_radius_,
|
||||
joystick_base_x_, joystick_base_y_ - joystick_radius_ + 8.0f, 0x90FFFFFF);
|
||||
draw_line(commands, joystick_base_x_, joystick_base_y_ + joystick_radius_ - 8.0f,
|
||||
joystick_base_x_, joystick_base_y_ + joystick_radius_, 0x90FFFFFF);
|
||||
|
||||
if (joystick_active_) {
|
||||
draw_line(commands, joystick_base_x_, joystick_base_y_, joystick_knob_x_, joystick_knob_y_, 0xB000FFFF);
|
||||
}
|
||||
const uint32_t knob_color = joystick_active_ ? 0xB040A0FF : 0x6040A0FF;
|
||||
draw_filled_disc(commands, joystick_knob_x_, joystick_knob_y_, joystick_knob_radius_, knob_color);
|
||||
draw_circle(commands, joystick_knob_x_, joystick_knob_y_, joystick_knob_radius_ * 0.5f, 0x80FFFFFF, 12);
|
||||
}
|
||||
|
||||
// Desktop mouse testing simulation
|
||||
bool on_mouse_button(int button, bool pressed, int x, int y) {
|
||||
if (!enabled_) return false;
|
||||
const float norm_x = float(x) / float(screen_w_);
|
||||
const float norm_y = float(y) / float(screen_h_);
|
||||
if (pressed) {
|
||||
if (button == 1) {
|
||||
return on_finger_down(101, norm_x, norm_y);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (button == 1) {
|
||||
return on_finger_up(101, norm_x, norm_y);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool on_mouse_motion(int x, int y) {
|
||||
if (!enabled_) return false;
|
||||
const float norm_x = float(x) / float(screen_w_);
|
||||
const float norm_y = float(y) / float(screen_h_);
|
||||
bool handled = false;
|
||||
if (ui_touch_finger_id_ == 101) {
|
||||
handled |= on_finger_motion(101, norm_x, norm_y);
|
||||
}
|
||||
if (joystick_active_ && joystick_finger_id_ == 101) {
|
||||
handled |= on_finger_motion(101, norm_x, norm_y);
|
||||
}
|
||||
if (camera_finger_id_ == 101) {
|
||||
handled |= on_finger_motion(101, norm_x, norm_y);
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
private:
|
||||
void init_buttons() {
|
||||
buttons_.clear();
|
||||
// Combat Action Wheel (Lower-Right)
|
||||
buttons_.push_back({1, 0x39, 0, 0, 42.0f, "ATK", 0x80D48820, 0xD0FFB040});
|
||||
buttons_.push_back({2, 0x02, 0, 0, 26.0f, "S1", 0x803060C0, 0xD05080FF});
|
||||
buttons_.push_back({3, 0x03, 0, 0, 26.0f, "S2", 0x80903090, 0xD0D050D0});
|
||||
buttons_.push_back({4, 0x04, 0, 0, 26.0f, "S3", 0x80309060, 0xD050D080});
|
||||
buttons_.push_back({5, 0x05, 0, 0, 22.0f, "POT", 0x90A03030, 0xD0FF5050});
|
||||
buttons_.push_back({6, 0x2c, 0, 0, 22.0f, "PICK", 0x80208080, 0xD040B0B0});
|
||||
|
||||
// Top-Right Drawer Menu Toggle
|
||||
buttons_.push_back({99, 0, 0, 0, 20.0f, "MENU", 0x90283848, 0xD0FFB040});
|
||||
|
||||
// Drawer Menu Buttons
|
||||
buttons_.push_back({10, 0x17, 0, 0, 18.0f, "BAG", 0x90D09020, 0xD0FFB040}); // DIK_I
|
||||
buttons_.push_back({11, 0x2e, 0, 0, 18.0f, "CHAR", 0x903060B0, 0xD05080FF}); // DIK_C
|
||||
buttons_.push_back({12, 0x2f, 0, 0, 18.0f, "SKILL", 0x90803090, 0xD0D050D0}); // DIK_V
|
||||
buttons_.push_back({13, 0x31, 0, 0, 18.0f, "QUEST", 0x90308050, 0xD050D070}); // DIK_N
|
||||
buttons_.push_back({14, 0x32, 0, 0, 18.0f, "COMM", 0x90905020, 0xD0E07030}); // DIK_M
|
||||
buttons_.push_back({15, 0x01, 0, 0, 18.0f, "SET", 0x90506070, 0xD08090A0}); // DIK_ESCAPE
|
||||
}
|
||||
|
||||
void layout_buttons() {
|
||||
const float W = float(screen_w_);
|
||||
const float H = float(screen_h_);
|
||||
for (auto& btn : buttons_) {
|
||||
switch (btn.id) {
|
||||
case 1:
|
||||
btn.x = W - 85.0f;
|
||||
btn.y = H - 85.0f;
|
||||
btn.radius = std::min(46.0f, H * 0.12f);
|
||||
break;
|
||||
case 2:
|
||||
btn.x = W - 165.0f;
|
||||
btn.y = H - 85.0f;
|
||||
btn.radius = std::min(28.0f, H * 0.08f);
|
||||
break;
|
||||
case 3:
|
||||
btn.x = W - 145.0f;
|
||||
btn.y = H - 155.0f;
|
||||
btn.radius = std::min(28.0f, H * 0.08f);
|
||||
break;
|
||||
case 4:
|
||||
btn.x = W - 85.0f;
|
||||
btn.y = H - 175.0f;
|
||||
btn.radius = std::min(28.0f, H * 0.08f);
|
||||
break;
|
||||
case 5:
|
||||
btn.x = W - 225.0f;
|
||||
btn.y = H - 75.0f;
|
||||
btn.radius = std::min(24.0f, H * 0.065f);
|
||||
break;
|
||||
case 6:
|
||||
btn.x = W - 85.0f;
|
||||
btn.y = H - 235.0f;
|
||||
btn.radius = std::min(24.0f, H * 0.065f);
|
||||
break;
|
||||
case 99: // MENU toggle button (to the left of MiniMap)
|
||||
btn.x = W - 170.0f;
|
||||
btn.y = 35.0f;
|
||||
btn.radius = 20.0f;
|
||||
break;
|
||||
case 10: // BAG
|
||||
btn.x = W - 220.0f;
|
||||
btn.y = 35.0f;
|
||||
btn.radius = 18.0f;
|
||||
break;
|
||||
case 11: // CHAR
|
||||
btn.x = W - 265.0f;
|
||||
btn.y = 35.0f;
|
||||
btn.radius = 18.0f;
|
||||
break;
|
||||
case 12: // SKILL
|
||||
btn.x = W - 310.0f;
|
||||
btn.y = 35.0f;
|
||||
btn.radius = 18.0f;
|
||||
break;
|
||||
case 13: // QUEST
|
||||
btn.x = W - 355.0f;
|
||||
btn.y = 35.0f;
|
||||
btn.radius = 18.0f;
|
||||
break;
|
||||
case 14: // COMM
|
||||
btn.x = W - 400.0f;
|
||||
btn.y = 35.0f;
|
||||
btn.radius = 18.0f;
|
||||
break;
|
||||
case 15: // SET
|
||||
btn.x = W - 445.0f;
|
||||
btn.y = 35.0f;
|
||||
btn.radius = 18.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int find_button(float x, float y) {
|
||||
for (size_t i = 0; i < buttons_.size(); ++i) {
|
||||
const auto& btn = buttons_[i];
|
||||
if (btn.id >= 10 && btn.id <= 15 && !drawer_open_)
|
||||
continue;
|
||||
const float dist = std::hypot(x - btn.x, y - btn.y);
|
||||
if (dist <= btn.radius * 1.25f) {
|
||||
return int(i);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void trigger_button(int dik, bool pressed) {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
if (dik == 0x39) {
|
||||
PythonBoot::SetAttackKey(pressed);
|
||||
}
|
||||
if (dik != 0) {
|
||||
PythonBoot::UIKey(dik, pressed);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void update_joystick_motion(float px, float py) {
|
||||
const float dx = px - joystick_base_x_;
|
||||
const float dy = py - joystick_base_y_;
|
||||
const float dist = std::hypot(dx, dy);
|
||||
|
||||
if (dist <= joystick_radius_) {
|
||||
joystick_knob_x_ = px;
|
||||
joystick_knob_y_ = py;
|
||||
} else if (dist > 0.0f) {
|
||||
joystick_knob_x_ = joystick_base_x_ + (dx / dist) * joystick_radius_;
|
||||
joystick_knob_y_ = joystick_base_y_ + (dy / dist) * joystick_radius_;
|
||||
}
|
||||
|
||||
if (dist > 8.0f) {
|
||||
const float rad = std::atan2(-dx, -dy);
|
||||
move_angle_ = rad * 180.0f / 3.14159265358979323846f;
|
||||
if (move_angle_ < 0.0f) move_angle_ += 360.0f;
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::SetMoveDirection(move_angle_, true);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef MT_NATIVE_HAS_LIVE_CLIENT
|
||||
PythonBoot::SetMoveDirection(0.0f, false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_line(std::vector<UIRenderCommand>& commands,
|
||||
float x1, float y1, float x2, float y2, uint32_t argb) {
|
||||
UIRenderCommand cmd{};
|
||||
cmd.kind = UIRenderCommand::Line;
|
||||
cmd.x1 = x1; cmd.y1 = y1;
|
||||
cmd.x2 = x2; cmd.y2 = y2;
|
||||
cmd.argb = argb;
|
||||
commands.push_back(cmd);
|
||||
}
|
||||
|
||||
static void draw_rect_bar(std::vector<UIRenderCommand>& commands,
|
||||
float x1, float y1, float x2, float y2, uint32_t argb) {
|
||||
UIRenderCommand bar{};
|
||||
bar.kind = UIRenderCommand::Bar;
|
||||
bar.x1 = x1; bar.y1 = y1;
|
||||
bar.x2 = x2; bar.y2 = y2;
|
||||
bar.argb = argb;
|
||||
commands.push_back(bar);
|
||||
}
|
||||
|
||||
static void draw_rect_lines(std::vector<UIRenderCommand>& commands,
|
||||
float x1, float y1, float x2, float y2, uint32_t argb) {
|
||||
draw_line(commands, x1, y1, x2, y1, argb);
|
||||
draw_line(commands, x2, y1, x2, y2, argb);
|
||||
draw_line(commands, x2, y2, x1, y2, argb);
|
||||
draw_line(commands, x1, y2, x1, y1, argb);
|
||||
}
|
||||
|
||||
static void draw_circle(std::vector<UIRenderCommand>& commands,
|
||||
float cx, float cy, float radius, uint32_t argb, int segments = 16) {
|
||||
const float step = 2.0f * 3.14159265f / float(segments);
|
||||
for (int i = 0; i < segments; ++i) {
|
||||
const float a1 = float(i) * step;
|
||||
const float a2 = float(i + 1) * step;
|
||||
draw_line(commands,
|
||||
cx + std::cos(a1) * radius, cy + std::sin(a1) * radius,
|
||||
cx + std::cos(a2) * radius, cy + std::sin(a2) * radius,
|
||||
argb);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_filled_disc(std::vector<UIRenderCommand>& commands,
|
||||
float cx, float cy, float radius, uint32_t argb) {
|
||||
// Base rectangular fill
|
||||
draw_rect_bar(commands, cx - radius * 0.65f, cy - radius * 0.65f, cx + radius * 0.65f, cy + radius * 0.65f, (argb & 0x00FFFFFF) | 0x55000000);
|
||||
// Cross fills for roundness
|
||||
draw_rect_bar(commands, cx - radius * 0.85f, cy - radius * 0.35f, cx + radius * 0.85f, cy + radius * 0.35f, (argb & 0x00FFFFFF) | 0x55000000);
|
||||
draw_rect_bar(commands, cx - radius * 0.35f, cy - radius * 0.85f, cx + radius * 0.35f, cy + radius * 0.85f, (argb & 0x00FFFFFF) | 0x55000000);
|
||||
// Border rings
|
||||
draw_circle(commands, cx, cy, radius, argb, 20);
|
||||
draw_circle(commands, cx, cy, radius - 1.0f, (argb & 0x00FFFFFF) | 0x40000000, 20);
|
||||
}
|
||||
|
||||
static double get_time_sec() {
|
||||
using namespace std::chrono;
|
||||
return duration_cast<duration<double>>(steady_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
bool enabled_ = false;
|
||||
int screen_w_ = 1280;
|
||||
int screen_h_ = 720;
|
||||
|
||||
bool drawer_open_ = false;
|
||||
int64_t ui_touch_finger_id_ = -1;
|
||||
|
||||
bool joystick_active_ = false;
|
||||
int64_t joystick_finger_id_ = -1;
|
||||
float joystick_base_x_ = 140.0f;
|
||||
float joystick_base_y_ = 580.0f;
|
||||
float joystick_knob_x_ = 140.0f;
|
||||
float joystick_knob_y_ = 580.0f;
|
||||
float joystick_radius_ = 65.0f;
|
||||
float joystick_knob_radius_ = 28.0f;
|
||||
float move_angle_ = 0.0f;
|
||||
|
||||
int64_t camera_finger_id_ = -1;
|
||||
float camera_start_x_ = 0.0f;
|
||||
float camera_start_y_ = 0.0f;
|
||||
float camera_last_x_ = 0.0f;
|
||||
float camera_last_y_ = 0.0f;
|
||||
double camera_down_time_ = 0.0;
|
||||
bool camera_dragged_ = false;
|
||||
|
||||
int64_t pinch_finger1_ = -1;
|
||||
int64_t pinch_finger2_ = -1;
|
||||
float pinch_last_dist_ = 0.0f;
|
||||
|
||||
std::vector<ButtonDef> buttons_;
|
||||
};
|
||||
Reference in New Issue
Block a user