Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
S
srvpro2
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nanahira
srvpro2
Commits
91dcb148
Commit
91dcb148
authored
Feb 16, 2026
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add menu
parent
4af43c04
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
545 additions
and
8 deletions
+545
-8
config.example.yaml
config.example.yaml
+11
-0
package-lock.json
package-lock.json
+4
-4
package.json
package.json
+1
-1
src/client/client-handler.ts
src/client/client-handler.ts
+14
-0
src/client/index.ts
src/client/index.ts
+1
-0
src/config.ts
src/config.ts
+9
-0
src/constants/trans.ts
src/constants/trans.ts
+26
-0
src/feats/index.ts
src/feats/index.ts
+1
-0
src/feats/welcome.ts
src/feats/welcome.ts
+16
-3
src/join-handlers/join-blank-pass-menu.ts
src/join-handlers/join-blank-pass-menu.ts
+399
-0
src/join-handlers/join-handler-module.ts
src/join-handlers/join-handler-module.ts
+2
-0
src/room/index.ts
src/room/index.ts
+1
-0
src/utility/index.ts
src/utility/index.ts
+1
-0
src/utility/panel-pagination.ts
src/utility/panel-pagination.ts
+59
-0
No files found.
config.example.yaml
View file @
91dcb148
...
...
@@ -63,6 +63,17 @@ randomDuelDisableChat: 0
randomDuelReadyTime
:
20
randomDuelHangTimeout
:
90
sideTimeoutMinutes
:
3
enableMenu
:
0
menu
:
"
#{menu_random_duel}"
:
"
"
"
#{menu_random_duel_match}"
:
M
"
#{menu_ai_duel}"
:
AI
"
#{menu_more}"
:
"
#{menu_random_duel_single}"
:
S
"
#{menu_random_duel_tag}"
:
T
"
#{menu_ai_duel_match}"
:
AI,M
"
#{menu_ai_duel_tag}"
:
AI,T
"
#{menu_return}"
:
{}
hostinfoLflist
:
0
hostinfoRule
:
0
hostinfoMode
:
0
...
...
package-lock.json
View file @
91dcb148
...
...
@@ -17,7 +17,7 @@
"https-proxy-agent"
:
"^7.0.6"
,
"ipaddr.js"
:
"^2.3.0"
,
"koishipro-core.js"
:
"^1.3.4"
,
"nfkit"
:
"^1.0.3
2
"
,
"nfkit"
:
"^1.0.3
3
"
,
"p-queue"
:
"6.6.2"
,
"pg"
:
"^8.18.0"
,
"pino"
:
"^10.3.1"
,
...
...
@@ -5330,9 +5330,9 @@
"license"
:
"MIT"
},
"node_modules/nfkit"
:
{
"version"
:
"1.0.3
2
"
,
"resolved"
:
"https://registry.npmjs.org/nfkit/-/nfkit-1.0.3
2
.tgz"
,
"integrity"
:
"sha512-
y+UoxDBs6JV4CSBZkidBGK4GfzJ1Qev8uU4m4oClWGs09oxOCh6TQqnOGRaZY1yCmD8yzYcED+8waSMU4WS5fg
=="
,
"version"
:
"1.0.3
3
"
,
"resolved"
:
"https://registry.npmjs.org/nfkit/-/nfkit-1.0.3
3
.tgz"
,
"integrity"
:
"sha512-
mhF4ZAoGUD3cI0sB/+qH2AothZG2j5y18FkyTKF6etR6nod8jBJWQ5hAr3Q6HnaWlG3HpUKN5i1wfZqQP6hyZw
=="
,
"license"
:
"MIT"
},
"node_modules/node-int64"
:
{
...
...
package.json
View file @
91dcb148
...
...
@@ -70,7 +70,7 @@
"
https-proxy-agent
"
:
"
^7.0.6
"
,
"
ipaddr.js
"
:
"
^2.3.0
"
,
"
koishipro-core.js
"
:
"
^1.3.4
"
,
"
nfkit
"
:
"
^1.0.3
2
"
,
"
nfkit
"
:
"
^1.0.3
3
"
,
"
p-queue
"
:
"
6.6.2
"
,
"
pg
"
:
"
^8.18.0
"
,
"
pino
"
:
"
^10.3.1
"
,
...
...
src/client/client-handler.ts
View file @
91dcb148
...
...
@@ -52,6 +52,14 @@ export class ClientHandler {
.
middleware
(
YGOProCtosBase
,
async
(
msg
,
client
,
next
)
=>
{
const
bypassEstablished
=
msg
instanceof
YGOProCtosJoinGame
&&
msg
.
bypassEstablished
;
if
(
bypassEstablished
)
{
delete
msg
.
bypassEstablished
;
return
next
();
}
const
isPreHandshakeMsg
=
[
YGOProCtosExternalAddress
,
YGOProCtosPlayerInfo
,
...
...
@@ -125,3 +133,9 @@ export class ClientHandler {
});
}
}
declare
module
'
ygopro-msg-encode
'
{
interface
YGOProCtosJoinGame
{
bypassEstablished
?:
boolean
;
}
}
src/client/index.ts
View file @
91dcb148
export
*
from
'
./client
'
;
export
*
from
'
./client-handler
'
;
export
*
from
'
./chnroute
'
;
export
*
from
'
./i18n
'
;
src/config.ts
View file @
91dcb148
...
...
@@ -152,6 +152,15 @@ export const defaultConfig = {
// Room hostinfo defaults expanded into HOSTINFO_* keys.
// Format: each HOSTINFO_* value is a string; numeric fields use integer strings.
// Unit note: HOSTINFO_TIME_LIMIT is in seconds (s).
// Enable blank-pass panel menu.
// Boolean parse rule (default false): ''/'0'/'false'/'null' => false, otherwise true.
ENABLE_MENU
:
'
0
'
,
// Blank-pass panel definition in JSON object format.
// Format: {"Display Text": "ROOM_PASS"}.
// - key: text shown to client; supports i18n placeholder like "#{menu_random_duel}".
// - value(string): equivalent room password, then redispatches CTOS_JOIN_GAME with this pass.
// - value(object): submenu; empty object {} means "return to previous level".
MENU
:
'
{"#{menu_random_duel}":"","#{menu_random_duel_match}":"M","#{menu_ai_duel}":"AI","#{menu_more}":{"#{menu_random_duel_single}":"S","#{menu_random_duel_tag}":"T","#{menu_ai_duel_match}":"AI,M","#{menu_ai_duel_tag}":"AI,T","#{menu_return}":{}}}
'
,
...(
Object
.
fromEntries
(
Object
.
entries
(
DefaultHostinfo
).
map
(([
key
,
value
])
=>
[
`HOSTINFO_
${
key
.
toUpperCase
()}
`
,
...
...
src/constants/trans.ts
View file @
91dcb148
...
...
@@ -82,6 +82,19 @@ export const TRANSLATIONS = {
chat_disabled
:
'
Chat is disabled in this room.
'
,
chat_warn_level1
:
'
Please avoid sensitive words.
'
,
chat_warn_level2
:
'
Your message contains blocked words.
'
,
menu_random_duel
:
'
Random Duel
'
,
menu_random_duel_match
:
'
Random Duel (Match)
'
,
menu_ai_duel
:
'
AI Duel
'
,
menu_more
:
'
More
'
,
menu_random_duel_single
:
'
Random Duel (Single)
'
,
menu_random_duel_tag
:
'
Random Duel (Tag)
'
,
menu_ai_duel_match
:
'
AI Duel (Match)
'
,
menu_ai_duel_tag
:
'
AI Duel (Tag)
'
,
menu_return
:
'
Return
'
,
menu_match_random_duel
:
'
Match Random Duel
'
,
menu_single_random_duel
:
'
Single Random Duel
'
,
menu_prev_page
:
'
Previous Page
'
,
menu_next_page
:
'
Next Page
'
,
},
'
zh-CN
'
:
{
update_required
:
'
请更新你的客户端版本
'
,
...
...
@@ -157,5 +170,18 @@ export const TRANSLATIONS = {
chat_disabled
:
'
本房间禁止聊天。
'
,
chat_warn_level1
:
'
请注意发言,敏感词已被替换。
'
,
chat_warn_level2
:
'
消息包含敏感词,已被拦截。
'
,
menu_random_duel
:
'
随机对战
'
,
menu_random_duel_match
:
'
随机对战(比赛)
'
,
menu_ai_duel
:
'
人机对战
'
,
menu_more
:
'
更多
'
,
menu_random_duel_single
:
'
随机对战(单局)
'
,
menu_random_duel_tag
:
'
随机对战(双打)
'
,
menu_ai_duel_match
:
'
人机对战(比赛)
'
,
menu_ai_duel_tag
:
'
人机对战(双打)
'
,
menu_return
:
'
返回
'
,
menu_match_random_duel
:
'
随机对战(比赛)
'
,
menu_single_random_duel
:
'
随机对战(单局)
'
,
menu_prev_page
:
'
上一页
'
,
menu_next_page
:
'
下一页
'
,
},
};
src/feats/index.ts
View file @
91dcb148
export
*
from
'
./client-version-check
'
;
export
*
from
'
./welcome
'
;
export
*
from
'
./random-duel
'
;
export
*
from
'
./reconnect
'
;
export
*
from
'
./wait-for-player-provider
'
;
...
...
src/feats/welcome.ts
View file @
91dcb148
import
{
ChatColor
}
from
'
ygopro-msg-encode
'
;
import
{
Context
}
from
'
../app
'
;
import
{
Client
}
from
'
../client
'
;
import
{
OnRoomJoin
}
from
'
../room/room-event/on-room-join
'
;
declare
module
'
../room
'
{
...
...
@@ -9,15 +10,19 @@ declare module '../room' {
}
}
declare
module
'
../client
'
{
interface
Client
{
configWelcomeSent
?:
boolean
;
}
}
export
class
Welcome
{
private
welcomeMessage
=
this
.
ctx
.
config
.
getString
(
'
WELCOME
'
);
constructor
(
private
ctx
:
Context
)
{
this
.
ctx
.
middleware
(
OnRoomJoin
,
async
(
event
,
client
,
next
)
=>
{
const
room
=
event
.
room
;
if
(
this
.
welcomeMessage
)
{
await
client
.
sendChat
(
this
.
welcomeMessage
,
ChatColor
.
GREEN
);
}
await
this
.
sendConfigWelcome
(
client
);
if
(
room
.
welcome
)
{
await
client
.
sendChat
(
room
.
welcome
,
ChatColor
.
BABYBLUE
);
}
...
...
@@ -27,4 +32,12 @@ export class Welcome {
return
next
();
});
}
async
sendConfigWelcome
(
client
:
Client
)
{
if
(
!
this
.
welcomeMessage
||
client
.
configWelcomeSent
)
{
return
;
}
client
.
configWelcomeSent
=
true
;
await
client
.
sendChat
(
this
.
welcomeMessage
,
ChatColor
.
GREEN
);
}
}
src/join-handlers/join-blank-pass-menu.ts
0 → 100644
View file @
91dcb148
import
{
GameMode
,
NetPlayerType
,
YGOProCtosBase
,
YGOProCtosHsToDuelist
,
YGOProCtosJoinGame
,
YGOProCtosKick
,
YGOProStocHsPlayerEnter
,
YGOProStocJoinGame
,
YGOProStocTypeChange
,
}
from
'
ygopro-msg-encode
'
;
import
{
Context
}
from
'
../app
'
;
import
{
Chnroute
,
Client
,
I18nService
}
from
'
../client
'
;
import
{
Welcome
}
from
'
../feats
'
;
import
{
DefaultHostinfo
}
from
'
../room
'
;
import
{
resolvePanelPageLayout
}
from
'
../utility
'
;
interface
PanelMenuNode
{
[
key
:
string
]:
string
|
PanelMenuNode
;
}
type
PanelMenuAction
=
|
{
type
:
'
entry
'
;
rawLabel
:
string
;
value
:
string
|
PanelMenuNode
;
}
|
{
type
:
'
next
'
;
rawLabel
:
string
;
offset
:
number
;
}
|
{
type
:
'
prev
'
;
rawLabel
:
string
;
offset
:
number
;
};
type
PanelView
=
{
actions
:
PanelMenuAction
[];
mode
:
GameMode
;
slotCount
:
number
;
};
export
class
JoinBlankPassMenu
{
private
logger
=
this
.
ctx
.
createLogger
(
this
.
constructor
.
name
);
private
i18n
=
this
.
ctx
.
get
(()
=>
I18nService
);
private
chnroute
=
this
.
ctx
.
get
(()
=>
Chnroute
);
private
welcome
=
this
.
ctx
.
get
(()
=>
Welcome
);
private
enabled
=
this
.
ctx
.
config
.
getBoolean
(
'
ENABLE_MENU
'
);
private
rootMenu
=
this
.
loadRootMenu
();
constructor
(
private
ctx
:
Context
)
{
if
(
!
this
.
enabled
)
{
return
;
}
if
(
!
this
.
rootMenu
||
!
Object
.
keys
(
this
.
rootMenu
).
length
)
{
this
.
logger
.
warn
(
'
MENU is empty or invalid, panel feature disabled
'
);
return
;
}
this
.
ctx
.
middleware
(
YGOProCtosBase
,
async
(
msg
,
client
,
next
)
=>
{
const
bypassEstablished
=
msg
instanceof
YGOProCtosJoinGame
&&
msg
.
bypassEstablished
;
if
(
!
client
.
isInPanel
)
{
return
next
();
}
if
(
bypassEstablished
)
{
return
next
();
}
if
(
msg
instanceof
YGOProCtosHsToDuelist
||
msg
instanceof
YGOProCtosKick
)
{
return
next
();
}
return
undefined
;
},
true
,
);
this
.
ctx
.
middleware
(
YGOProCtosJoinGame
,
async
(
msg
,
client
,
next
)
=>
{
msg
.
pass
=
(
msg
.
pass
||
''
).
trim
();
if
(
msg
.
pass
)
{
if
(
client
.
isInPanel
)
{
this
.
exitPanel
(
client
);
}
return
next
();
}
if
(
client
.
isInPanel
)
{
this
.
exitPanel
(
client
);
return
next
();
}
this
.
enterPanel
(
client
,
msg
);
await
this
.
welcome
.
sendConfigWelcome
(
client
);
await
this
.
renderPanel
(
client
);
return
msg
;
});
this
.
ctx
.
middleware
(
YGOProCtosHsToDuelist
,
async
(
_msg
,
client
,
next
)
=>
{
if
(
!
client
.
isInPanel
)
{
return
next
();
}
await
this
.
renderPanel
(
client
);
return
_msg
;
});
this
.
ctx
.
middleware
(
YGOProCtosKick
,
async
(
msg
,
client
,
next
)
=>
{
if
(
!
client
.
isInPanel
)
{
return
next
();
}
await
this
.
handlePanelKick
(
client
,
Number
(
msg
.
pos
));
return
undefined
;
});
}
private
loadRootMenu
()
{
const
raw
=
this
.
ctx
.
config
.
getString
(
'
MENU
'
).
trim
();
if
(
!
raw
)
{
return
undefined
;
}
try
{
const
parsed
=
JSON
.
parse
(
raw
)
as
unknown
;
return
this
.
parseMenuNode
(
parsed
,
'
MENU
'
);
}
catch
(
e
)
{
this
.
logger
.
warn
(
{
error
:
(
e
as
Error
).
message
},
'
Failed to parse MENU config
'
,
);
return
undefined
;
}
}
private
parseMenuNode
(
value
:
unknown
,
path
:
string
):
PanelMenuNode
{
if
(
!
value
||
typeof
value
!==
'
object
'
||
Array
.
isArray
(
value
))
{
throw
new
Error
(
`
${
path
}
must be a JSON object`
);
}
const
parsed
:
PanelMenuNode
=
{};
for
(
const
[
label
,
entryValue
]
of
Object
.
entries
(
value
as
Record
<
string
,
unknown
>
,
))
{
if
(
typeof
entryValue
===
'
string
'
)
{
parsed
[
label
]
=
entryValue
;
continue
;
}
if
(
entryValue
&&
typeof
entryValue
===
'
object
'
&&
!
Array
.
isArray
(
entryValue
)
)
{
parsed
[
label
]
=
this
.
parseMenuNode
(
entryValue
,
`
${
path
}
.
${
label
}
`
);
continue
;
}
throw
new
Error
(
`
${
path
}
.
${
label
}
must be a string or object`
);
}
return
parsed
;
}
private
enterPanel
(
client
:
Client
,
msg
:
YGOProCtosJoinGame
)
{
client
.
isInPanel
=
true
;
client
.
panelMenuPath
=
[];
client
.
panelOffset
=
0
;
client
.
panelJoinVersion
=
msg
.
version
;
client
.
panelJoinGameId
=
msg
.
gameid
;
}
private
exitPanel
(
client
:
Client
)
{
client
.
isInPanel
=
false
;
client
.
panelMenuPath
=
undefined
;
client
.
panelOffset
=
undefined
;
client
.
panelJoinVersion
=
undefined
;
client
.
panelJoinGameId
=
undefined
;
}
private
resolveCurrentMenu
(
client
:
Client
)
{
if
(
!
this
.
rootMenu
)
{
return
undefined
;
}
let
node
=
this
.
rootMenu
;
const
path
=
client
.
panelMenuPath
||
[];
for
(
const
key
of
path
)
{
const
next
=
node
[
key
];
if
(
!
next
||
typeof
next
===
'
string
'
)
{
return
undefined
;
}
node
=
next
;
}
return
node
;
}
private
ensureCurrentMenu
(
client
:
Client
)
{
let
menu
=
this
.
resolveCurrentMenu
(
client
);
if
(
menu
)
{
return
menu
;
}
client
.
panelMenuPath
=
[];
client
.
panelOffset
=
0
;
menu
=
this
.
resolveCurrentMenu
(
client
);
return
menu
;
}
private
buildPanelView
(
client
:
Client
):
PanelView
{
const
menu
=
this
.
ensureCurrentMenu
(
client
);
if
(
!
menu
)
{
return
{
actions
:
[],
mode
:
GameMode
.
SINGLE
,
slotCount
:
2
,
};
}
const
entries
=
Object
.
entries
(
menu
).
map
(([
rawLabel
,
value
])
=>
({
rawLabel
,
value
,
}));
if
(
entries
.
length
<=
2
)
{
return
{
actions
:
entries
.
map
((
entry
)
=>
({
type
:
'
entry
'
,
rawLabel
:
entry
.
rawLabel
,
value
:
entry
.
value
,
})),
mode
:
GameMode
.
SINGLE
,
slotCount
:
2
,
};
}
if
(
entries
.
length
<=
4
)
{
return
{
actions
:
entries
.
map
((
entry
)
=>
({
type
:
'
entry
'
,
rawLabel
:
entry
.
rawLabel
,
value
:
entry
.
value
,
})),
mode
:
GameMode
.
TAG
,
slotCount
:
4
,
};
}
const
layout
=
resolvePanelPageLayout
(
entries
.
length
,
client
.
panelOffset
||
0
);
client
.
panelOffset
=
layout
.
pageStart
;
const
pageActions
:
PanelMenuAction
[]
=
[];
if
(
layout
.
isFirstPage
)
{
for
(
const
entry
of
entries
.
slice
(
layout
.
pageStart
,
layout
.
pageStart
+
3
))
{
pageActions
.
push
({
type
:
'
entry
'
,
rawLabel
:
entry
.
rawLabel
,
value
:
entry
.
value
,
});
}
pageActions
.
push
({
type
:
'
next
'
,
rawLabel
:
'
#{menu_next_page}
'
,
offset
:
layout
.
pageStarts
[
layout
.
pageIndex
+
1
],
});
}
else
if
(
layout
.
isLastPage
)
{
pageActions
.
push
({
type
:
'
prev
'
,
rawLabel
:
'
#{menu_prev_page}
'
,
offset
:
layout
.
pageStarts
[
layout
.
pageIndex
-
1
],
});
for
(
const
entry
of
entries
.
slice
(
layout
.
pageStart
,
layout
.
pageStart
+
3
))
{
pageActions
.
push
({
type
:
'
entry
'
,
rawLabel
:
entry
.
rawLabel
,
value
:
entry
.
value
,
});
}
}
else
{
pageActions
.
push
({
type
:
'
prev
'
,
rawLabel
:
'
#{menu_prev_page}
'
,
offset
:
layout
.
pageStarts
[
layout
.
pageIndex
-
1
],
});
for
(
const
entry
of
entries
.
slice
(
layout
.
pageStart
,
layout
.
pageStart
+
2
))
{
pageActions
.
push
({
type
:
'
entry
'
,
rawLabel
:
entry
.
rawLabel
,
value
:
entry
.
value
,
});
}
pageActions
.
push
({
type
:
'
next
'
,
rawLabel
:
'
#{menu_next_page}
'
,
offset
:
layout
.
pageStarts
[
layout
.
pageIndex
+
1
],
});
}
return
{
actions
:
pageActions
,
mode
:
GameMode
.
TAG
,
slotCount
:
4
,
};
}
private
async
translateLabel
(
client
:
Client
,
label
:
string
)
{
const
locale
=
this
.
chnroute
.
getLocale
(
client
.
ip
);
return
String
(
await
this
.
i18n
.
translate
(
locale
,
label
));
}
private
async
renderPanel
(
client
:
Client
)
{
const
view
=
this
.
buildPanelView
(
client
);
if
(
!
view
.
actions
.
length
)
{
client
.
disconnect
();
return
;
}
await
client
.
send
(
new
YGOProStocJoinGame
().
fromPartial
({
info
:
{
...
DefaultHostinfo
,
mode
:
view
.
mode
,
},
}),
);
await
client
.
send
(
new
YGOProStocTypeChange
().
fromPartial
({
type
:
NetPlayerType
.
OBSERVER
|
0x10
,
}),
);
for
(
let
i
=
0
;
i
<
view
.
slotCount
;
i
++
)
{
const
action
=
view
.
actions
[
i
];
const
translated
=
action
?
await
this
.
translateLabel
(
client
,
action
.
rawLabel
)
:
''
;
await
client
.
send
(
new
YGOProStocHsPlayerEnter
().
fromPartial
({
name
:
translated
.
slice
(
0
,
20
),
pos
:
i
,
}),
);
}
}
private
async
handlePanelKick
(
client
:
Client
,
index
:
number
)
{
const
view
=
this
.
buildPanelView
(
client
);
const
selected
=
view
.
actions
[
index
];
if
(
!
selected
)
{
await
this
.
renderPanel
(
client
);
return
;
}
if
(
selected
.
type
===
'
next
'
||
selected
.
type
===
'
prev
'
)
{
client
.
panelOffset
=
selected
.
offset
;
await
this
.
renderPanel
(
client
);
return
;
}
if
(
typeof
selected
.
value
===
'
string
'
)
{
await
this
.
dispatchJoinGameFromPanel
(
client
,
selected
.
value
);
return
;
}
const
nextMenuKeys
=
Object
.
keys
(
selected
.
value
);
if
(
!
nextMenuKeys
.
length
)
{
await
this
.
backFromPanel
(
client
);
return
;
}
client
.
panelMenuPath
=
[...(
client
.
panelMenuPath
||
[]),
selected
.
rawLabel
];
client
.
panelOffset
=
0
;
await
this
.
renderPanel
(
client
);
}
private
async
backFromPanel
(
client
:
Client
)
{
const
currentPath
=
[...(
client
.
panelMenuPath
||
[])];
if
(
!
currentPath
.
length
)
{
client
.
disconnect
();
return
;
}
currentPath
.
pop
();
client
.
panelMenuPath
=
currentPath
;
client
.
panelOffset
=
0
;
await
this
.
renderPanel
(
client
);
}
private
async
dispatchJoinGameFromPanel
(
client
:
Client
,
pass
:
string
)
{
const
joinMsg
=
new
YGOProCtosJoinGame
().
fromPartial
({
version
:
client
.
panelJoinVersion
||
this
.
ctx
.
config
.
getInt
(
'
YGOPRO_VERSION
'
),
gameid
:
client
.
panelJoinGameId
||
0
,
pass
,
});
joinMsg
.
bypassEstablished
=
true
;
await
this
.
ctx
.
dispatch
(
joinMsg
,
client
);
}
}
declare
module
'
../client
'
{
interface
Client
{
isInPanel
?:
boolean
;
panelMenuPath
?:
string
[];
panelOffset
?:
number
;
panelJoinVersion
?:
number
;
panelJoinGameId
?:
number
;
}
}
src/join-handlers/join-handler-module.ts
View file @
91dcb148
...
...
@@ -9,6 +9,7 @@ import { RandomDuelJoinHandler } from './random-duel-join-handler';
import
{
BadwordPlayerInfoChecker
}
from
'
./badword-player-info-checker
'
;
import
{
JoinBlankPassRandomDuel
}
from
'
./join-blank-pass-random-duel
'
;
import
{
JoinBlankPassWindbotAi
}
from
'
./join-blank-pass-windbot-ai
'
;
import
{
JoinBlankPassMenu
}
from
'
./join-blank-pass-menu
'
;
export
const
JoinHandlerModule
=
createAppContext
<
ContextState
>
()
.
provide
(
ClientVersionCheck
)
...
...
@@ -18,6 +19,7 @@ export const JoinHandlerModule = createAppContext<ContextState>()
.
provide
(
RandomDuelJoinHandler
)
.
provide
(
JoinWindbotAi
)
.
provide
(
JoinRoom
)
.
provide
(
JoinBlankPassMenu
)
.
provide
(
JoinBlankPassRandomDuel
)
.
provide
(
JoinBlankPassWindbotAi
)
.
provide
(
JoinFallback
)
...
...
src/room/index.ts
View file @
91dcb148
...
...
@@ -17,3 +17,4 @@ export * from './room-event/on-room-siding-ready';
export
*
from
'
./room-event/on-room-siding-start
'
;
export
*
from
'
./room-event/on-room-win
'
;
export
*
from
'
./default-hostinfo-provder
'
;
export
*
from
'
./default-hostinfo
'
;
src/utility/index.ts
0 → 100644
View file @
91dcb148
export
*
from
'
./panel-pagination
'
;
src/utility/panel-pagination.ts
0 → 100644
View file @
91dcb148
export
type
PanelPageLayout
=
{
pageStarts
:
number
[];
pageIndex
:
number
;
pageStart
:
number
;
isFirstPage
:
boolean
;
isLastPage
:
boolean
;
};
function
resolvePageIndex
(
pageStarts
:
number
[],
requestedStart
:
number
)
{
if
(
!
pageStarts
.
length
)
{
return
0
;
}
if
(
requestedStart
<=
pageStarts
[
0
])
{
return
0
;
}
for
(
let
i
=
pageStarts
.
length
-
1
;
i
>=
0
;
i
--
)
{
if
(
requestedStart
>=
pageStarts
[
i
])
{
return
i
;
}
}
return
0
;
}
export
function
buildPanelPageStarts
(
totalEntries
:
number
)
{
if
(
totalEntries
<=
4
)
{
return
[
0
];
}
const
pageStarts
=
[
0
];
const
lastPageStart
=
totalEntries
-
3
;
let
cursor
=
3
;
while
(
cursor
<
lastPageStart
)
{
pageStarts
.
push
(
cursor
);
cursor
+=
2
;
}
if
(
pageStarts
[
pageStarts
.
length
-
1
]
!==
lastPageStart
)
{
pageStarts
.
push
(
lastPageStart
);
}
return
pageStarts
;
}
export
function
resolvePanelPageLayout
(
totalEntries
:
number
,
requestedStart
:
number
,
):
PanelPageLayout
{
const
pageStarts
=
buildPanelPageStarts
(
totalEntries
);
const
pageIndex
=
resolvePageIndex
(
pageStarts
,
requestedStart
);
const
pageStart
=
pageStarts
[
pageIndex
]
||
0
;
return
{
pageStarts
,
pageIndex
,
pageStart
,
isFirstPage
:
pageIndex
===
0
,
isLastPage
:
pageIndex
===
pageStarts
.
length
-
1
,
};
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment