Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
N
Neos
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
baichixing
Neos
Commits
a754c2c2
Commit
a754c2c2
authored
Jul 16, 2023
by
timel
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'dev/ui' into 'main'
Dev/ui See merge request
!246
parents
53a9bdf4
0cad7ff4
Changes
35
Hide whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
505 additions
and
456 deletions
+505
-456
src/infra/eventbus.ts
src/infra/eventbus.ts
+7
-4
src/service/duel/attack.ts
src/service/duel/attack.ts
+8
-10
src/service/duel/chaining.ts
src/service/duel/chaining.ts
+1
-5
src/service/duel/confirmCards.ts
src/service/duel/confirmCards.ts
+1
-4
src/service/duel/draw.ts
src/service/duel/draw.ts
+2
-2
src/service/duel/gameMsg.ts
src/service/duel/gameMsg.ts
+0
-6
src/service/duel/move.ts
src/service/duel/move.ts
+11
-10
src/service/duel/posChange.ts
src/service/duel/posChange.ts
+3
-2
src/service/duel/shuffleHandExtra.ts
src/service/duel/shuffleHandExtra.ts
+21
-19
src/service/duel/shuffleSetCard.ts
src/service/duel/shuffleSetCard.ts
+31
-28
src/service/duel/swapGraveDeck.ts
src/service/duel/swapGraveDeck.ts
+3
-3
src/service/duel/updateData.ts
src/service/duel/updateData.ts
+3
-4
src/ui/Duel/Message/SelectActionsModal/index.tsx
src/ui/Duel/Message/SelectActionsModal/index.tsx
+3
-26
src/ui/Duel/PlayMat/Bg/index.scss
src/ui/Duel/PlayMat/Bg/index.scss
+62
-3
src/ui/Duel/PlayMat/Bg/index.tsx
src/ui/Duel/PlayMat/Bg/index.tsx
+67
-25
src/ui/Duel/PlayMat/Card/index.scss
src/ui/Duel/PlayMat/Card/index.scss
+5
-1
src/ui/Duel/PlayMat/Card/index.tsx
src/ui/Duel/PlayMat/Card/index.tsx
+65
-77
src/ui/Duel/PlayMat/Card/springs/attack.ts
src/ui/Duel/PlayMat/Card/springs/attack.ts
+13
-19
src/ui/Duel/PlayMat/Card/springs/focus.ts
src/ui/Duel/PlayMat/Card/springs/focus.ts
+3
-8
src/ui/Duel/PlayMat/Card/springs/index.ts
src/ui/Duel/PlayMat/Card/springs/index.ts
+3
-5
src/ui/Duel/PlayMat/Card/springs/move.ts
src/ui/Duel/PlayMat/Card/springs/move.ts
+35
-0
src/ui/Duel/PlayMat/Card/springs/moveToDeck.ts
src/ui/Duel/PlayMat/Card/springs/moveToDeck.ts
+7
-12
src/ui/Duel/PlayMat/Card/springs/moveToGround.ts
src/ui/Duel/PlayMat/Card/springs/moveToGround.ts
+29
-29
src/ui/Duel/PlayMat/Card/springs/moveToHand.ts
src/ui/Duel/PlayMat/Card/springs/moveToHand.ts
+13
-16
src/ui/Duel/PlayMat/Card/springs/moveToOutside.ts
src/ui/Duel/PlayMat/Card/springs/moveToOutside.ts
+24
-9
src/ui/Duel/PlayMat/Card/springs/moveToToken.ts
src/ui/Duel/PlayMat/Card/springs/moveToToken.ts
+4
-1
src/ui/Duel/PlayMat/Card/springs/types.ts
src/ui/Duel/PlayMat/Card/springs/types.ts
+23
-1
src/ui/Duel/PlayMat/Card/springs/utils.ts
src/ui/Duel/PlayMat/Card/springs/utils.ts
+0
-11
src/ui/Duel/PlayMat/LifeBar/index.scss
src/ui/Duel/PlayMat/LifeBar/index.scss
+0
-22
src/ui/Duel/PlayMat/LifeBar/index.tsx
src/ui/Duel/PlayMat/LifeBar/index.tsx
+0
-1
src/ui/Duel/PlayMat/utils/cssConfig.ts
src/ui/Duel/PlayMat/utils/cssConfig.ts
+0
-91
src/ui/Duel/PlayMat/utils/index.ts
src/ui/Duel/PlayMat/utils/index.ts
+0
-1
src/ui/Shared/YgoCard/index.tsx
src/ui/Shared/YgoCard/index.tsx
+1
-1
src/ui/Shared/css.ts
src/ui/Shared/css.ts
+56
-0
src/ui/Shared/index.ts
src/ui/Shared/index.ts
+1
-0
No files found.
src/infra/eventbus.ts
View file @
a754c2c2
...
...
@@ -12,12 +12,15 @@ export enum Task {
const
getEnd
=
(
task
:
Task
)
=>
`
${
task
}
-end`
;
/** 在组件之中注册方法 */
const
register
=
(
task
:
Task
,
fn
:
(...
args
:
any
[])
=>
Promise
<
any
>
)
=>
{
const
register
=
<
T
extends
unknown
[]
>
(
task
:
Task
,
fn
:
(...
args
:
T
)
=>
Promise
<
boolean
>
)
=>
{
eventEmitter
.
on
(
task
,
async
({
taskId
,
args
}:
{
taskId
:
string
;
args
:
any
[]
})
=>
{
await
fn
(...
args
);
eventEmitter
.
emit
(
getEnd
(
task
),
taskId
);
async
({
taskId
,
args
}:
{
taskId
:
string
;
args
:
T
})
=>
{
const
result
=
await
fn
(...
args
);
if
(
result
)
eventEmitter
.
emit
(
getEnd
(
task
),
taskId
);
}
);
};
...
...
src/service/duel/attack.ts
View file @
a754c2c2
import
{
ygopro
}
from
"
@/api
"
;
import
{
eventbus
,
sleep
,
Task
}
from
"
@/infra
"
;
import
{
cardStore
,
fetchEsHintMeta
}
from
"
@/stores
"
;
import
{
callCardAttack
}
from
"
@/ui/Duel/PlayMat/Card
"
;
export
default
async
(
attack
:
ygopro
.
StocGameMessage
.
MsgAttack
)
=>
{
fetchEsHintMeta
({
...
...
@@ -16,18 +16,16 @@ export default async (attack: ygopro.StocGameMessage.MsgAttack) => {
if
(
attacker
)
{
if
(
attack
.
direct_attack
)
{
await
eventbus
.
call
(
Task
.
Attack
,
attacker
.
uuid
,
true
);
await
callCardAttack
(
attacker
.
uuid
,
{
directAttack
:
true
,
});
}
else
{
await
eventbus
.
call
(
Task
.
Attack
,
attacker
.
uuid
,
false
,
attack
.
target_location
);
await
callCardAttack
(
attacker
.
uuid
,
{
directAttack
:
false
,
target
:
attack
.
target_location
,
});
}
}
else
{
console
.
warn
(
`<Attack>attacker from
${
attack
.
attacker_location
}
is null`
);
}
await
sleep
(
2000
);
};
src/service/duel/chaining.ts
View file @
a754c2c2
import
{
fetchCard
,
ygopro
}
from
"
@/api
"
;
import
{
eventbus
,
sleep
,
Task
}
from
"
@/infra
"
;
import
{
eventbus
,
Task
}
from
"
@/infra
"
;
import
{
cardStore
,
fetchEsHintMeta
,
matStore
}
from
"
@/stores
"
;
export
default
async
(
chaining
:
ygopro
.
StocGameMessage
.
MsgChaining
)
=>
{
...
...
@@ -29,10 +29,6 @@ export default async (chaining: ygopro.StocGameMessage.MsgChaining) => {
// 发动效果动画
await
eventbus
.
call
(
Task
.
Focus
,
target
.
uuid
);
console
.
color
(
"
blue
"
)(
`
${
target
.
meta
.
text
.
name
}
chaining`
);
// 临时办法,这里延迟800ms
// 长期:需要实现动画序列,一个动画完成后才执行下一个动画
await
sleep
(
800
);
}
else
{
console
.
warn
(
`<Chaining>target from
${
location
}
is null`
);
}
...
...
src/service/duel/confirmCards.ts
View file @
a754c2c2
import
{
fetchCard
,
ygopro
}
from
"
@/api
"
;
import
{
eventbus
,
sleep
,
Task
}
from
"
@/infra
"
;
import
{
eventbus
,
Task
}
from
"
@/infra
"
;
import
{
cardStore
}
from
"
@/stores
"
;
export
default
async
(
confirmCards
:
ygopro
.
StocGameMessage
.
MsgConfirmCards
)
=>
{
...
...
@@ -15,9 +15,6 @@ export default async (confirmCards: ygopro.StocGameMessage.MsgConfirmCards) => {
target
.
meta
=
meta
;
// 动画
await
eventbus
.
call
(
Task
.
Focus
,
target
.
uuid
);
// 临时措施,延迟一会,让动画逐个展示
// 长期:需要实现动画序列,一个动画完成后才执行下一个动画
await
sleep
(
500
);
}
else
{
console
.
warn
(
`card of
${
card
}
is null`
);
}
...
...
src/service/duel/draw.ts
View file @
a754c2c2
import
{
fetchCard
,
ygopro
}
from
"
@/api
"
;
import
{
eventbus
,
Task
}
from
"
@/infra
"
;
import
{
cardStore
,
fetchEsHintMeta
}
from
"
@/stores
"
;
import
{
callCardMove
}
from
"
@/ui/Duel/PlayMat/Card
"
;
export
default
async
(
draw
:
ygopro
.
StocGameMessage
.
MsgDraw
)
=>
{
fetchEsHintMeta
({
originMsg
:
"
玩家抽卡时
"
});
...
...
@@ -27,6 +27,6 @@ export default async (draw: ygopro.StocGameMessage.MsgDraw) => {
await
Promise
.
all
(
cardStore
.
at
(
ygopro
.
CardZone
.
HAND
,
draw
.
player
)
.
map
((
card
)
=>
eventbus
.
call
(
Task
.
Move
,
card
.
uuid
))
.
map
((
card
)
=>
callCardMove
(
card
.
uuid
))
);
};
src/service/duel/gameMsg.ts
View file @
a754c2c2
import
{
ygopro
}
from
"
@/api
"
;
import
{
sleep
}
from
"
@/infra
"
;
import
{
matStore
}
from
"
@/stores
"
;
import
{
showWaiting
}
from
"
@/ui/Duel/Message
"
;
...
...
@@ -93,7 +92,6 @@ let animation: Promise<unknown> = new Promise<void>((rs) => rs());
export
default
async
function
handleGameMsg
(
pb
:
ygopro
.
YgoStocMsg
)
{
animation
=
animation
.
then
(()
=>
_handleGameMsg
(
pb
));
// _handleGameMsg(pb);
}
async
function
_handleGameMsg
(
pb
:
ygopro
.
YgoStocMsg
)
{
...
...
@@ -143,8 +141,6 @@ async function _handleGameMsg(pb: ygopro.YgoStocMsg) {
}
case
"
move
"
:
{
await
onMsgMove
(
msg
.
move
);
await
sleep
(
500
);
break
;
}
case
"
select_card
"
:
{
...
...
@@ -269,7 +265,6 @@ async function _handleGameMsg(pb: ygopro.YgoStocMsg) {
}
case
"
chaining
"
:
{
await
onMsgChaining
(
msg
.
chaining
);
break
;
}
case
"
chain_solved
"
:
{
...
...
@@ -324,7 +319,6 @@ async function _handleGameMsg(pb: ygopro.YgoStocMsg) {
}
case
"
confirm_cards
"
:
{
await
onConfirmCards
(
msg
.
confirm_cards
);
break
;
}
case
"
become_target
"
:
{
...
...
src/service/duel/move.ts
View file @
a754c2c2
import
{
fetchCard
,
ygopro
}
from
"
@/api
"
;
import
{
eventbus
,
Task
}
from
"
@/infra
"
;
import
{
cardStore
,
CardType
}
from
"
@/stores
"
;
import
{
callCardMove
}
from
"
@/ui/Duel/PlayMat/Card
"
;
import
{
REASON_MATERIAL
,
TYPE_TOKEN
}
from
"
../../common
"
;
...
...
@@ -114,7 +114,7 @@ export default async (move: MsgMove) => {
overlayMaterial
.
location
.
zone
=
to
.
zone
;
overlayMaterial
.
location
.
sequence
=
to
.
sequence
;
await
eventbus
.
call
(
Task
.
Move
,
overlayMaterial
.
uuid
);
await
callCardMove
(
overlayMaterial
.
uuid
);
}
else
{
console
.
warn
(
`<Move>overlayMaterial from zone=
${
location
.
zone
}
, controller=
${
location
.
controller
}
, sequence=
${
location
.
sequence
}
, overlay_sequence=
${
location
.
overlay_sequence
}
is null`
...
...
@@ -157,15 +157,16 @@ export default async (move: MsgMove) => {
target
.
location
=
to
;
// 维护完了之后,开始动画
await
eventbus
.
call
(
Task
.
Move
,
target
.
uuid
,
from
.
zone
);
const
p
=
callCardMove
(
target
.
uuid
,
{
fromZone
:
from
.
zone
}
);
// 如果from或者to是手卡,那么需要刷新除了这张卡之外,这个玩家的所有手卡
if
([
from
.
zone
,
to
.
zone
].
includes
(
HAND
))
{
await
Promise
.
all
(
cardStore
.
at
(
HAND
,
target
.
location
.
controller
)
.
filter
((
c
)
=>
c
.
uuid
!==
target
.
uuid
)
.
map
(
async
(
c
)
=>
await
eventbus
.
call
(
Task
.
Move
,
c
.
uuid
))
);
const
pHands
=
cardStore
.
at
(
HAND
,
target
.
location
.
controller
)
.
filter
((
c
)
=>
c
.
uuid
!==
target
.
uuid
)
.
map
(
async
(
c
)
=>
await
callCardMove
(
c
.
uuid
));
await
Promise
.
all
([
p
,
...
pHands
]);
}
else
{
await
p
;
}
// 超量素材位置跟随超量怪兽移动
...
...
@@ -180,7 +181,7 @@ export default async (move: MsgMove) => {
overlay
.
location
.
sequence
=
to
.
sequence
;
overlay
.
location
.
position
=
to
.
position
;
await
eventbus
.
call
(
Task
.
Move
,
overlay
.
uuid
);
await
callCardMove
(
overlay
.
uuid
);
}
}
};
src/service/duel/posChange.ts
View file @
a754c2c2
import
{
ygopro
}
from
"
@/api
"
;
import
MsgPosChange
=
ygopro
.
StocGameMessage
.
MsgPosChange
;
import
{
eventbus
,
Task
}
from
"
@/infra
"
;
import
{
cardStore
,
fetchEsHintMeta
}
from
"
@/stores
"
;
import
{
callCardMove
}
from
"
@/ui/Duel/PlayMat/Card
"
;
export
default
async
(
posChange
:
MsgPosChange
)
=>
{
const
{
location
,
controller
,
sequence
}
=
posChange
.
card_info
;
...
...
@@ -10,7 +11,7 @@ export default async (posChange: MsgPosChange) => {
target
.
location
.
position
=
posChange
.
cur_position
;
// TODO: 暂时用`Move`动画,后续可以单独实现一个改变表示形式的动画
await
eventbus
.
call
(
Task
.
Move
,
target
.
uuid
);
await
callCardMove
(
target
.
uuid
);
}
else
{
console
.
warn
(
`<PosChange>target from
${
posChange
.
card_info
}
is null`
);
}
...
...
src/service/duel/shuffleHandExtra.ts
View file @
a754c2c2
import
{
ygopro
}
from
"
@/api
"
;
import
{
eventbus
,
Task
}
from
"
@/infra
"
;
import
{
cardStore
}
from
"
@/stores
"
;
import
{
callCardMove
}
from
"
@/ui/Duel/PlayMat/Card
"
;
type
MsgShuffleHandExtra
=
ygopro
.
StocGameMessage
.
MsgShuffleHandExtra
;
...
...
@@ -14,27 +14,29 @@ export default async (shuffleHandExtra: MsgShuffleHandExtra) => {
hash
.
get
(
code
)?.
push
(
sequence
);
});
for
(
const
card
of
cards
)
{
const
sequences
=
hash
.
get
(
card
.
code
);
if
(
sequences
!==
undefined
)
{
const
sequence
=
sequences
.
pop
();
if
(
sequence
!==
undefined
)
{
card
.
location
.
sequence
=
sequence
;
hash
.
set
(
card
.
code
,
sequences
);
Promise
.
all
(
cards
.
map
(
async
(
card
)
=>
{
const
sequences
=
hash
.
get
(
card
.
code
);
if
(
sequences
!==
undefined
)
{
const
sequence
=
sequences
.
pop
();
if
(
sequence
!==
undefined
)
{
card
.
location
.
sequence
=
sequence
;
hash
.
set
(
card
.
code
,
sequences
);
// 触发动画
await
eventbus
.
call
(
Task
.
Move
,
card
.
uuid
);
// 触发动画
await
callCardMove
(
card
.
uuid
);
}
else
{
console
.
warn
(
`<ShuffleHandExtra>sequence poped is none, controller=
${
controller
}
, code=
${
card
.
code
}
, sequence=
${
sequence
}
`
);
}
}
else
{
console
.
warn
(
`<ShuffleHandExtra>sequence poped is none, controller=
${
controller
}
, code=
${
card
.
code
}
, sequence=
${
sequence
}
`
`<ShuffleHandExtra>target from records is null, controller=
${
controller
}
, cards=
${
cards
.
map
(
(
card
)
=>
card
.
code
)}
, codes=
${
codes
}
`
);
}
}
else
{
console
.
warn
(
`<ShuffleHandExtra>target from records is null, controller=
${
controller
}
, cards=
${
cards
.
map
(
(
card
)
=>
card
.
code
)}
, codes=
${
codes
}
`
);
}
}
})
);
};
src/service/duel/shuffleSetCard.ts
View file @
a754c2c2
import
{
ygopro
}
from
"
@/api
"
;
import
{
eventbus
,
Task
}
from
"
@/infra
"
;
import
{
cardStore
}
from
"
@/stores
"
;
import
{
callCardMove
}
from
"
@/ui/Duel/PlayMat/Card
"
;
import
MsgShuffleSetCard
=
ygopro
.
StocGameMessage
.
MsgShuffleSetCard
;
// 后端传过来的`from_locations`的列表是切洗前场上卡的location,它们在列表里面按照切洗后的顺序排列
...
...
@@ -18,33 +18,36 @@ export default async (shuffleSetCard: MsgShuffleSetCard) => {
}
const
count
=
from_locations
.
length
;
for
(
let
i
=
0
;
i
<
count
;
i
++
)
{
const
from
=
from_locations
[
i
];
const
target
=
cardStore
.
at
(
from
.
zone
,
from
.
controller
,
from
.
sequence
);
if
(
target
)
{
// 设置code为0,洗切后的code会由`UpdateData`指定
target
.
code
=
0
;
target
.
meta
.
id
=
0
;
target
.
meta
.
text
.
id
=
0
;
}
else
{
console
.
warn
(
`<ShuffleSetCard>target from
${
from
}
is null`
);
}
// 处理超量
const
overlay_location
=
overlay_locations
[
i
];
if
(
overlay_location
.
zone
>
0
)
{
// 如果没有超量素材,后端会全传0
for
(
const
overlay
of
cardStore
.
findOverlay
(
from
.
zone
,
from
.
controller
,
from
.
sequence
))
{
// 更新sequence
overlay
.
location
.
sequence
=
overlay_location
.
sequence
;
// 渲染动画
await
eventbus
.
call
(
Task
.
Move
,
overlay
.
uuid
);
// 这里其实有个疑惑,如果超量素材也跟着洗切的话,洗切的意义好像就没有了,感觉算是个k社没想好的设计?
Promise
.
all
(
Array
.
from
({
length
:
count
}).
map
(
async
(
_
,
i
)
=>
{
const
from
=
from_locations
[
i
];
const
target
=
cardStore
.
at
(
from
.
zone
,
from
.
controller
,
from
.
sequence
);
if
(
target
)
{
// 设置code为0,洗切后的code会由`UpdateData`指定
target
.
code
=
0
;
target
.
meta
.
id
=
0
;
target
.
meta
.
text
.
id
=
0
;
}
else
{
console
.
warn
(
`<ShuffleSetCard>target from
${
from
}
is null`
);
}
}
}
// 处理超量
const
overlay_location
=
overlay_locations
[
i
];
if
(
overlay_location
.
zone
>
0
)
{
// 如果没有超量素材,后端会全传0
for
(
const
overlay
of
cardStore
.
findOverlay
(
from
.
zone
,
from
.
controller
,
from
.
sequence
))
{
// 更新sequence
overlay
.
location
.
sequence
=
overlay_location
.
sequence
;
// 渲染动画
await
callCardMove
(
overlay
.
uuid
);
// 这里其实有个疑惑,如果超量素材也跟着洗切的话,洗切的意义好像就没有了,感觉算是个k社没想好的设计?
}
}
})
);
};
src/service/duel/swapGraveDeck.ts
View file @
a754c2c2
import
{
ygopro
}
from
"
@/api
"
;
import
{
eventbus
,
Task
}
from
"
@/infra
"
;
import
{
cardStore
}
from
"
@/stores
"
;
import
{
callCardMove
}
from
"
@/ui/Duel/PlayMat/Card
"
;
import
MsgSwapGraveDeck
=
ygopro
.
StocGameMessage
.
MsgSwapGraveDeck
;
const
{
DECK
,
GRAVE
}
=
ygopro
.
CardZone
;
...
...
@@ -12,11 +12,11 @@ export default async (swapGraveDeck: MsgSwapGraveDeck) => {
for
(
const
card
of
deck
)
{
card
.
location
.
zone
=
GRAVE
;
await
eventbus
.
call
(
Task
.
Move
,
card
.
uuid
);
await
callCardMove
(
card
.
uuid
);
}
for
(
const
card
of
grave
)
{
card
.
location
.
zone
=
DECK
;
await
eventbus
.
call
(
Task
.
Move
,
card
.
uuid
);
await
callCardMove
(
card
.
uuid
);
}
};
src/service/duel/updateData.ts
View file @
a754c2c2
import
{
fetchCard
,
ygopro
}
from
"
@/api
"
;
import
MsgUpdateData
=
ygopro
.
StocGameMessage
.
MsgUpdateData
;
import
{
eventbus
,
Task
}
from
"
@/infra
"
;
import
{
cardStore
}
from
"
@/stores
"
;
import
{
callCardMove
}
from
"
@/ui/Duel/PlayMat/Card
"
;
import
MsgUpdateData
=
ygopro
.
StocGameMessage
.
MsgUpdateData
;
export
default
async
(
updateData
:
MsgUpdateData
)
=>
{
const
{
player
:
controller
,
zone
,
actions
}
=
updateData
;
if
(
controller
!==
undefined
&&
zone
!==
undefined
&&
actions
!==
undefined
)
{
...
...
@@ -28,7 +27,7 @@ export default async (updateData: MsgUpdateData) => {
// Currently only update position
target
.
location
.
position
=
action
.
location
.
position
;
// animation
await
eventbus
.
call
(
Task
.
Move
,
target
.
uuid
);
await
callCardMove
(
target
.
uuid
);
}
}
if
(
action
?.
type_
>=
0
)
{
...
...
src/ui/Duel/Message/SelectActionsModal/index.tsx
View file @
a754c2c2
...
...
@@ -34,24 +34,11 @@ const defaultProps: Omit<
const
localStore
=
proxy
(
defaultProps
);
export
const
SelectActionsModal
:
React
.
FC
=
()
=>
{
const
{
isOpen
,
isChain
,
min
,
max
,
single
,
selecteds
,
selectables
,
mustSelects
,
cancelable
,
finishable
,
totalLevels
,
overflow
,
}
=
useSnapshot
(
localStore
);
const
snap
=
useSnapshot
(
localStore
);
const
onSubmit
=
(
options
:
Snapshot
<
Option
[]
>
)
=>
{
const
values
=
options
.
map
((
option
)
=>
option
.
response
!
);
if
(
isChain
)
{
if
(
localStore
.
isChain
)
{
sendSelectSingleResponse
(
values
[
0
]);
}
else
{
sendSelectMultiResponse
(
values
);
...
...
@@ -72,17 +59,7 @@ export const SelectActionsModal: React.FC = () => {
return
(
<
SelectCardsModal
{
...
{
isOpen
,
min
,
max
,
single
,
selecteds
,
selectables
,
mustSelects
,
cancelable
,
finishable
,
totalLevels
,
overflow
,
...
snap
,
onSubmit
,
onFinish
,
onCancel
,
...
...
src/ui/Duel/PlayMat/Bg/index.scss
View file @
a754c2c2
...
...
@@ -67,6 +67,54 @@ section#mat {
}
}
}
// 下面应该和moveToOutside、moveToGround对应
.bg-other-blocks
{
&
.op
{
transform
:
rotate
(
180deg
);
}
position
:
absolute
;
--height
:
var
(
--
card-height-o
);
--width
:
calc
(
var
(
--
height
)
*
var
(
--
card-ratio
));
--left
:
calc
(
var
(
--
col-gap
)
*
2
+
var
(
--
block-width
)
*
2
.5
+
var
(
--
block-outside-offset-x
)
+
var
(
--
width
)
/
2
);
--top
:
calc
(
var
(
--
row-gap
)
+
var
(
--
block-height-m
)
+
(
var
(
--
block-height-m
)
-
var
(
--
height
))
/
2
);
.block
{
position
:
absolute
;
transform
:
translate
(
-50%
,
-50%
);
height
:
var
(
--
height
);
width
:
var
(
--
width
);
top
:
var
(
--
top
);
left
:
var
(
--
left
);
}
.field
{
left
:
calc
(
-1
*
var
(
--
left
));
}
.banish
{
top
:
calc
(
var
(
--
top
)
-
var
(
--
row-gap
)
-
var
(
--
height
));
}
.deck
{
--left
:
calc
(
var
(
--
deck-offset-x
)
+
2
*
(
var
(
--
block-width
)
+
var
(
--
col-gap
))
);
left
:
var
(
--
left
);
top
:
calc
(
var
(
--
deck-offset-y
)
+
2
*
var
(
--
block-height-m
)
+
2
*
var
(
--
row-gap
)
);
transform
:
translate
(
-50%
,
-50%
)
rotate
(
calc
(
-1
*
var
(
--
deck-rotate-z
)));
height
:
var
(
--
deck-card-height
);
width
:
calc
(
var
(
--
deck-card-height
)
*
var
(
--
card-ratio
));
&
.extra-deck
{
left
:
calc
(
-1
*
var
(
--
left
));
transform
:
translate
(
-50%
,
-50%
)
rotate
(
var
(
--
deck-rotate-z
));
}
}
}
}
// 被禁用的样式
...
...
@@ -92,8 +140,19 @@ section#mat {
);
display
:
none
;
}
.block.disabled
{
.disabled-cross
{
display
:
block
;
.disabled-cross.show
{
display
:
block
;
}
section
#mat
{
.block.glowing
{
--card-shadow-color
:
#13a1ff
;
box-shadow
:
0
0
3px
3px
var
(
--
card-shadow-color
)
,
0
0
25px
2px
#0099ff
87
;
background
:
var
(
--
card-shadow-color
);
border-radius
:
2px
;
.triangle
{
display
:
none
;
}
}
}
src/ui/Duel/PlayMat/Bg/index.tsx
View file @
a754c2c2
...
...
@@ -5,12 +5,38 @@ import { type INTERNAL_Snapshot as Snapshot, useSnapshot } from "valtio";
import
{
sendSelectPlaceResponse
,
ygopro
}
from
"
@/api
"
;
import
{
BlockState
,
type
BlockState
,
cardStore
,
isMe
,
type
PlaceInteractivity
,
placeStore
,
}
from
"
@/stores
"
;
const
BgBlock
:
React
.
FC
<
React
.
HTMLProps
<
HTMLDivElement
>
&
{
disabled
?:
boolean
;
highlight
?:
boolean
;
glowing
?:
boolean
;
}
>
=
({
disabled
=
false
,
highlight
=
false
,
glowing
=
false
,
className
,
...
rest
})
=>
(
<
div
{
...
rest
}
className=
{
classnames
(
"
block
"
,
className
,
{
highlight
,
glowing
,
})
}
>
{
<
DecoTriangles
/>
}
{
<
DisabledCross
disabled=
{
disabled
}
/>
}
</
div
>
);
const
BgExtraRow
:
React
.
FC
<
{
meSnap
:
Snapshot
<
BlockState
[]
>
;
opSnap
:
Snapshot
<
BlockState
[]
>
;
...
...
@@ -18,60 +44,74 @@ const BgExtraRow: React.FC<{
return
(
<
div
className=
{
classnames
(
"
bg-row
"
)
}
>
{
Array
.
from
({
length
:
2
}).
map
((
_
,
i
)
=>
(
<
div
<
BgBlock
key=
{
i
}
className=
{
classnames
(
"
block
"
,
"
extra
"
,
{
highlight
:
!!
meSnap
[
i
].
interactivity
||
!!
opSnap
[
i
].
interactivity
,
disabled
:
meSnap
[
i
].
disabled
||
opSnap
[
i
].
disabled
,
})
}
className=
"extra"
onClick=
{
()
=>
{
onBlockClick
(
meSnap
[
i
].
interactivity
);
onBlockClick
(
opSnap
[
i
].
interactivity
);
}
}
>
{
<
DecoTriangles
/>
}
{
<
DisabledCross
/>
}
</
div
>
disabled=
{
meSnap
[
i
].
disabled
||
opSnap
[
i
].
disabled
}
highlight=
{
!!
meSnap
[
i
].
interactivity
||
!!
opSnap
[
i
].
interactivity
}
/>
))
}
</
div
>
);
};
const
BgRow
:
React
.
FC
<
{
isS
zone
?:
boolean
;
s
zone
?:
boolean
;
opponent
?:
boolean
;
snap
:
Snapshot
<
BlockState
[]
>
;
}
>
=
({
isS
zone
=
false
,
opponent
=
false
,
snap
})
=>
(
}
>
=
({
s
zone
=
false
,
opponent
=
false
,
snap
})
=>
(
<
div
className=
{
classnames
(
"
bg-row
"
,
{
opponent
})
}
>
{
Array
.
from
({
length
:
5
}).
map
((
_
,
i
)
=>
(
<
div
<
BgBlock
key=
{
i
}
className=
{
classnames
(
"
block
"
,
{
szone
:
isSzone
,
highlight
:
!!
snap
[
i
].
interactivity
,
disabled
:
snap
[
i
].
disabled
,
})
}
className=
{
classnames
({
szone
})
}
onClick=
{
()
=>
onBlockClick
(
snap
[
i
].
interactivity
)
}
>
{
<
DecoTriangles
/>
}
{
<
DisabledCross
/>
}
</
div
>
disabled=
{
snap
[
i
].
disabled
}
highlight=
{
!!
snap
[
i
].
interactivity
}
/>
))
}
</
div
>
);
const
BgOtherBlocks
:
React
.
FC
<
{
me
?:
boolean
}
>
=
({
me
})
=>
{
useSnapshot
(
cardStore
);
const
meController
=
isMe
(
0
)
?
0
:
1
;
const
judgeGlowing
=
(
zone
:
ygopro
.
CardZone
)
=>
!!
cardStore
.
at
(
zone
,
meController
)
.
reduce
((
sum
,
c
)
=>
(
sum
+=
c
.
idleInteractivities
.
length
),
0
);
const
glowingExtra
=
judgeGlowing
(
ygopro
.
CardZone
.
EXTRA
);
const
glowingGraveyard
=
judgeGlowing
(
ygopro
.
CardZone
.
GRAVE
);
const
glowingBanish
=
judgeGlowing
(
ygopro
.
CardZone
.
REMOVED
);
return
(
<
div
className=
{
classnames
(
"
bg-other-blocks
"
,
{
me
,
op
:
!
me
})
}
>
<
BgBlock
className=
"banish"
glowing=
{
me
&&
glowingBanish
}
/>
<
BgBlock
className=
"graveyard"
glowing=
{
me
&&
glowingGraveyard
}
/>
<
BgBlock
className=
"field"
/>
<
BgBlock
className=
"deck"
/>
<
BgBlock
className=
"deck extra-deck"
glowing=
{
me
&&
glowingExtra
}
/>
</
div
>
);
};
export
const
Bg
:
React
.
FC
=
()
=>
{
const
snap
=
useSnapshot
(
placeStore
.
inner
);
return
(
<
div
className=
"mat-bg"
>
<
BgRow
snap=
{
snap
[
ygopro
.
CardZone
.
SZONE
].
op
}
isS
zone
opponent
/>
<
BgRow
snap=
{
snap
[
ygopro
.
CardZone
.
SZONE
].
op
}
s
zone
opponent
/>
<
BgRow
snap=
{
snap
[
ygopro
.
CardZone
.
MZONE
].
op
}
opponent
/>
<
BgExtraRow
meSnap=
{
snap
[
ygopro
.
CardZone
.
MZONE
].
me
.
slice
(
5
,
7
)
}
opSnap=
{
snap
[
ygopro
.
CardZone
.
MZONE
].
op
.
slice
(
5
,
7
)
}
/>
<
BgRow
snap=
{
snap
[
ygopro
.
CardZone
.
MZONE
].
me
}
/>
<
BgRow
snap=
{
snap
[
ygopro
.
CardZone
.
SZONE
].
me
}
isSzone
/>
<
BgRow
snap=
{
snap
[
ygopro
.
CardZone
.
SZONE
].
me
}
szone
/>
<
BgOtherBlocks
me
/>
<
BgOtherBlocks
/>
</
div
>
);
};
...
...
@@ -92,4 +132,6 @@ const DecoTriangles: React.FC = () => (
</>
);
const
DisabledCross
:
React
.
FC
=
()
=>
<
div
className=
"disabled-cross"
></
div
>;
const
DisabledCross
:
React
.
FC
<
{
disabled
:
boolean
}
>
=
({
disabled
})
=>
(
<
div
className=
{
classnames
(
"
disabled-cross
"
,
{
show
:
disabled
})
}
></
div
>
);
src/ui/Duel/PlayMat/Card/index.scss
View file @
a754c2c2
...
...
@@ -106,7 +106,7 @@ section#mat {
}
}
.mat-card.
highlight
.card-shadow
{
.mat-card.
glowing
.card-shadow
{
--card-shadow-color
:
#0099ff
;
display
:
block
!
important
;
background
:
var
(
--
card-shadow-color
)
!
important
;
...
...
@@ -133,3 +133,7 @@ section#mat {
}
text-align
:
center
;
}
.card-dropdown-disabled
{
display
:
none
;
}
src/ui/Duel/PlayMat/Card/index.tsx
View file @
a754c2c2
...
...
@@ -26,23 +26,20 @@ import {
import
{
interactTypeToString
}
from
"
../../utils
"
;
import
{
attack
,
type
AttackOptions
,
focus
,
moveToDeck
,
moveToGround
,
moveToHand
,
moveToOutside
,
moveToToken
,
move
,
type
MoveOptions
,
}
from
"
./springs
"
;
import
type
{
SpringApiProps
}
from
"
./springs/types
"
;
const
{
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
,
MZONE
,
SZONE
,
TZONE
}
=
ygopro
.
CardZone
;
const
{
HAND
,
GRAVE
,
REMOVED
,
EXTRA
,
MZONE
,
SZONE
,
TZONE
}
=
ygopro
.
CardZone
;
export
const
Card
:
React
.
FC
<
{
idx
:
number
}
>
=
React
.
memo
(({
idx
})
=>
{
const
state
=
cardStore
.
inner
[
idx
];
const
snap
=
useSnapshot
(
state
);
const
card
=
cardStore
.
inner
[
idx
];
const
snap
=
useSnapshot
(
card
);
const
[
styles
,
api
]
=
useSpring
(
const
[
styles
,
api
]
=
useSpring
<
SpringApiProps
>
(
()
=>
({
x
:
0
,
...
...
@@ -57,42 +54,17 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
focusDisplay
:
"
none
"
,
focusOpacity
:
1
,
subZ
:
0
,
opacity
:
1
,
}
satisfies
SpringApiProps
)
);
// FIXME: move不应该只根据目的地判断,还要根据先前的位置判断。例子是Token。
const
move
=
async
(
toZone
:
ygopro
.
CardZone
,
fromZone
?:
ygopro
.
CardZone
)
=>
{
switch
(
toZone
)
{
case
MZONE
:
case
SZONE
:
await
moveToGround
({
card
:
state
,
api
,
fromZone
});
break
;
case
HAND
:
await
moveToHand
({
card
:
state
,
api
,
fromZone
});
break
;
case
DECK
:
case
EXTRA
:
await
moveToDeck
({
card
:
state
,
api
,
fromZone
});
break
;
case
GRAVE
:
case
REMOVED
:
await
moveToOutside
({
card
:
state
,
api
,
fromZone
});
break
;
case
TZONE
:
// FIXME: 这里应该实现一个衍生物消散的动画,现在暂时让它在动画在展示上回到卡组
await
moveToToken
({
card
:
state
,
api
,
fromZone
});
break
;
}
};
// 每张卡都需要移动到初始位置
useEffect
(()
=>
{
move
(
state
.
location
.
zone
);
addToAnimation
(()
=>
move
({
card
,
api
})
);
},
[]);
const
[
highlight
,
setHighlight
]
=
useState
(
false
);
const
[
glowing
,
setGrowing
]
=
useState
(
false
);
const
[
classFocus
,
setClassFocus
]
=
useState
(
false
);
// const [shadowOpacity, setShadowOpacity] = useState(0); // TODO: 透明度
// >>> 动画 >>>
/** 动画序列的promise */
...
...
@@ -103,40 +75,32 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
animationQueue
.
current
=
animationQueue
.
current
.
then
(
p
).
then
(
rs
);
});
const
register
=
<
T
extends
any
[]
>
(
task: Task,
fn: (...args: T) =
>
Promise
<
unknown
>
) =
>
{
eventbus
.
register
(
task
,
async
(
uuid
,
...
rest
:
T
)
=>
{
if
(
uuid
===
card
.
uuid
)
{
await
fn
(...
rest
);
return
true
;
}
else
return
false
;
});
}
;
useEffect(() =
>
{
eventbus
.
register
(
Task
.
Move
,
async
(
uuid
:
string
,
fromZone
?:
ygopro
.
CardZone
)
=>
{
if
(
uuid
===
state
.
uuid
)
{
await
addToAnimation
(()
=>
move
(
state
.
location
.
zone
,
fromZone
));
}
}
);
register
(
Task
.
Move
,
async
(
options
?:
MoveOptions
)
=>
{
await
addToAnimation
(()
=>
move
({
card
,
api
,
options
}));
});
eventbus
.
register
(
Task
.
Focus
,
async
(
uuid
:
string
)
=>
{
if
(
uuid
===
state
.
uuid
)
{
await
addToAnimation
(
async
()
=>
{
setClassFocus
(
true
);
setTimeout
(()
=>
setClassFocus
(
false
),
1000
);
await
focus
({
card
:
state
,
api
});
});
}
register
(
Task
.
Focus
,
async
()
=>
{
setClassFocus
(
true
);
setTimeout
(()
=>
setClassFocus
(
false
),
1000
);
// TODO: 这儿为啥要这么写呢
await
focus
({
card
,
api
});
});
eventbus
.
register
(
Task
.
Attack
,
async
(
uuid
:
string
,
directAttack
:
boolean
,
target
?:
ygopro
.
CardLocation
)
=>
{
if
(
uuid
===
state
.
uuid
)
{
await
addToAnimation
(()
=>
attack
({
card
:
state
,
api
,
target
,
directAttack
})
);
}
}
);
register
(
Task
.
Attack
,
async
(
options
:
AttackOptions
)
=>
{
await
addToAnimation
(()
=>
attack
({
card
,
api
,
options
}));
});
}
, []);
//
<
<<
动画
<<<
...
...
@@ -144,13 +108,19 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
// >>> 效果 >>>
const
idleInteractivities
=
snap
.
idleInteractivities
;
useEffect
(()
=
>
{
setHighlight
(
!!
idleInteractivities
.
length
);
setGrowing
(
!!
idleInteractivities
.
length
&&
[
MZONE
,
SZONE
,
HAND
,
TZONE
].
includes
(
card
.
location
.
zone
)
);
}
, [idleInteractivities]);
const [dropdownMenu, setDropdownMenu] = useState(
{
items
:
[]
as
DropdownItem
[],
}
);
// 是否禁用下拉菜单
const [dropdownMenuDisabled, setDropdownMenuDisabled] = useState(false);
// 发动效果
// 1. 下拉菜单里面选择[召唤 / 特殊召唤 /.../效果发动]
// 2. 如果是非效果发动,那么直接选择哪张卡(单张卡直接选择那张)
...
...
@@ -165,6 +135,13 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
map
.
get
(
interactType
)?.
push
(
card
);
});
});
if
(
!
map
.
size
)
{
setDropdownMenuDisabled
(
true
);
return
;
}
else
{
setDropdownMenuDisabled
(
false
);
}
const
actions
=
[...
map
.
entries
()];
const
nonEffectActions
=
actions
.
filter
(
([
action
])
=>
action
!==
InteractType
.
ACTIVATE
...
...
@@ -259,7 +236,7 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
// 中央弹窗展示选中卡牌信息
// TODO: 同一张卡片,是否重复点击会关闭CardModal?
displayCardModal
(
card
);
if
(
card
.
idleInteractivities
.
length
)
handleDropdownMenu
([
card
],
false
);
handleDropdownMenu
([
card
],
false
);
// 侧边栏展示超量素材信息
const
overlayMaterials
=
cardStore
.
findOverlay
(
...
...
@@ -286,17 +263,17 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
handleDropdownMenu
(
cards
,
true
);
};
if
([
MZONE
,
SZONE
,
HAND
].
includes
(
state
.
location
.
zone
))
{
onCardClick
(
state
);
}
else
if
([
EXTRA
,
GRAVE
,
REMOVED
].
includes
(
state
.
location
.
zone
))
{
onFieldClick
(
state
);
if
([
MZONE
,
SZONE
,
HAND
].
includes
(
card
.
location
.
zone
))
{
onCardClick
(
card
);
}
else
if
([
EXTRA
,
GRAVE
,
REMOVED
].
includes
(
card
.
location
.
zone
))
{
onFieldClick
(
card
);
}
}
;
//
<
<<
效果
<<<
return
(
<
animated
.
div
className=
{
classnames
(
"
mat-card
"
,
{
highlight
})
}
className=
{
classnames
(
"
mat-card
"
,
{
glowing
})
}
style=
{
{
transform
:
to
(
...
...
@@ -312,6 +289,7 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
"
--focus-scale
"
:
styles
.
focusScale
,
"
--focus-display
"
:
styles
.
focusDisplay
,
"
--focus-opacity
"
:
styles
.
focusOpacity
,
opacity
:
styles
.
opacity
,
}
as
any
as
CSSProperties
}
onClick=
{
onClick
}
...
...
@@ -321,10 +299,11 @@ export const Card: React.FC<{ idx: number }> = React.memo(({ idx }) => {
<
Dropdown
menu=
{
dropdownMenu
}
placement=
"top"
overlayClassName=
"card-dropdown"
overlayClassName=
{
classnames
(
"
card-dropdown
"
,
{
"
card-dropdown-disabled
"
:
dropdownMenuDisabled
,
})
}
arrow
trigger=
{
[
"
click
"
]
}
// disabled={!highlight} // TODO: 这里的disable要考虑到field的情况,比如额外卡组
>
<
div
className=
{
classnames
(
"
card-img-wrap
"
,
{
focus
:
classFocus
})
}
>
<
YgoCard
...
...
@@ -375,3 +354,12 @@ const handleEffectActivation = (
}
;
//
<
<<
下拉菜单
<<<
const
call
=
<
Options
,
>
(task: Task) =
>
(uuid: string, options?: Options extends
{}
? Options : never) =
>
eventbus.call(task, uuid, options);
export const callCardMove = call
<
MoveOptions
>
(Task.Move);
export const callCardFocus = call(Task.Focus);
export const callCardAttack = call
<
AttackOptions
>
(Task.Attack);
src/ui/Duel/PlayMat/Card/springs/attack.ts
View file @
a754c2c2
// 暂时先简单实现攻击动画,后面有时间再慢慢优化
import
{
easings
}
from
"
@react-spring/web
"
;
import
{
ygopro
}
from
"
@/api
"
;
import
{
CardType
,
isMe
}
from
"
@/stores
"
;
import
{
isMe
}
from
"
@/stores
"
;
import
{
matConfig
}
from
"
@/ui/Shared
"
;
import
{
matConfig
}
from
"
../../utils
"
;
import
type
{
SpringApi
}
from
"
./types
"
;
import
type
{
AttackFunc
}
from
"
./types
"
;
import
{
asyncStart
}
from
"
./utils
"
;
const
{
BLOCK_WIDTH
,
BLOCK_HEIGHT_M
,
BLOCK_HEIGHT_S
,
COL_GAP
,
ROW_GAP
}
=
matConfig
;
export
const
attack
=
async
(
props
:
{
card
:
CardType
;
api
:
SpringApi
;
directAttack
:
boolean
;
target
?:
ygopro
.
CardLocation
;
})
=>
{
const
{
card
,
api
,
directAttack
,
target
}
=
props
;
export
const
attack
:
AttackFunc
=
async
(
props
)
=>
{
const
{
card
,
api
,
options
}
=
props
;
const
current
=
api
.
current
[
0
].
get
();
let
x
=
current
.
x
;
let
y
=
current
.
y
;
let
rz
=
current
.
rz
;
if
(
directAttack
)
{
if
(
options
?.
directAttack
)
{
// 直接攻击
y
=
BLOCK_HEIGHT_M
.
value
+
BLOCK_HEIGHT_S
.
value
;
y
=
BLOCK_HEIGHT_M
+
BLOCK_HEIGHT_S
;
if
(
isMe
(
card
.
location
.
controller
))
{
y
=
-
y
;
}
}
else
if
(
target
)
{
}
else
if
(
options
?.
target
)
{
// 攻击`target`
const
{
controller
,
sequence
}
=
target
;
const
{
controller
,
sequence
}
=
options
.
target
;
if
(
sequence
>
4
)
{
// 额外怪兽区
x
=
(
sequence
>
5
?
1
:
-
1
)
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
x
=
(
sequence
>
5
?
1
:
-
1
)
*
(
BLOCK_WIDTH
+
COL_GAP
);
y
=
0
;
}
else
{
x
=
(
sequence
-
2
)
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
y
=
BLOCK_HEIGHT_M
.
value
+
ROW_GAP
.
value
;
x
=
(
sequence
-
2
)
*
(
BLOCK_WIDTH
+
COL_GAP
);
y
=
BLOCK_HEIGHT_M
+
ROW_GAP
;
}
if
(
!
isMe
(
controller
))
{
...
...
@@ -61,7 +55,7 @@ export const attack = async (props: {
await
asyncStart
(
api
)({
y
:
current
.
y
+
(
BLOCK_HEIGHT_M
.
value
/
2
)
*
(
isMe
(
card
.
location
.
controller
)
?
1
:
-
1
),
(
BLOCK_HEIGHT_M
/
2
)
*
(
isMe
(
card
.
location
.
controller
)
?
1
:
-
1
),
rz
,
});
// 加速前冲
...
...
src/ui/Duel/PlayMat/Card/springs/focus.ts
View file @
a754c2c2
...
...
@@ -11,19 +11,14 @@ export const focus = async (props: { card: CardType; api: SpringApi }) => {
card
.
location
.
zone
==
ygopro
.
CardZone
.
HAND
||
card
.
location
.
zone
==
ygopro
.
CardZone
.
DECK
)
{
const
current
=
api
.
current
[
0
].
get
()
;
const
current
=
{
...
api
.
current
[
0
].
get
()
}
;
await
asyncStart
(
api
)({
y
:
current
.
y
+
(
matStore
.
isMe
(
card
.
location
.
controller
)
?
-
1
:
1
)
*
120
,
// TODO: 放到config之中
ry
:
0
,
rz
:
0
,
//
rz: 0,
z
:
current
.
z
+
50
,
});
await
asyncStart
(
api
)({
y
:
current
.
y
,
ry
:
current
.
ry
,
rz
:
current
.
rz
,
z
:
current
.
z
,
});
await
asyncStart
(
api
)(
current
);
}
else
{
await
asyncStart
(
api
)({
focusScale
:
1.5
,
...
...
src/ui/Duel/PlayMat/Card/springs/index.ts
View file @
a754c2c2
export
*
from
"
./attack
"
;
export
*
from
"
./focus
"
;
export
*
from
"
./moveToDeck
"
;
export
*
from
"
./moveToGround
"
;
export
*
from
"
./moveToHand
"
;
export
*
from
"
./moveToOutside
"
;
export
*
from
"
./moveToToken
"
;
export
*
from
"
./move
"
;
export
*
from
"
./types
"
;
export
*
from
"
./utils
"
;
src/ui/Duel/PlayMat/Card/springs/move.ts
0 → 100644
View file @
a754c2c2
import
{
ygopro
}
from
"
@/api
"
;
import
{
moveToDeck
}
from
"
./moveToDeck
"
;
import
{
moveToGround
}
from
"
./moveToGround
"
;
import
{
moveToHand
}
from
"
./moveToHand
"
;
import
{
moveToOutside
}
from
"
./moveToOutside
"
;
import
{
moveToToken
}
from
"
./moveToToken
"
;
import
type
{
MoveFunc
}
from
"
./types
"
;
const
{
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
,
MZONE
,
SZONE
,
TZONE
}
=
ygopro
.
CardZone
;
export
const
move
:
MoveFunc
=
async
(
props
)
=>
{
const
{
card
}
=
props
;
switch
(
card
.
location
.
zone
)
{
case
MZONE
:
case
SZONE
:
await
moveToGround
(
props
);
break
;
case
HAND
:
await
moveToHand
(
props
);
break
;
case
DECK
:
case
EXTRA
:
await
moveToDeck
(
props
);
break
;
case
GRAVE
:
case
REMOVED
:
await
moveToOutside
(
props
);
break
;
case
TZONE
:
await
moveToToken
(
props
);
break
;
}
};
src/ui/Duel/PlayMat/Card/springs/moveToDeck.ts
View file @
a754c2c2
import
{
ygopro
}
from
"
@/api
"
;
import
{
isMe
}
from
"
@/stores
"
;
import
{
matConfig
}
from
"
@/ui/Shared
"
;
import
{
matConfig
}
from
"
../../util
s
"
;
import
{
asyncStart
,
type
MoveFunc
}
from
"
./utils
"
;
import
type
{
MoveFunc
}
from
"
./type
s
"
;
import
{
asyncStart
}
from
"
./utils
"
;
const
{
BLOCK_WIDTH
,
BLOCK_HEIGHT_M
,
BLOCK_HEIGHT_S
,
COL_GAP
,
ROW_GAP
,
DECK_OFFSET_X
,
...
...
@@ -24,21 +24,16 @@ export const moveToDeck: MoveFunc = async (props) => {
const
{
location
}
=
card
;
const
{
controller
,
zone
,
sequence
}
=
location
;
const
rightX
=
DECK_OFFSET_X
.
value
+
2
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
const
rightX
=
DECK_OFFSET_X
+
2
*
(
BLOCK_WIDTH
+
COL_GAP
);
const
leftX
=
-
rightX
;
const
bottomY
=
DECK_OFFSET_Y
.
value
+
2
*
BLOCK_HEIGHT_M
.
value
+
BLOCK_HEIGHT_S
.
value
+
2
*
ROW_GAP
.
value
-
BLOCK_HEIGHT_S
.
value
;
const
bottomY
=
DECK_OFFSET_Y
+
2
*
BLOCK_HEIGHT_M
+
2
*
ROW_GAP
;
const
topY
=
-
bottomY
;
let
x
=
isMe
(
controller
)
?
rightX
:
leftX
;
let
y
=
isMe
(
controller
)
?
bottomY
:
topY
;
if
(
zone
===
EXTRA
)
{
x
=
isMe
(
controller
)
?
leftX
:
rightX
;
}
let
rz
=
zone
===
EXTRA
?
DECK_ROTATE_Z
.
value
:
-
DECK_ROTATE_Z
.
value
;
let
rz
=
zone
===
EXTRA
?
DECK_ROTATE_Z
:
-
DECK_ROTATE_Z
;
rz
+=
isMe
(
controller
)
?
0
:
180
;
const
z
=
sequence
;
...
...
@@ -49,6 +44,6 @@ export const moveToDeck: MoveFunc = async (props) => {
rz
,
ry
:
isMe
(
controller
)
?
(
zone
===
DECK
?
180
:
0
)
:
180
,
zIndex
:
z
,
height
:
DECK_CARD_HEIGHT
.
value
,
height
:
DECK_CARD_HEIGHT
,
});
};
src/ui/Duel/PlayMat/Card/springs/moveToGround.ts
View file @
a754c2c2
...
...
@@ -2,9 +2,10 @@ import { easings } from "@react-spring/web";
import
{
ygopro
}
from
"
@/api
"
;
import
{
isMe
}
from
"
@/stores
"
;
import
{
matConfig
}
from
"
@/ui/Shared
"
;
import
{
matConfig
}
from
"
../../util
s
"
;
import
{
asyncStart
,
type
MoveFunc
}
from
"
./utils
"
;
import
type
{
MoveFunc
}
from
"
./type
s
"
;
import
{
asyncStart
}
from
"
./utils
"
;
const
{
BLOCK_WIDTH
,
...
...
@@ -13,24 +14,20 @@ const {
CARD_RATIO
,
COL_GAP
,
ROW_GAP
,
BLOCK_OUTSIDE_OFFSET_X
,
CARD_HEIGHT_O
,
}
=
matConfig
;
const
{
MZONE
,
SZONE
,
TZONE
}
=
ygopro
.
CardZone
;
export
const
moveToGround
:
MoveFunc
=
async
(
props
)
=>
{
const
{
card
,
api
,
fromZone
}
=
props
;
const
{
card
,
api
,
options
}
=
props
;
const
{
location
}
=
card
;
const
{
controller
,
zone
,
sequence
,
position
,
is_overlay
}
=
location
;
// 根据zone计算卡片的宽度
const
cardWidth
=
zone
===
SZONE
?
BLOCK_HEIGHT_S
.
value
*
CARD_RATIO
.
value
:
BLOCK_HEIGHT_M
.
value
*
CARD_RATIO
.
value
;
let
height
=
zone
===
SZONE
?
BLOCK_HEIGHT_S
.
value
:
BLOCK_HEIGHT_M
.
value
;
let
height
=
zone
===
SZONE
?
BLOCK_HEIGHT_S
:
BLOCK_HEIGHT_M
;
// 首先计算 x 和 y
let
x
=
0
,
...
...
@@ -38,28 +35,31 @@ export const moveToGround: MoveFunc = async (props) => {
switch
(
zone
)
{
case
SZONE
:
{
if
(
sequence
===
5
)
{
height
=
CARD_HEIGHT_O
;
// 场地魔法
x
=
-
(
3
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
)
-
(
BLOCK_WIDTH
.
value
-
cardWidth
)
/
2
BLOCK_WIDTH
*
2.5
+
COL_GAP
*
2
+
BLOCK_OUTSIDE_OFFSET_X
+
CARD_HEIGHT_O
*
CARD_RATIO
*
0.5
);
y
=
BLOCK_HEIGHT_M
.
value
+
ROW_GAP
.
value
;
y
=
ROW_GAP
+
BLOCK_HEIGHT_M
+
(
BLOCK_HEIGHT_M
-
CARD_HEIGHT_O
)
/
2
;
}
else
{
x
=
(
sequence
-
2
)
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
x
=
(
sequence
-
2
)
*
(
BLOCK_WIDTH
+
COL_GAP
);
y
=
2
*
(
BLOCK_HEIGHT_M
.
value
+
ROW_GAP
.
value
)
-
(
BLOCK_HEIGHT_M
.
value
-
BLOCK_HEIGHT_S
.
value
)
/
2
;
2
*
(
BLOCK_HEIGHT_M
+
ROW_GAP
)
-
(
BLOCK_HEIGHT_M
-
BLOCK_HEIGHT_S
)
/
2
;
}
break
;
}
case
MZONE
:
{
if
(
sequence
>
4
)
{
// 额外怪兽区
x
=
(
sequence
>
5
?
1
:
-
1
)
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
x
=
(
sequence
>
5
?
1
:
-
1
)
*
(
BLOCK_WIDTH
+
COL_GAP
);
y
=
0
;
}
else
{
x
=
(
sequence
-
2
)
*
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
);
y
=
BLOCK_HEIGHT_M
.
value
+
ROW_GAP
.
value
;
x
=
(
sequence
-
2
)
*
(
BLOCK_WIDTH
+
COL_GAP
);
y
=
BLOCK_HEIGHT_M
+
ROW_GAP
;
}
break
;
}
...
...
@@ -76,9 +76,8 @@ export const moveToGround: MoveFunc = async (props) => {
ygopro
.
CardPosition
.
FACEDOWN_DEFENSE
,
ygopro
.
CardPosition
.
FACEUP_DEFENSE
,
].
includes
(
position
??
5
);
height
=
defence
?
BLOCK_WIDTH
.
value
:
height
;
let
rz
=
isMe
(
controller
)
?
0
:
180
;
rz
+=
defence
?
90
:
0
;
height
=
defence
?
BLOCK_WIDTH
:
height
;
const
rz
=
(
isMe
(
controller
)
?
0
:
180
)
+
(
defence
?
90
:
0
);
const
ry
=
[
ygopro
.
CardPosition
.
FACEDOWN
,
...
...
@@ -89,7 +88,8 @@ export const moveToGround: MoveFunc = async (props) => {
:
0
;
// 动画
if
(
fromZone
===
TZONE
)
{
const
isToken
=
options
?.
fromZone
===
TZONE
;
if
(
isToken
)
{
// 如果是Token,直接先移动到那个位置,然后再放大
api
.
set
({
x
,
...
...
@@ -107,8 +107,9 @@ export const moveToGround: MoveFunc = async (props) => {
ry
,
rz
,
config
:
{
// mass: 0.5,
easing
:
easings
.
easeInOutSine
,
tension
:
250
,
clamp
:
true
,
easing
:
easings
.
easeOutSine
,
},
});
}
...
...
@@ -116,13 +117,12 @@ export const moveToGround: MoveFunc = async (props) => {
await
asyncStart
(
api
)({
height
,
z
:
0
,
subZ
:
isToken
?
100
:
0
,
zIndex
:
is_overlay
?
1
:
3
,
config
:
{
easing
:
easings
.
easeInOutQuad
,
mass
:
5
,
tension
:
300
,
// 170
friction
:
12
,
// 26
easing
:
easings
.
easeInQuad
,
clamp
:
true
,
},
});
if
(
isToken
)
api
.
set
({
subZ
:
0
});
};
src/ui/Duel/PlayMat/Card/springs/moveToHand.ts
View file @
a754c2c2
import
{
ygopro
}
from
"
@/api
"
;
import
{
cardStore
,
isMe
}
from
"
@/stores
"
;
import
{
matConfig
}
from
"
@/ui/Shared
"
;
import
{
matConfig
}
from
"
../../util
s
"
;
import
{
asyncStart
,
type
MoveFunc
}
from
"
./utils
"
;
import
type
{
MoveFunc
}
from
"
./type
s
"
;
import
{
asyncStart
}
from
"
./utils
"
;
const
{
BLOCK_HEIGHT_M
,
...
...
@@ -22,27 +23,23 @@ export const moveToHand: MoveFunc = async (props) => {
// 手卡会有很复杂的计算...
const
hand_circle_center_x
=
0
;
const
hand_circle_center_y
=
1
*
BLOCK_HEIGHT_M
.
value
+
1
*
BLOCK_HEIGHT_S
.
value
+
2
*
ROW_GAP
.
value
+
(
HAND_MARGIN_TOP
.
value
+
HAND_CARD_HEIGHT
.
value
+
HAND_CIRCLE_CENTER_OFFSET_Y
.
value
);
const
hand_card_width
=
CARD_RATIO
.
value
*
HAND_CARD_HEIGHT
.
value
;
BLOCK_HEIGHT_M
+
BLOCK_HEIGHT_S
+
2
*
ROW_GAP
+
(
HAND_MARGIN_TOP
+
HAND_CARD_HEIGHT
+
HAND_CIRCLE_CENTER_OFFSET_Y
);
const
hand_card_width
=
CARD_RATIO
*
HAND_CARD_HEIGHT
;
const
THETA
=
2
*
Math
.
atan
(
hand_card_width
/
2
/
(
HAND_CIRCLE_CENTER_OFFSET_Y
.
value
+
HAND_CARD_HEIGHT
.
value
)
hand_card_width
/
2
/
(
HAND_CIRCLE_CENTER_OFFSET_Y
+
HAND_CARD_HEIGHT
)
)
*
0.9
;
// 接下来计算每一张手卡
const
hands_length
=
cardStore
.
at
(
HAND
,
controller
).
length
;
const
angle
=
(
sequence
-
(
hands_length
-
1
)
/
2
)
*
THETA
;
const
r
=
HAND_CIRCLE_CENTER_OFFSET_Y
.
value
+
HAND_CARD_HEIGHT
.
value
/
2
;
const
r
=
HAND_CIRCLE_CENTER_OFFSET_Y
+
HAND_CARD_HEIGHT
/
2
;
const
negativeX
=
Math
.
sin
(
angle
)
*
r
;
const
negativeY
=
Math
.
cos
(
angle
)
*
r
+
HAND_CARD_HEIGHT
.
value
/
2
;
const
negativeY
=
Math
.
cos
(
angle
)
*
r
+
HAND_CARD_HEIGHT
/
2
;
const
x
=
hand_circle_center_x
+
negativeX
*
(
isMe
(
controller
)
?
1
:
-
1
);
const
y
=
hand_circle_center_y
-
negativeY
+
140
;
// FIXME: 常量 是手动调的 这里肯定有问题 有空来修
...
...
@@ -54,8 +51,8 @@ export const moveToHand: MoveFunc = async (props) => {
z
:
sequence
+
5
,
rz
:
isMe
(
controller
)
?
_rz
:
180
-
_rz
,
ry
:
isMe
(
controller
)
?
0
:
180
,
height
:
HAND_CARD_HEIGHT
.
value
,
height
:
HAND_CARD_HEIGHT
,
zIndex
:
sequence
,
// rx: -PLANE_ROTATE_X
.value
,
// rx: -PLANE_ROTATE_X,
});
};
src/ui/Duel/PlayMat/Card/springs/moveToOutside.ts
View file @
a754c2c2
import
{
ygopro
}
from
"
@/api
"
;
import
{
isMe
}
from
"
@/stores
"
;
import
{
matConfig
}
from
"
@/ui/Shared
"
;
import
{
matConfig
}
from
"
../../util
s
"
;
import
{
asyncStart
,
type
MoveFunc
}
from
"
./utils
"
;
import
type
{
MoveFunc
}
from
"
./type
s
"
;
import
{
asyncStart
}
from
"
./utils
"
;
const
{
BLOCK_WIDTH
,
BLOCK_HEIGHT_M
,
BLOCK_HEIGHT_S
,
COL_GAP
,
ROW_GAP
}
=
matConfig
;
const
{
BLOCK_WIDTH
,
BLOCK_HEIGHT_M
,
COL_GAP
,
ROW_GAP
,
CARD_HEIGHT_O
,
BLOCK_OUTSIDE_OFFSET_X
,
CARD_RATIO
,
}
=
matConfig
;
const
{
GRAVE
}
=
ygopro
.
CardZone
;
const
{
REMOVED
}
=
ygopro
.
CardZone
;
export
const
moveToOutside
:
MoveFunc
=
async
(
props
)
=>
{
const
{
card
,
api
}
=
props
;
// report
const
{
zone
,
controller
,
position
,
sequence
}
=
card
.
location
;
let
x
=
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
)
*
3
,
y
=
zone
===
GRAVE
?
BLOCK_HEIGHT_M
.
value
+
ROW_GAP
.
value
:
0
;
let
x
=
BLOCK_WIDTH
*
2.5
+
COL_GAP
*
2
+
BLOCK_OUTSIDE_OFFSET_X
+
CARD_HEIGHT_O
*
CARD_RATIO
*
0.5
,
y
=
ROW_GAP
+
BLOCK_HEIGHT_M
+
(
BLOCK_HEIGHT_M
-
CARD_HEIGHT_O
)
/
2
;
if
(
zone
===
REMOVED
)
y
-=
ROW_GAP
+
CARD_HEIGHT_O
;
if
(
!
isMe
(
controller
))
{
x
=
-
x
;
y
=
-
y
;
...
...
@@ -24,11 +36,14 @@ export const moveToOutside: MoveFunc = async (props) => {
x
,
y
,
z
:
0
,
height
:
BLOCK_HEIGHT_S
.
value
,
height
:
CARD_HEIGHT_O
,
rz
:
isMe
(
controller
)
?
0
:
180
,
ry
:
[
ygopro
.
CardPosition
.
FACEDOWN
].
includes
(
position
)
?
180
:
0
,
subZ
:
100
,
zIndex
:
sequence
,
config
:
{
tension
:
140
,
},
});
api
.
set
({
subZ
:
0
});
};
src/ui/Duel/PlayMat/Card/springs/moveToToken.ts
View file @
a754c2c2
import
{
asyncStart
,
type
MoveFunc
}
from
"
./utils
"
;
import
type
{
MoveFunc
}
from
"
./types
"
;
import
{
asyncStart
}
from
"
./utils
"
;
export
const
moveToToken
:
MoveFunc
=
async
(
props
)
=>
{
const
{
api
}
=
props
;
await
asyncStart
(
api
)({
height
:
0
,
opacity
:
0
,
});
api
.
set
({
opacity
:
1
});
};
src/ui/Duel/PlayMat/Card/springs/types.ts
View file @
a754c2c2
import
{
type
SpringRef
}
from
"
@react-spring/web
"
;
import
type
{
SpringRef
}
from
"
@react-spring/web
"
;
import
type
{
ygopro
}
from
"
@/api
"
;
import
type
{
CardType
}
from
"
@/stores
"
;
export
interface
SpringApiProps
{
x
:
number
;
...
...
@@ -9,6 +12,7 @@ export interface SpringApiProps {
rz
:
number
;
zIndex
:
number
;
height
:
number
;
opacity
:
number
;
// >>> focus
focusScale
:
number
;
focusDisplay
:
string
;
...
...
@@ -19,3 +23,21 @@ export interface SpringApiProps {
}
export
type
SpringApi
=
SpringRef
<
SpringApiProps
>
;
type
OptionsToFunc
<
Options
>
=
(
props
:
{
card
:
CardType
;
api
:
SpringApi
;
options
?:
Options
;
})
=>
Promise
<
void
>
;
export
interface
MoveOptions
{
fromZone
?:
ygopro
.
CardZone
;
}
export
type
MoveFunc
=
OptionsToFunc
<
MoveOptions
>
;
export
type
AttackOptions
=
|
{
directAttack
:
true
;
}
|
{
directAttack
:
false
;
target
:
ygopro
.
CardLocation
};
export
type
AttackFunc
=
OptionsToFunc
<
AttackOptions
>
;
src/ui/Duel/PlayMat/Card/springs/utils.ts
View file @
a754c2c2
import
{
type
SpringConfig
,
type
SpringRef
}
from
"
@react-spring/web
"
;
import
type
{
ygopro
}
from
"
@/api
"
;
import
{
type
CardType
}
from
"
@/stores
"
;
import
type
{
SpringApi
}
from
"
./types
"
;
export
const
asyncStart
=
<
T
extends
{}
>
(
api
:
SpringRef
<
T
>
)
=>
{
return
(
p
:
Partial
<
T
>
&
{
config
?:
SpringConfig
})
=>
new
Promise
((
resolve
)
=>
{
...
...
@@ -14,9 +9,3 @@ export const asyncStart = <T extends {}>(api: SpringRef<T>) => {
});
});
};
export
type
MoveFunc
=
(
props
:
{
card
:
CardType
;
api
:
SpringApi
;
fromZone
?:
ygopro
.
CardZone
;
})
=>
Promise
<
void
>
;
src/ui/Duel/PlayMat/LifeBar/index.scss
View file @
a754c2c2
...
...
@@ -54,25 +54,3 @@
min-width
:
3
.25em
;
}
}
.floodlight
{
position
:
absolute
;
height
:
100%
;
width
:
40px
;
background-color
:
#aaa
;
top
:
0
;
right
:
0
;
filter
:
blur
(
30px
);
transform
:
skewX
(
-20deg
);
}
.floodlight-run
{
animation
:
floodlight
4s
linear
infinite
;
}
@keyframes
floodlight
{
0
%
{
right
:
-80px
;
}
100
%
{
right
:
calc
(
100%
+
80px
);
}
}
src/ui/Duel/PlayMat/LifeBar/index.tsx
View file @
a754c2c2
...
...
@@ -115,7 +115,6 @@ const LifeBarItem: React.FC<{
size=
{
14
}
/>
<
div
className=
"timer-text"
>
{
timeText
}
</
div
>
<
div
className=
"floodlight floodlight-run"
/>
</
div
>
)
}
</
div
>
...
...
src/ui/Duel/PlayMat/utils/cssConfig.ts
deleted
100644 → 0
View file @
53a9bdf4
// type CSSValue = [number, string] | number;
export
type
CSSConfig
=
Record
<
string
,
{
value
:
number
;
unit
:
UNIT
}
>
;
/** 转为CSS变量: BOARD_ROTATE_Z -> --board-rotate-z */
export
const
toCssProperties
=
(
config
:
CSSConfig
)
=>
Object
.
entries
(
config
)
.
map
(
([
k
,
v
])
=>
[
`--
${
k
.
split
(
"
_
"
)
.
map
((
s
)
=>
s
.
toLowerCase
())
.
join
(
"
-
"
)}
`
,
`
${
v
.
value
}${
v
.
unit
}
`
,
]
as
[
string
,
string
]
)
.
reduce
((
acc
,
cur
)
=>
[...
acc
,
cur
],
[]
as
[
string
,
string
][]);
enum
UNIT
{
PX
=
"
px
"
,
DEG
=
"
deg
"
,
NONE
=
""
,
}
export
const
matConfig
=
{
PERSPECTIVE
:
{
value
:
1500
,
unit
:
UNIT
.
PX
,
},
PLANE_ROTATE_X
:
{
value
:
0
,
unit
:
UNIT
.
DEG
,
},
BLOCK_WIDTH
:
{
value
:
120
,
unit
:
UNIT
.
PX
,
},
BLOCK_HEIGHT_M
:
{
value
:
120
,
unit
:
UNIT
.
PX
,
},
// 主要怪兽区
BLOCK_HEIGHT_S
:
{
value
:
110
,
unit
:
UNIT
.
PX
,
},
// 魔法陷阱区
ROW_GAP
:
{
value
:
10
,
unit
:
UNIT
.
PX
,
},
COL_GAP
:
{
value
:
10
,
unit
:
UNIT
.
PX
,
},
CARD_RATIO
:
{
value
:
5.9
/
8.6
,
unit
:
UNIT
.
NONE
,
},
HAND_MARGIN_TOP
:
{
value
:
0
,
unit
:
UNIT
.
PX
,
},
HAND_CIRCLE_CENTER_OFFSET_Y
:
{
value
:
2000
,
unit
:
UNIT
.
PX
,
},
HAND_CARD_HEIGHT
:
{
value
:
130
,
unit
:
UNIT
.
PX
,
},
DECK_OFFSET_X
:
{
value
:
140
,
unit
:
UNIT
.
PX
,
},
DECK_OFFSET_Y
:
{
value
:
80
,
unit
:
UNIT
.
PX
,
},
DECK_ROTATE_Z
:
{
value
:
30
,
unit
:
UNIT
.
DEG
,
},
DECK_CARD_HEIGHT
:
{
value
:
120
,
unit
:
UNIT
.
PX
,
},
};
toCssProperties
(
matConfig
).
forEach
(([
k
,
v
])
=>
{
document
.
body
.
style
.
setProperty
(
k
,
v
);
});
src/ui/Duel/PlayMat/utils/index.ts
deleted
100644 → 0
View file @
53a9bdf4
export
*
from
"
./cssConfig
"
;
src/ui/Shared/YgoCard/index.tsx
View file @
a754c2c2
...
...
@@ -38,7 +38,7 @@ export const YgoCard: React.FC<Props> = (props) => {
const
NeosConfig
=
useConfig
();
function
getCardImgUrl
(
code
:
number
,
back
=
false
)
{
export
function
getCardImgUrl
(
code
:
number
,
back
=
false
)
{
const
ASSETS_BASE
=
import
.
meta
.
env
.
BASE_URL
===
"
/
"
?
NeosConfig
.
assetsPath
...
...
src/ui/Shared/css.ts
0 → 100644
View file @
a754c2c2
// 此文件目的是在js和CSS之间共享一些变量,并且这些变量是0运行时的。
type
CSSConfig
=
Record
<
string
,
[
number
,
UNIT
]
>
;
/** 转为CSS变量: BOARD_ROTATE_Z -> --board-rotate-z */
const
toCssProperties
=
(
config
:
CSSConfig
)
=>
Object
.
entries
(
config
)
.
map
(
([
k
,
v
])
=>
[
`--
${
k
.
split
(
"
_
"
)
.
map
((
s
)
=>
s
.
toLowerCase
())
.
join
(
"
-
"
)}
`
,
`
${
v
[
0
]}${
v
[
1
]}
`
,
]
as
[
string
,
string
]
)
.
reduce
((
acc
,
cur
)
=>
[...
acc
,
cur
],
[]
as
[
string
,
string
][]);
enum
UNIT
{
PX
=
"
px
"
,
DEG
=
"
deg
"
,
NONE
=
""
,
}
const
matConfigWithUnit
=
{
PERSPECTIVE
:
[
1500
,
UNIT
.
PX
],
PLANE_ROTATE_X
:
[
0
,
UNIT
.
DEG
],
BLOCK_WIDTH
:
[
120
,
UNIT
.
PX
],
BLOCK_HEIGHT_M
:
[
120
,
UNIT
.
PX
],
BLOCK_HEIGHT_S
:
[
110
,
UNIT
.
PX
],
// 魔法陷阱区
ROW_GAP
:
[
10
,
UNIT
.
PX
],
COL_GAP
:
[
10
,
UNIT
.
PX
],
CARD_RATIO
:
[
5.9
/
8.6
,
UNIT
.
NONE
],
HAND_MARGIN_TOP
:
[
0
,
UNIT
.
PX
],
HAND_CIRCLE_CENTER_OFFSET_Y
:
[
2000
,
UNIT
.
PX
],
HAND_CARD_HEIGHT
:
[
130
,
UNIT
.
PX
],
DECK_OFFSET_X
:
[
140
,
UNIT
.
PX
],
DECK_OFFSET_Y
:
[
80
,
UNIT
.
PX
],
DECK_ROTATE_Z
:
[
30
,
UNIT
.
DEG
],
DECK_CARD_HEIGHT
:
[
120
,
UNIT
.
PX
],
CARD_HEIGHT_O
:
[
100
,
UNIT
.
PX
],
// 场地魔法/墓地/除外的卡片高度
BLOCK_OUTSIDE_OFFSET_X
:
[
15
,
UNIT
.
PX
],
}
satisfies
CSSConfig
;
export
const
matConfig
=
Object
.
keys
(
matConfigWithUnit
).
reduce
(
(
prev
,
key
)
=>
({
...
prev
,
// @ts-ignore
[
key
]:
matConfigWithUnit
[
key
][
0
],
}),
{}
as
Record
<
keyof
typeof
matConfigWithUnit
,
number
>
);
toCssProperties
(
matConfigWithUnit
).
forEach
(([
k
,
v
])
=>
{
document
.
body
.
style
.
setProperty
(
k
,
v
);
});
src/ui/Shared/index.ts
View file @
a754c2c2
export
*
from
"
./css
"
;
export
*
from
"
./YgoCard
"
;
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