- 将密码框滚动速度从500ms调整为250ms提升用户体验 - 添加解锁成功动画序列和音效反馈 - 增加全部解锁后的处理逻辑和动画播放 - 优化宝箱动画显示比例 - 添加解锁失败音效提示
414 lines
12 KiB
Plaintext
414 lines
12 KiB
Plaintext
/*
|
|
文件名:Steal.nut
|
|
路径:Project/Steal/Steal.nut
|
|
创建日期:2026-04-07 05:19
|
|
文件用途:
|
|
*/
|
|
|
|
class StealC_Password_Box extends LenheartNewUI_CommonUi {
|
|
|
|
NumberSeq = null;
|
|
ScrollPixelOffset = 0;
|
|
ScrollBaseIndex = 0;
|
|
InitBaseIndex = 0;
|
|
ScrollStartTimeMs = 0;
|
|
RowDurationMs = 250;
|
|
RowHeight = 24;
|
|
ColumnYOffset = 0;
|
|
CorrectIndex = 0;
|
|
IsStopped = false;
|
|
IsLockedCorrect = false;
|
|
StopSnapOffsetY = 0;
|
|
|
|
PassStr = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
|
|
|
|
constructor(X, Y, ...) {
|
|
LenheartNewUI_CommonUi.constructor(X, Y, 30, 120);
|
|
local PhaseIndex = 0;
|
|
if (vargc > 0) {
|
|
PhaseIndex = vargv[0];
|
|
}
|
|
ColumnYOffset = -(PhaseIndex * 8);
|
|
|
|
NumberSeq = [];
|
|
for (local i = 0; i< 10; i++) {
|
|
local Info = {
|
|
num = PassStr[sq_getRandom(0, PassStr.len() - 1)],
|
|
CorrectFlag = false,
|
|
}
|
|
NumberSeq.append(Info);
|
|
}
|
|
|
|
CorrectIndex = sq_getRandom(0, 9);
|
|
NumberSeq[CorrectIndex].CorrectFlag = true;
|
|
|
|
local SeqLen = NumberSeq.len();
|
|
if (SeqLen > 0) {
|
|
local Phase = PhaseIndex % SeqLen;
|
|
if (Phase< 0) {
|
|
Phase += SeqLen;
|
|
}
|
|
ScrollBaseIndex = Phase;
|
|
InitBaseIndex = Phase;
|
|
}
|
|
ScrollStartTimeMs = Clock();
|
|
}
|
|
|
|
function GetLoopIndex(Index) {
|
|
local SeqLen = NumberSeq.len();
|
|
if (SeqLen <= 0) {
|
|
return 0;
|
|
}
|
|
local Value = Index % SeqLen;
|
|
if (Value< 0) {
|
|
Value += SeqLen;
|
|
}
|
|
return Value;
|
|
}
|
|
|
|
function UpdateScrollByClock() {
|
|
if (RowDurationMs <= 0) {
|
|
RowDurationMs = 1;
|
|
}
|
|
local RunTimeMs = Clock() - ScrollStartTimeMs;
|
|
if (RunTimeMs< 0) {
|
|
RunTimeMs = 0;
|
|
}
|
|
local PassedRows = (RunTimeMs / RowDurationMs).tointeger();
|
|
local CycleMs = RunTimeMs % RowDurationMs;
|
|
ScrollPixelOffset = sq_GetUniformVelocity(0, RowHeight - 1, CycleMs, RowDurationMs).tointeger();
|
|
ScrollBaseIndex = GetLoopIndex(InitBaseIndex - PassedRows);
|
|
}
|
|
|
|
function GetCorrectCenterY(RefY) {
|
|
local SeqLen = NumberSeq.len();
|
|
if (SeqLen <= 0) {
|
|
return Y;
|
|
}
|
|
local Delta = GetLoopIndex(CorrectIndex - ScrollBaseIndex);
|
|
local TextY = Y + 10;
|
|
local BaseY = TextY + Delta * RowHeight + ScrollPixelOffset + ColumnYOffset + 8;
|
|
local CycleHeight = SeqLen * RowHeight;
|
|
local BestY = BaseY;
|
|
local BestDist = abs(BestY - RefY);
|
|
for (local k = -2; k <= 2; k++) {
|
|
local CY = BaseY + (k * CycleHeight);
|
|
local Dist = abs(CY - RefY);
|
|
if (Dist< BestDist) {
|
|
BestY = CY;
|
|
BestDist = Dist;
|
|
}
|
|
}
|
|
return BestY;
|
|
}
|
|
|
|
function TryLockByRect(LockTopY, LockHeight, SnapExtraOffsetY) {
|
|
if (IsStopped) {
|
|
return false;
|
|
}
|
|
UpdateScrollByClock();
|
|
local LockCenterY = LockTopY + (LockHeight / 2).tointeger();
|
|
local CorrectCenterY = GetCorrectCenterY(LockCenterY);
|
|
if (CorrectCenterY >= LockTopY && CorrectCenterY <= (LockTopY + LockHeight)) {
|
|
StopSnapOffsetY = (LockCenterY - CorrectCenterY) + SnapExtraOffsetY;
|
|
IsStopped = true;
|
|
IsLockedCorrect = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function Show(obj) {
|
|
Parent.Img.DrawExPng(10, X, Y, 0, sq_RGBA(255, 255, 255, 90), 1.0, 1.0);
|
|
|
|
local SeqLen = NumberSeq.len();
|
|
if (SeqLen <= 0) {
|
|
return;
|
|
}
|
|
|
|
if (!IsStopped) {
|
|
UpdateScrollByClock();
|
|
}
|
|
|
|
local DrawCount = (Height / RowHeight).tointeger() + 4;
|
|
local TextY = Y + 10;
|
|
for (local i = -2; i< DrawCount - 2; i++) {
|
|
local Info = NumberSeq[GetLoopIndex(ScrollBaseIndex + i)];
|
|
local NumStr = Info.num.tostring();
|
|
local Color = Info.CorrectFlag ? sq_RGBA(157, 245, 10, 255) : sq_RGBA(150, 150, 150, 255);
|
|
local DrawY = TextY + i * RowHeight + ScrollPixelOffset + ColumnYOffset + StopSnapOffsetY;
|
|
L_sq_DrawCode(NumStr, X + 15 - LenheartTextClass.GetStringLength(NumStr) / 2, DrawY, Color, 0, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class StealC extends LenheartNewUI_Windows {
|
|
//调试模式
|
|
// DeBugMode = true;
|
|
|
|
//不是窗口
|
|
// NoWindow = true;
|
|
|
|
//是否可见
|
|
// Visible = false;
|
|
|
|
Img = Rindro_Image("steal/widget.img");
|
|
|
|
//解锁完成度
|
|
UnlockComplete = 0.0;
|
|
CorrectLockedCount = 0;
|
|
LockBoxHeight = 24;
|
|
LockSnapOffsetY = -6;
|
|
AllUnlockedTriggered = false;
|
|
AniQueue = null;
|
|
UnlockSuccessAni = null;
|
|
UnlockAniPlaying = false;
|
|
UnlockAniFinished = false;
|
|
UnlockFinalizeDone = false;
|
|
UnlockAniFrameDelay = 60;
|
|
|
|
constructor(gObjectId, gX, gY, gWidth, gHeight, gTitleH) {
|
|
Childrens = [];
|
|
AniQueue = QuestQueue();
|
|
//注册控件
|
|
RegisterWidget();
|
|
|
|
LenheartNewUI_Windows.constructor(gObjectId, gX, gY, gWidth, gHeight, gTitleH);
|
|
RefreshUnlockComplete();
|
|
}
|
|
|
|
function RegisterWidget() {
|
|
// //关闭按钮
|
|
// local CloseButton = LenheartNewUI_BaseButton(278, 0, 11, 12, "interface/lenheartwindowcommon.img", 276);
|
|
// CloseButton.OnClick = function() {
|
|
// this.Visible = false;
|
|
// }.bindenv(this);
|
|
// Childrens.append(CloseButton);
|
|
|
|
for (local i = 0; i< 8; i++) {
|
|
local PasswordBox = StealC_Password_Box(280 + i * 30, 268, i);
|
|
AddChild(PasswordBox);
|
|
}
|
|
}
|
|
|
|
|
|
//绘制主界面
|
|
function GetLockTopY() {
|
|
return Y + 268 + 60;
|
|
}
|
|
|
|
function GetFocusPasswordBox() {
|
|
foreach(Child in Childrens) {
|
|
if ((Child instanceof StealC_Password_Box) && !Child.IsStopped) {
|
|
return Child;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function GetPasswordBoxTotal() {
|
|
local Total = 0;
|
|
foreach(Child in Childrens) {
|
|
if (Child instanceof StealC_Password_Box) {
|
|
Total += 1;
|
|
}
|
|
}
|
|
return Total;
|
|
}
|
|
|
|
function RefreshUnlockComplete() {
|
|
local Total = GetPasswordBoxTotal();
|
|
if (Total <= 0) {
|
|
UnlockComplete = 0.0;
|
|
return;
|
|
}
|
|
UnlockComplete = CorrectLockedCount.tofloat() / Total.tofloat();
|
|
}
|
|
|
|
function BuildUnlockFrameArr() {
|
|
local FrameArr = [];
|
|
local Delay = UnlockAniFrameDelay;
|
|
if (Delay <= 0) {
|
|
Delay = 60;
|
|
}
|
|
for (local i = 0; i< 13; i++) {
|
|
FrameArr.append({
|
|
ImgIndex = i,
|
|
Delay = Delay,
|
|
Pos = [0, 0]
|
|
});
|
|
}
|
|
return FrameArr;
|
|
}
|
|
|
|
function StartUnlockSuccessSequence() {
|
|
if (UnlockAniPlaying || UnlockAniFinished) {
|
|
return;
|
|
}
|
|
|
|
local FrameArr = BuildUnlockFrameArr();
|
|
UnlockSuccessAni = Lenheart_Ani("", FrameArr, [X + 269, Y + 260]);
|
|
UnlockSuccessAni.ImgPath = "steal/unlock.img";
|
|
UnlockSuccessAni.LoopFlag = false;
|
|
UnlockSuccessAni.Reset();
|
|
|
|
UnlockAniPlaying = true;
|
|
UnlockAniFinished = false;
|
|
if (AniQueue) {
|
|
AniQueue.RemoveQuest("StealUnlockSuccessAni");
|
|
AniQueue.AddQuest("StealUnlockSuccessAni", function(Name, Time) {
|
|
if (!UnlockSuccessAni) {
|
|
UnlockAniPlaying = false;
|
|
UnlockAniFinished = true;
|
|
AniQueue.RemoveQuest(Name);
|
|
FinalizeUnlockSuccess();
|
|
return;
|
|
}
|
|
UnlockSuccessAni.X = X + 324;
|
|
UnlockSuccessAni.Y = Y + 109;
|
|
UnlockSuccessAni.Show(Duration);
|
|
if (UnlockSuccessAni.State == 0) {
|
|
UnlockAniPlaying = false;
|
|
UnlockAniFinished = true;
|
|
AniQueue.RemoveQuest(Name);
|
|
FinalizeUnlockSuccess();
|
|
}
|
|
}.bindenv(this));
|
|
} else {
|
|
UnlockAniPlaying = false;
|
|
UnlockAniFinished = true;
|
|
FinalizeUnlockSuccess();
|
|
}
|
|
}
|
|
|
|
function FinalizeUnlockSuccess() {
|
|
if (UnlockFinalizeDone) {
|
|
return;
|
|
}
|
|
UnlockFinalizeDone = true;
|
|
SendStealUnlockPack();
|
|
CloseWindow();
|
|
Visible = false;
|
|
}
|
|
|
|
function SendStealUnlockPack() {
|
|
// TODO: fill op and fields when server protocol is finalized.
|
|
// SendPackEx({ op = xxxx });
|
|
}
|
|
|
|
function OnAllUnlockedInternal() {
|
|
R_Utils.PlaySound("GAMBLE_RESULT_GOOD");
|
|
print("Steal all unlocked: " + CorrectLockedCount);
|
|
StartUnlockSuccessSequence();
|
|
}
|
|
|
|
function CheckAllUnlocked() {
|
|
if (AllUnlockedTriggered) {
|
|
return;
|
|
}
|
|
local Total = GetPasswordBoxTotal();
|
|
if (Total <= 0) {
|
|
return;
|
|
}
|
|
if (CorrectLockedCount >= Total) {
|
|
AllUnlockedTriggered = true;
|
|
OnAllUnlockedInternal();
|
|
}
|
|
}
|
|
|
|
function CheckSpaceLock() {
|
|
if (!Visible) {
|
|
return;
|
|
}
|
|
if (!KeyPressNB.isKeyPress(7, "StealC")) {
|
|
return;
|
|
}
|
|
local FocusBox = GetFocusPasswordBox();
|
|
if (!FocusBox) {
|
|
return;
|
|
}
|
|
if (FocusBox.TryLockByRect(GetLockTopY(), LockBoxHeight, LockSnapOffsetY)) {
|
|
R_Utils.PlaySound("TRADE_OK");
|
|
CorrectLockedCount += 1;
|
|
RefreshUnlockComplete();
|
|
CheckAllUnlocked();
|
|
} else {
|
|
R_Utils.PlaySound("ALERT_2");
|
|
}
|
|
}
|
|
|
|
function DrawMain(obj) {
|
|
//灰色背景
|
|
DrawNineBoxAlpha(X, Y, 800, 600, "interface2/common/mypopup/popup.img", 0, 150);
|
|
|
|
//宝箱动画
|
|
local Ani = DrawAniEx(X + 425, Y + 230, "npc/animation/storagediamond.ani");
|
|
Ani.setImageRateFromOriginal(1.7, 1.7);
|
|
//如果未解锁
|
|
if (UnlockComplete< 1.0) {
|
|
//宝箱锁图像
|
|
Img.DrawPng(12, X + 335, Y + 120);
|
|
}
|
|
|
|
|
|
|
|
//完成度文字
|
|
local StealCompleteStr = "解锁完成度: " + UnlockComplete * 100.0 + "%";
|
|
L_sq_DrawCode(StealCompleteStr, X + 410 - LenheartTextClass.GetStringLength(StealCompleteStr) / 2, Y + 406, sq_RGBA(255, 255, 255, 255), 0, 1);
|
|
|
|
//完成度进度条
|
|
Img.DrawPng(2, X + 162, Y + 425);
|
|
setClip(X + 168, Y + 429, X + 168 + (463 * UnlockComplete).tointeger(), Y + 429 + 6);
|
|
Img.DrawPng(3, X + 168, Y + 429);
|
|
releaseClip(); //裁切结束
|
|
|
|
|
|
//解锁界面
|
|
Img.DrawPng(9, X + 269, Y + 260);
|
|
//解锁锁定条
|
|
Img.DrawPng(11, X + 269 + 8, Y + 260 + 60);
|
|
}
|
|
|
|
function Show(obj) {
|
|
DrawMain(obj);
|
|
if (AniQueue) {
|
|
AniQueue.Run();
|
|
}
|
|
|
|
//裁切遮罩密码框
|
|
setClip(X + 280, Y + 268, X + 280 + 8 * 30, Y + 268 + 120);
|
|
LenheartNewUI_Windows.Show(obj);
|
|
releaseClip(); //裁切结束
|
|
|
|
}
|
|
|
|
//逻辑入口
|
|
function Proc(obj) {
|
|
LenheartNewUI_Windows.SyncPos(X, Y);
|
|
CheckSpaceLock();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
L_Windows_List <- [];
|
|
getroottable().rawdelete("LenheartPluginsInitFlag");
|
|
getroottable().rawdelete("EventList_Obj")
|
|
getroottable().rawdelete("Steal_Obj");
|
|
getroottable().rawdelete("L_Each_Obj");
|
|
|
|
|
|
|
|
|
|
function Lenheart_Steal_Fun(obj) {
|
|
local RootTab = getroottable();
|
|
if (!RootTab.rawin("Steal_Obj")) {
|
|
RootTab.rawset("Steal_Obj", true);
|
|
LenheartNewUI_CreateWindow(StealC, "Steal_Window", 0, 0, 800, 600, 0);
|
|
}
|
|
}
|
|
|
|
getroottable()["LenheartFuncTab"].rawset("StealFuncN", Lenheart_Steal_Fun); |