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
b658bd0a
Commit
b658bd0a
authored
Feb 17, 2026
by
purerosefallen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix menu and tcp
parent
ab857f6c
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
116 additions
and
78 deletions
+116
-78
.gitignore
.gitignore
+1
-0
src/client/transport/tcp/server.ts
src/client/transport/tcp/server.ts
+19
-0
src/feats/cloud-replay/cloud-replay-service.ts
src/feats/cloud-replay/cloud-replay-service.ts
+61
-52
src/feats/menu-manager.ts
src/feats/menu-manager.ts
+35
-26
No files found.
.gitignore
View file @
b658bd0a
...
...
@@ -12,6 +12,7 @@ lerna-debug.log*
# OS
.DS_Store
._*
# Tests
/coverage
...
...
src/client/transport/tcp/server.ts
View file @
b658bd0a
...
...
@@ -36,6 +36,25 @@ export class TcpServer {
}
private
handleConnection
(
socket
:
Socket
):
void
{
socket
.
on
(
'
error
'
,
(
err
:
NodeJS
.
ErrnoException
)
=>
{
if
(
err
.
code
===
'
ECONNRESET
'
)
{
this
.
logger
.
debug
(
{
remoteAddress
:
socket
.
remoteAddress
,
remotePort
:
socket
.
remotePort
},
'
TCP socket reset by peer
'
,
);
return
;
}
this
.
logger
.
warn
(
{
err
,
remoteAddress
:
socket
.
remoteAddress
,
remotePort
:
socket
.
remotePort
,
},
'
TCP socket error
'
,
);
});
const
client
=
new
TcpClient
(
this
.
ctx
,
socket
);
const
handler
=
this
.
ctx
.
get
(()
=>
ClientHandler
);
handler
.
handleClient
(
client
).
catch
((
err
)
=>
{
...
...
src/feats/cloud-replay/cloud-replay-service.ts
View file @
b658bd0a
...
...
@@ -168,7 +168,11 @@ export class CloudReplayService {
player
.
score
=
resolvePlayerScore
(
room
,
client
);
player
.
startDeckBuffer
=
encodeDeckBase64
(
client
.
startDeck
);
player
.
startDeckMainc
=
resolveStartDeckMainc
(
client
);
player
.
currentDeckBuffer
=
encodeCurrentDeckBase64
(
room
,
client
,
wasSwapped
);
player
.
currentDeckBuffer
=
encodeCurrentDeckBase64
(
room
,
client
,
wasSwapped
,
);
player
.
currentDeckMainc
=
resolveCurrentDeckMainc
(
room
,
client
,
wasSwapped
);
player
.
ingameDeckBuffer
=
encodeIngameDeckBase64
(
room
,
client
,
wasSwapped
);
player
.
ingameDeckMainc
=
resolveIngameDeckMainc
(
room
,
client
,
wasSwapped
);
...
...
@@ -201,44 +205,45 @@ export class CloudReplayService {
}
private
async
renderReplayListMenu
(
client
:
Client
)
{
const
page
=
await
this
.
getReplayPage
(
client
);
if
(
!
page
.
entries
.
length
)
{
await
client
.
die
(
'
#{cloud_replay_no}
'
,
ChatColor
.
RED
);
return
;
}
const
menu
:
MenuEntry
[]
=
[];
if
(
!
this
.
isFirstReplayPage
(
client
))
{
menu
.
push
({
title
:
'
#{menu_prev_page}
'
,
callback
:
async
(
currentClient
)
=>
{
this
.
goToPrevReplayPage
(
currentClient
);
await
this
.
renderReplayListMenu
(
currentClient
);
},
});
}
await
this
.
menuManager
.
launchMenu
(
client
,
async
()
=>
{
const
page
=
await
this
.
getReplayPage
(
client
);
if
(
!
page
.
entries
.
length
)
{
await
client
.
die
(
'
#{cloud_replay_no}
'
,
ChatColor
.
RED
);
return
;
}
for
(
const
replay
of
page
.
entries
)
{
menu
.
push
({
title
:
this
.
formatDate
(
replay
.
endTime
),
callback
:
async
(
currentClient
)
=>
{
currentClient
.
cloudReplaySelectedReplayId
=
replay
.
id
;
await
this
.
renderReplayDetailMenu
(
currentClient
,
replay
.
id
);
},
});
}
const
menu
:
MenuEntry
[]
=
[];
if
(
!
this
.
isFirstReplayPage
(
client
))
{
menu
.
push
({
title
:
'
#{menu_prev_page}
'
,
callback
:
async
(
currentClient
)
=>
{
this
.
goToPrevReplayPage
(
currentClient
);
await
this
.
renderReplayListMenu
(
currentClient
);
},
});
}
if
(
page
.
hasNext
&&
page
.
nextCursor
!=
null
)
{
menu
.
push
({
title
:
'
#{menu_next_page}
'
,
callback
:
async
(
currentClient
)
=>
{
this
.
goToNextReplayPage
(
currentClient
,
page
.
nextCursor
!
)
;
await
this
.
renderReplayListMenu
(
currentClient
);
},
});
}
for
(
const
replay
of
page
.
entries
)
{
menu
.
push
({
title
:
this
.
formatDate
(
replay
.
endTime
)
,
callback
:
async
(
currentClient
)
=>
{
currentClient
.
cloudReplaySelectedReplayId
=
replay
.
id
;
await
this
.
renderReplayDetailMenu
(
currentClient
,
replay
.
id
);
},
});
}
await
this
.
menuManager
.
launchMenu
(
client
,
menu
);
if
(
page
.
hasNext
&&
page
.
nextCursor
!=
null
)
{
menu
.
push
({
title
:
'
#{menu_next_page}
'
,
callback
:
async
(
currentClient
)
=>
{
this
.
goToNextReplayPage
(
currentClient
,
page
.
nextCursor
!
);
await
this
.
renderReplayListMenu
(
currentClient
);
},
});
}
return
menu
;
});
}
private
async
renderReplayDetailMenu
(
client
:
Client
,
replayId
:
number
)
{
...
...
@@ -297,10 +302,22 @@ export class CloudReplayService {
const
score
=
this
.
formatReplayScore
(
replay
);
const
winners
=
this
.
formatReplayWinners
(
replay
);
await
client
.
sendChat
(
`#{cloud_replay_detail_time}
${
dateText
}
`
,
ChatColor
.
BABYBLUE
);
await
client
.
sendChat
(
`#{cloud_replay_detail_players}
${
versus
}
`
,
ChatColor
.
BABYBLUE
);
await
client
.
sendChat
(
`#{cloud_replay_detail_score}
${
score
}
`
,
ChatColor
.
BABYBLUE
);
await
client
.
sendChat
(
`#{cloud_replay_detail_winner}
${
winners
}
`
,
ChatColor
.
BABYBLUE
);
await
client
.
sendChat
(
`#{cloud_replay_detail_time}
${
dateText
}
`
,
ChatColor
.
BABYBLUE
,
);
await
client
.
sendChat
(
`#{cloud_replay_detail_players}
${
versus
}
`
,
ChatColor
.
BABYBLUE
,
);
await
client
.
sendChat
(
`#{cloud_replay_detail_score}
${
score
}
`
,
ChatColor
.
BABYBLUE
,
);
await
client
.
sendChat
(
`#{cloud_replay_detail_winner}
${
winners
}
`
,
ChatColor
.
BABYBLUE
,
);
}
private
async
playReplayStream
(
...
...
@@ -440,11 +457,7 @@ export class CloudReplayService {
return
{
...
hostInfo
,
mode
:
hostInfo
.
mode
>
2
?
this
.
isTagMode
(
hostInfo
)
?
2
:
1
:
hostInfo
.
mode
,
hostInfo
.
mode
>
2
?
(
this
.
isTagMode
(
hostInfo
)
?
2
:
1
)
:
hostInfo
.
mode
,
};
}
...
...
@@ -471,11 +484,7 @@ export class CloudReplayService {
for
(
const
player
of
sortedPlayers
)
{
const
deckBuffer
=
player
.
ingameDeckBuffer
||
player
.
currentDeckBuffer
;
const
mainc
=
player
.
ingameDeckMainc
??
player
.
currentDeckMainc
??
0
;
const
ingamePos
=
resolveIngamePosBySeat
(
player
.
pos
,
isTag
,
wasSwapped
,
);
const
ingamePos
=
resolveIngamePosBySeat
(
player
.
pos
,
isTag
,
wasSwapped
);
players
[
ingamePos
]
=
{
name
:
player
.
name
,
deck
:
decodeDeckBase64
(
deckBuffer
,
mainc
),
...
...
@@ -692,10 +701,10 @@ export class CloudReplayService {
const
teamOffsetBit
=
isTag
?
1
:
0
;
const
team0
=
sortedPlayers
.
filter
(
(
player
)
=>
(
(
player
.
pos
&
(
0x1
<<
teamOffsetBit
))
>>
teamOffsetBit
)
===
0
,
(
player
)
=>
(
player
.
pos
&
(
0x1
<<
teamOffsetBit
))
>>
teamOffsetBit
===
0
,
);
const
team1
=
sortedPlayers
.
filter
(
(
player
)
=>
(
(
player
.
pos
&
(
0x1
<<
teamOffsetBit
))
>>
teamOffsetBit
)
===
1
,
(
player
)
=>
(
player
.
pos
&
(
0x1
<<
teamOffsetBit
))
>>
teamOffsetBit
===
1
,
);
return
[
team0
,
team1
]
as
const
;
}
...
...
src/feats/menu-manager.ts
View file @
b658bd0a
...
...
@@ -12,12 +12,15 @@ import { Context } from '../app';
import
{
Chnroute
,
Client
,
I18nService
}
from
'
../client
'
;
import
{
DefaultHostinfo
}
from
'
../room
'
;
import
{
resolvePanelPageLayout
}
from
'
../utility
'
;
import
{
Awaitable
}
from
'
nfkit
'
;
export
type
MenuEntry
=
{
title
:
string
;
callback
:
(
client
:
Client
)
=>
Promise
<
unknown
>
|
unknown
;
};
export
type
MenuFactory
=
(
client
:
Client
)
=>
Awaitable
<
MenuEntry
[]
>
;
type
MenuAction
=
|
{
type
:
'
entry
'
;
...
...
@@ -79,10 +82,8 @@ export class MenuManager {
});
}
async
launchMenu
(
client
:
Client
,
menu
:
MenuEntry
[])
{
client
.
currentMenu
=
menu
.
filter
(
(
entry
):
entry
is
MenuEntry
=>
!!
entry
,
);
async
launchMenu
(
client
:
Client
,
menu
:
MenuFactory
|
MenuEntry
[])
{
client
.
currentMenu
=
Array
.
isArray
(
menu
)
?
()
=>
menu
:
menu
;
if
(
client
.
menuOffset
==
null
)
{
client
.
menuOffset
=
0
;
}
...
...
@@ -91,13 +92,12 @@ export class MenuManager {
clearMenu
(
client
:
Client
)
{
client
.
currentMenu
=
undefined
;
client
.
currentMenuView
=
undefined
;
client
.
menuOffset
=
undefined
;
}
private
buildMenuView
(
client
:
Client
):
MenuView
{
const
menu
=
(
client
.
currentMenu
||
[]).
filter
(
(
entry
):
entry
is
MenuEntry
=>
!!
entry
,
);
private
async
buildMenuView
(
client
:
Client
):
Promise
<
MenuView
>
{
const
menu
=
(
await
client
.
currentMenu
!
(
client
))
||
[];
if
(
menu
.
length
<=
2
)
{
return
{
actions
:
menu
.
map
((
entry
)
=>
({
type
:
'
entry
'
,
entry
})),
...
...
@@ -163,23 +163,30 @@ export class MenuManager {
return
;
}
const
view
=
this
.
buildMenuView
(
client
);
await
client
.
send
(
new
YGOProStocJoinGame
().
fromPartial
({
info
:
{
...
DefaultHostinfo
,
mode
:
view
.
mode
,
},
}),
);
await
client
.
send
(
new
YGOProStocTypeChange
().
fromPartial
({
type
:
NetPlayerType
.
OBSERVER
|
0x10
,
}),
);
const
view
=
await
this
.
buildMenuView
(
client
);
client
.
currentMenuView
=
view
;
if
(
!
client
.
maxRenderedMenuSlots
||
client
.
maxRenderedMenuSlots
<
view
.
slotCount
)
{
client
.
maxRenderedMenuSlots
=
view
.
slotCount
;
await
client
.
send
(
new
YGOProStocJoinGame
().
fromPartial
({
info
:
{
...
DefaultHostinfo
,
mode
:
view
.
mode
,
},
}),
);
await
client
.
send
(
new
YGOProStocTypeChange
().
fromPartial
({
type
:
NetPlayerType
.
OBSERVER
|
0x10
,
}),
);
}
const
locale
=
this
.
chnroute
.
getLocale
(
client
.
ip
);
for
(
let
i
=
0
;
i
<
view
.
slotCount
;
i
++
)
{
for
(
let
i
=
0
;
i
<
client
.
maxRenderedMenuSlots
;
i
++
)
{
const
action
=
view
.
actions
[
i
];
const
rawTitle
=
action
?.
type
===
'
entry
'
?
action
.
entry
.
title
:
action
?.
title
||
''
;
...
...
@@ -196,11 +203,11 @@ export class MenuManager {
}
private
async
handleKick
(
client
:
Client
,
index
:
number
)
{
if
(
!
client
.
currentMenu
)
{
if
(
!
client
.
currentMenu
View
)
{
return
;
}
const
view
=
this
.
buildMenuView
(
client
)
;
const
view
=
client
.
currentMenuView
;
const
selected
=
view
.
actions
[
index
];
if
(
!
selected
)
{
await
this
.
renderMenu
(
client
);
...
...
@@ -221,7 +228,9 @@ export class MenuManager {
declare
module
'
../client
'
{
interface
Client
{
currentMenu
?:
MenuEntry
[];
currentMenu
?:
MenuFactory
;
currentMenuView
?:
MenuView
;
maxRenderedMenuSlots
?:
number
;
menuOffset
?:
number
;
}
}
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