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
76b99077
Commit
76b99077
authored
Feb 13, 2026
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
wip
parent
9a20f3ef
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
72 additions
and
13 deletions
+72
-13
.dockerignore
.dockerignore
+1
-0
.gitignore
.gitignore
+1
-0
src/client/client-handler.ts
src/client/client-handler.ts
+7
-3
src/join-handlers/join-room.ts
src/join-handlers/join-room.ts
+2
-0
src/room/duel-record.ts
src/room/duel-record.ts
+5
-3
src/room/room.ts
src/room/room.ts
+50
-7
src/room/ygopro-resource-loader.ts
src/room/ygopro-resource-loader.ts
+6
-0
No files found.
.dockerignore
View file @
76b99077
...
@@ -42,3 +42,4 @@ Dockerfile
...
@@ -42,3 +42,4 @@ Dockerfile
.dockerignore
.dockerignore
/tests
/tests
/ssl
/ssl
/ygopro
.gitignore
View file @
76b99077
...
@@ -37,3 +37,4 @@ lerna-debug.log*
...
@@ -37,3 +37,4 @@ lerna-debug.log*
/output
/output
/config.yaml
/config.yaml
/ssl
/ssl
/ygopro
src/client/client-handler.ts
View file @
76b99077
...
@@ -16,6 +16,7 @@ import {
...
@@ -16,6 +16,7 @@ import {
firstValueFrom
,
firstValueFrom
,
merge
,
merge
,
map
,
map
,
take
,
}
from
'
rxjs
'
;
}
from
'
rxjs
'
;
import
{
YGOProCtosDisconnect
}
from
'
../utility/ygopro-ctos-disconnect
'
;
import
{
YGOProCtosDisconnect
}
from
'
../utility/ygopro-ctos-disconnect
'
;
...
@@ -52,8 +53,8 @@ export class ClientHandler {
...
@@ -52,8 +53,8 @@ export class ClientHandler {
YGOProCtosExternalAddress
,
YGOProCtosExternalAddress
,
YGOProCtosPlayerInfo
,
YGOProCtosPlayerInfo
,
YGOProCtosJoinGame
,
YGOProCtosJoinGame
,
].
some
((
allowed
)
=>
msg
instanceof
allowed
);
].
some
((
allowed
)
=>
(
msg
instanceof
allowed
)
as
boolean
);
if
(
client
.
established
!
==
isPreHandshakeMsg
)
{
if
(
client
.
established
=
==
isPreHandshakeMsg
)
{
// disallow any messages before handshake is complete, except for the ones needed for handshake
// disallow any messages before handshake is complete, except for the ones needed for handshake
return
undefined
;
return
undefined
;
}
}
...
@@ -85,7 +86,7 @@ export class ClientHandler {
...
@@ -85,7 +86,7 @@ export class ClientHandler {
await
this
.
ctx
.
dispatch
(
msg
,
client
);
await
this
.
ctx
.
dispatch
(
msg
,
client
);
}
catch
(
e
)
{
}
catch
(
e
)
{
this
.
logger
.
warn
(
this
.
logger
.
warn
(
`Error dispatching message
${
msg
.
constructor
.
name
}
from
${
client
.
loggingIp
()}
:
${(
e
as
Error
).
message
}
`
,
`Error dispatching message
${
msg
.
constructor
.
name
}
from
${
client
.
loggingIp
()}
:
${(
e
as
Error
).
stack
}
`
,
);
);
}
}
});
});
...
@@ -93,16 +94,19 @@ export class ClientHandler {
...
@@ -93,16 +94,19 @@ export class ClientHandler {
const
handshake$
=
forkJoin
([
const
handshake$
=
forkJoin
([
client
.
receive$
.
pipe
(
client
.
receive$
.
pipe
(
filter
((
msg
)
=>
msg
instanceof
YGOProCtosPlayerInfo
),
filter
((
msg
)
=>
msg
instanceof
YGOProCtosPlayerInfo
),
take
(
1
),
takeUntil
(
client
.
disconnect$
),
takeUntil
(
client
.
disconnect$
),
),
),
client
.
receive$
.
pipe
(
client
.
receive$
.
pipe
(
filter
((
msg
)
=>
msg
instanceof
YGOProCtosJoinGame
),
filter
((
msg
)
=>
msg
instanceof
YGOProCtosJoinGame
),
take
(
1
),
takeUntil
(
client
.
disconnect$
),
takeUntil
(
client
.
disconnect$
),
),
),
]).
pipe
(
timeout
(
5000
),
takeUntil
(
client
.
disconnect$
));
]).
pipe
(
timeout
(
5000
),
takeUntil
(
client
.
disconnect$
));
firstValueFrom
(
handshake$
)
firstValueFrom
(
handshake$
)
.
then
(()
=>
{
.
then
(()
=>
{
this
.
logger
.
debug
({
client
:
client
.
name
},
'
Handshake completed
'
);
client
.
established
=
true
;
client
.
established
=
true
;
})
})
.
catch
(()
=>
{
.
catch
(()
=>
{
...
...
src/join-handlers/join-room.ts
View file @
76b99077
...
@@ -3,11 +3,13 @@ import { Context } from '../app';
...
@@ -3,11 +3,13 @@ import { Context } from '../app';
import
{
RoomManager
}
from
'
../room/room-manager
'
;
import
{
RoomManager
}
from
'
../room/room-manager
'
;
export
class
JoinRoom
{
export
class
JoinRoom
{
private
logger
=
this
.
ctx
.
createLogger
(
this
.
constructor
.
name
);
constructor
(
private
ctx
:
Context
)
{
constructor
(
private
ctx
:
Context
)
{
this
.
ctx
.
middleware
(
YGOProCtosJoinGame
,
async
(
msg
,
client
,
next
)
=>
{
this
.
ctx
.
middleware
(
YGOProCtosJoinGame
,
async
(
msg
,
client
,
next
)
=>
{
if
(
!
msg
.
pass
)
{
if
(
!
msg
.
pass
)
{
return
next
();
return
next
();
}
}
this
.
logger
.
debug
({
name
:
client
.
name
,
pass
:
msg
.
pass
},
'
Joining room
'
);
const
roomManager
=
this
.
ctx
.
get
(()
=>
RoomManager
);
const
roomManager
=
this
.
ctx
.
get
(()
=>
RoomManager
);
const
room
=
await
roomManager
.
findOrCreateByName
(
msg
.
pass
);
const
room
=
await
roomManager
.
findOrCreateByName
(
msg
.
pass
);
return
room
.
join
(
client
);
return
room
.
join
(
client
);
...
...
src/room/duel-record.ts
View file @
76b99077
...
@@ -10,10 +10,12 @@ const REPLAY_ID_YRP2 = 0x32707279;
...
@@ -10,10 +10,12 @@ const REPLAY_ID_YRP2 = 0x32707279;
const
PRO_VERSION
=
0x1362
;
const
PRO_VERSION
=
0x1362
;
export
class
DuelRecord
{
export
class
DuelRecord
{
seed
:
number
[];
constructor
(
players
:
{
name
:
string
;
deck
:
YGOProDeck
}[];
public
seed
:
number
[],
public
players
:
{
name
:
string
;
deck
:
YGOProDeck
}[],
)
{}
winPosition
?:
number
;
winPosition
?:
number
;
responses
:
Buffer
[];
responses
:
Buffer
[]
=
[]
;
toYrp
(
room
:
Room
)
{
toYrp
(
room
:
Room
)
{
const
isTag
=
room
.
isTag
;
const
isTag
=
room
.
isTag
;
...
...
src/room/room.ts
View file @
76b99077
...
@@ -27,6 +27,8 @@ import {
...
@@ -27,6 +27,8 @@ import {
YGOProStocDuelEnd
,
YGOProStocDuelEnd
,
YGOProStocChangeSide
,
YGOProStocChangeSide
,
YGOProStocWaitingSide
,
YGOProStocWaitingSide
,
YGOProCtosTpResult
,
TurnPlayerResult
,
}
from
'
ygopro-msg-encode
'
;
}
from
'
ygopro-msg-encode
'
;
import
{
DefaultHostInfoProvider
}
from
'
./default-hostinfo-provder
'
;
import
{
DefaultHostInfoProvider
}
from
'
./default-hostinfo-provder
'
;
import
{
CardReaderFinalized
}
from
'
koishipro-core.js
'
;
import
{
CardReaderFinalized
}
from
'
koishipro-core.js
'
;
...
@@ -45,11 +47,12 @@ import { OnRoomLeavePlayer } from './room-event/on-room-leave-player';
...
@@ -45,11 +47,12 @@ import { OnRoomLeavePlayer } from './room-event/on-room-leave-player';
import
{
OnRoomLeaveObserver
}
from
'
./room-event/on-room-leave-observer
'
;
import
{
OnRoomLeaveObserver
}
from
'
./room-event/on-room-leave-observer
'
;
import
{
OnRoomMatchStart
}
from
'
./room-event/on-room-match-start
'
;
import
{
OnRoomMatchStart
}
from
'
./room-event/on-room-match-start
'
;
import
{
OnRoomGameStart
}
from
'
./room-event/on-room-game-start
'
;
import
{
OnRoomGameStart
}
from
'
./room-event/on-room-game-start
'
;
// import { OnRoomDuelStart } from './room-event/on-room-duel-start'; // 备用事件,暂未使用
import
YGOProDeck
from
'
ygopro-deck-encode
'
;
import
YGOProDeck
from
'
ygopro-deck-encode
'
;
import
{
checkDeck
,
checkChangeSide
}
from
'
../utility/check-deck
'
;
import
{
checkDeck
,
checkChangeSide
}
from
'
../utility/check-deck
'
;
import
{
DuelRecord
}
from
'
./duel-record
'
;
import
{
DuelRecord
}
from
'
./duel-record
'
;
import
{
last
}
from
'
rxjs
'
;
import
{
RoomEvent
}
from
'
./room-event/room-event
'
;
import
{
generateSeed
}
from
'
../utility/generate-seed
'
;
import
{
OnRoomDuelStart
}
from
'
./room-event/on-room-duel-start
'
;
export
type
RoomFinalizor
=
(
self
:
Room
)
=>
Awaitable
<
any
>
;
export
type
RoomFinalizor
=
(
self
:
Room
)
=>
Awaitable
<
any
>
;
...
@@ -336,6 +339,7 @@ export class Room {
...
@@ -336,6 +339,7 @@ export class Room {
.
forEach
((
p
)
=>
p
.
send
(
new
YGOProStocDuelStart
()));
.
forEach
((
p
)
=>
p
.
send
(
new
YGOProStocDuelStart
()));
}
}
const
duelPos
=
this
.
getSwappedDuelPosByDuelPos
(
winMsg
.
player
!
);
const
duelPos
=
this
.
getSwappedDuelPosByDuelPos
(
winMsg
.
player
!
);
this
.
isPosSwapped
=
false
;
await
Promise
.
all
(
await
Promise
.
all
(
this
.
allPlayers
.
map
((
p
)
=>
this
.
allPlayers
.
map
((
p
)
=>
p
.
send
(
p
.
send
(
...
@@ -513,15 +517,21 @@ export class Room {
...
@@ -513,15 +517,21 @@ export class Room {
const
oldPos
=
client
.
pos
;
const
oldPos
=
client
.
pos
;
// 从当前位置的下一个位置开始循环查找空位
let
nextPos
=
(
oldPos
+
1
)
%
4
;
while
(
this
.
players
[
nextPos
])
{
nextPos
=
(
nextPos
+
1
)
%
4
;
}
// 移动到新位置
// 移动到新位置
this
.
players
[
oldPos
]
=
undefined
;
this
.
players
[
oldPos
]
=
undefined
;
this
.
players
[
firstEmptyPlayerSlot
]
=
client
;
this
.
players
[
nextPos
]
=
client
;
client
.
pos
=
firstEmptyPlayerSlot
;
client
.
pos
=
nextPos
;
// 发送 PlayerChange 给所有人
// 发送 PlayerChange 给所有人
const
changeMsg
=
new
YGOProStocHsPlayerChange
().
fromPartial
({
const
changeMsg
=
new
YGOProStocHsPlayerChange
().
fromPartial
({
playerPosition
:
oldPos
,
playerPosition
:
oldPos
,
playerState
:
firstEmptyPlayerSlot
,
playerState
:
nextPos
,
});
});
this
.
allPlayers
.
forEach
((
p
)
=>
p
.
send
(
changeMsg
));
this
.
allPlayers
.
forEach
((
p
)
=>
p
.
send
(
changeMsg
));
...
@@ -713,7 +723,7 @@ export class Room {
...
@@ -713,7 +723,7 @@ export class Room {
if
(
!
[
DuelStage
.
Finger
,
DuelStage
.
Siding
].
includes
(
this
.
duelStage
))
{
if
(
!
[
DuelStage
.
Finger
,
DuelStage
.
Siding
].
includes
(
this
.
duelStage
))
{
return
false
;
return
false
;
}
}
if
(
this
.
winnerPosition
s
.
length
===
0
)
{
if
(
this
.
duelRecord
s
.
length
===
0
)
{
this
.
allPlayers
.
forEach
((
p
)
=>
p
.
send
(
new
YGOProStocDuelStart
()));
this
.
allPlayers
.
forEach
((
p
)
=>
p
.
send
(
new
YGOProStocDuelStart
()));
const
displayCountDecks
=
[
0
,
1
].
map
(
const
displayCountDecks
=
[
0
,
1
].
map
(
(
p
)
=>
this
.
getDuelPosPlayers
(
p
)[
0
].
deck
!
,
(
p
)
=>
this
.
getDuelPosPlayers
(
p
)[
0
].
deck
!
,
...
@@ -754,7 +764,7 @@ export class Room {
...
@@ -754,7 +764,7 @@ export class Room {
}
}
// 触发事件
// 触发事件
if
(
this
.
winnerPosition
s
.
length
===
0
)
{
if
(
this
.
duelRecord
s
.
length
===
0
)
{
// 触发比赛开始事件(第一局)
// 触发比赛开始事件(第一局)
await
this
.
ctx
.
dispatch
(
await
this
.
ctx
.
dispatch
(
new
OnRoomMatchStart
(
this
),
new
OnRoomMatchStart
(
this
),
...
@@ -767,4 +777,37 @@ export class Room {
...
@@ -767,4 +777,37 @@ export class Room {
return
true
;
return
true
;
}
}
@
RoomMethod
()
private
async
onDuelStart
(
client
:
Client
,
msg
:
YGOProCtosTpResult
)
{
if
(
this
.
duelStage
!==
DuelStage
.
FirstGo
)
{
return
;
}
if
(
client
!==
this
.
firstgoPlayer
)
{
return
;
}
this
.
isPosSwapped
=
(
msg
.
res
===
TurnPlayerResult
.
FIRST
)
!==
(
this
.
getDuelPos
(
client
)
===
0
);
const
duelRecord
=
new
DuelRecord
(
generateSeed
(),
this
.
playingPlayers
.
map
((
p
)
=>
({
name
:
p
.
name
,
deck
:
p
.
deck
!
})),
);
if
(
this
.
isPosSwapped
)
{
this
.
playingPlayers
.
forEach
((
p
)
=>
{
duelRecord
.
players
[
this
.
getSwappedDuelPos
(
p
)]
=
{
name
:
p
.
name
,
deck
:
p
.
deck
!
,
};
});
}
this
.
duelRecords
.
push
(
duelRecord
);
await
this
.
ctx
.
dispatch
(
new
OnRoomDuelStart
(
this
),
this
.
getDuelPosPlayers
(
this
.
getSwappedDuelPos
(
0
))[
0
],
);
this
.
allPlayers
.
forEach
((
p
)
=>
p
.
sendChat
(
'
TODO: duel start
'
));
return
this
.
finalize
();
}
}
}
src/room/ygopro-resource-loader.ts
View file @
76b99077
...
@@ -13,7 +13,13 @@ export class YGOProResourceLoader {
...
@@ -13,7 +13,13 @@ export class YGOProResourceLoader {
]);
]);
extraScriptPaths
=
loadPaths
(
this
.
ctx
.
getConfig
(
'
EXTRA_SCRIPT_PATH
'
));
extraScriptPaths
=
loadPaths
(
this
.
ctx
.
getConfig
(
'
EXTRA_SCRIPT_PATH
'
));
private
logger
=
this
.
ctx
.
createLogger
(
this
.
constructor
.
name
);
async
getCardReader
()
{
async
getCardReader
()
{
this
.
logger
.
debug
(
{
ygoproPaths
:
this
.
ygoproPaths
,
sql
:
typeof
this
.
ctx
.
SQL
.
Database
},
'
Getting card reader
'
,
);
return
DirCardReader
(
this
.
ctx
.
SQL
,
...
this
.
ygoproPaths
);
return
DirCardReader
(
this
.
ctx
.
SQL
,
...
this
.
ygoproPaths
);
}
}
...
...
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