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
83e457e0
Commit
83e457e0
authored
Feb 17, 2026
by
nanahira
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
tournament mode
parent
41a6b406
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
167 additions
and
2 deletions
+167
-2
AGENTS.md
AGENTS.md
+5
-1
config.example.yaml
config.example.yaml
+3
-0
src/config.ts
src/config.ts
+9
-0
src/constants/trans.ts
src/constants/trans.ts
+13
-0
src/feats/block-replay.ts
src/feats/block-replay.ts
+19
-0
src/feats/client-key-provider/provider.ts
src/feats/client-key-provider/provider.ts
+6
-1
src/feats/feats-module.ts
src/feats/feats-module.ts
+4
-0
src/feats/index.ts
src/feats/index.ts
+3
-0
src/feats/lock-deck-check.ts
src/feats/lock-deck-check.ts
+20
-0
src/feats/lock-deck-service.ts
src/feats/lock-deck-service.ts
+62
-0
src/join-handlers/badword-player-info-checker.ts
src/join-handlers/badword-player-info-checker.ts
+4
-0
src/room/index.ts
src/room/index.ts
+1
-0
src/utility/deck-compare.ts
src/utility/deck-compare.ts
+18
-0
No files found.
AGENTS.md
View file @
83e457e0
...
...
@@ -35,10 +35,14 @@
-
srvpro 里面的 client.send(发送给客户端)还是对应 client.send
-
srvpro 里面 server.send(模拟客户端发送消息)对应 this.ctx.dispatch(msgClassInstance, client)
## ts-rest 相关
## ts-rest 相关
(如果用到的话)
-
契约文件放在 src/api/contract.ts 一个文件里面,方便其他项目复制。
## TypeORM 相关
-
删除一律用 softDelete。
## 参考项目
可以参考电脑的下面的项目,用来参考。这些代码只能看,不能改。~ 指代这台电脑的 HOME 目录。
...
...
config.example.yaml
View file @
83e457e0
...
...
@@ -62,6 +62,9 @@ chatgptTokenLimit: 12000
chatgptExtraOpts
:
{}
enableReconnect
:
1
enableCloudReplay
:
1
tournamentMode
:
0
tournamentModeCheckDeck
:
1
blockReplayToPlayer
:
0
enableRoomlist
:
1
reconnectTimeout
:
180000
hidePlayerName
:
0
...
...
src/config.ts
View file @
83e457e0
...
...
@@ -142,6 +142,15 @@ export const defaultConfig = {
// Enable cloud replay menu entry (R/W pass handling).
// Boolean parse rule (default true): only '0'/'false'/'null' => false, otherwise true.
ENABLE_CLOUD_REPLAY
:
'
1
'
,
// Enable tournament mode compatibility behavior.
// Boolean parse rule (default false): ''/'0'/'false'/'null' => false, otherwise true.
TOURNAMENT_MODE
:
'
0
'
,
// Enable tournament mode deck lock check hook.
// Boolean parse rule (default true): only '0'/'false'/'null' => false, otherwise true.
TOURNAMENT_MODE_CHECK_DECK
:
'
1
'
,
// Block replay packets to players who are currently in a room.
// Boolean parse rule (default false): ''/'0'/'false'/'null' => false, otherwise true.
BLOCK_REPLAY_TO_PLAYER
:
'
0
'
,
// Enable room list menu entry (L pass handling).
// Boolean parse rule (default true): only '0'/'false'/'null' => false, otherwise true.
ENABLE_ROOMLIST
:
'
1
'
,
...
...
src/constants/trans.ts
View file @
83e457e0
...
...
@@ -33,6 +33,14 @@ export const TRANSLATIONS = {
pre_reconnecting_to_room
:
'
You will be reconnected to your previous game. Please pick your previous deck.
'
,
deck_incorrect_reconnect
:
'
Please pick your previous deck.
'
,
deck_correct_part1
:
'
Your deck
'
,
deck_correct_part2
:
'
has passed the deck check for this tournament.
'
,
deck_incorrect_part1
:
'
The deck you are using is not the same as your submitted deck
'
,
deck_incorrect_part2
:
'
. Please make sure all the cards are in the correct sequence.
'
,
deck_not_found
:
'
, we did not receive your registration. Please make sure you are using the correct ID for the tournament.
'
,
reconnect_failed
:
'
Reconnect failed.
'
,
reconnecting_to_room
:
'
Reconnecting to server...
'
,
side_timeout_part1
:
'
Changing side time is limited to
'
,
...
...
@@ -166,6 +174,11 @@ export const TRANSLATIONS = {
pre_reconnecting_to_room
:
'
你有未完成的对局,即将重新连接,请选择你在本局决斗中使用的卡组并准备。
'
,
deck_incorrect_reconnect
:
'
请选择你在本局决斗中使用的卡组。
'
,
deck_correct_part1
:
'
成功使用卡组
'
,
deck_correct_part2
:
'
参加比赛。
'
,
deck_incorrect_part1
:
'
您的卡组与报名卡组
'
,
deck_incorrect_part2
:
'
不符。注意卡组不能有包括卡片顺序在内的任何修改。
'
,
deck_not_found
:
'
,没有找到您的报名信息,请确定您使用昵称与报名ID一致。
'
,
reconnect_failed
:
'
重新连接失败。
'
,
reconnecting_to_room
:
'
正在重新连接到服务器……
'
,
side_timeout_part1
:
'
你现在有
'
,
...
...
src/feats/block-replay.ts
0 → 100644
View file @
83e457e0
import
{
YGOProStocReplay
}
from
'
ygopro-msg-encode
'
;
import
{
Context
}
from
'
../app
'
;
export
class
BlockReplay
{
private
enabled
=
this
.
ctx
.
config
.
getBoolean
(
'
BLOCK_REPLAY_TO_PLAYER
'
);
constructor
(
private
ctx
:
Context
)
{
if
(
!
this
.
enabled
)
{
return
;
}
this
.
ctx
.
middleware
(
YGOProStocReplay
,
async
(
_msg
,
client
,
next
)
=>
{
if
(
client
.
roomName
)
{
return
;
}
return
next
();
});
}
}
src/feats/client-key-provider/provider.ts
View file @
83e457e0
import
{
Context
}
from
'
../../app
'
;
import
{
Client
}
from
'
../../client
'
;
export
class
ClientKeyProvider
{
constructor
(
private
ctx
:
Context
)
{}
// Keep this switch for future compatibility with srvpro identity policies.
isLooseIdentityRule
=
false
;
get
isLooseIdentityRule
()
{
return
this
.
ctx
.
config
.
getBoolean
(
'
TOURNAMENT_MODE
'
);
}
getClientKey
(
client
:
Client
):
string
{
if
(
!
this
.
isLooseIdentityRule
&&
client
.
vpass
)
{
...
...
src/feats/feats-module.ts
View file @
83e457e0
...
...
@@ -16,6 +16,8 @@ import { CommandsService, KoishiContextService } from '../koishi';
import
{
ChatgptService
}
from
'
./chatgpt-service
'
;
import
{
CloudReplayService
}
from
'
./cloud-replay
'
;
import
{
LpLowHintService
}
from
'
./lp-low-hint-service
'
;
import
{
LockDeckService
}
from
'
./lock-deck-service
'
;
import
{
BlockReplay
}
from
'
./block-replay
'
;
export
const
FeatsModule
=
createAppContext
<
ContextState
>
()
.
provide
(
ClientKeyProvider
)
...
...
@@ -27,8 +29,10 @@ export const FeatsModule = createAppContext<ContextState>()
.
provide
(
ClientVersionCheck
)
.
provide
(
PlayerStatusNotify
)
.
provide
(
CloudReplayService
)
// persist duel records
.
provide
(
BlockReplay
)
// block replay packets for in-room players
.
provide
(
ChatgptService
)
// AI-room chat replies
.
provide
(
LpLowHintService
)
// low LP hint in duel
.
provide
(
LockDeckService
)
// srvpro-style tournament deck lock check
.
provide
(
RefreshFieldService
)
.
provide
(
Reconnect
)
.
provide
(
WaitForPlayerProvider
)
// chat refresh
...
...
src/feats/index.ts
View file @
83e457e0
export
*
from
'
./client-version-check
'
;
export
*
from
'
./client-key-provider
'
;
export
*
from
'
./chatgpt-service
'
;
export
*
from
'
./block-replay
'
;
export
*
from
'
./cloud-replay
'
;
export
*
from
'
./hide-player-name-provider
'
;
export
*
from
'
./lock-deck-check
'
;
export
*
from
'
./lock-deck-service
'
;
export
*
from
'
./menu-manager
'
;
export
*
from
'
./welcome
'
;
export
*
from
'
./random-duel
'
;
...
...
src/feats/lock-deck-check.ts
0 → 100644
View file @
83e457e0
import
YGOProDeck
from
'
ygopro-deck-encode
'
;
import
{
Client
}
from
'
../client
'
;
import
{
Room
}
from
'
../room
'
;
import
{
ValueContainer
}
from
'
../utility/value-container
'
;
export
class
LockDeckExpectedDeckCheck
extends
ValueContainer
<
YGOProDeck
|
null
|
undefined
>
{
constructor
(
public
room
:
Room
,
public
client
:
Client
,
public
deck
:
YGOProDeck
,
)
{
super
(
undefined
);
}
get
expectedDeck
()
{
return
this
.
value
;
}
}
src/feats/lock-deck-service.ts
0 → 100644
View file @
83e457e0
import
{
YGOProLFListError
,
YGOProLFListErrorReason
}
from
'
ygopro-lflist-encode
'
;
import
{
ChatColor
}
from
'
ygopro-msg-encode
'
;
import
{
Context
}
from
'
../app
'
;
import
{
RoomCheckDeck
}
from
'
../room
'
;
import
{
isSrvproTournamentDeckEqual
}
from
'
../utility/deck-compare
'
;
import
{
LockDeckExpectedDeckCheck
}
from
'
./lock-deck-check
'
;
class
SrvproDeckBadError
extends
YGOProLFListError
{
constructor
()
{
super
(
YGOProLFListErrorReason
.
LFLIST
,
0
);
}
toPayload
()
{
// srvpro 的 deck_bad 发的是 ERROR_MSG code=0
return
0
;
}
}
export
class
LockDeckService
{
constructor
(
private
ctx
:
Context
)
{
if
(
!
this
.
ctx
.
config
.
getBoolean
(
'
TOURNAMENT_MODE_CHECK_DECK
'
))
{
return
;
}
this
.
ctx
.
middleware
(
RoomCheckDeck
,
async
(
msg
,
client
,
next
)
=>
{
const
current
=
await
next
();
if
(
msg
.
value
)
{
return
current
;
}
const
expectedDeckCheck
=
await
this
.
ctx
.
dispatch
(
new
LockDeckExpectedDeckCheck
(
msg
.
room
,
msg
.
client
,
msg
.
deck
),
client
,
);
const
expectedDeck
=
expectedDeckCheck
?.
expectedDeck
;
if
(
expectedDeck
===
undefined
)
{
return
current
;
}
if
(
expectedDeck
===
null
)
{
await
client
.
sendChat
(
`
${
client
.
name
}
#{deck_not_found}`
,
ChatColor
.
RED
);
return
msg
.
use
(
new
SrvproDeckBadError
());
}
const
deckName
=
expectedDeck
.
name
||
''
;
if
(
isSrvproTournamentDeckEqual
(
msg
.
deck
,
expectedDeck
))
{
await
client
.
sendChat
(
`#{deck_correct_part1}
${
deckName
}
#{deck_correct_part2}`
,
ChatColor
.
BABYBLUE
,
);
return
current
;
}
await
client
.
sendChat
(
`#{deck_incorrect_part1}
${
deckName
}
#{deck_incorrect_part2}`
,
ChatColor
.
RED
,
);
return
msg
.
use
(
new
SrvproDeckBadError
());
});
}
}
src/join-handlers/badword-player-info-checker.ts
View file @
83e457e0
...
...
@@ -7,6 +7,10 @@ export class BadwordPlayerInfoChecker {
private
badwordProvider
=
this
.
ctx
.
get
(()
=>
BadwordProvider
);
constructor
(
private
ctx
:
Context
)
{
if
(
this
.
ctx
.
config
.
getBoolean
(
'
TOURNAMENT_MODE
'
))
{
return
;
}
if
(
!
this
.
badwordProvider
.
enabled
)
{
return
;
}
...
...
src/room/index.ts
View file @
83e457e0
...
...
@@ -17,5 +17,6 @@ export * from './room-event/on-room-select-tp';
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
'
./room-event/room-check-deck
'
;
export
*
from
'
./default-hostinfo-provder
'
;
export
*
from
'
./default-hostinfo
'
;
src/utility/deck-compare.ts
View file @
83e457e0
...
...
@@ -18,3 +18,21 @@ export function isUpdateDeckPayloadEqual(
return
buffer1
.
equals
(
buffer2
);
}
/**
* srvpro 锦标赛模式的对比方式:
* 1. 用 UPDATE_DECK payload 形态归一化(main+extra 统一到 payload 语义)
* 2. 忽略顺序进行比较
*/
export
function
isSrvproTournamentDeckEqual
(
deck1
:
YGOProDeck
,
deck2
:
YGOProDeck
,
):
boolean
{
const
normalizedDeck1
=
YGOProDeck
.
fromUpdateDeckPayload
(
deck1
.
toUpdateDeckPayload
(),
);
const
normalizedDeck2
=
YGOProDeck
.
fromUpdateDeckPayload
(
deck2
.
toUpdateDeckPayload
(),
);
return
normalizedDeck1
.
isEqual
(
normalizedDeck2
,
{
ignoreOrder
:
true
});
}
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