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
love_飞影
Neos
Commits
c9d97b95
Commit
c9d97b95
authored
May 26, 2023
by
timel
Committed by
Chunchi Che
May 28, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: new mat
parent
b028d9b2
Changes
22
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
630 additions
and
147 deletions
+630
-147
src/api/cards.ts
src/api/cards.ts
+2
-0
src/api/ocgcore/idl/ocgcore.ts
src/api/ocgcore/idl/ocgcore.ts
+1
-0
src/service/duel/move.ts
src/service/duel/move.ts
+108
-16
src/service/duel/posChange.ts
src/service/duel/posChange.ts
+3
-1
src/service/duel/reloadField.ts
src/service/duel/reloadField.ts
+4
-1
src/service/duel/shuffleHand.ts
src/service/duel/shuffleHand.ts
+22
-2
src/service/duel/start.ts
src/service/duel/start.ts
+31
-15
src/stores/cardStore.ts
src/stores/cardStore.ts
+7
-25
src/stores/matStore/store.ts
src/stores/matStore/store.ts
+1
-1
src/styles/core.scss
src/styles/core.scss
+36
-34
src/styles/mat.css
src/styles/mat.css
+22
-14
src/ui/Duel/Main.tsx
src/ui/Duel/Main.tsx
+4
-1
src/ui/Duel/NewPlayMat/Bg/index.scss
src/ui/Duel/NewPlayMat/Bg/index.scss
+25
-0
src/ui/Duel/NewPlayMat/Bg/index.tsx
src/ui/Duel/NewPlayMat/Bg/index.tsx
+29
-0
src/ui/Duel/NewPlayMat/Card/index.scss
src/ui/Duel/NewPlayMat/Card/index.scss
+11
-0
src/ui/Duel/NewPlayMat/Card/index.tsx
src/ui/Duel/NewPlayMat/Card/index.tsx
+157
-0
src/ui/Duel/NewPlayMat/Mat/index.scss
src/ui/Duel/NewPlayMat/Mat/index.scss
+19
-0
src/ui/Duel/NewPlayMat/Mat/index.tsx
src/ui/Duel/NewPlayMat/Mat/index.tsx
+39
-0
src/ui/Duel/NewPlayMat/index.ts
src/ui/Duel/NewPlayMat/index.ts
+1
-0
src/ui/Duel/NewPlayMat/utils/cssConfig.ts
src/ui/Duel/NewPlayMat/utils/cssConfig.ts
+55
-0
src/ui/Duel/NewPlayMat/utils/index.ts
src/ui/Duel/NewPlayMat/utils/index.ts
+1
-0
src/ui/Duel/Test.tsx
src/ui/Duel/Test.tsx
+52
-37
No files found.
src/api/cards.ts
View file @
c9d97b95
...
...
@@ -57,6 +57,8 @@ export async function fetchCard(id: number): Promise<CardMeta> {
return
res
.
selectResult
?
res
.
selectResult
:
{
id
,
data
:
{},
text
:
{}
};
}
window
.
fetchCard
=
fetchCard
;
export
function
getCardStr
(
meta
:
CardMeta
,
idx
:
number
):
string
|
undefined
{
switch
(
idx
)
{
case
0
:
{
...
...
src/api/ocgcore/idl/ocgcore.ts
View file @
c9d97b95
...
...
@@ -23,6 +23,7 @@ export namespace ygopro {
ONFIELD = 8,
FZONE = 9,
PZONE = 10,
TZONE = 11, // 还在想有没有什么好的解决方案
}
export enum CardPosition {
FACEUP_ATTACK = 0,
...
...
src/service/duel/move.ts
View file @
c9d97b95
import
{
v4
as
v4uuid
}
from
"
uuid
"
;
import
{
ygopro
}
from
"
@/api
"
;
import
{
fetchOverlayMeta
,
store
,
cardStore
}
from
"
@/stores
"
;
import
{
fetchCard
,
ygopro
}
from
"
@/api
"
;
import
{
fetchOverlayMeta
,
store
,
cardStore
,
CardType
}
from
"
@/stores
"
;
type
MsgMove
=
ygopro
.
StocGameMessage
.
MsgMove
;
import
{
useConfig
}
from
"
@/config
"
;
import
{
sleep
}
from
"
@/infra
"
;
...
...
@@ -11,30 +11,23 @@ import { REASON_MATERIAL } from "../../common";
const
{
matStore
}
=
store
;
const
NeosConfig
=
useConfig
();
const
{
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
,
MZONE
,
SZONE
,
TZONE
,
OVERLAY
}
=
ygopro
.
CardZone
;
const
OVERLAY_STACK
:
{
uuid
:
string
;
code
:
number
;
sequence
:
number
}[]
=
[];
const
overlayStack
:
CardType
[]
=
[];
export
default
async
(
move
:
MsgMove
)
=>
{
const
code
=
move
.
code
;
const
from
=
move
.
from
;
const
to
=
move
.
to
;
const
reason
=
move
.
reason
;
cardStore
.
move
(
code
,
{
zone
:
from
.
location
,
controller
:
from
.
controler
,
sequence
:
from
.
sequence
,
},
{
zone
:
to
.
location
,
controller
:
to
.
controler
,
sequence
:
to
.
sequence
,
}
);
// FIXME: 考虑超量素材的情况
// FIXME:需要考虑【卡名当作另一张卡】的情况
let
uuid
;
let
chainIndex
;
switch
(
from
.
location
)
{
...
...
@@ -166,4 +159,103 @@ export default async (move: MsgMove) => {
break
;
}
}
// card store
const
fromCards
=
cardStore
.
at
(
from
.
location
,
from
.
controler
);
const
toCards
=
cardStore
.
at
(
to
.
location
,
to
.
controler
);
const
fromZone
=
move
.
from
.
toArray
()[
1
]
===
undefined
?
ygopro
.
CardZone
.
TZONE
:
from
.
location
;
const
toZone
=
move
.
to
.
toArray
()[
1
]
===
undefined
?
ygopro
.
CardZone
.
TZONE
:
to
.
location
;
// 处理token
let
target
:
CardType
;
if
(
fromZone
===
TZONE
)
{
// 召唤 token
target
=
cardStore
.
at
(
TZONE
,
from
.
controler
)[
0
];
// 必有,随便取一个没用到的token
}
else
if
(
fromZone
===
OVERLAY
)
{
// 超量素材的去除
const
xyzMoster
=
cardStore
.
at
(
MZONE
,
from
.
controler
,
from
.
sequence
);
target
=
xyzMoster
.
overlayMaterials
.
splice
(
from
.
overlay_sequence
,
1
)[
0
];
target
.
xyzMonster
=
undefined
;
}
else
{
target
=
cardStore
.
at
(
fromZone
,
from
.
controler
,
from
.
sequence
);
}
(
async
()
=>
{
const
{
text
}
=
await
fetchCard
(
code
);
console
.
warn
(
"
---
"
);
console
.
log
(
"
move
"
,
text
.
name
,
ygopro
.
CardZone
[
fromZone
],
from
.
sequence
,
"
->
"
,
ygopro
.
CardZone
[
toZone
],
to
.
sequence
);
console
.
log
(
"
over
"
,
from
.
overlay_sequence
,
to
.
overlay_sequence
);
console
.
log
({
fromCards
});
console
.
log
({
target
});
console
.
warn
(
"
---
"
);
})();
if
(
toZone
===
OVERLAY
)
{
// 准备超量召唤,超量素材入栈
if
(
reason
==
REASON_MATERIAL
)
overlayStack
.
push
(
target
);
// 超量素材的添加
else
{
target
.
overlayMaterials
.
splice
(
to
.
overlay_sequence
,
0
,
target
);
target
.
xyzMonster
=
undefined
;
}
}
if
(
toZone
===
MZONE
&&
overlayStack
.
length
)
{
// 超量召唤
target
.
overlayMaterials
=
overlayStack
.
splice
(
0
,
overlayStack
.
length
);
target
.
overlayMaterials
.
forEach
((
c
)
=>
(
c
.
xyzMonster
=
target
));
}
// 维护sequence
if
([
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
].
includes
(
fromZone
))
fromCards
.
forEach
((
c
)
=>
c
.
sequence
>
from
.
sequence
&&
c
.
sequence
--
);
if
([
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
].
includes
(
toZone
))
toCards
.
forEach
((
c
)
=>
c
.
sequence
>=
to
.
sequence
&&
c
.
sequence
++
);
target
.
zone
=
toZone
;
target
.
controller
=
to
.
controler
;
target
.
sequence
=
to
.
sequence
;
target
.
code
=
code
;
target
.
position
=
to
.
position
;
// 注意,一个monster的overlayMaterials中的每一项都是一个cardType,
// 并且,overlayMaterials的idx就是超量素材的sequence。
// 如果一个card的zone是OVERLAY,那么它本身的sequence项是无意义的。
// 超量召唤:
// - 超量素材:toZone === OVERLAY, reason === REASON_MATERIAL
// - 超量怪兽:toZone === MZONE
// 解决方法是将超量素材放到一个list之中,等待超量怪兽的Move消息到来时从list中获取超量素材补充到超量怪兽的素材中
// 超量怪兽增加超量素材
// - 超量素材:toZone === OVERLAY, reason !== REASON_MATERIAL
// 这里要注意toZone和toSequence的不一致
// 超量素材(target)是cardStore.at(from.location, from.controler, from.sequence)
// 超量怪兽(xyzMonster)是cardStore.at(MZONE, to.controler, to.sequence)
// 超量怪兽失去超量素材
// - 超量素材:fromZone === OVERLAY
// 超量怪兽(xyzMonster)是cardStore.at(MZONE, from.controler, from.sequence)
// 超量素材(target)是xyzMoster.overlayMaterials[from.overlay_sequence]
// 在超量召唤/超量素材更改时候,target是超量素材,但同时也要维护超量怪兽的overlayMaterials
// token登场
// - token:fromZone === TZONE
// token离场
// - token:toZone === TZONE
};
src/service/duel/posChange.ts
View file @
c9d97b95
import
{
ygopro
}
from
"
@/api
"
;
import
MsgPosChange
=
ygopro
.
StocGameMessage
.
MsgPosChange
;
import
{
fetchEsHintMeta
,
matStore
}
from
"
@/stores
"
;
import
{
fetchEsHintMeta
,
matStore
,
cardStore
}
from
"
@/stores
"
;
export
default
(
posChange
:
MsgPosChange
)
=>
{
const
{
location
,
controler
,
sequence
}
=
posChange
.
card_info
;
cardStore
.
at
(
location
,
controler
,
sequence
).
position
=
posChange
.
cur_position
;
switch
(
location
)
{
case
ygopro
.
CardZone
.
MZONE
:
{
matStore
.
monsters
.
of
(
controler
)[
sequence
].
location
.
position
=
...
...
src/service/duel/reloadField.ts
View file @
c9d97b95
import
{
v4
as
uuidv4
}
from
"
uuid
"
;
import
{
ygopro
}
from
"
@/api
"
;
import
{
matStore
}
from
"
@/stores
"
;
import
{
matStore
,
cardStore
}
from
"
@/stores
"
;
type
MsgReloadField
=
ygopro
.
StocGameMessage
.
MsgReloadField
;
type
ZoneActions
=
ygopro
.
StocGameMessage
.
MsgReloadField
.
ZoneAction
[];
...
...
@@ -61,4 +61,7 @@ function reloadDuelField(
.
in
(
cardZone
)
.
of
(
controller
)
.
push
(...
cards
);
// FIXME cardStore的逻辑不是很好处理...
// 以后再写
}
src/service/duel/shuffleHand.ts
View file @
c9d97b95
import
{
ygopro
}
from
"
@/api
"
;
import
{
matStore
}
from
"
@/stores
"
;
import
{
matStore
,
cardStore
}
from
"
@/stores
"
;
import
{
zip
}
from
"
@/ui/Duel/utils
"
;
type
MsgShuffleHand
=
ygopro
.
StocGameMessage
.
MsgShuffleHand
;
export
default
(
shuffleHand
:
MsgShuffleHand
)
=>
{
const
{
hands
:
codes
,
player
:
controller
}
=
shuffleHand
;
const
indexMap
=
new
Map
(
codes
.
map
((
code
,
idx
)
=>
[
code
,
idx
]));
// 本质上是要将手卡的sequence变成和codes一样的顺序
const
hands
=
cardStore
.
at
(
ygopro
.
CardZone
.
HAND
,
controller
);
const
t
:
Record
<
number
,
number
[]
>
=
{};
codes
.
forEach
((
code
,
sequence
)
=>
{
t
[
code
]
=
t
[
code
]
||
[];
t
[
code
].
push
(
sequence
);
});
hands
.
forEach
((
hand
)
=>
{
const
sequence
=
t
[
hand
.
code
].
shift
();
if
(
sequence
===
undefined
)
{
throw
new
Error
(
"
手牌数量和洗牌后的数量不一致
"
);
}
hand
.
sequence
=
sequence
;
});
const
uuids
=
matStore
.
hands
.
of
(
controller
).
map
((
hand
)
=>
hand
.
uuid
);
const
data
=
zip
(
uuids
,
codes
).
map
(([
uuid
,
id
])
=>
{
return
{
uuid
,
id
};
});
const
indexMap
=
new
Map
(
codes
.
map
((
code
,
idx
)
=>
[
code
,
idx
]));
matStore
.
hands
.
of
(
controller
).
sort
((
a
,
b
)
=>
{
const
indexA
=
indexMap
.
get
(
a
.
occupant
?.
id
??
0
)
??
0
;
const
indexB
=
indexMap
.
get
(
b
.
occupant
?.
id
??
0
)
??
0
;
...
...
src/service/duel/start.ts
View file @
c9d97b95
...
...
@@ -100,6 +100,8 @@ export default (start: ygopro.StocGameMessage.MsgStart) => {
.
in
(
ygopro
.
CardZone
.
EXTRA
)
.
me
.
forEach
((
state
)
=>
(
state
.
location
.
controler
=
1
-
opponent
));
// 下面是cardStore的初始化
/** 自动从code推断出occupant */
const
genCard
=
(
o
:
CardType
)
=>
{
// FIXME 还没处理超量
...
...
@@ -112,22 +114,36 @@ export default (start: ygopro.StocGameMessage.MsgStart) => {
return
t
;
};
const
TOKEN_SIZE
=
13
;
// 每人场上最多就只可能有13个token
const
cards
=
flatten
(
[
start
.
deckSize1
,
start
.
extraSize1
,
start
.
deckSize2
,
start
.
extraSize2
].
map
(
(
length
,
i
)
=>
Array
.
from
({
length
}).
map
((
_
,
sequence
)
=>
genCard
({
uuid
:
v4uuid
(),
code
:
0
,
controller
:
i
<
2
?
1
-
opponent
:
opponent
,
// 前两个是自己的卡组,后两个是对手的卡组
zone
:
i
%
2
?
ygopro
.
CardZone
.
EXTRA
:
ygopro
.
CardZone
.
DECK
,
counters
:
{},
idleInteractivities
:
[],
sequence
,
data
:
{},
text
:
{},
})
)
[
start
.
deckSize1
,
start
.
extraSize1
,
TOKEN_SIZE
,
start
.
deckSize2
,
start
.
extraSize2
,
TOKEN_SIZE
,
].
map
((
length
,
i
)
=>
Array
.
from
({
length
}).
map
((
_
,
sequence
)
=>
genCard
({
// uuid: v4uuid(),
code
:
0
,
controller
:
i
<
3
?
1
-
opponent
:
opponent
,
// 前3个是自己的卡组,后3个是对手的卡组
originController
:
i
<
3
?
1
-
opponent
:
opponent
,
zone
:
[
ygopro
.
CardZone
.
DECK
,
ygopro
.
CardZone
.
EXTRA
,
ygopro
.
CardZone
.
TZONE
,
][
i
%
3
],
counters
:
{},
idleInteractivities
:
[],
sequence
,
data
:
{},
text
:
{},
isToken
:
!
((
i
+
1
)
%
3
),
overlayMaterials
:
[],
})
)
)
);
...
...
src/stores/cardStore.ts
View file @
c9d97b95
import
{
CardData
,
Card
Meta
,
Card
Text
,
fetchCard
,
ygopro
}
from
"
@/api
"
;
import
{
CardData
,
CardText
,
fetchCard
,
ygopro
}
from
"
@/api
"
;
import
{
proxy
}
from
"
valtio
"
;
import
{
Interactivity
}
from
"
./matStore/types
"
;
const
{
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
,
MZONE
,
SZONE
}
=
ygopro
.
CardZone
;
/**
* 场上某位置的状态,
* 以后会更名为 BlockState
*/
export
interface
CardType
{
uuid
:
string
;
// 一张卡的唯一标识
// uuid: string; // FIXME 一张卡的唯一标识 一定需要这个吗?list的idx是不是就够了?
code
:
number
;
data
:
CardData
;
text
:
CardText
;
controller
?:
number
;
// 控制这个位置的玩家,0或1
controller
:
number
;
// 控制这个位置的玩家,0或1
originController
:
number
;
// 在卡组构建之中持有这张卡的玩家,方便reloadField的使用
zone
:
ygopro
.
CardZone
;
// 怪兽区/魔法陷阱区/手牌/卡组/墓地/除外区
position
?:
ygopro
.
CardPosition
;
// 卡片的姿势:攻击还是守备
sequence
:
number
;
// 卡片在区域中的序号
...
...
@@ -23,9 +22,11 @@ export interface CardType {
zone
:
ygopro
.
CardZone
;
sequence
:
number
;
}
>
;
// 选择位置状态下的互动信息
overlay_materials
?:
CardMeta
[];
// 超量素材, FIXME: 这里需要加上UUID
overlayMaterials
:
CardType
[];
// 超量素材, FIXME: 这里需要加上UUID
xyzMonster
?:
CardType
;
// 超量怪兽(这张卡作为这个怪兽的超量素材)
counters
:
{
[
type
:
number
]:
number
};
// 指示器
reload
?:
boolean
;
// 这个字段会在收到MSG_RELOAD_FIELD的时候设置成true,在收到MSG_UPDATE_DATE的时候设置成false
isToken
:
boolean
;
// 是否是token
}
class
CardStore
{
...
...
@@ -46,25 +47,6 @@ class CardStore {
);
}
}
move
(
code
:
number
,
from
:
{
zone
:
ygopro
.
CardZone
;
controller
:
number
;
sequence
:
number
},
to
:
{
zone
:
ygopro
.
CardZone
;
controller
:
number
;
sequence
:
number
}
)
{
// TODO:考虑超量素材的情况
const
fromCards
=
this
.
at
(
from
.
zone
,
from
.
controller
);
const
toCards
=
this
.
at
(
to
.
zone
,
to
.
controller
);
const
target
=
this
.
at
(
from
.
zone
,
from
.
controller
,
from
.
sequence
);
if
([
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
].
includes
(
from
.
zone
))
fromCards
.
forEach
((
c
)
=>
c
.
sequence
>
from
.
sequence
&&
c
.
sequence
--
);
if
([
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
].
includes
(
to
.
zone
))
toCards
.
forEach
((
c
)
=>
c
.
sequence
>=
to
.
sequence
&&
c
.
sequence
++
);
target
.
zone
=
to
.
zone
;
target
.
controller
=
to
.
controller
;
target
.
sequence
=
to
.
sequence
;
target
.
code
=
code
;
}
}
export
const
cardStore
=
proxy
(
new
CardStore
());
...
...
src/stores/matStore/store.ts
View file @
c9d97b95
...
...
@@ -141,7 +141,7 @@ const genDuelCardArray = (cardStates: CardState[], zone: ygopro.CardZone) => {
* 根据自己的先后手判断是否是自己
* 原本名字叫judgeSelf
*/
const
isMe
=
(
controller
:
number
):
boolean
=>
{
export
const
isMe
=
(
controller
:
number
):
boolean
=>
{
switch
(
matStore
.
selfType
)
{
case
1
:
// 自己是先攻
...
...
src/styles/core.scss
View file @
c9d97b95
...
...
@@ -2,40 +2,39 @@
// thanks!
@charset
"utf-8"
;
ol
,
ul
{
list-style
:
none
;
ol
,
ul
{
list-style
:
none
;
}
blockquote
,
q
{
quotes
:
none
;
blockquote
,
q
{
quotes
:
none
;
}
blockquote
:before
,
blockquote
:after
,
q
:before
,
q
:after
{
content
:
''
;
content
:
none
;
blockquote
:before
,
blockquote
:after
,
q
:before
,
q
:after
{
content
:
""
;
content
:
none
;
}
table
{
border-collapse
:
collapse
;
border-spacing
:
0
;
border-collapse
:
collapse
;
border-spacing
:
0
;
}
#root
{
display
:
flex
;
margin
:
0
auto
;
text-align
:
center
;
}
@import
url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css")
,
"commom"
,
"header"
,
"login-form"
,
"sign-in"
;
@import
url("https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css")
,
"commom"
,
"header"
,
"login-form"
,
"sign-in"
;
body
{
color-scheme
:
light
dark
;
color
:
rgba
(
255
,
255
,
255
,
0
.87
);
background-color
:
#242424
;
font
:
87
.5%
/
1
.5em
'Open Sans'
,
sans-serif
;
font
:
87
.5%
/
1
.5em
"Open Sans"
,
sans-serif
;
display
:
flex
;
margin
:
0
;
place-items
:
center
;
...
...
@@ -44,20 +43,20 @@ body {
}
a
{
text-decoration
:
none
;
text-decoration
:
none
;
}
input
{
border
:
none
;
font-family
:
'Open Sans'
,
Arial
,
sans-serif
;
font-size
:
14px
;
line-height
:
1
.5em
;
padding
:
0
;
-webkit-appearance
:
none
;
border
:
none
;
font-family
:
"Open Sans"
,
Arial
,
sans-serif
;
font-size
:
14px
;
line-height
:
1
.5em
;
padding
:
0
;
-webkit-appearance
:
none
;
}
p
{
line-height
:
1
.5em
;
line-height
:
1
.5em
;
}
.clearfix
{
...
...
@@ -65,25 +64,28 @@ p {
&
:before
,
&
:after
{
content
:
' '
;
content
:
" "
;
display
:
table
;
}
&
:after
{
clear
:
both
;
}
}
.container
{
margin
:
0
auto
;
// left: 50%;
// position: fixed;
// top: 50%;
// transform: translate(-50%, -50%);
margin
:
0
auto
;
width
:
100%
;
max-width
:
300px
;
margin-top
:
200px
;
max-width
:
300px
;
margin-top
:
200px
;
}
.g-row
{
margin
:
0
auto
;
width
:
100%
;
max-width
:
1000px
;
margin
:
0
auto
;
width
:
100%
;
max-width
:
1000px
;
}
src/styles/mat.css
View file @
c9d97b95
...
...
@@ -44,8 +44,9 @@ button:focus-visible {
position
:
fixed
;
display
:
flex
;
gap
:
20px
;
bottom
:
20px
;
top
:
20px
;
right
:
20px
;
z-index
:
999
;
}
#life-bar-container
{
...
...
@@ -62,12 +63,12 @@ button:focus-visible {
#life-bar
{
padding
:
0.8em
1.6em
;
background-color
:
#
A9A9A
9
;
background-color
:
#
a9a9a
9
;
border-radius
:
8px
;
text-align
:
left
;
border
:
1px
solid
transparent
;
color
:
black
;
opacity
:
.4
;
opacity
:
0
.4
;
}
#camera
{
...
...
@@ -106,7 +107,8 @@ button:focus-visible {
rotate
:
calc
(
var
(
--opponent-deg
)
*
(
1
-
var
(
--vertical
)));
transform-style
:
preserve-3d
;
z-index
:
10
;
animation
:
glow
calc
(
var
(
--highlight-interval
)
*
var
(
--highlight-on
))
ease-out
infinite
alternate
;
animation
:
glow
calc
(
var
(
--highlight-interval
)
*
var
(
--highlight-on
))
ease-out
infinite
alternate
;
}
.card-defense
{
...
...
@@ -128,7 +130,8 @@ button:focus-visible {
transform
:
translateZ
(
var
(
--z
));
translate
:
var
(
--x
)
var
(
--y
);
rotate
:
calc
(
90deg
+
var
(
--opponent-deg
));
animation
:
glow
calc
(
var
(
--highlight-interval
)
*
var
(
--highlight-on
))
ease-out
infinite
alternate
;
animation
:
glow
calc
(
var
(
--highlight-interval
)
*
var
(
--highlight-on
))
ease-out
infinite
alternate
;
}
.card
::after
{
...
...
@@ -154,7 +157,8 @@ button:focus-visible {
height
:
var
(
--block-height
);
background-color
:
#333
;
cursor
:
pointer
;
animation
:
glow
calc
(
var
(
--highlight-interval
)
*
var
(
--highlight-on
))
ease-out
infinite
alternate
;
animation
:
glow
calc
(
var
(
--highlight-interval
)
*
var
(
--highlight-on
))
ease-out
infinite
alternate
;
}
.block-extra
{
...
...
@@ -231,22 +235,26 @@ button:focus-visible {
@keyframes
glow
{
0
%
{
border-color
:
var
(
--highlight-color-x
);
box-shadow
:
0
0
5px
rgba
(
0
,
255
,
0
,
.2
),
inset
0
0
5px
rgba
(
0
,
255
,
0
,
.1
),
0
1px
0
#393
;
border-color
:
var
(
--highlight-color-x
);
box-shadow
:
0
0
5px
rgba
(
0
,
255
,
0
,
0.2
),
inset
0
0
5px
rgba
(
0
,
255
,
0
,
0.1
),
0
1px
0
#393
;
}
100
%
{
border-color
:
var
(
--highlight-color-y
);
box-shadow
:
0
0
20px
rgba
(
0
,
255
,
0
,
.6
),
inset
0
0
10px
rgba
(
0
,
255
,
0
,
.4
),
0
1px
0
#6f6
;
border-color
:
var
(
--highlight-color-y
);
box-shadow
:
0
0
20px
rgba
(
0
,
255
,
0
,
0.6
),
inset
0
0
10px
rgba
(
0
,
255
,
0
,
0.4
),
0
1px
0
#6f6
;
}
}
@keyframes
glow-hover
{
0
%
{
border-color
:
#CBCC24
;
box-shadow
:
0
0
5px
rgba
(
255
,
255
,
0
,
.2
),
inset
0
0
5px
rgba
(
255
,
255
,
0
,
.1
),
0
1px
0
#CBCC24
;
border-color
:
#cbcc24
;
box-shadow
:
0
0
5px
rgba
(
255
,
255
,
0
,
0.2
),
inset
0
0
5px
rgba
(
255
,
255
,
0
,
0.1
),
0
1px
0
#cbcc24
;
}
100
%
{
border-color
:
#F0F224
;
box-shadow
:
0
0
20px
rgba
(
255
,
255
,
0
,
.6
),
inset
0
0
10px
rgba
(
255
,
255
,
0
,
.4
),
0
1px
0
#F0F224
;
border-color
:
#f0f224
;
box-shadow
:
0
0
20px
rgba
(
255
,
255
,
0
,
0.6
),
inset
0
0
10px
rgba
(
255
,
255
,
0
,
0.4
),
0
1px
0
#f0f224
;
}
}
src/ui/Duel/Main.tsx
View file @
c9d97b95
...
...
@@ -16,12 +16,15 @@ import {
import
Mat
from
"
./PlayMat
"
;
import
{
Test
}
from
"
./Test
"
;
import
{
Mat
as
NewMat
}
from
"
./NewPlayMat
"
;
const
NeosDuel
=
()
=>
{
return
(
<>
<
Alert
/>
<
Test
/>
{
/* <Test /> */
}
<
Mat
/>
<
NewMat
/>
<
CardModal
/>
<
CardListModal
/>
<
HintNotification
/>
...
...
src/ui/Duel/NewPlayMat/Bg/index.scss
0 → 100644
View file @
c9d97b95
section
#mat
{
.mat-bg
{
display
:
flex
;
flex-direction
:
column
;
row-gap
:
var
(
--
row-gap
);
justify-content
:
center
;
align-items
:
center
;
.bg-row
{
display
:
flex
;
column-gap
:
var
(
--
col-gap
);
}
}
.block
{
height
:
var
(
--
block-height-m
);
width
:
var
(
--
block-width
);
background-color
:
gray
;
&
.extra
{
margin-inline
:
calc
(
var
(
--
block-width
)
/
2
+
var
(
--
col-gap
)
/
2
);
}
&
.szone
{
height
:
var
(
--
block-height-s
);
}
}
}
src/ui/Duel/NewPlayMat/Bg/index.tsx
0 → 100644
View file @
c9d97b95
import
{
type
FC
}
from
"
react
"
;
import
classnames
from
"
classnames
"
;
import
"
./index.scss
"
;
const
BgRow
:
FC
<
{
isExtra
?:
boolean
;
isSzone
?:
boolean
}
>
=
({
isExtra
=
false
,
isSzone
=
false
,
})
=>
(
<
div
className=
{
classnames
(
"
bg-row
"
)
}
>
{
Array
.
from
({
length
:
isExtra
?
2
:
5
}).
map
((
_
,
i
)
=>
(
<
div
key=
{
i
}
className=
{
classnames
(
"
block
"
,
{
extra
:
isExtra
},
{
szone
:
isSzone
})
}
></
div
>
))
}
</
div
>
);
export
const
Bg
:
FC
=
()
=>
{
return
(
<
div
className=
"mat-bg"
>
<
BgRow
isSzone
/>
<
BgRow
/>
<
BgRow
isExtra
/>
<
BgRow
/>
<
BgRow
isSzone
/>
</
div
>
);
};
src/ui/Duel/NewPlayMat/Card/index.scss
0 → 100644
View file @
c9d97b95
section
#mat
{
.mat-card
{
position
:
absolute
;
left
:
0
;
top
:
0
;
--card-height
:
100px
;
height
:
var
(
--
card-height
);
aspect-ratio
:
var
(
--
card-ratio
);
background-color
:
red
;
}
}
src/ui/Duel/NewPlayMat/Card/index.tsx
0 → 100644
View file @
c9d97b95
import
React
,
{
type
FC
}
from
"
react
"
;
import
classnames
from
"
classnames
"
;
import
{
CardType
,
cardStore
,
isMe
}
from
"
@/stores
"
;
import
"
./index.scss
"
;
import
{
useSnapshot
,
INTERNAL_Snapshot
as
Snapshot
}
from
"
valtio
"
;
import
{
watch
}
from
"
valtio/utils
"
;
import
{
useSpringRef
,
useSpring
,
animated
,
to
}
from
"
@react-spring/web
"
;
import
{
matConfig
}
from
"
../utils
"
;
import
{
ygopro
}
from
"
@/api
"
;
const
{
HAND
,
GRAVE
,
REMOVED
,
DECK
,
EXTRA
,
MZONE
,
SZONE
,
TZONE
,
OVERLAY
}
=
ygopro
.
CardZone
;
const
{
BLOCK_WIDTH
,
BLOCK_HEIGHT_M
,
BLOCK_HEIGHT_S
,
CARD_RATIO
,
COL_GAP
,
ROW_GAP
,
}
=
matConfig
;
export
const
Card
:
FC
<
{
idx
:
number
}
>
=
React
.
memo
(({
idx
})
=>
{
const
state
=
cardStore
.
inner
[
idx
];
const
snap
=
useSnapshot
(
state
);
const
inintialCoord
=
calcCoordinate
(
state
,
!
isMe
(
state
.
controller
));
const
api
=
useSpringRef
();
const
props
=
useSpring
({
ref
:
api
,
from
:
{
x
:
inintialCoord
.
translateX
,
y
:
inintialCoord
.
translateY
,
z
:
inintialCoord
.
translateZ
,
rotateX
:
inintialCoord
.
rotateX
,
rotateY
:
inintialCoord
.
rotateY
,
rotateZ
:
inintialCoord
.
rotateZ
,
height
:
inintialCoord
.
height
,
},
});
watch
((
get
)
=>
{
const
{
zone
,
sequence
,
controller
,
xyzMonster
}
=
get
(
state
);
const
coord
=
calcCoordinate
(
state
,
!
isMe
(
state
.
controller
));
api
.
start
({
to
:
{
x
:
coord
.
translateX
,
y
:
coord
.
translateY
,
z
:
coord
.
translateZ
,
rotateX
:
coord
.
rotateX
,
rotateY
:
coord
.
rotateY
,
rotateZ
:
coord
.
rotateZ
,
height
:
coord
.
height
,
},
});
});
return
(
<
animated
.
div
className=
"mat-card"
style=
{
{
transform
:
to
(
[
props
.
x
,
props
.
y
,
props
.
z
,
props
.
rotateX
,
props
.
rotateY
,
props
.
rotateZ
,
],
(
x
,
y
,
z
,
rx
,
ry
,
rz
)
=>
`translate3d(${x}px, ${y}px, ${z}px) rotateZ(${rz}deg)`
),
height
:
props
.
height
,
}
}
>
{
snap
.
text
.
name
}
{
(
Math
.
random
()
*
1000
).
toFixed
()
}
</
animated
.
div
>
);
});
function
calcCoordinate
(
{
zone
,
sequence
,
position
,
xyzMonster
}:
CardType
,
opponent
:
boolean
)
{
const
res
=
{
translateX
:
0
,
translateY
:
0
,
translateZ
:
0
,
rotateX
:
0
,
rotateY
:
0
,
rotateZ
:
0
,
height
:
0
,
};
let
row
=
-
1
,
col
=
-
1
;
if
([
MZONE
,
SZONE
].
includes
(
zone
))
{
row
=
zone
===
MZONE
?
(
sequence
>
4
?
2
:
opponent
?
1
:
3
)
:
opponent
?
0
:
4
;
col
=
sequence
>
4
?
(
sequence
>
5
?
3
:
1
)
:
sequence
;
if
(
opponent
)
col
=
posHelper
[
col
];
}
if
(
zone
===
OVERLAY
&&
xyzMonster
)
{
const
{
zone
,
sequence
}
=
xyzMonster
;
row
=
zone
===
MZONE
?
(
sequence
>
4
?
2
:
opponent
?
1
:
3
)
:
opponent
?
0
:
4
;
col
=
sequence
>
4
?
(
sequence
>
5
?
3
:
1
)
:
sequence
;
if
(
opponent
)
col
=
posHelper
[
col
];
}
const
isField
=
zone
===
SZONE
&&
sequence
===
5
;
if
(
isField
)
{
row
=
opponent
?
1
:
3
;
col
=
opponent
?
5
:
-
1
;
}
const
_position
=
zone
===
OVERLAY
&&
xyzMonster
?
xyzMonster
.
position
:
position
;
const
defense
=
[
ygopro
.
CardPosition
.
DEFENSE
,
ygopro
.
CardPosition
.
FACEDOWN_DEFENSE
,
ygopro
.
CardPosition
.
FACEUP_DEFENSE
,
].
includes
(
_position
??
5
);
res
.
rotateZ
=
opponent
?
180
:
0
;
res
.
rotateZ
+=
defense
?
90
:
0
;
res
.
height
=
defense
?
BLOCK_WIDTH
.
value
:
zone
===
MZONE
?
BLOCK_HEIGHT_M
.
value
:
BLOCK_HEIGHT_S
.
value
;
const
blockPaddingX
=
(
BLOCK_WIDTH
.
value
-
res
.
height
*
CARD_RATIO
.
value
)
/
2
;
if
(
row
>
-
1
)
{
// 说明是场上的卡
res
.
translateX
=
(
BLOCK_WIDTH
.
value
+
COL_GAP
.
value
)
*
col
+
blockPaddingX
;
res
.
translateY
=
ROW_GAP
.
value
*
row
+
BLOCK_HEIGHT_M
.
value
*
Math
.
min
(
Math
.
max
(
0
,
row
-
1
),
3
)
+
BLOCK_HEIGHT_S
.
value
*
Math
.
ceil
(
row
/
4
);
}
console
.
log
({
col
,
row
});
return
res
;
}
const
posHelper
:
Record
<
number
,
number
>
=
{
0
:
4
,
1
:
3
,
2
:
2
,
3
:
1
,
4
:
0
,
5
:
6
,
6
:
5
,
};
src/ui/Duel/NewPlayMat/Mat/index.scss
0 → 100644
View file @
c9d97b95
section
#mat
{
margin-top
:
200px
;
padding-top
:
50px
;
// 先不管 后面调整
position
:
relative
;
#camera
{
height
:
100%
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
perspective
:
var
(
--
perspective
);
transform-style
:
preserve-3d
;
}
#plane
{
transform
:
translateX
(
0
)
translateY
(
0
)
translateZ
(
0
)
rotateX
(
var
(
--
plane-rotate-z
));
width
:
fit-content
;
}
}
src/ui/Duel/NewPlayMat/Mat/index.tsx
0 → 100644
View file @
c9d97b95
import
type
{
FC
,
PropsWithChildren
}
from
"
react
"
;
import
"
./index.scss
"
;
import
{
Bg
}
from
"
../Bg
"
;
import
{
Card
}
from
"
../Card
"
;
import
{
type
CSSConfig
,
toCssProperties
,
matConfig
}
from
"
../utils
"
;
import
{
cardStore
}
from
"
@/stores
"
;
import
{
useSnapshot
}
from
"
valtio
"
;
// 后面再改名
export
const
Mat
:
FC
=
()
=>
{
const
snap
=
useSnapshot
(
cardStore
.
inner
);
return
(
<
section
id=
"mat"
style=
{
{
width
:
"
100%
"
,
// height: "100vh",
backgroundColor
:
"
black
"
,
...
toCssProperties
(
matConfig
),
}
}
>
<
Plane
>
<
Bg
/>
{
snap
.
map
((
cardSnap
,
i
)
=>
cardSnap
.
zone
?
<
Card
key=
{
i
}
idx=
{
i
}
/>
:
null
)
}
</
Plane
>
</
section
>
);
};
const
Plane
:
FC
<
PropsWithChildren
>
=
({
children
})
=>
(
<
div
id=
"camera"
>
<
div
id=
"plane"
>
{
children
}
</
div
>
</
div
>
);
src/ui/Duel/NewPlayMat/index.ts
0 → 100644
View file @
c9d97b95
export
*
from
"
./Mat
"
;
src/ui/Duel/NewPlayMat/utils/cssConfig.ts
0 → 100644
View file @
c9d97b95
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
}
`
,
}))
.
reduce
((
acc
,
cur
)
=>
({
...
acc
,
...
cur
}),
{});
enum
UNIT
{
PX
=
"
px
"
,
DEG
=
"
deg
"
,
NONE
=
""
,
}
export
const
matConfig
=
{
PERSPECTIVE
:
{
value
:
1500
,
unit
:
UNIT
.
PX
,
},
PLANE_ROTATE_Z
:
{
value
:
20
,
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
,
},
};
src/ui/Duel/NewPlayMat/utils/index.ts
0 → 100644
View file @
c9d97b95
export
*
from
"
./cssConfig
"
;
src/ui/Duel/Test.tsx
View file @
c9d97b95
import
{
cardStore
}
from
"
@/stores
"
;
import
{
useSnapshot
}
from
"
valtio
"
;
import
{
subscribeKey
,
watch
}
from
"
valtio/utils
"
;
import
{
FC
,
memo
,
useEffect
,
useState
}
from
"
react
"
;
import
{
ygopro
}
from
"
@/api
"
;
import
{
...
...
@@ -10,6 +11,7 @@ import {
}
from
"
@react-spring/web
"
;
export
const
Test
=
()
=>
{
const
snap
=
useSnapshot
(
cardStore
.
inner
);
return
(
<
div
style=
{
{
...
...
@@ -22,46 +24,59 @@ export const Test = () => {
fontSize
:
12
,
}
}
>
{
cardStore
.
inner
.
map
((
cardState
,
i
)
=>
(
<
Card
idx=
{
i
}
key=
{
cardState
.
uuid
}
/>
{
snap
.
map
((
cardState
,
i
)
=>
(
<
Card
idx=
{
i
}
key=
{
i
}
show=
{
[
ygopro
.
CardZone
.
HAND
,
ygopro
.
CardZone
.
MZONE
,
ygopro
.
CardZone
.
SZONE
,
ygopro
.
CardZone
.
GRAVE
,
].
includes
(
cardState
.
zone
)
}
/>
))
}
</
div
>
);
};
export
const
Card
:
FC
<
{
idx
:
number
}
>
=
memo
(({
idx
})
=>
{
const
snap
=
useSnapshot
(
cardStore
.
inner
[
idx
]);
const
[
show
,
setShow
]
=
useState
(
false
);
const
api
=
useSpringRef
();
const
props
=
useSpring
({
ref
:
api
,
from
:
{
x
:
0
},
});
useEffect
(()
=>
{
setShow
(
[
ygopro
.
CardZone
.
HAND
,
ygopro
.
CardZone
.
MZONE
,
ygopro
.
CardZone
.
SZONE
,
].
includes
(
snap
.
zone
)
);
api
.
start
({
to
:
{
x
:
props
.
x
.
get
()
===
100
?
0
:
100
,
},
export
const
Card
:
FC
<
{
idx
:
number
;
show
:
boolean
}
>
=
memo
(
({
idx
,
show
})
=>
{
const
snap
=
useSnapshot
(
cardStore
.
inner
[
idx
]);
const
api
=
useSpringRef
();
const
props
=
useSpring
({
ref
:
api
,
from
:
{
x
:
0
},
});
},
[
snap
.
zone
]);
// 添加 show 到依赖项中
return
show
?
(
<
animated
.
div
style=
{
{
transform
:
props
.
x
.
to
((
value
)
=>
`translateX(${value}px)`
),
background
:
"
white
"
,
}
}
>
<
div
>
code:
{
snap
.
code
}
</
div
>
<
div
>
{
(
Math
.
random
()
*
100
).
toFixed
(
0
)
}
</
div
>
</
animated
.
div
>
)
:
(
<></>
);
});
// subscribeKey(cardStore.inner[idx], "zone", (value) => {
// api.start({
// to: {
// x: value * 100,
// },
// });
// });
watch
((
get
)
=>
{
get
(
cardStore
.
inner
[
idx
]);
const
zone
=
get
(
cardStore
.
inner
[
idx
]).
zone
;
api
.
start
({
to
:
{
x
:
zone
*
100
,
},
});
});
return
show
?
(
<
animated
.
div
style=
{
{
transform
:
props
.
x
.
to
((
value
)
=>
`translateX(${value}px)`
),
background
:
"
white
"
,
}
}
>
<
div
>
code:
{
snap
.
code
}
</
div
>
<
div
>
{
(
Math
.
random
()
*
100
).
toFixed
(
0
)
}
</
div
>
</
animated
.
div
>
)
:
(
<></>
);
},
(
prev
,
next
)
=>
prev
.
show
===
next
.
show
// 只有 show 变化时才会重新渲染
);
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