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
@@ -0,0 +1,385 @@
// The auto hunt window (PORT; 40250 has no counterpart), run by PythonBoot::RunMainScript as module
// mt_autohunt. It hooks game.GameWindow.Open/Close so the window lives exactly as long as the game phase,
// whichever way the host reached it. The logic is CPythonPlayer's (UserInterface/PythonPlayerAutoHunt.cpp);
// this is only the player.AutoHunt* front end. Strings are UTF-8 here and go through
// player.AutoHuntLocalize into the locale's code page, as the packed locale strings are.
static const char c_szAutoHuntScript[] = R"PY(# -*- coding: utf-8 -*-
import app
import chat
import game
import player
import ui
import wndMgr
L = player.AutoHuntLocalize
STATE_OFF, STATE_SEARCH, STATE_ENGAGE, STATE_LOOT, STATE_RETURN, STATE_PAUSED = range(6)
STOP_NONE, STOP_USER, STOP_DEAD, STOP_MAP, STOP_INVENTORY_FULL = range(5)
SKILL_SLOT_NUM = 8
STATE_TEXT = {
STATE_SEARCH: "寻找目标",
STATE_ENGAGE: "战斗中",
STATE_LOOT: "拾取中",
STATE_RETURN: "返回挂机点",
STATE_PAUSED: "手动操作,3秒后继续",
}
STOP_TEXT = {
STOP_USER: "自动狩猎已停止",
STOP_DEAD: "角色死亡,自动狩猎已停止",
STOP_MAP: "切换地图,自动狩猎已停止",
STOP_INVENTORY_FULL: "背包已满,自动狩猎已停止",
}
RANGE_VALUES = (1000, 2000, 3500)
RANGE_TEXTS = ("近", "中", "远")
HP_VALUES = (0, 30, 50, 70)
SP_VALUES = (0, 20, 40, 60)
PICKUP_TEXTS = ("不拾取", "全部", "金币装备")
GATHER_VALUES = (0, 3, 5, 8)
GATHER_TEXTS = ("关", "3只", "5只", "8只")
SMALL_BUTTON = "d:/ymir work/ui/public/small_button_%02d.sub"
MIDDLE_BUTTON = "d:/ymir work/ui/public/middle_button_%02d.sub"
XSMALL_BUTTON = "d:/ymir work/ui/public/xsmall_button_%02d.sub"
COLOR_ON = 0xff7fff7f
COLOR_OFF = 0xffffcf7f
COLOR_SELECTED = 0xffffd24a
COLOR_UNSELECTED = 0xffc8c8c8
def _MarkButton(button, isSelected):
"Down and gold text while selected: the down visual alone is hard to tell apart on a phone."
if isSelected:
button.Down()
else:
button.SetUp()
if button.ButtonText:
button.ButtonText.SetPackedFontColor(isSelected and COLOR_SELECTED or COLOR_UNSELECTED)
def _Nearest(values, value):
best = 0
for i in xrange(len(values)):
if abs(values[i] - value) < abs(values[best] - value):
best = i
return best
class ChoiceRow:
"One setting: a label and a row of buttons of which exactly one stays down."
def __init__(self, parent, y, label, texts, image, step, event):
self.label = ui.TextLine()
self.label.SetParent(parent)
self.label.SetPosition(18, y + 4)
self.label.SetText(L(label))
self.label.Show()
self.buttons = []
self.event = event
self.index = -1
x = 70
for i in xrange(len(texts)):
button = ui.RadioButton()
button.SetParent(parent)
button.SetUpVisual(image % 1)
button.SetOverVisual(image % 2)
button.SetDownVisual(image % 3)
button.SetPosition(x, y)
button.SetText(L(texts[i]))
button.SetEvent(lambda i=i: self.Select(i, True))
button.Show()
self.buttons.append(button)
x += step
def Select(self, index, notify):
self.index = index
for i in xrange(len(self.buttons)):
_MarkButton(self.buttons[i], i == index)
if notify and self.event:
self.event()
def Destroy(self):
self.event = None
self.buttons = []
self.label = None
class FlagButton(ui.ToggleButton):
"A button that stays down while its flag is on."
def __init__(self, parent, x, y, image, text, event):
ui.ToggleButton.__init__(self)
self.SetParent(parent)
self.SetUpVisual(image % 1)
self.SetOverVisual(image % 2)
self.SetDownVisual(image % 3)
self.SetPosition(x, y)
self.SetText(L(text))
self.flag = False
self.flagEvent = event
self.SetToggleDownEvent(lambda: self.__OnToggle(True))
self.SetToggleUpEvent(lambda: self.__OnToggle(False))
self.Show()
def __OnToggle(self, flag):
self.SetFlag(flag)
if self.flagEvent:
self.flagEvent()
def SetFlag(self, flag):
self.flag = flag
_MarkButton(self, flag)
def Destroy(self):
self.flagEvent = None
self.eventUp = None
self.eventDown = None
class SettingDialog(ui.BoardWithTitleBar):
WIDTH = 330
HEIGHT = 300
def __init__(self):
ui.BoardWithTitleBar.__init__(self)
self.AddFlag("movable")
self.AddFlag("float")
self.SetSize(self.WIDTH, self.HEIGHT)
self.SetTitleName(L("自动狩猎"))
self.SetCloseEvent(self.Close)
y = 36
self.rangeRow = ChoiceRow(self, y, "范围", RANGE_TEXTS, SMALL_BUTTON, 48, self.__Save)
y += 28
self.hpRow = ChoiceRow(self, y, "红药", ["关"] + ["%d%%" % v for v in HP_VALUES[1:]], SMALL_BUTTON, 48, self.__Save)
y += 28
self.spRow = ChoiceRow(self, y, "蓝药", ["关"] + ["%d%%" % v for v in SP_VALUES[1:]], SMALL_BUTTON, 48, self.__Save)
y += 28
self.pickupRow = ChoiceRow(self, y, "拾取", PICKUP_TEXTS, MIDDLE_BUTTON, 66, self.__Save)
y += 28
self.gatherRow = ChoiceRow(self, y, "聚怪", GATHER_TEXTS, SMALL_BUTTON, 48, self.__Save)
y += 28
self.skillLabel = ui.TextLine()
self.skillLabel.SetParent(self)
self.skillLabel.SetPosition(18, y + 4)
self.skillLabel.SetText(L("技能栏"))
self.skillLabel.Show()
self.skillButtons = []
for i in xrange(SKILL_SLOT_NUM):
self.skillButtons.append(FlagButton(self, 70 + i * 31, y, XSMALL_BUTTON, str(i + 1), self.__Save))
y += 28
self.targetLabel = ui.TextLine()
self.targetLabel.SetParent(self)
self.targetLabel.SetPosition(18, y + 4)
self.targetLabel.SetText(L("攻击"))
self.targetLabel.Show()
self.stoneButton = FlagButton(self, 70, y, MIDDLE_BUTTON, "石头", self.__Save)
self.bossButton = FlagButton(self, 136, y, MIDDLE_BUTTON, "首领", self.__Save)
y += 30
self.hintLine = ui.TextLine()
self.hintLine.SetParent(self)
self.hintLine.SetPosition(0, y + 2)
self.hintLine.SetWindowHorizontalAlignCenter()
self.hintLine.SetHorizontalAlignCenter()
self.hintLine.SetPackedFontColor(0xffa0a0a0)
self.hintLine.SetText(L("手动操作时暂停,3秒后继续"))
self.hintLine.Show()
y += 26
self.startButton = ui.Button()
self.startButton.SetParent(self)
self.startButton.SetUpVisual("d:/ymir work/ui/public/large_button_01.sub")
self.startButton.SetOverVisual("d:/ymir work/ui/public/large_button_02.sub")
self.startButton.SetDownVisual("d:/ymir work/ui/public/large_button_03.sub")
self.startButton.SetPosition(0, y)
self.startButton.SetWindowHorizontalAlignCenter()
self.startButton.SetEvent(self.__OnClickStart)
self.startButton.Show()
self.SetSize(self.WIDTH, y + 36)
self.SetCenterPosition()
self.isStarted = None
def Destroy(self):
for row in (self.rangeRow, self.hpRow, self.spRow, self.pickupRow, self.gatherRow):
row.Destroy()
for button in self.skillButtons + [self.stoneButton, self.bossButton]:
button.Destroy()
self.skillButtons = []
self.startButton = None
self.Hide()
def Open(self):
(range_, hp, sp, pickup, stone, boss, mask, gather) = player.AutoHuntGetConfig()
self.rangeRow.Select(_Nearest(RANGE_VALUES, range_), False)
self.hpRow.Select(_Nearest(HP_VALUES, hp), False)
self.spRow.Select(_Nearest(SP_VALUES, sp), False)
self.pickupRow.Select(pickup, False)
self.gatherRow.Select(_Nearest(GATHER_VALUES, gather), False)
for i in xrange(SKILL_SLOT_NUM):
self.skillButtons[i].SetFlag(bool(mask & (1 << i)))
self.stoneButton.SetFlag(bool(stone))
self.bossButton.SetFlag(bool(boss))
self.isStarted = None
self.Show()
self.SetTop()
def Close(self):
self.Hide()
def OnPressEscapeKey(self):
self.Close()
return True
def OnUpdate(self):
isStarted = bool(player.AutoHuntIsEnabled())
if isStarted != self.isStarted:
self.isStarted = isStarted
self.startButton.SetText(L(isStarted and "停止狩猎" or "开始狩猎"))
def __Save(self):
mask = 0
for i in xrange(SKILL_SLOT_NUM):
if self.skillButtons[i].flag:
mask |= 1 << i
player.AutoHuntSetConfig(RANGE_VALUES[self.rangeRow.index], HP_VALUES[self.hpRow.index],
SP_VALUES[self.spRow.index], self.pickupRow.index, int(self.stoneButton.flag), int(self.bossButton.flag), mask,
GATHER_VALUES[self.gatherRow.index])
def __OnClickStart(self):
Toggle()
if player.AutoHuntIsEnabled():
self.Close()
class StatusBoard(ui.ThinBoard):
def __init__(self):
ui.ThinBoard.__init__(self)
self.AddFlag("not_pick")
self.SetSize(260, 28)
self.SetWindowHorizontalAlignCenter()
self.SetPosition(0, 64)
self.textLine = ui.TextLine()
self.textLine.SetParent(self)
self.textLine.SetPosition(0, 7)
self.textLine.SetWindowHorizontalAlignCenter()
self.textLine.SetHorizontalAlignCenter()
self.textLine.Show()
def SetStatus(self, text, color):
self.textLine.SetText(text)
self.textLine.SetPackedFontColor(color)
self.Show()
class Ticker(ui.Window):
"Always shown and empty: polls the hunt state for the status board, which is hidden while off."
def __init__(self, status):
ui.Window.__init__(self)
self.AddFlag("not_pick")
self.status = status
self.last = None
self.stopShowTime = 0.0
def OnUpdate(self):
state = player.AutoHuntGetState()
if state != self.last:
(stateIndex, reason, noHP, noSP, gathering, gathered) = state
wasOn = self.last and self.last[0] != STATE_OFF
self.last = state
if stateIndex != STATE_OFF:
text = L("自动狩猎 · ") + L(STATE_TEXT.get(stateIndex, ""))
if gathering:
text = L("自动狩猎 · 聚怪中 %d/%d" % (gathered, player.AutoHuntGetConfig()[7]))
if noHP:
text += L(" · 无红药")
if noSP:
text += L(" · 无蓝药")
self.status.SetStatus(text, COLOR_ON)
elif wasOn and reason in STOP_TEXT:
self.status.SetStatus(L(STOP_TEXT[reason]), COLOR_OFF)
self.stopShowTime = app.GetTime() + 5.0
if reason != STOP_USER:
chat.AppendChat(chat.CHAT_TYPE_INFO, L(STOP_TEXT[reason]))
else:
self.status.Hide()
elif state[0] == STATE_OFF and self.stopShowTime and app.GetTime() > self.stopShowTime:
self.stopShowTime = 0.0
self.status.Hide()
g_status = None
g_ticker = None
g_dialog = None
def Toggle():
if player.AutoHuntIsEnabled():
player.AutoHuntSetEnabled(0)
return
player.AutoHuntSetEnabled(1)
if player.AutoHuntIsEnabled():
chat.AppendChat(chat.CHAT_TYPE_INFO, L("自动狩猎已开启(再次点击AUTO或F9停止)"))
def OpenSettings():
if g_dialog:
if g_dialog.IsShow():
g_dialog.Close()
else:
g_dialog.Open()
def IsSettingsOpen():
return g_dialog and g_dialog.IsShow()
def Attach(gameWindow):
global g_status, g_ticker, g_dialog
Detach()
g_status = StatusBoard()
g_ticker = Ticker(g_status)
g_ticker.Show()
g_dialog = SettingDialog()
if gameWindow.onPressKeyDict is not None:
gameWindow.onPressKeyDict[app.DIK_F9] = Toggle
gameWindow.onPressKeyDict[app.DIK_F8] = OpenSettings
def Detach():
global g_status, g_ticker, g_dialog
player.AutoHuntSetEnabled(0)
if g_dialog:
g_dialog.Destroy()
if g_ticker:
g_ticker.Hide()
g_ticker.status = None
if g_status:
g_status.Hide()
g_status = g_ticker = g_dialog = None
def __Install():
openGame = game.GameWindow.Open
closeGame = game.GameWindow.Close
def Open(self):
openGame(self)
Attach(self)
def Close(self):
Detach()
closeGame(self)
game.GameWindow.Open = Open
game.GameWindow.Close = Close
__Install()
)PY";