android-native: auto hunt, in-client account registration, mobile UI polish

- Auto hunt (client-only): PythonPlayerAutoHunt.cpp + AutoHuntScript.inc as the
  embedded mt_autohunt module, AUTO touch button, F9/F8.
- Account registration: tools/40250/register_server.py (rc.d mt_register, :11080)
  inserts into account.account; net.RegisterAccount/GetRegisterAccountResult
  (PythonAccountRegister.cpp) and the mt_register login-window dialog
  (RegisterScript.inc).
- --py-exec also runs in --login-screen mode for scripted login-screen tests.
- Touch controller, window manager and render command updates for the
  Android native client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-29 16:15:04 +09:00
co-authored by Claude Opus 5.5
parent 53d06c3c26
commit 3e3708ef06
31 changed files with 2975 additions and 140 deletions
+187 -38
View File
@@ -5,6 +5,7 @@
#include "ScriptLib/Resource.h"
#include "UserInterface/StdAfx.h" // initpack and the rest of the module initializer list
#include "EterPythonLib/StdAfx.h"
#include "EterPythonLib/PythonWindow.h"
#include "EterBase/Timer.h"
#include "EterLib/Util.h"
#include "EterLib/GrpBase.h"
@@ -151,6 +152,7 @@ struct GameSingletons
};
std::unique_ptr<GameSingletons> g_game_singletons;
bool g_host_hardware_cursor_enabled = false;
bool g_touch_input = false;
bool fail(std::string* error, const std::string& text)
{
@@ -268,11 +270,20 @@ bool IsRunning()
return g_launcher != nullptr;
}
static int g_ui_safe_inset = 0;
void SetUISafeInset(int inset)
{
g_ui_safe_inset = std::max(0, inset);
}
void SetUISize(int width, int height)
{
if (!g_window_manager || width <= 0 || height <= 0) return;
g_window_manager->SetScreenSize(width, height);
const int inset = std::min(g_ui_safe_inset, width / 8);
g_window_manager->SetScreenSize(width - 2 * inset, height);
g_window_manager->SetResolution(width, height);
g_window_manager->SetScreenOffsetX(inset);
UIRenderSetSize(width, height);
CGraphicBase::SetBackBufferSize(width, height);
if (g_game_singletons)
@@ -290,8 +301,10 @@ void update_3d_pick_ray()
return;
long lx = 0, ly = 0;
g_window_manager->GetMousePosition(lx, ly);
const long sw = g_window_manager->GetScreenWidth();
const long sh = g_window_manager->GetScreenHeight();
// The pick ray goes through the full-screen 3D view: screen coordinates and resolution.
lx += g_window_manager->GetScreenOffsetX();
long sw = 0, sh = 0;
g_window_manager->GetResolution(sw, sh);
if (sw <= 0 || sh <= 0)
return;
CScreen s;
@@ -330,6 +343,11 @@ void UIMouseButton(int button, bool pressed, int x, int y)
CPythonApplication::Instance().OnMouseMove(x, y);
else
g_window_manager->RunMouseMove(x, y);
{
long lx = x, ly = y;
g_window_manager->GetMousePosition(lx, ly);
MtHostSetCursor(static_cast<int>(lx), static_cast<int>(ly));
}
update_3d_pick_ray();
switch (button)
{
@@ -398,7 +416,8 @@ void SetMoveDirection(float angleDeg, bool moving)
{
if (!g_game_singletons) return;
if (moving)
CPythonPlayer::Instance().NEW_MoveToDirection(angleDeg);
// The touch stick only steers the player; the camera turns by the right-hand drag.
CPythonPlayer::Instance().NEW_MoveToDirection(angleDeg, false);
else
CPythonPlayer::Instance().NEW_Stop();
}
@@ -415,6 +434,30 @@ void UseLocalQuickSlot(int localSlotIndex)
CPythonPlayer::Instance().RequestUseLocalQuickSlot(static_cast<DWORD>(localSlotIndex));
}
void AutoHuntToggle()
{
std::string error;
if (g_game_singletons)
RunLine("__import__('mt_autohunt').Toggle()", &error);
}
void AutoHuntOpenSettings()
{
std::string error;
if (g_game_singletons)
RunLine("__import__('mt_autohunt').OpenSettings()", &error);
}
bool AutoHuntIsEnabled()
{
return g_game_singletons && CPythonPlayer::Instance().AutoHunt_IsEnabled();
}
bool AutoHuntAvailable()
{
return g_game_singletons && CPythonPlayer::Instance().NEW_GetMainActorPtr() != nullptr;
}
bool TryAssignAttachedObjectToLocalQuickSlot(int localSlotIndex)
{
if (!g_game_singletons || localSlotIndex < 0 || localSlotIndex >= 8) return false;
@@ -429,55 +472,130 @@ bool TryAssignAttachedObjectToLocalQuickSlot(int localSlotIndex)
return RunLine(command.c_str(), &error);
}
std::string LocalQuickSlotSkillIcon(int localSlotIndex)
namespace
{
if (!g_game_singletons || localSlotIndex < 0 || localSlotIndex >= 8) return {};
// The icons are .sub images (a rect of a shared texture). A resource only loads while referenced, and
// on touch hosts the taskbar slots that would reference it are hidden, so the overlay holds one.
bool quick_slot_image_uv(CGraphicImage* image, std::string* texture, float uv[4])
{
if (!image) return false;
static std::vector<CGraphicImage::TRef> s_heldIcons;
if (std::none_of(s_heldIcons.begin(), s_heldIcons.end(),
[image](const CGraphicImage::TRef& ref) { return ref.GetPointer() == image; }))
s_heldIcons.emplace_back(image);
CGraphicTexture* tex = image->GetTexturePointer();
if (image->IsEmpty() || !tex || tex->GetWidth() <= 0 || tex->GetHeight() <= 0) return false;
*texture = UIRenderTextureName(tex);
if (texture->empty()) return false;
// CGraphicImageInstance::OnRender's texture coordinates.
const RECT& rc = image->GetRectReference();
uv[0] = float(rc.left) / float(tex->GetWidth());
uv[1] = float(rc.top) / float(tex->GetHeight());
uv[2] = float(rc.right) / float(tex->GetWidth());
uv[3] = float(rc.bottom) / float(tex->GetHeight());
return true;
}
}
bool LocalQuickSlotIcon(int localSlotIndex, std::string* texture, float uv[4], int* count)
{
if (!g_game_singletons || localSlotIndex < 0 || localSlotIndex >= 8) return false;
DWORD type = SLOT_TYPE_NONE;
DWORD position = 0;
CPythonPlayer& player = CPythonPlayer::Instance();
player.GetLocalQuickSlotData(static_cast<DWORD>(localSlotIndex), &type, &position);
if (type != SLOT_TYPE_SKILL) return {};
*count = 0;
CPythonSkill::TSkillData* skillData = nullptr;
if (!CPythonSkill::Instance().GetSkillData(player.GetSkillIndex(position), &skillData) || !skillData)
return {};
const int grade = std::clamp(player.GetSkillGrade(position), 0, CPythonSkill::SKILL_GRADE_COUNT - 1);
CGraphicImage* image = skillData->GradeData[grade].pImage;
if (!image) image = skillData->pImage;
return image ? image->GetFileName() : std::string{};
}
void CameraBeginDrag(int x, int y)
{
CCameraManager& rkCmrMgr = CCameraManager::Instance();
CCamera* pkCmrCur = rkCmrMgr.GetCurrentCamera();
if (pkCmrCur)
pkCmrCur->BeginDrag(x, y);
}
void CameraDrag(int x, int y)
{
CCameraManager& rkCmrMgr = CCameraManager::Instance();
CCamera* pkCmrCur = rkCmrMgr.GetCurrentCamera();
if (pkCmrCur)
{
POINT pt;
pkCmrCur->Drag(x, y, &pt);
if (type == SLOT_TYPE_SKILL) {
CPythonSkill::TSkillData* skillData = nullptr;
if (!CPythonSkill::Instance().GetSkillData(player.GetSkillIndex(position), &skillData) || !skillData)
return false;
const int grade = std::clamp(player.GetSkillGrade(position), 0, CPythonSkill::SKILL_GRADE_COUNT - 1);
CGraphicImage* image = skillData->GradeData[grade].pImage;
if (!image) image = skillData->pImage;
return quick_slot_image_uv(image, texture, uv);
}
if (type == SLOT_TYPE_INVENTORY) {
// uitaskbar.RefreshQuickSlot: the item's icon and, above one, its count.
const TItemPos cell(INVENTORY, static_cast<WORD>(position));
CItemData* itemData = nullptr;
if (!CItemManager::Instance().GetItemDataPointer(player.GetItemIndex(cell), &itemData) || !itemData)
return false;
const DWORD itemCount = player.GetItemCount(cell);
*count = itemCount > 1 ? static_cast<int>(itemCount) : 0;
return quick_slot_image_uv(itemData->GetIconImage(), texture, uv);
}
return false;
}
void CameraEndDrag()
void UIMouseDoubleClick(int x, int y)
{
CCameraManager& rkCmrMgr = CCameraManager::Instance();
CCamera* pkCmrCur = rkCmrMgr.GetCurrentCamera();
if (pkCmrCur)
pkCmrCur->EndDrag();
MtHostSetCursor(x, y);
if (!g_window_manager) return;
if (g_game_singletons)
CPythonApplication::Instance().OnMouseMove(x, y);
else
g_window_manager->RunMouseMove(x, y);
update_3d_pick_ray();
g_window_manager->RunMouseLeftButtonDoubleClick(x, y);
}
bool UIIsAttaching()
{
return g_window_manager && g_window_manager->IsAttaching();
}
void UIDeattachObject()
{
std::string error;
RunLine("__import__('mouseModule').mouseController.DeattachObject()", &error);
}
void CameraRotateBy(float rollDeg, float pitchDeg)
{
CCamera* pkCmrCur = CCameraManager::Instance().GetCurrentCamera();
if (!pkCmrCur || pkCmrCur->IsLock())
return;
// Same collision limits CCamera::Drag applies to its velocities.
switch (pkCmrCur->GetCameraState())
{
case CAMERA_STATE_CANTGOLEFT: rollDeg = std::max(0.0f, rollDeg); break;
case CAMERA_STATE_CANTGORIGHT: rollDeg = std::min(0.0f, rollDeg); break;
case CAMERA_STATE_CANTGODOWN: pitchDeg = std::max(0.0f, pitchDeg); break;
default: break;
}
pkCmrCur->RotateEyeAroundTarget(pitchDeg, rollDeg);
}
void SetTouchInput(bool touch)
{
g_touch_input = touch;
UI::CWindow::SetHideVirtualKeyboard(touch);
}
bool TouchInput()
{
return g_touch_input;
}
bool TextInputFocused()
{
return g_window_manager && CIME::ms_bCaptureInput;
}
bool IsPointInsideFocusedWindow(int x, int y)
{
if (!g_window_manager) return false;
UI::CWindow* pWin = g_window_manager->GetActivateWindow();
return pWin && pWin->IsShow() && pWin->IsIn(x - g_window_manager->GetScreenOffsetX(), y);
}
bool IsPointInsideActiveUI(int x, int y)
{
if (!g_window_manager) return false;
return g_window_manager->IsPointInsideActiveUI(x, y);
return g_window_manager->IsPointInsideActiveUI(x - g_window_manager->GetScreenOffsetX(), y);
}
PlayerStatusInfo GetPlayerStatusInfo()
@@ -745,6 +863,32 @@ bool run_main_script_body(const char* lpCmdLine, std::string* error)
namespace PythonBoot
{
// PORT: the auto hunt window (AutoHuntScript.inc) as module mt_autohunt and the account registration
// window (RegisterScript.inc) as mt_register. They hook game.GameWindow / introLogin.LoginWindow, so they
// have to run once the root scripts are importable; a failure leaves the game without them, not broken.
#include "AutoHuntScript.inc"
#include "RegisterScript.inc"
static void install_script_module(const char* c_szModule, const char* c_szScript)
{
PyObject* module = PyImport_AddModule(c_szModule);
PyObject* dict = module ? PyModule_GetDict(module) : nullptr;
if (!dict)
{
PyErr_Clear();
return;
}
if (!PyDict_GetItemString(dict, "__builtins__"))
PyDict_SetItemString(dict, "__builtins__", PyEval_GetBuiltins());
PyObject* result = PyRun_String(c_szScript, Py_file_input, dict, dict);
if (!result)
{
TraceError("%s: install failed", c_szModule);
PyErr_Print();
}
Py_XDECREF(result);
}
bool RunMainScript(const char* lpCmdLine, std::string* error)
{
if (!g_launcher)
@@ -775,6 +919,11 @@ bool RunMainScript(const char* lpCmdLine, std::string* error)
if (!g_fiber.ok)
return fail(error, g_fiber.error);
}
if (IsAppLooping())
{
install_script_module("mt_autohunt", c_szAutoHuntScript);
install_script_module("mt_register", c_szRegisterScript);
}
return true;
}