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:
co-authored by
Claude Opus 5.5
parent
53d06c3c26
commit
3e3708ef06
@@ -816,6 +816,7 @@ private:
|
||||
draw.viewport[3] = float(m_viewport.Height);
|
||||
draw.viewport_z[0] = m_viewport.MinZ;
|
||||
draw.viewport_z[1] = m_viewport.MaxZ;
|
||||
draw.ui_offset_x = UIRenderOffsetX();
|
||||
draw.pretransformed = layout.rhw;
|
||||
draw.lines = type == D3DPT_LINELIST || type == D3DPT_LINESTRIP;
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ struct Render3DDraw {
|
||||
float proj[16];
|
||||
float viewport[4] = {}; // x, y, width, height
|
||||
float viewport_z[2] = {0, 1}; // MinZ, MaxZ
|
||||
// PORT: UIRenderOffsetX() when recorded (a draw from a shifted window layer); the host moves the
|
||||
// viewport (and a pretransformed draw's screen positions) right by it.
|
||||
float ui_offset_x = 0;
|
||||
|
||||
// IDirect3DDevice8::Clear on the back buffer, kept in draw order. A clear entry has no geometry;
|
||||
// clear_flags holds D3DCLEAR_TARGET / D3DCLEAR_ZBUFFER / D3DCLEAR_STENCIL.
|
||||
|
||||
@@ -34,8 +34,12 @@ int canvas_width = 0;
|
||||
int canvas_height = 0;
|
||||
float clip_x1 = 0, clip_y1 = 0, clip_x2 = 0, clip_y2 = 0;
|
||||
float saved_x1 = 0, saved_y1 = 0, saved_x2 = 0, saved_y2 = 0;
|
||||
float offset_x = 0;
|
||||
}
|
||||
|
||||
void UIRenderSetOffsetX(float x) { offset_x = x; }
|
||||
float UIRenderOffsetX() { return offset_x; }
|
||||
|
||||
void UIRenderSetSize(int width, int height) { canvas_width = width; canvas_height = height; }
|
||||
void UIRenderGetSize(unsigned* width, unsigned* height) {
|
||||
if (width) *width = canvas_width;
|
||||
@@ -50,6 +54,12 @@ void UIRenderBeginFrame() {
|
||||
}
|
||||
std::uint64_t UIRenderFrameId() { return frame_id; }
|
||||
void UIRenderAdd(UIRenderCommand command) {
|
||||
if (offset_x != 0) {
|
||||
command.x1 += offset_x;
|
||||
command.x2 += offset_x;
|
||||
if (command.quad)
|
||||
for (float& x : command.qx) x += offset_x;
|
||||
}
|
||||
command.clip_x1 = clip_x1; command.clip_y1 = clip_y1;
|
||||
command.clip_x2 = clip_x2; command.clip_y2 = clip_y2;
|
||||
command.behind_3d = Render3DDraws().empty();
|
||||
@@ -84,8 +94,8 @@ void UIRenderAdd(UIRenderCommand command) {
|
||||
const std::vector<UIRenderCommand>& UIRenderCommands() { return commands; }
|
||||
void UIRenderSetClip(float x, float y, float width, float height) {
|
||||
saved_x1 = clip_x1; saved_y1 = clip_y1; saved_x2 = clip_x2; saved_y2 = clip_y2;
|
||||
clip_x1 = x; clip_y1 = y;
|
||||
clip_x2 = x + width; clip_y2 = y + height;
|
||||
clip_x1 = x + offset_x; clip_y1 = y;
|
||||
clip_x2 = x + offset_x + width; clip_y2 = y + height;
|
||||
}
|
||||
void UIRenderRestoreClip() {
|
||||
clip_x1 = saved_x1; clip_y1 = saved_y1;
|
||||
|
||||
@@ -71,6 +71,11 @@ std::uint64_t UIRenderFrameId();
|
||||
void UIRenderAdd(UIRenderCommand command);
|
||||
const std::vector<UIRenderCommand>& UIRenderCommands();
|
||||
void UIRenderSetClip(float x, float y, float width, float height);
|
||||
// PORT (mobile safe area): shifts the commands (and clips) recorded after it right by x canvas
|
||||
// pixels, so the 40250 window layers can sit inside the rounded screen corners while the GAME layer
|
||||
// (3D world, text tails) keeps the full canvas. 0 is the verbatim 40250 path.
|
||||
void UIRenderSetOffsetX(float x);
|
||||
float UIRenderOffsetX();
|
||||
void UIRenderRestoreClip();
|
||||
|
||||
// HiDPI text: CGraphicFontTexture rasterises its glyph pages at this multiple of the logical font
|
||||
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,10 @@ bool Evaluate(const char* expression, std::string* result, std::string* error);
|
||||
// Forward physical Godot input to the original 40250 window manager. Coordinates
|
||||
// are viewport pixels; SetUISize also establishes its logical resolution.
|
||||
void SetUISize(int width, int height);
|
||||
// Mobile safe area: the 40250 windows are laid out width - 2 * inset wide and drawn inset pixels
|
||||
// right of the screen edge (clear of rounded corners and cutouts) while the 3D world keeps the whole
|
||||
// screen. Input coordinates stay screen pixels. Takes effect at the next SetUISize; 0 = off.
|
||||
void SetUISafeInset(int inset);
|
||||
void UIMouseMove(int x, int y);
|
||||
void UIMouseButton(int button, bool pressed, int x, int y);
|
||||
void UIMouseWheel(int nLen);
|
||||
@@ -68,11 +72,32 @@ void SetMoveDirection(float angleDeg, bool moving);
|
||||
void SetAttackKey(bool pressed);
|
||||
void UseLocalQuickSlot(int localSlotIndex);
|
||||
bool TryAssignAttachedObjectToLocalQuickSlot(int localSlotIndex);
|
||||
std::string LocalQuickSlotSkillIcon(int localSlotIndex);
|
||||
void CameraBeginDrag(int x, int y);
|
||||
void CameraDrag(int x, int y);
|
||||
void CameraEndDrag();
|
||||
// PORT: auto hunt (CPythonPlayer::AutoHunt_*, window in AutoHuntScript.inc): start/stop, the settings
|
||||
// window, and whether it runs / a character is in the world to run it.
|
||||
void AutoHuntToggle();
|
||||
void AutoHuntOpenSettings();
|
||||
bool AutoHuntIsEnabled();
|
||||
bool AutoHuntAvailable();
|
||||
// The skill or inventory item in the local quick slot: its UI texture name, the icon's (su, sv, eu, ev)
|
||||
// inside it and the item count (0 for a skill).
|
||||
bool LocalQuickSlotIcon(int localSlotIndex, std::string* texture, float uv[4], int* count);
|
||||
// Touch gestures: a Win32 WM_LBUTTONDBLCLK (40250 CPythonApplication::OnMouseLeftButtonDoubleClick),
|
||||
// whether an icon hangs on the cursor (CWindowManager::IsAttaching) and mouseController.DeattachObject().
|
||||
void UIMouseDoubleClick(int x, int y);
|
||||
bool UIIsAttaching();
|
||||
void UIDeattachObject();
|
||||
// Turns the camera around the player immediately, without CCamera::Drag's decaying velocity.
|
||||
void CameraRotateBy(float rollDeg, float pitchDeg);
|
||||
bool IsPointInsideActiveUI(int x, int y);
|
||||
// Touch hosts: hide 40250's on-screen login keypad (the system keyboard types instead).
|
||||
// Also drops the mouse arrow: touch hosts run 40250's hardware-cursor mode with no OS cursor, so
|
||||
// mouseModule draws only the attached icon.
|
||||
void SetTouchInput(bool touch);
|
||||
bool TouchInput();
|
||||
// An EditLine has focus (40250 ui.EditLine.OnSetFocus -> ime.EnableCaptureInput).
|
||||
bool TextInputFocused();
|
||||
// (x, y) hits the focused window, i.e. the tap landed on the focused EditLine.
|
||||
bool IsPointInsideFocusedWindow(int x, int y);
|
||||
|
||||
struct PlayerStatusInfo {
|
||||
int level = 1;
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
// The account registration window (PORT; 40250 has no counterpart, its accounts were made on the web),
|
||||
// run by PythonBoot::RunMainScript as module mt_register. It hooks introLogin.LoginWindow.Open/Close and
|
||||
// puts a register button under the login board; the dialog posts to the server's registration service
|
||||
// (tools/40250/register_server.py) through net.RegisterAccount. Strings are UTF-8 here and go through
|
||||
// player.AutoHuntLocalize into the locale's code page.
|
||||
static const char c_szRegisterScript[] = R"PY(# -*- coding: utf-8 -*-
|
||||
import introLogin
|
||||
import net
|
||||
import player
|
||||
import ui
|
||||
import wndMgr
|
||||
|
||||
L = player.AutoHuntLocalize
|
||||
|
||||
REGISTER_PORT = 11080
|
||||
LOGIN_MIN, LOGIN_MAX = 4, 16
|
||||
PASSWORD_MIN, PASSWORD_MAX = 4, 16
|
||||
SOCIAL_ID_LEN = 7
|
||||
|
||||
LARGE_BUTTON = "d:/ymir work/ui/public/large_button_%02d.sub"
|
||||
MIDDLE_BUTTON = "d:/ymir work/ui/public/middle_button_%02d.sub"
|
||||
|
||||
COLOR_ERROR = 0xffff7f7f
|
||||
COLOR_INFO = 0xffc8c8c8
|
||||
|
||||
ERROR_TEXT = {
|
||||
"BAD_LOGIN": "账号需为4-16位英文字母或数字",
|
||||
"BAD_PASSWORD": "密码需为4-16位字母、数字或符号",
|
||||
"BAD_SOCIAL_ID": "删除码需为7位数字",
|
||||
"EXISTS": "该账号已存在",
|
||||
"RATE_LIMIT": "注册过于频繁,请稍后再试",
|
||||
"CONNECT": "无法连接注册服务器",
|
||||
"TIMEOUT": "注册服务器无响应",
|
||||
"SERVER": "注册服务器出错,请稍后再试",
|
||||
}
|
||||
|
||||
|
||||
def _IsAlnum(text):
|
||||
for c in text:
|
||||
if not (("a" <= c <= "z") or ("A" <= c <= "Z") or ("0" <= c <= "9")):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _IsPrintable(text):
|
||||
for c in text:
|
||||
if not ("!" <= c <= "~"):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _Validate(login, password, confirm, socialID):
|
||||
if not (LOGIN_MIN <= len(login) <= LOGIN_MAX) or not _IsAlnum(login):
|
||||
return ERROR_TEXT["BAD_LOGIN"]
|
||||
if not (PASSWORD_MIN <= len(password) <= PASSWORD_MAX) or not _IsPrintable(password):
|
||||
return ERROR_TEXT["BAD_PASSWORD"]
|
||||
if password != confirm:
|
||||
return "两次输入的密码不一致"
|
||||
if len(socialID) != SOCIAL_ID_LEN or not socialID.isdigit():
|
||||
return ERROR_TEXT["BAD_SOCIAL_ID"]
|
||||
return None
|
||||
|
||||
|
||||
def _MakeButton(parent, x, y, image, text, event):
|
||||
button = ui.Button()
|
||||
button.SetParent(parent)
|
||||
button.SetPosition(x, y)
|
||||
button.SetUpVisual(image % 1)
|
||||
button.SetOverVisual(image % 2)
|
||||
button.SetDownVisual(image % 3)
|
||||
button.SetText(L(text))
|
||||
button.SetEvent(event)
|
||||
button.Show()
|
||||
return button
|
||||
|
||||
|
||||
class InputRow:
|
||||
LABEL_X = 18
|
||||
SLOT_X = 96
|
||||
SLOT_WIDTH = 160
|
||||
|
||||
def __init__(self, parent, y, label, maxLen):
|
||||
self.label = ui.TextLine()
|
||||
self.label.SetParent(parent)
|
||||
self.label.SetPosition(self.LABEL_X, y + 2)
|
||||
self.label.SetText(L(label))
|
||||
self.label.Show()
|
||||
|
||||
self.slot = ui.SlotBar()
|
||||
self.slot.SetParent(parent)
|
||||
self.slot.SetPosition(self.SLOT_X, y)
|
||||
self.slot.SetSize(self.SLOT_WIDTH, 18)
|
||||
self.slot.Show()
|
||||
|
||||
self.edit = ui.EditLine()
|
||||
self.edit.SetParent(self.slot)
|
||||
self.edit.SetPosition(4, 2)
|
||||
self.edit.SetSize(self.SLOT_WIDTH - 8, 16)
|
||||
self.edit.SetMax(maxLen)
|
||||
self.edit.SetIMEFlag(0)
|
||||
self.edit.Show()
|
||||
|
||||
def GetText(self):
|
||||
return self.edit.GetText()
|
||||
|
||||
def Destroy(self):
|
||||
self.edit.SetReturnEvent(0)
|
||||
self.edit.SetTabEvent(0)
|
||||
self.edit = None
|
||||
self.slot = None
|
||||
self.label = None
|
||||
|
||||
|
||||
class RegisterDialog(ui.BoardWithTitleBar):
|
||||
WIDTH = 280
|
||||
HEIGHT = 222
|
||||
|
||||
def __init__(self, loginWindow):
|
||||
ui.BoardWithTitleBar.__init__(self)
|
||||
self.loginWindow = loginWindow
|
||||
self.pending = False
|
||||
self.pendingLogin = ""
|
||||
self.pendingPassword = ""
|
||||
|
||||
self.AddFlag("movable")
|
||||
self.AddFlag("float")
|
||||
self.SetSize(self.WIDTH, self.HEIGHT)
|
||||
self.SetTitleName(L("注册账号"))
|
||||
self.SetCloseEvent(ui.__mem_func__(self.Close))
|
||||
|
||||
self.loginRow = InputRow(self, 38, "账号", LOGIN_MAX)
|
||||
self.passwordRow = InputRow(self, 66, "密码", PASSWORD_MAX)
|
||||
self.passwordRow.edit.SetSecret(True)
|
||||
self.confirmRow = InputRow(self, 94, "确认密码", PASSWORD_MAX)
|
||||
self.confirmRow.edit.SetSecret(True)
|
||||
self.socialRow = InputRow(self, 122, "删除码", SOCIAL_ID_LEN)
|
||||
self.socialRow.edit.SetNumberMode()
|
||||
|
||||
rows = (self.loginRow, self.passwordRow, self.confirmRow, self.socialRow)
|
||||
for i in xrange(len(rows)):
|
||||
nextEdit = rows[(i + 1) % len(rows)].edit
|
||||
rows[i].edit.SetTabEvent(ui.__mem_func__(nextEdit.SetFocus))
|
||||
if i + 1 < len(rows):
|
||||
rows[i].edit.SetReturnEvent(ui.__mem_func__(nextEdit.SetFocus))
|
||||
self.socialRow.edit.SetReturnEvent(ui.__mem_func__(self.__OnClickRegister))
|
||||
for row in rows:
|
||||
row.edit.SetEscapeEvent(ui.__mem_func__(self.Close))
|
||||
|
||||
self.hintLine = ui.TextLine()
|
||||
self.hintLine.SetParent(self)
|
||||
self.hintLine.SetPosition(0, 150)
|
||||
self.hintLine.SetWindowHorizontalAlignCenter()
|
||||
self.hintLine.SetHorizontalAlignCenter()
|
||||
self.hintLine.Show()
|
||||
|
||||
self.registerButton = _MakeButton(self, self.WIDTH / 2 - 66, 180, MIDDLE_BUTTON, "注册",
|
||||
ui.__mem_func__(self.__OnClickRegister))
|
||||
self.cancelButton = _MakeButton(self, self.WIDTH / 2 + 5, 180, MIDDLE_BUTTON, "取消",
|
||||
ui.__mem_func__(self.Close))
|
||||
|
||||
def Destroy(self):
|
||||
self.Hide()
|
||||
for row in (self.loginRow, self.passwordRow, self.confirmRow, self.socialRow):
|
||||
row.Destroy()
|
||||
self.loginRow = self.passwordRow = self.confirmRow = self.socialRow = None
|
||||
self.registerButton = None
|
||||
self.cancelButton = None
|
||||
self.hintLine = None
|
||||
self.loginWindow = None
|
||||
|
||||
def Open(self):
|
||||
for row in (self.loginRow, self.passwordRow, self.confirmRow, self.socialRow):
|
||||
row.edit.SetText("")
|
||||
self.__SetHint("账号仅限英文字母和数字,删除码用于删除角色", COLOR_INFO)
|
||||
self.SetPosition((wndMgr.GetScreenWidth() - self.WIDTH) / 2, (wndMgr.GetScreenHeight() - self.HEIGHT) / 2)
|
||||
self.Show()
|
||||
self.SetTop()
|
||||
self.loginRow.edit.SetFocus()
|
||||
|
||||
def Close(self):
|
||||
for row in (self.loginRow, self.passwordRow, self.confirmRow, self.socialRow):
|
||||
row.edit.KillFocus()
|
||||
self.Hide()
|
||||
if self.loginWindow and self.loginWindow.idEditLine:
|
||||
self.loginWindow.idEditLine.SetFocus()
|
||||
return True
|
||||
|
||||
def OnPressEscapeKey(self):
|
||||
return self.Close()
|
||||
|
||||
def __SetHint(self, text, color):
|
||||
self.hintLine.SetText(L(text))
|
||||
self.hintLine.SetPackedFontColor(color)
|
||||
|
||||
def __GetHost(self):
|
||||
stream = self.loginWindow.stream
|
||||
if stream.account_addr:
|
||||
return stream.account_addr
|
||||
return stream.addr
|
||||
|
||||
def __OnClickRegister(self):
|
||||
if self.pending:
|
||||
return
|
||||
login = self.loginRow.GetText()
|
||||
password = self.passwordRow.GetText()
|
||||
error = _Validate(login, password, self.confirmRow.GetText(), self.socialRow.GetText())
|
||||
if error:
|
||||
self.__SetHint(error, COLOR_ERROR)
|
||||
return
|
||||
host = self.__GetHost()
|
||||
if not host:
|
||||
self.__SetHint("请先选择服务器", COLOR_ERROR)
|
||||
return
|
||||
if not net.RegisterAccount(host, REGISTER_PORT, login, password, self.socialRow.GetText()):
|
||||
return
|
||||
self.pending = True
|
||||
self.pendingLogin = login.lower()
|
||||
self.pendingPassword = password
|
||||
self.registerButton.Disable()
|
||||
self.__SetHint("正在注册...", COLOR_INFO)
|
||||
|
||||
def OnUpdate(self):
|
||||
if not self.pending:
|
||||
return
|
||||
result = net.GetRegisterAccountResult()
|
||||
if not result:
|
||||
return
|
||||
self.pending = False
|
||||
self.registerButton.Enable()
|
||||
if result == "OK":
|
||||
self.__OnRegistered()
|
||||
return
|
||||
code = result[4:] if result.startswith("ERR ") else "SERVER"
|
||||
self.__SetHint(ERROR_TEXT.get(code, ERROR_TEXT["SERVER"]), COLOR_ERROR)
|
||||
|
||||
def __OnRegistered(self):
|
||||
loginWindow = self.loginWindow
|
||||
login, password = self.pendingLogin, self.pendingPassword
|
||||
self.pendingPassword = ""
|
||||
self.Close()
|
||||
if loginWindow.idEditLine:
|
||||
loginWindow.idEditLine.SetText(login)
|
||||
if loginWindow.pwdEditLine:
|
||||
loginWindow.pwdEditLine.SetText(password)
|
||||
loginWindow.PopupNotifyMessage(L("账号 %s 注册成功,请点击登录") % login)
|
||||
|
||||
|
||||
class RegisterEntry(ui.Window):
|
||||
"The button under the login board; it follows the board's position and visibility every frame."
|
||||
|
||||
def __init__(self, loginWindow):
|
||||
ui.Window.__init__(self)
|
||||
self.loginWindow = loginWindow
|
||||
self.dialog = RegisterDialog(loginWindow)
|
||||
self.button = ui.Button()
|
||||
self.button.SetUpVisual(LARGE_BUTTON % 1)
|
||||
self.button.SetOverVisual(LARGE_BUTTON % 2)
|
||||
self.button.SetDownVisual(LARGE_BUTTON % 3)
|
||||
self.button.SetText(L("注册账号"))
|
||||
self.button.SetEvent(ui.__mem_func__(self.dialog.Open))
|
||||
self.SetSize(0, 0)
|
||||
self.Show()
|
||||
|
||||
def Destroy(self):
|
||||
self.Hide()
|
||||
self.button.Hide()
|
||||
self.button = None
|
||||
self.dialog.Destroy()
|
||||
self.dialog = None
|
||||
self.loginWindow = None
|
||||
|
||||
def OnUpdate(self):
|
||||
board = self.loginWindow.loginBoard if self.loginWindow else None
|
||||
if not board or not self.loginWindow.IsShow() or not board.IsShow() or self.dialog.IsShow():
|
||||
if self.button.IsShow():
|
||||
self.button.Hide()
|
||||
return
|
||||
x, y = board.GetGlobalPosition()
|
||||
self.button.SetPosition(x + (board.GetWidth() - self.button.GetWidth()) / 2, y + board.GetHeight() + 6)
|
||||
if not self.button.IsShow():
|
||||
self.button.Show()
|
||||
self.button.SetTop()
|
||||
|
||||
|
||||
_entries = {}
|
||||
|
||||
|
||||
def Attach(loginWindow):
|
||||
key = id(loginWindow)
|
||||
if key not in _entries:
|
||||
_entries[key] = RegisterEntry(loginWindow)
|
||||
|
||||
|
||||
def Detach(loginWindow):
|
||||
entry = _entries.pop(id(loginWindow), None)
|
||||
if entry:
|
||||
entry.Destroy()
|
||||
|
||||
|
||||
def __Install():
|
||||
openLogin = introLogin.LoginWindow.Open
|
||||
closeLogin = introLogin.LoginWindow.Close
|
||||
|
||||
def Open(self):
|
||||
result = openLogin(self)
|
||||
try:
|
||||
Attach(self)
|
||||
except:
|
||||
import dbg
|
||||
dbg.TraceError("mt_register: attach failed")
|
||||
return result
|
||||
|
||||
def Close(self):
|
||||
Detach(self)
|
||||
return closeLogin(self)
|
||||
|
||||
introLogin.LoginWindow.Open = Open
|
||||
introLogin.LoginWindow.Close = Close
|
||||
|
||||
# system.py reaches app.Loop() before this runs, so a login window may already be open.
|
||||
import gc
|
||||
for obj in gc.get_objects():
|
||||
if isinstance(obj, introLogin.LoginWindow) and getattr(obj, "loginBoard", None):
|
||||
Attach(obj)
|
||||
|
||||
|
||||
__Install()
|
||||
)PY";
|
||||
@@ -176,7 +176,7 @@ bool CPythonApplication::Create(PyObject*, const char*, int, int, int)
|
||||
|
||||
if (IsNativeTerrainRenderEnabled())
|
||||
{
|
||||
const bool use_hardware_cursor = PythonBoot::HostHardwareCursorEnabled();
|
||||
const bool use_hardware_cursor = PythonBoot::HostHardwareCursorEnabled() || PythonBoot::TouchInput();
|
||||
if (CPythonSystem::InstancePtr() && CPythonSystem::Instance().GetConfig())
|
||||
CPythonSystem::Instance().GetConfig()->is_software_cursor = !use_hardware_cursor;
|
||||
SetCursorMode(use_hardware_cursor ? CURSOR_MODE_HARDWARE : CURSOR_MODE_SOFTWARE);
|
||||
@@ -214,6 +214,8 @@ void CPythonApplication::RenderGame()
|
||||
{
|
||||
long lx, ly;
|
||||
UI::CWindowManager::Instance().GetMousePosition(lx, ly);
|
||||
// PORT: window coordinates -> the screen coordinates of the full-width 3D view.
|
||||
lx += UI::CWindowManager::Instance().GetScreenOffsetX();
|
||||
CPythonGraphic::Instance().SetCursorPosition(lx, ly);
|
||||
}
|
||||
|
||||
@@ -453,7 +455,8 @@ int s_iCursorMode = CPythonApplication::CURSOR_MODE_HARDWARE; // m_iCurs
|
||||
void CPythonApplication::SetCursorVisible(BOOL bFlag, bool bLiarCursorOn)
|
||||
{
|
||||
s_bCursorVisible = bFlag;
|
||||
s_bLiarCursorOn = bLiarCursorOn;
|
||||
// PORT: a touch screen has no cursor, not even the stand-in 40250 draws while the real one is hidden.
|
||||
s_bLiarCursorOn = bLiarCursorOn && !PythonBoot::TouchInput();
|
||||
// PORT: 40250 also counts ShowCursor up or down in hardware mode; the host has no OS cursor to hide.
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user