Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Y
ygopro-scripts-888
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
3
Merge Requests
3
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
MyCard
ygopro-scripts-888
Commits
6c193bc3
Commit
6c193bc3
authored
Jul 14, 2025
by
Vury Leo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
tuning 白の枢機竜
parent
0d056a5a
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
725 additions
and
781 deletions
+725
-781
c11321089.lua
c11321089.lua
+20
-1
c93053159.lua
c93053159.lua
+19
-3
procedure.lua
procedure.lua
+686
-777
No files found.
c11321089.lua
View file @
6c193bc3
...
@@ -8,7 +8,26 @@ function s.initial_effect(c)
...
@@ -8,7 +8,26 @@ function s.initial_effect(c)
Fusion
.
Slot
.
Group
({
Fusion
.
Slot
.
Group
({
min
=
3
,
min
=
3
,
max
=
3
,
max
=
3
,
unique_by
=
function
(
mc
)
return
{
mc
:
GetCode
(),
mc
:
GetFusionCode
()
}
end
,
--- @param mc Card
unique_by
=
function
(
mc
)
local
function
dedup_represent
(
code
)
if
code
==
78734254
then
return
17955766
elseif
code
==
13857930
then
return
17732278
end
return
code
end
local
code
=
dedup_represent
(
mc
:
GetCode
())
local
res
=
{
code
}
--- Add Fusion Tag names
for
_
,
eff
in
ipairs
({
mc
:
IsHasEffect
(
EFFECT_ADD_FUSION_CODE
)})
do
local
fusion_tag_code
=
eff
:
GetValue
()
assert
(
type
(
fusion_tag_code
)
==
'number'
)
table.insert
(
res
,
dedup_represent
(
fusion_tag_code
))
end
return
res
end
,
})
})
},
},
mat_filter
=
s
.
mat_filter
,
mat_filter
=
s
.
mat_filter
,
...
...
c93053159.lua
View file @
6c193bc3
...
@@ -9,9 +9,25 @@ function s.initial_effect(c)
...
@@ -9,9 +9,25 @@ function s.initial_effect(c)
Fusion
.
Slot
.
Group
({
-- 6 monsters in your GY with different names
Fusion
.
Slot
.
Group
({
-- 6 monsters in your GY with different names
min
=
6
,
min
=
6
,
max
=
6
,
max
=
6
,
filter
=
function
(
mc
)
return
mc
:
IsLocation
(
LOCATION_GRAVE
)
end
,
filter
=
function
(
mc
,
tc
)
return
mc
:
IsLocation
(
LOCATION_GRAVE
)
end
,
unique_by
=
function
(
tc
)
unique_by
=
function
(
mc
)
return
{
tc
:
GetFusionCode
()}
local
function
dedup_represent
(
code
)
if
code
==
78734254
then
return
17955766
elseif
code
==
13857930
then
return
17732278
end
return
code
end
local
code
=
dedup_represent
(
mc
:
GetCode
())
local
res
=
{
code
}
--- Add Fusion Tag names
for
_
,
eff
in
ipairs
({
mc
:
IsHasEffect
(
EFFECT_ADD_FUSION_CODE
)})
do
local
fusion_tag_code
=
eff
:
GetValue
()
assert
(
type
(
fusion_tag_code
)
==
'number'
)
table.insert
(
res
,
dedup_represent
(
fusion_tag_code
))
end
return
res
end
,
end
,
})
})
}
}
...
...
procedure.lua
View file @
6c193bc3
...
@@ -3420,395 +3420,303 @@ Fusion.LockedCodes=nil
...
@@ -3420,395 +3420,303 @@ Fusion.LockedCodes=nil
-- Refactored Fusion.AddFusionProcedure using the above helpers
-- Refactored Fusion.AddFusionProcedure using the above helpers
function
Fusion
.
AddFusionProcedure
(
c
,
opts
)
function
Fusion
.
AddFusionProcedure
(
c
,
opts
)
local
patterns
=
Fusion
.
BuildPatterns
(
opts
)
local
patterns
=
Fusion
.
BuildPatterns
(
opts
)
--=== Metadata registration ===--
--=== Metadata registration ===--
if
c
:
IsStatus
(
STATUS_COPYING_EFFECT
)
then
return
end
if
c
:
IsStatus
(
STATUS_COPYING_EFFECT
)
then
return
end
-- collect all fusion codes and compute global min/max across patterns
-- collect all fusion codes and compute global min/max across patterns
local
code_set
=
{}
local
code_set
=
{}
local
global_min
,
global_max
=
math.huge
,
0
local
global_min
,
global_max
=
math.huge
,
0
for
_
,
pat
in
ipairs
(
patterns
)
do
for
_
,
pat
in
ipairs
(
patterns
)
do
local
pmin
,
pmax
=
0
,
0
local
pmin
,
pmax
=
0
,
0
for
_
,
slot
in
ipairs
(
pat
.
slots
)
do
for
_
,
slot
in
ipairs
(
pat
.
slots
)
do
if
slot
.
match_code
then
if
slot
.
match_code
then
code_set
[
slot
.
match_code
]
=
true
code_set
[
slot
.
match_code
]
=
true
pmin
=
pmin
+
1
pmin
=
pmin
+
1
pmax
=
pmax
+
1
pmax
=
pmax
+
1
elseif
slot
.
match_codes
then
elseif
slot
.
match_codes
then
for
_
,
cd
in
ipairs
(
slot
.
match_codes
)
do
for
_
,
cd
in
ipairs
(
slot
.
match_codes
)
do
code_set
[
cd
]
=
true
code_set
[
cd
]
=
true
end
end
pmin
=
pmin
+
1
pmin
=
pmin
+
1
pmax
=
pmax
+
1
pmax
=
pmax
+
1
elseif
slot
.
group
then
elseif
slot
.
group
then
pmin
=
pmin
+
slot
.
group
.
min
pmin
=
pmin
+
slot
.
group
.
min
pmax
=
pmax
+
slot
.
group
.
max
pmax
=
pmax
+
slot
.
group
.
max
else
else
-- generic filter-slot
-- generic filter-slot
pmin
=
pmin
+
1
pmin
=
pmin
+
1
pmax
=
pmax
+
1
pmax
=
pmax
+
1
end
end
end
end
global_min
=
math.min
(
global_min
,
pmin
)
global_min
=
math.min
(
global_min
,
pmin
)
global_max
=
math.max
(
global_max
,
pmax
)
global_max
=
math.max
(
global_max
,
pmax
)
end
end
local
mt
=
getmetatable
(
c
)
local
mt
=
getmetatable
(
c
)
if
mt
.
material
==
nil
then
if
mt
.
material
==
nil
then
mt
.
material
=
code_set
mt
.
material
=
code_set
end
end
if
mt
.
material_count
==
nil
then
if
mt
.
material_count
==
nil
then
mt
.
material_count
=
{
global_min
,
global_max
}
mt
.
material_count
=
{
global_min
,
global_max
}
end
end
for
cd
,
_
in
pairs
(
code_set
)
do
for
cd
,
_
in
pairs
(
code_set
)
do
Auxiliary
.
AddCodeList
(
c
,
cd
)
Auxiliary
.
AddCodeList
(
c
,
cd
)
end
end
--=== Effect registration ===--
--=== Effect registration ===--
local
e
=
Effect
.
CreateEffect
(
c
)
local
e
=
Effect
.
CreateEffect
(
c
)
e
:
SetType
(
EFFECT_TYPE_SINGLE
)
e
:
SetType
(
EFFECT_TYPE_SINGLE
)
e
:
SetProperty
(
EFFECT_FLAG_CANNOT_DISABLE
+
EFFECT_FLAG_UNCOPYABLE
)
e
:
SetProperty
(
EFFECT_FLAG_CANNOT_DISABLE
+
EFFECT_FLAG_UNCOPYABLE
)
e
:
SetCode
(
EFFECT_FUSION_MATERIAL
)
e
:
SetCode
(
EFFECT_FUSION_MATERIAL
)
e
:
SetCondition
(
Fusion
.
FusionCondition
(
c
,
patterns
))
e
:
SetCondition
(
Fusion
.
FusionCondition
(
c
,
patterns
))
e
:
SetOperation
(
Fusion
.
FusionOperation
(
c
,
patterns
))
e
:
SetOperation
(
Fusion
.
FusionOperation
(
c
,
patterns
))
e
:
SetDescription
(
1379
)
e
:
SetDescription
(
1379
)
c
:
RegisterEffect
(
e
)
c
:
RegisterEffect
(
e
)
end
end
---@param allow_extras boolean whether to allow extra materials for checking propose
---@param allow_extras boolean whether to allow extra materials for checking propose
function
Fusion
.
BasicCondition
(
tc
,
slots
,
mat_filter
,
fgoalcheck
,
allow_extras
,
selected
)
function
Fusion
.
BasicCondition
(
tc
,
slots
,
mat_filter
,
fgoalcheck
,
allow_extras
,
selected
)
--g:Material group(nil for Instant Fusion)
return
function
(
e
,
g
,
gc
,
chkf
)
--gc:Material already used
--chkf: check field, default:PLAYER_NONE
--chkf&0x100: Not fusion summon, can use substitute (Hex-Sealed Fusion)
--chkf&0x200: Not fusion summon, can't use substitute ("Contact Fusion", Neos Fusion)
return
function
(
e
,
g
,
gc
,
chkf
)
if
not
g
then
return
false
end
if
not
g
then
return
false
end
-- array
-- Collect cards and apply global mat_filter
local
cards
=
{}
local
cards
=
{}
for
mc
in
aux
.
Next
(
g
)
do
for
c
in
aux
.
Next
(
g
)
do
table.insert
(
cards
,
mc
)
if
not
mat_filter
or
mat_filter
(
c
,
tc
)
then
table.insert
(
cards
,
c
)
end
end
end
-- apply the global mat_filter if present
if
mat_filter
~=
nil
then
-- Split slots into singles and (at most one) group
local
tg
=
{}
local
single_slots
=
{}
for
_
,
mc
in
ipairs
(
cards
)
do
local
grp
if
mat_filter
(
mc
,
tc
)
then
for
_
,
slot
in
ipairs
(
slots
)
do
table.insert
(
tg
,
mc
)
if
slot
.
group
then
end
assert
(
not
grp
,
"only one group slot supported"
)
end
grp
=
slot
.
group
cards
=
tg
else
end
table.insert
(
single_slots
,
slot
)
if
#
cards
<#
slots
then
return
false
end
end
-- split slots
end
local
single_slots
,
group_slots
=
{},
{}
for
_
,
slot
in
ipairs
(
slots
)
do
-- Quick minimal‐count check
if
slot
.
group
then
local
total_min
=#
single_slots
+
(
grp
and
grp
.
min
or
0
)
table.insert
(
group_slots
,
slot
.
group
)
if
#
cards
<
total_min
then
else
table.insert
(
single_slots
,
slot
)
end
end
-- minimal total count
local
total_req
=#
single_slots
for
_
,
grp
in
ipairs
(
group_slots
)
do
total_req
=
total_req
+
grp
.
min
end
if
g
:
GetCount
()
<
total_req
then
return
false
return
false
end
end
--
prepare per-slot lock flags (lock only first matching slot per code or code list)
--
Prepare locked flags for substitute materials
local
locked
_slots
=
{}
local
locked
=
{}
if
Fusion
.
LockedCodes
then
if
Fusion
.
LockedCodes
then
for
_
,
code
in
ipairs
(
Fusion
.
LockedCodes
)
do
for
_
,
code
in
ipairs
(
Fusion
.
LockedCodes
)
do
for
i
dx
,
slot
in
ipairs
(
single_slots
)
do
for
i
,
slot
in
ipairs
(
single_slots
)
do
--
lock slot if its match_code equals code
--
direct match_code?
if
slot
.
match_code
==
code
then
if
slot
.
match_code
==
code
then
locked
_slots
[
idx
]
=
true
locked
[
i
]
=
true
break
break
end
end
--
lock slot if it's a multi-code slot containing code
--
or match_codes list?
if
slot
.
match_codes
then
if
slot
.
match_codes
then
for
_
,
c
in
ipairs
(
slot
.
match_codes
)
do
for
_
,
c
2
in
ipairs
(
slot
.
match_codes
)
do
if
c
==
code
then
if
c
2
==
code
then
locked
_slots
[
idx
]
=
true
locked
[
i
]
=
true
break
break
end
end
end
end
if
locked_slots
[
idx
]
then
break
end
if
locked
[
i
]
then
break
end
end
end
end
end
end
end
end
end
local
used
=
{}
-- 5) DFS assign single‐slots
local
sub_count
=
0
local
used
=
{}
-- DFS assign slots
local
sub_count
=
0
local
function
dfs
(
i
)
local
function
dfs_singles
(
i
)
if
i
>#
single_slots
then
if
i
>#
single_slots
then
-- check group slots on leftovers
-- Build base material group (selected + assigned singles)
local
leftovers
=
{}
local
mg_base
=
selected
and
selected
:
Clone
()
or
Group
.
CreateGroup
()
for
idx
,
mc
in
ipairs
(
cards
)
do
if
not
used
[
idx
]
then
table.insert
(
leftovers
,
mc
)
end
end
-- if strict (no extra), ensure leftover only match group slots
if
not
allow_extras
then
for
_
,
mc
in
ipairs
(
leftovers
)
do
local
ok
=
false
for
_
,
grp
in
ipairs
(
group_slots
)
do
if
(
not
grp
.
filter
or
grp
.
filter
(
mc
))
then
ok
=
true
break
end
end
if
not
ok
then
return
false
end
end
end
-- check each group-slot’s min/max its unique_by
for
_
,
grp
in
ipairs
(
group_slots
)
do
-- collect exactly the cards from leftovers that match this grp
local
matched
=
{}
for
_
,
mc
in
ipairs
(
leftovers
)
do
if
(
not
grp
.
filter
or
grp
.
filter
(
mc
))
then
table.insert
(
matched
,
mc
)
end
end
-- cardinality check
if
#
matched
<
grp
.
min
then
return
false
end
if
not
allow_extras
then
if
#
matched
>
grp
.
max
then
return
false
end
end
-- unique‐key check
if
grp
.
unique_by
then
if
allow_extras
then
-- in “search” mode we only require *some* subset of size grp.min
local
matcher
=
Fusion
.
UniqueByMatching
(
grp
.
unique_by
,
grp
.
min
)
if
not
matcher
(
matched
)
then
return
false
end
else
-- strict: need to cover *all* matched cards
local
matcher
=
Fusion
.
UniqueByMatching
(
grp
.
unique_by
,
#
matched
)
if
not
matcher
(
matched
)
then
return
false
end
end
end
-- has_same: each fn must overlap
if
grp
.
has_same
then
if
allow_extras
then
-- search-mode: find SOME subset of size grp.min satisfying both unique_by & all has_same
local
n
,
k
=#
matched
,
grp
.
min
local
comb
,
found
=
{},
false
local
function
check_subset
()
local
S
=
{}
for
_
,
idx_sub
in
ipairs
(
comb
)
do
table.insert
(
S
,
matched
[
idx_sub
])
end
-- unique_by on S
if
grp
.
unique_by
then
local
m
=
Fusion
.
UniqueByMatching
(
grp
.
unique_by
,
k
)
if
not
m
(
S
)
then
return
false
end
end
-- all has_same on S
for
_
,
fn
in
ipairs
(
grp
.
has_same
)
do
local
v0
=
fn
(
S
[
1
],
e
:
GetHandler
())
if
type
(
v0
)
==
"number"
then
local
mask
=
v0
for
j
=
2
,
#
S
do
mask
=
mask
&
fn
(
S
[
j
],
e
:
GetHandler
())
if
mask
==
0
then
return
false
end
end
else
local
common
=
{}
for
_
,
key
in
ipairs
(
v0
)
do
common
[
key
]
=
true
end
for
j
=
2
,
#
S
do
local
next_common
=
{}
for
_
,
key
in
ipairs
(
fn
(
S
[
j
],
e
:
GetHandler
()))
do
if
common
[
key
]
then
next_common
[
key
]
=
true
end
end
common
=
next_common
if
next
(
common
)
==
nil
then
return
false
end
end
end
end
-- build mg
local
mg
=
Group
.
FromCards
(
table.unpack
(
S
))
-- merge selected
mg
:
Merge
(
selected
)
-- merge in the DFS‐used cards
for
idx
,
flag
in
pairs
(
used
)
do
if
flag
then
mg
:
AddCard
(
cards
[
idx
])
end
end
if
fgoalcheck
then
if
not
fgoalcheck
(
mg
)
then
return
false
end
end
if
aux
.
FCheckAdditional
then
if
not
aux
.
FCheckAdditional
(
tc
:
GetOwner
(),
mg
,
tc
)
then
return
false
end
end
if
aux
.
FGoalCheckAdditional
then
if
not
aux
.
FGoalCheckAdditional
(
tc
:
GetOwner
(),
mg
,
tc
)
then
return
false
end
end
if
not
aux
.
TuneMagicianCheckAdditionalX
(
EFFECT_TUNE_MAGICIAN_F
)(
mg
)
then
return
false
end
-- ensure there's at least one free Monster Zone if chkf requests a field-checked summon
if
chkf
&
0xf
~=
PLAYER_NONE
then
local
tp
=
chkf
&
0xf
if
Duel
.
GetLocationCountFromEx
(
tp
,
tp
,
mg
,
tc
)
<=
0
then
return
false
end
end
return
true
end
local
function
dfs_comb
(
start
)
if
found
then
return
end
if
#
comb
==
k
then
if
check_subset
()
then
found
=
true
end
return
end
for
idx
=
start
,
n
do
table.insert
(
comb
,
idx
)
dfs_comb
(
idx
+
1
)
table.remove
(
comb
)
if
found
then
return
end
end
end
dfs_comb
(
1
)
if
not
found
then
return
false
end
else
for
_
,
fn
in
ipairs
(
grp
.
has_same
)
do
local
v0
=
fn
(
matched
[
1
],
e
:
GetHandler
())
if
type
(
v0
)
==
"number"
then
-- bitmask path
local
common_mask
=
v0
for
j
=
2
,
#
matched
do
common_mask
=
common_mask
&
fn
(
matched
[
j
],
e
:
GetHandler
())
if
common_mask
==
0
then
return
false
end
end
elseif
type
(
v0
)
==
"table"
then
-- list-of-keys path
local
common
=
{}
for
_
,
k
in
ipairs
(
v0
)
do
common
[
k
]
=
true
end
for
j
=
2
,
#
matched
do
local
next_common
=
{}
for
_
,
k
in
ipairs
(
fn
(
matched
[
j
],
e
:
GetHandler
()))
do
if
common
[
k
]
then
next_common
[
k
]
=
true
end
end
common
=
next_common
if
next
(
common
)
==
nil
then
return
false
end
end
else
error
(
"unsupported return type of group.has_same"
)
return
false
end
end
end
end
end
-- build mg
local
mg
=
selected
and
selected
:
Clone
()
or
Group
.
CreateGroup
()
-- merge in the DFS‐used cards
for
idx
,
flag
in
pairs
(
used
)
do
for
idx
,
flag
in
pairs
(
used
)
do
if
flag
then
if
flag
then
mg_base
:
AddCard
(
cards
[
idx
])
end
mg
:
AddCard
(
cards
[
idx
])
end
end
if
fgoalcheck
then
if
not
fgoalcheck
(
mg
)
then
return
false
end
end
if
aux
.
FCheckAdditional
then
if
not
aux
.
FCheckAdditional
(
tc
:
GetOwner
(),
mg
,
tc
)
then
return
false
end
end
end
if
aux
.
FGoalCheckAdditional
then
-- Build leftovers list
if
not
aux
.
FGoalCheckAdditional
(
tc
:
GetOwner
(),
mg
,
tc
)
then
local
leftovers
=
{}
return
false
for
idx
,
c
in
ipairs
(
cards
)
do
if
not
used
[
idx
]
then
table.insert
(
leftovers
,
c
)
end
end
end
end
if
not
aux
.
TuneMagicianCheckAdditionalX
(
EFFECT_TUNE_MAGICIAN_F
)(
mg
)
then
return
false
-- No group slot? Just do final checks
if
not
grp
then
return
Fusion
.
FinalCheck
(
e
,
mg_base
,
tc
,
fgoalcheck
,
chkf
)
end
end
-- ensure there's at least one free Monster Zone if chkf requests a field-checked summon
if
chkf
&
0xf
~=
PLAYER_NONE
then
-- Group slot exists: branch on allow_extras
local
tp
=
chkf
&
0xf
if
allow_extras
then
if
Duel
.
GetLocationCountFromEx
(
tp
,
tp
,
mg
,
tc
)
<=
0
then
-- search mode: try to find some subset of size grp.min
return
false
return
Fusion
.
SearchGroup
(
e
,
leftovers
,
mg_base
,
grp
,
tc
,
fgoalcheck
,
chkf
)
end
else
-- strict mode: leftovers must *all* satisfy grp
return
Fusion
.
StrictGroup
(
e
,
leftovers
,
mg_base
,
grp
,
tc
,
fgoalcheck
,
chkf
)
end
end
return
true
end
end
-- prune if not enough cards left
-- Prune if not enough cards remain
local
used_count
=
0
local
used_count
=
0
for
_
,
v
in
pairs
(
used
)
do
for
_
,
v
in
pairs
(
used
)
do
if
v
then
if
v
then
used_count
=
used_count
+
1
used_count
=
used_count
+
1
end
end
end
end
if
(
#
cards
-
used_count
)
<
(
#
single_slots
-
i
+
1
)
then
if
(
#
cards
-
used_count
)
<
(
#
single_slots
-
i
+
1
)
then
return
false
end
local
slot
=
single_slots
[
i
]
for
idx
,
c
in
ipairs
(
cards
)
do
if
not
used
[
idx
]
then
local
ok
,
is_sub
=
Fusion
.
MatchSlot
(
c
,
slot
,
tc
)
if
ok
and
not
(
is_sub
and
locked
[
i
])
then
used
[
idx
]
=
true
local
prev_sub
=
sub_count
if
is_sub
then
sub_count
=
sub_count
+
1
end
if
sub_count
<=
1
and
dfs_singles
(
i
+
1
)
then
return
true
end
used
[
idx
]
=
false
sub_count
=
prev_sub
end
end
end
return
false
end
return
dfs_singles
(
1
)
end
end
-- Final, post‐fusion checks (common to both modes)
function
Fusion
.
FinalCheck
(
e
,
mg
,
tc
,
fgoalcheck
,
chkf
)
if
fgoalcheck
and
not
fgoalcheck
(
mg
)
then
return
false
end
if
aux
.
FCheckAdditional
and
not
aux
.
FCheckAdditional
(
tc
:
GetOwner
(),
mg
,
tc
)
then
return
false
end
if
aux
.
FGoalCheckAdditional
and
not
aux
.
FGoalCheckAdditional
(
tc
:
GetOwner
(),
mg
,
tc
)
then
return
false
end
if
not
aux
.
TuneMagicianCheckAdditionalX
(
EFFECT_TUNE_MAGICIAN_F
)(
mg
)
then
return
false
end
if
(
chkf
&
0xf
)
~=
PLAYER_NONE
then
local
tp
=
chkf
&
0xf
if
Duel
.
GetLocationCountFromEx
(
tp
,
tp
,
mg
,
tc
)
<=
0
then
return
false
end
end
return
true
end
-- Strict mode: *all* leftovers must fit grp, then final check
function
Fusion
.
StrictGroup
(
e
,
leftovers
,
mg_base
,
grp
,
tc
,
fgoalcheck
,
chkf
)
-- filter leftovers by grp.filter
local
matched
=
{}
for
_
,
c
in
ipairs
(
leftovers
)
do
if
not
grp
.
filter
or
grp
.
filter
(
c
,
tc
)
then
table.insert
(
matched
,
c
)
else
return
false
end
end
-- cardinality
if
#
matched
<
grp
.
min
or
#
matched
>
grp
.
max
then
return
false
end
-- has_same on full matched
if
grp
.
has_same
and
not
Fusion
.
CheckHasSame
(
grp
,
Group
.
FromCards
(
table.unpack
(
matched
)),
e
)
then
return
false
end
-- unique_by on full matched
if
grp
.
unique_by
then
local
matcher
=
Fusion
.
UniqueByMatching
(
grp
.
unique_by
,
#
matched
)
if
not
matcher
(
matched
)
then
return
false
end
end
-- merge and final
local
mg
=
mg_base
:
Clone
()
for
_
,
c
in
ipairs
(
matched
)
do
mg
:
AddCard
(
c
)
end
return
Fusion
.
FinalCheck
(
e
,
mg
,
tc
,
fgoalcheck
,
chkf
)
end
-- Search mode: find *some* size‐grp.min subset that passes
function
Fusion
.
SearchGroup
(
e
,
leftovers
,
mg_base
,
grp
,
tc
,
fgoalcheck
,
chkf
)
-- filter leftovers by grp.filter
local
matched
=
{}
for
_
,
c
in
ipairs
(
leftovers
)
do
if
not
grp
.
filter
or
grp
.
filter
(
c
,
tc
)
then
table.insert
(
matched
,
c
)
end
end
if
#
matched
<
grp
.
min
then
return
false
end
local
comb
=
{}
local
function
dfs_comb
(
start
,
depth
)
if
depth
>
grp
.
min
then
-- subset built in comb[1..grp.min]
local
subG
=
Group
.
FromCards
(
table.unpack
(
comb
))
-- has_same
if
grp
.
has_same
and
not
Fusion
.
CheckHasSame
(
grp
,
subG
,
e
)
then
return
false
return
false
end
end
-- try each card
-- unique_by
local
slot
=
single_slots
[
i
]
if
grp
.
unique_by
then
for
idx
,
mc
in
ipairs
(
cards
)
do
local
matcher
=
Fusion
.
UniqueByMatching
(
grp
.
unique_by
,
grp
.
min
)
if
not
used
[
idx
]
then
if
not
matcher
(
comb
)
then
local
match
,
is_sub
=
Fusion
.
MatchSlot
(
mc
,
slot
,
tc
)
return
false
if
match
==
true
then
end
-- skip sub material if slot is locked
end
if
not
(
is_sub
and
locked_slots
[
i
])
then
-- merge & final
-- commit material
local
mg
=
mg_base
:
Clone
()
used
[
idx
]
=
true
mg
:
Merge
(
subG
)
local
prev_sub
=
sub_count
return
Fusion
.
FinalCheck
(
e
,
mg
,
tc
,
fgoalcheck
,
chkf
)
if
is_sub
then
sub_count
=
sub_count
+
1
end
end
if
sub_count
<=
1
and
dfs
(
i
+
1
)
then
return
true
for
i
=
start
,
#
matched
-
(
grp
.
min
-
depth
)
+
1
do
end
comb
[
depth
]
=
matched
[
i
]
-- backtrack
if
dfs_comb
(
i
+
1
,
depth
+
1
)
then
used
[
idx
]
=
false
return
true
sub_count
=
prev_sub
end
end
end
end
return
false
end
end
end
return
false
return
dfs_comb
(
1
,
1
)
end
end
return
dfs
(
1
)
end
-- Shared has_same checker (bitmask or key‐list)
function
Fusion
.
CheckHasSame
(
grp
,
group_obj
,
e
)
local
cards
=
{}
for
c
in
aux
.
Next
(
group_obj
)
do
table.insert
(
cards
,
c
)
end
for
_
,
fn
in
ipairs
(
grp
.
has_same
)
do
local
v0
=
fn
(
cards
[
1
],
e
:
GetHandler
())
if
type
(
v0
)
==
"number"
then
local
mask
=
v0
for
j
=
2
,
#
cards
do
mask
=
mask
&
fn
(
cards
[
j
],
e
:
GetHandler
())
if
mask
==
0
then
return
false
end
end
else
local
common
=
{}
for
_
,
k
in
ipairs
(
v0
)
do
common
[
k
]
=
true
end
for
j
=
2
,
#
cards
do
local
nextc
=
{}
for
_
,
k
in
ipairs
(
fn
(
cards
[
j
],
e
:
GetHandler
()))
do
if
common
[
k
]
then
nextc
[
k
]
=
true
end
end
common
=
nextc
if
not
next
(
common
)
then
return
false
end
end
end
end
return
true
end
end
--- Helper: check one card against a named slot
--- Helper: check one card against a named slot
...
@@ -3818,25 +3726,25 @@ end
...
@@ -3818,25 +3726,25 @@ end
---@return boolean match
---@return boolean match
---@return boolean is_sub
---@return boolean is_sub
function
Fusion
.
MatchSlot
(
mc
,
slot
,
tc
)
function
Fusion
.
MatchSlot
(
mc
,
slot
,
tc
)
-- exact code match
-- exact code match
if
slot
.
match_code
and
mc
:
IsFusionCode
(
slot
.
match_code
)
then
if
slot
.
match_code
and
mc
:
IsFusionCode
(
slot
.
match_code
)
then
return
true
,
false
return
true
,
false
end
end
-- any-of-codes
-- any-of-codes
if
slot
.
match_codes
then
if
slot
.
match_codes
then
for
_
,
code
in
ipairs
(
slot
.
match_codes
)
do
for
_
,
code
in
ipairs
(
slot
.
match_codes
)
do
if
mc
:
IsFusionCode
(
code
)
then
if
mc
:
IsFusionCode
(
code
)
then
return
true
,
false
return
true
,
false
end
end
end
end
end
end
-- filter
-- filter
if
slot
.
filter
and
slot
.
filter
(
mc
,
tc
)
then
return
true
,
false
end
if
slot
.
filter
and
slot
.
filter
(
mc
,
tc
)
then
return
true
,
false
end
-- generic substitute
-- generic substitute
if
slot
.
allow_sub
and
mc
:
CheckFusionSubstitute
(
tc
)
then
if
slot
.
allow_sub
and
mc
:
CheckFusionSubstitute
(
tc
)
then
return
true
,
true
return
true
,
true
end
end
return
false
,
false
return
false
,
false
end
end
-- Slot constructors
-- Slot constructors
...
@@ -3846,11 +3754,11 @@ Fusion.Slot={}
...
@@ -3846,11 +3754,11 @@ Fusion.Slot={}
---@param opts table? {allow_sub?:boolean, allow_parasite?:boolean}
---@param opts table? {allow_sub?:boolean, allow_parasite?:boolean}
---@return table
---@return table
function
Fusion
.
Slot
.
Code
(
code
,
opts
)
function
Fusion
.
Slot
.
Code
(
code
,
opts
)
opts
=
opts
or
{}
opts
=
opts
or
{}
return
{
return
{
match_code
=
code
,
match_code
=
code
,
allow_sub
=
opts
.
allow_sub
~=
false
,
allow_sub
=
opts
.
allow_sub
~=
false
,
}
}
end
end
--- Create a slot for multiple codes (default allow_sub = true)
--- Create a slot for multiple codes (default allow_sub = true)
...
@@ -3858,18 +3766,20 @@ end
...
@@ -3858,18 +3766,20 @@ end
---@param opts table? {allow_sub?:boolean, allow_parasite?:boolean}
---@param opts table? {allow_sub?:boolean, allow_parasite?:boolean}
---@return table
---@return table
function
Fusion
.
Slot
.
Codes
(
codes
,
opts
)
function
Fusion
.
Slot
.
Codes
(
codes
,
opts
)
opts
=
opts
or
{}
opts
=
opts
or
{}
return
{
return
{
match_codes
=
codes
,
match_codes
=
codes
,
allow_sub
=
opts
.
allow_sub
~=
false
,
allow_sub
=
opts
.
allow_sub
~=
false
,
}
}
end
end
--- Create a slot for generic filter (no substitutes)
--- Create a slot for generic filter (no substitutes)
---@param filter fun(mc:Card,tc:Card):boolean
---@param filter fun(mc:Card,tc:Card):boolean
---@return table
---@return table
function
Fusion
.
Slot
.
Filter
(
filter
)
function
Fusion
.
Slot
.
Filter
(
filter
)
return
{
local
base_filter
=
filter
filter
=
aux
.
AND
(
base_filter
,
function
(
mc
,
tc
)
return
not
mc
:
IsHasEffect
(
6205579
)
end
)
return
{
filter
=
filter
,
filter
=
filter
,
allow_sub
=
false
allow_sub
=
false
}
}
...
@@ -3881,185 +3791,184 @@ end
...
@@ -3881,185 +3791,184 @@ end
function
Fusion
.
Slot
.
Group
(
opts
)
function
Fusion
.
Slot
.
Group
(
opts
)
opts
.
min
=
opts
.
min
or
1
opts
.
min
=
opts
.
min
or
1
opts
.
max
=
opts
.
max
or
math.huge
opts
.
max
=
opts
.
max
or
math.huge
opts
.
filter
=
opts
.
filter
or
aux
.
TRUE
local
base_filter
=
opts
.
filter
or
aux
.
TRUE
return
{
opts
.
filter
=
aux
.
AND
(
base_filter
,
function
(
mc
,
tc
)
return
not
mc
:
IsHasEffect
(
6205579
)
end
)
return
{
group
=
opts
group
=
opts
}
}
end
end
--- FindAllMapping
--- FindAllMapping
s
--- @param cards_sel Card[] currently picked materials
--- @param cards_sel Card[] currently picked materials
--- @param slots table[] your original slots spec
--- @param slots table[]
your original slots spec
--- @param tc Card the fusion monster (for substitutes)
--- @param tc Card
the fusion monster (for substitutes)
--- @return table[] an array of mapping tables (or empty if none)
--- @return table[] an array of mapping tables (or empty if none)
function
Fusion
.
FindAllMappings
(
cards_sel
,
slots
,
tc
)
function
Fusion
.
FindAllMappings
(
cards_sel
,
slots
,
tc
)
--- empty selection, just return
--- empty selection, just return
one “empty” mapping
if
#
cards_sel
==
0
then
if
#
cards_sel
==
0
then
return
{{}}
return
{{}}
end
end
-- Trackers
local
filled
=
{}
-- for single slots
-- Trackers: single‐slots vs group‐slots
local
group_assigned
=
{}
-- how many cards assigned so far to each group slot
local
filled
=
{}
-- filled[i] = true if single slot i is already taken
for
i
,
slot
in
ipairs
(
slots
)
do
local
group_assigned
=
{}
-- group_assigned[i] = count assigned to slots[i].group
if
slot
.
group
then
for
i
,
slot
in
ipairs
(
slots
)
do
group_assigned
[
i
]
=
0
if
slot
.
group
then
else
group_assigned
[
i
]
=
0
filled
[
i
]
=
false
else
end
filled
[
i
]
=
false
end
end
end
-- prepare locked_slots for this mapping
local
locked_slots
=
{}
-- Prepare locked_slots for substitute‐material restrictions
if
Fusion
.
LockedCodes
then
local
locked_slots
=
{}
for
_
,
code
in
ipairs
(
Fusion
.
LockedCodes
)
do
if
Fusion
.
LockedCodes
then
for
idx
,
slot
in
ipairs
(
slots
)
do
for
_
,
code
in
ipairs
(
Fusion
.
LockedCodes
)
do
local
matched
=
false
for
idx
,
slot
in
ipairs
(
slots
)
do
if
slot
.
match_code
==
code
then
matched
=
true
local
matched
=
false
elseif
slot
.
match_codes
then
-- direct code match?
for
_
,
c
in
ipairs
(
slot
.
match_codes
)
do
if
slot
.
match_code
==
code
then
if
c
==
code
then
matched
=
true
-- or inside match_codes?
elseif
slot
.
match_codes
then
for
_
,
c2
in
ipairs
(
slot
.
match_codes
)
do
if
c2
==
code
then
matched
=
true
matched
=
true
break
break
end
end
end
end
end
end
if
matched
then
if
matched
then
locked_slots
[
idx
]
=
true
locked_slots
[
idx
]
=
true
break
break
-- stop scanning slots for this code
end
end
end
end
local
all_maps
=
{}
local
mapping
=
{}
-- mapping[j] = slot index assigned to cards_sel[j]
--- Depth‐first search over cards_sel
local
function
dfs
(
idx
,
sub_count
)
-- never use more than 1 substitute material
if
sub_count
>
1
then
return
end
-- all cards assigned: check per‐group unique_by, then record
if
idx
>#
cards_sel
then
for
i
,
slot
in
ipairs
(
slots
)
do
local
assigned
=
group_assigned
[
i
]
if
slot
.
group
and
slot
.
group
.
unique_by
and
assigned
>
0
then
-- collect the cards mapped to slot i
local
sel_group
=
{}
for
j
,
mc
in
ipairs
(
cards_sel
)
do
if
mapping
[
j
]
==
i
then
table.insert
(
sel_group
,
mc
)
end
end
-- run the uniqueness matcher on that set
local
matcher
=
Fusion
.
UniqueByMatching
(
slot
.
group
.
unique_by
,
#
sel_group
)
if
not
matcher
(
sel_group
)
then
return
-- fails unique_by → abandon this mapping
end
end
end
end
end
end
-- passed all unique_by checks → save it
end
local
copy
=
{}
for
k
,
v
in
pairs
(
mapping
)
do
local
all_maps
=
{}
local
mapping
=
{}
-- mapping[idx_in_cards_sel] = slot_index
local
function
dfs
(
idx
,
sub_count
)
-- prune: no more than 1 substitute
if
sub_count
>
1
then
return
end
if
idx
>#
cards_sel
then
-- Before recording, run unique_by on each group‐slot that has one
for
i
,
slot
in
ipairs
(
slots
)
do
if
slot
.
group
and
slot
.
group
.
unique_by
then
-- collect the cards assigned to slot i
local
sel_group
=
{}
for
j
,
tc
in
ipairs
(
cards_sel
)
do
if
mapping
[
j
]
==
i
then
table.insert
(
sel_group
,
tc
)
end
end
-- run the matcher: must cover all |sel_group| cards
local
matcher
=
Fusion
.
UniqueByMatching
(
slot
.
group
.
unique_by
,
#
sel_group
)
if
not
matcher
(
sel_group
)
then
-- this mapping fails uniqueness → prune it
return
end
end
end
-- passes all uniqueness checks → record it
local
copy
=
{}
for
k
,
v
in
pairs
(
mapping
)
do
copy
[
k
]
=
v
copy
[
k
]
=
v
end
end
table.insert
(
all_maps
,
copy
)
table.insert
(
all_maps
,
copy
)
return
return
end
end
local
mc
=
cards_sel
[
idx
]
local
mc
=
cards_sel
[
idx
]
local
any_match
=
false
-- track if we managed to assign tc
local
any_match
=
false
-- try single slots
-- try single‐slots
for
i
,
slot
in
ipairs
(
slots
)
do
for
i
,
slot
in
ipairs
(
slots
)
do
if
not
slot
.
group
and
not
filled
[
i
]
then
if
not
slot
.
group
and
not
filled
[
i
]
then
local
match
,
is_sub
=
Fusion
.
MatchSlot
(
mc
,
slot
,
tc
)
local
ok
,
is_sub
=
Fusion
.
MatchSlot
(
mc
,
slot
,
tc
)
if
match
then
if
ok
and
not
(
is_sub
and
locked_slots
[
i
])
then
-- skip if substitute and this slot is locked
local
new_sub
=
sub_count
+
(
is_sub
and
1
or
0
)
if
not
(
is_sub
and
locked_slots
[
i
])
then
if
new_sub
<=
1
then
local
new_sub
=
sub_count
+
(
is_sub
and
1
or
0
)
any_match
=
true
if
new_sub
<=
1
then
filled
[
i
]
=
true
any_match
=
true
mapping
[
idx
]
=
i
filled
[
i
]
=
true
dfs
(
idx
+
1
,
new_sub
)
mapping
[
idx
]
=
i
filled
[
i
]
=
false
dfs
(
idx
+
1
,
new_sub
)
mapping
[
idx
]
=
nil
filled
[
i
]
=
false
end
mapping
[
idx
]
=
nil
end
end
end
end
end
end
end
-- try the group‐slot, if any
end
for
i
,
slot
in
ipairs
(
slots
)
do
if
slot
.
group
then
-- try group slots
local
assigned
=
group_assigned
[
i
]
for
i
,
slot
in
ipairs
(
slots
)
do
if
assigned
<
slot
.
group
.
max
then
if
slot
.
group
then
-- filter check
local
assigned
=
group_assigned
[
i
]
if
not
slot
.
group
.
filter
or
slot
.
group
.
filter
(
mc
,
tc
)
then
if
assigned
<
slot
.
group
.
max
then
-- has_same check (if this isn’t the first in the group)
-- base filter
local
ok
=
true
if
not
slot
.
group
.
filter
or
slot
.
group
.
filter
(
mc
,
tc
)
then
if
slot
.
group
.
has_same
and
assigned
>
0
then
-- has_same check: only if there's already ≥1 in this slot
for
_
,
fn
in
ipairs
(
slot
.
group
.
has_same
)
do
local
ok
=
true
local
v0
=
fn
(
mc
,
tc
)
if
slot
.
group
.
has_same
and
assigned
>
0
then
if
type
(
v0
)
==
"number"
then
for
_
,
fn
in
ipairs
(
slot
.
group
.
has_same
)
do
local
mask
=
v0
-- start intersection with this candidate
for
j
=
1
,
idx
-
1
do
local
v0
=
fn
(
mc
,
tc
)
if
mapping
[
j
]
==
i
then
if
type
(
v0
)
==
"number"
then
mask
=
mask
&
fn
(
cards_sel
[
j
],
tc
)
-- bitmask path
if
mask
==
0
then
break
end
local
mask
=
v0
end
-- AND with each already‐assigned card
end
for
j
=
1
,
idx
-
1
do
if
mask
==
0
then
ok
=
false
end
if
mapping
[
j
]
==
i
then
else
mask
=
mask
&
fn
(
cards_sel
[
j
],
tc
)
local
common
=
{}
if
mask
==
0
then
break
end
for
_
,
key
in
ipairs
(
v0
)
do
common
[
key
]
=
true
end
end
for
j
=
1
,
idx
-
1
do
end
if
mapping
[
j
]
==
i
then
if
mask
==
0
then
ok
=
false
end
local
nextc
=
{}
else
for
_
,
key
in
ipairs
(
fn
(
cards_sel
[
j
],
tc
))
do
-- list-of-keys path
if
common
[
key
]
then
nextc
[
key
]
=
true
end
local
common
=
{}
end
for
_
,
k
in
ipairs
(
v0
)
do
common
[
k
]
=
true
end
common
=
nextc
-- intersect with each already-assigned card
if
not
next
(
common
)
then
break
end
for
j
=
1
,
idx
-
1
do
end
if
mapping
[
j
]
==
i
then
end
local
next_c
=
{}
if
not
next
(
common
)
then
ok
=
false
end
for
_
,
k
in
ipairs
(
fn
(
cards_sel
[
j
],
tc
))
do
end
if
common
[
k
]
then
next_c
[
k
]
=
true
end
if
not
ok
then
break
end
end
end
common
=
next_c
end
if
not
next
(
common
)
then
break
end
end
if
ok
then
end
any_match
=
true
if
not
next
(
common
)
then
ok
=
false
end
group_assigned
[
i
]
=
assigned
+
1
end
mapping
[
idx
]
=
i
if
not
ok
then
break
end
dfs
(
idx
+
1
,
sub_count
)
end
group_assigned
[
i
]
=
assigned
end
mapping
[
idx
]
=
nil
end
if
ok
then
end
any_match
=
true
end
group_assigned
[
i
]
=
assigned
+
1
end
mapping
[
idx
]
=
i
end
dfs
(
idx
+
1
,
sub_count
)
group_assigned
[
i
]
=
assigned
-- if nothing matched, abandon this branch
mapping
[
idx
]
=
nil
if
not
any_match
then
end
return
end
end
end
end
end
end
dfs
(
1
,
0
)
return
all_maps
-- If we couldn’t assign tc to any slot, prune this branch
if
not
any_match
then
return
end
end
dfs
(
1
,
0
)
return
all_maps
end
end
--- “Given all mappings of sel into slots, can any one of them be completed by drawing from eg?”
--- “Given all mappings of sel into slots, can any one of them be completed by drawing from eg?”
--- @param sel Group currently picked materials
--- @param sel Group currently picked materials
--- @param eg Group the full pool (e.g. your material group)
--- @param eg Group the full pool (e.g. your material group)
...
@@ -4067,75 +3976,75 @@ end
...
@@ -4067,75 +3976,75 @@ end
--- @param tc Card the Fusion monster (for substitutes)
--- @param tc Card the Fusion monster (for substitutes)
--- @return boolean true if ∃ a mapping + completion path
--- @return boolean true if ∃ a mapping + completion path
function
Fusion
.
CanCompleteFromMappings
(
e
,
sel
,
eg
,
slots
,
mat_filter
,
fgoalcheck
,
tc
,
gc
,
chkf
)
function
Fusion
.
CanCompleteFromMappings
(
e
,
sel
,
eg
,
slots
,
mat_filter
,
fgoalcheck
,
tc
,
gc
,
chkf
)
-- arrayify sel so we can refer by index
-- arrayify sel so we can refer by index
local
cards_sel
=
{}
local
cards_sel
=
{}
for
c
in
aux
.
Next
(
sel
)
do
for
c
in
aux
.
Next
(
sel
)
do
table.insert
(
cards_sel
,
c
)
table.insert
(
cards_sel
,
c
)
end
end
-- get every possible assignment of sel→slots
-- get every possible assignment of sel→slots
local
mappings
=
Fusion
.
FindAllMappings
(
cards_sel
,
slots
,
tc
)
local
mappings
=
Fusion
.
FindAllMappings
(
cards_sel
,
slots
,
tc
)
if
#
mappings
==
0
then
if
#
mappings
==
0
then
return
false
return
false
end
end
-- build rem_pool = eg minus sel
-- build rem_pool = eg minus sel
local
rem_pool
=
Group
.
CreateGroup
()
local
rem_pool
=
Group
.
CreateGroup
()
for
c
in
aux
.
Next
(
eg
)
do
for
c
in
aux
.
Next
(
eg
)
do
if
not
sel
:
IsContains
(
c
)
then
if
not
sel
:
IsContains
(
c
)
then
rem_pool
:
AddCard
(
c
)
rem_pool
:
AddCard
(
c
)
end
end
end
end
-- for each mapping, build what’s still needed and test dfs_fill
-- for each mapping, build what’s still needed and test dfs_fill
for
_
,
mapping
in
ipairs
(
mappings
)
do
for
_
,
mapping
in
ipairs
(
mappings
)
do
-- track what’s been filled
-- track what’s been filled
local
filled_single
=
{}
local
filled_single
=
{}
local
group_need
=
{}
local
group_need
=
{}
for
i
,
slot
in
ipairs
(
slots
)
do
for
i
,
slot
in
ipairs
(
slots
)
do
if
slot
.
group
then
if
slot
.
group
then
group_need
[
i
]
=
slot
.
group
.
min
group_need
[
i
]
=
slot
.
group
.
min
else
else
filled_single
[
i
]
=
false
filled_single
[
i
]
=
false
end
end
end
end
-- consume sel into those trackers
-- consume sel into those trackers
for
_
,
slot_idx
in
pairs
(
mapping
)
do
for
_
,
slot_idx
in
pairs
(
mapping
)
do
local
slot
=
slots
[
slot_idx
]
local
slot
=
slots
[
slot_idx
]
if
slot
.
group
then
if
slot
.
group
then
group_need
[
slot_idx
]
=
group_need
[
slot_idx
]
-
1
group_need
[
slot_idx
]
=
group_need
[
slot_idx
]
-
1
else
else
filled_single
[
slot_idx
]
=
true
filled_single
[
slot_idx
]
=
true
end
end
end
end
-- build the “leftover” slots list
-- build the “leftover” slots list
local
rem_slots
=
{}
local
rem_slots
=
{}
-- rebuild dynamic locks per mapping
-- rebuild dynamic locks per mapping
local
old_locked
=
Fusion
.
LockedCodes
local
old_locked
=
Fusion
.
LockedCodes
local
new_locked
=
{}
local
new_locked
=
{}
if
old_locked
then
if
old_locked
then
for
_
,
code
in
ipairs
(
old_locked
)
do
for
_
,
code
in
ipairs
(
old_locked
)
do
-- find slot index for this code
-- find slot index for this code
for
j
,
slot
in
ipairs
(
slots
)
do
for
j
,
slot
in
ipairs
(
slots
)
do
local
matches
=
(
slot
.
match_code
==
code
or
(
slot
.
match_codes
and
(
function
()
local
matches
=
(
slot
.
match_code
==
code
or
(
slot
.
match_codes
and
(
function
()
for
_
,
c
in
ipairs
(
slot
.
match_codes
)
do
if
c
==
code
then
return
true
end
end
;
return
false
for
_
,
c
in
ipairs
(
slot
.
match_codes
)
do
if
c
==
code
then
return
true
end
end
;
return
false
end
)()))
end
)()))
if
matches
then
if
matches
then
-- if that slot was not filled, keep lock
-- if that slot was not filled, keep lock
if
not
filled_single
[
j
]
then
table.insert
(
new_locked
,
code
)
end
if
not
filled_single
[
j
]
then
table.insert
(
new_locked
,
code
)
end
break
break
end
end
end
end
end
end
end
end
Fusion
.
LockedCodes
=
new_locked
Fusion
.
LockedCodes
=
new_locked
for
i
,
slot
in
ipairs
(
slots
)
do
for
i
,
slot
in
ipairs
(
slots
)
do
if
slot
.
group
then
if
slot
.
group
then
local
need
=
group_need
[
i
]
or
0
local
need
=
group_need
[
i
]
or
0
-- if this slot has a unique_by, wrap its filter to exclude collided keys
-- if this slot has a unique_by, wrap its filter to exclude collided keys
local
new_filter
=
slot
.
group
.
filter
local
new_filter
=
slot
.
group
.
filter
if
slot
.
group
.
unique_by
then
if
slot
.
group
.
unique_by
then
...
@@ -4145,14 +4054,14 @@ function Fusion.CanCompleteFromMappings(e,sel,eg,slots,mat_filter,fgoalcheck,tc,
...
@@ -4145,14 +4054,14 @@ function Fusion.CanCompleteFromMappings(e,sel,eg,slots,mat_filter,fgoalcheck,tc,
-- if this slot has a has_same, wrap its filter to include only sames
-- if this slot has a has_same, wrap its filter to include only sames
if
slot
.
group
.
has_same
then
if
slot
.
group
.
has_same
then
-- only if there is at least one already‐selected card for this slot…
-- only if there is at least one already‐selected card for this slot…
local
has_sel
=
false
local
has_sel
=
false
for
_
,
mapped_i
in
pairs
(
mapping
)
do
for
_
,
mapped_i
in
pairs
(
mapping
)
do
if
mapped_i
==
i
then
if
mapped_i
==
i
then
has_sel
=
true
has_sel
=
true
break
break
end
end
end
end
if
has_sel
then
if
has_sel
then
-- precompute, for each fn in has_same, the overlap of already-mapped cards
-- precompute, for each fn in has_same, the overlap of already-mapped cards
local
used_overlap
=
{}
-- fn → (number mask) or (table of keys)
local
used_overlap
=
{}
-- fn → (number mask) or (table of keys)
for
_
,
fn
in
ipairs
(
slot
.
group
.
has_same
)
do
for
_
,
fn
in
ipairs
(
slot
.
group
.
has_same
)
do
...
@@ -4181,12 +4090,12 @@ function Fusion.CanCompleteFromMappings(e,sel,eg,slots,mat_filter,fgoalcheck,tc,
...
@@ -4181,12 +4090,12 @@ function Fusion.CanCompleteFromMappings(e,sel,eg,slots,mat_filter,fgoalcheck,tc,
local
prev
=
new_filter
local
prev
=
new_filter
new_filter
=
function
(
mc
,
p_tc
)
new_filter
=
function
(
mc
,
p_tc
)
-- original criteria
-- original criteria
if
prev
and
not
prev
(
mc
,
tc
)
then
if
prev
and
not
prev
(
mc
,
p_
tc
)
then
return
false
return
false
end
end
-- for each fn in has_same, check against the precomputed overlap
-- for each fn in has_same, check against the precomputed overlap
for
fn
,
overlap
in
pairs
(
used_overlap
)
do
for
fn
,
overlap
in
pairs
(
used_overlap
)
do
local
v
=
fn
(
mc
,
tc
)
local
v
=
fn
(
mc
,
p_
tc
)
if
type
(
overlap
)
==
"number"
then
if
type
(
overlap
)
==
"number"
then
-- bitmask path
-- bitmask path
if
(
overlap
&
v
)
==
0
then
if
(
overlap
&
v
)
==
0
then
...
@@ -4222,35 +4131,35 @@ function Fusion.CanCompleteFromMappings(e,sel,eg,slots,mat_filter,fgoalcheck,tc,
...
@@ -4222,35 +4131,35 @@ function Fusion.CanCompleteFromMappings(e,sel,eg,slots,mat_filter,fgoalcheck,tc,
}
}
})
})
end
end
else
else
if
not
filled_single
[
i
]
then
if
not
filled_single
[
i
]
then
-- single‐slot still unfilled
-- single‐slot still unfilled
table.insert
(
rem_slots
,
slot
)
table.insert
(
rem_slots
,
slot
)
end
end
end
end
end
end
-- if nothing remains, we exhausted slots, check fgoal
-- if nothing remains, we exhausted slots, check fgoal
if
#
rem_slots
==
0
then
if
#
rem_slots
==
0
then
if
(
fgoalcheck
==
nil
or
fgoalcheck
(
sel
)
==
true
)
and
(
aux
.
FGoalCheckAdditional
==
nil
or
aux
.
FGoalCheckAdditional
(
tc
:
GetOwner
(),
sel
,
tc
))
and
aux
.
TuneMagicianCheckAdditionalX
(
EFFECT_TUNE_MAGICIAN_F
)(
sel
)
then
if
Fusion
.
FinalCheck
(
e
,
sel
,
tc
,
fgoalcheck
,
chkf
)
then
Fusion
.
LockedCodes
=
old_locked
Fusion
.
LockedCodes
=
old_locked
return
true
return
true
else
else
Fusion
.
LockedCodes
=
old_locked
Fusion
.
LockedCodes
=
old_locked
return
false
return
false
end
end
end
end
-- delegate to FusionCondition (allow_extras =
true)
-- delegate to FusionCondition (allow_extras=
true)
local
search_cond
=
Fusion
.
BasicCondition
(
tc
,
rem_slots
,
mat_filter
,
fgoalcheck
,
true
,
sel
)
local
search_cond
=
Fusion
.
BasicCondition
(
tc
,
rem_slots
,
mat_filter
,
fgoalcheck
,
true
,
sel
)
if
search_cond
(
e
,
rem_pool
,
gc
,
chkf
)
then
if
search_cond
(
e
,
rem_pool
,
gc
,
chkf
)
then
Fusion
.
LockedCodes
=
old_locked
Fusion
.
LockedCodes
=
old_locked
return
true
return
true
end
end
Fusion
.
LockedCodes
=
old_locked
Fusion
.
LockedCodes
=
old_locked
end
end
return
false
return
false
end
end
--- Fusion.UniqueByMatching(key_func, min_needed)
--- Fusion.UniqueByMatching(key_func, min_needed)
...
@@ -4343,121 +4252,121 @@ end
...
@@ -4343,121 +4252,121 @@ end
--- @param used_keys table as returned above
--- @param used_keys table as returned above
--- @return fun(mc:Card,tc:Card):boolean
--- @return fun(mc:Card,tc:Card):boolean
function
Fusion
.
MakeExcludeFilter
(
orig_filter
,
key_func
,
used_keys
)
function
Fusion
.
MakeExcludeFilter
(
orig_filter
,
key_func
,
used_keys
)
return
function
(
mc
,
tc
)
return
function
(
mc
,
tc
)
-- first, pass the original criteria
-- first, pass the original criteria
if
orig_filter
and
not
orig_filter
(
mc
,
tc
)
then
if
orig_filter
and
not
orig_filter
(
mc
,
tc
)
then
return
false
return
false
end
end
-- then ensure no key overlaps
-- then ensure no key overlaps
for
_
,
k
in
ipairs
(
key_func
(
mc
))
do
for
_
,
k
in
ipairs
(
key_func
(
mc
))
do
if
used_keys
[
k
]
then
if
used_keys
[
k
]
then
return
false
return
false
end
end
end
end
return
true
return
true
end
end
end
end
-- Helper: build patterns list (single or variants)
-- Helper: build patterns list (single or variants)
function
Fusion
.
BuildPatterns
(
opts
)
function
Fusion
.
BuildPatterns
(
opts
)
local
patterns
=
{}
local
patterns
=
{}
if
opts
.
variants
then
if
opts
.
variants
then
for
_
,
v
in
ipairs
(
opts
.
variants
)
do
for
_
,
v
in
ipairs
(
opts
.
variants
)
do
table.insert
(
patterns
,{
table.insert
(
patterns
,{
slots
=
v
.
slots
,
slots
=
v
.
slots
,
mat_filter
=
v
.
mat_filter
,
mat_filter
=
v
.
mat_filter
,
fgoalcheck
=
v
.
fgoalcheck
or
aux
.
TRUE
,
fgoalcheck
=
v
.
fgoalcheck
or
aux
.
TRUE
,
})
})
end
end
else
else
table.insert
(
patterns
,{
table.insert
(
patterns
,{
slots
=
opts
.
slots
,
slots
=
opts
.
slots
,
mat_filter
=
opts
.
mat_filter
,
mat_filter
=
opts
.
mat_filter
,
fgoalcheck
=
opts
.
fgoalcheck
or
aux
.
TRUE
,
fgoalcheck
=
opts
.
fgoalcheck
or
aux
.
TRUE
,
})
})
end
end
return
patterns
return
patterns
end
end
-- Helper: combine multiple patterns into one condition function
-- Helper: combine multiple patterns into one condition function
function
Fusion
.
MultiCondition
(
tc
,
patterns
,
allow_extras
)
function
Fusion
.
MultiCondition
(
tc
,
patterns
,
allow_extras
)
return
function
(
e
,
g
,
gc
,
chkf
,
selected
)
return
function
(
e
,
g
,
gc
,
chkf
,
selected
)
if
not
g
then
return
false
end
if
not
g
then
return
false
end
local
locked
=
Fusion
.
LockedCodes
local
locked
=
Fusion
.
LockedCodes
for
_
,
pat
in
ipairs
(
patterns
)
do
for
_
,
pat
in
ipairs
(
patterns
)
do
-- skip patterns that don't include all locked codes
-- skip patterns that don't include all locked codes
local
ok
=
true
local
ok
=
true
if
locked
then
if
locked
then
for
_
,
code
in
ipairs
(
locked
)
do
for
_
,
code
in
ipairs
(
locked
)
do
if
not
Fusion
.
PatternIncludesCode
(
pat
,
code
)
then
if
not
Fusion
.
PatternIncludesCode
(
pat
,
code
)
then
ok
=
false
ok
=
false
break
break
end
end
end
end
end
end
if
ok
then
if
ok
then
local
cond
=
Fusion
.
BasicCondition
(
tc
,
pat
.
slots
,
pat
.
mat_filter
,
pat
.
fgoalcheck
,
allow_extras
,
selected
)
local
cond
=
Fusion
.
BasicCondition
(
tc
,
pat
.
slots
,
pat
.
mat_filter
,
pat
.
fgoalcheck
,
allow_extras
,
selected
)
if
cond
(
e
,
g
,
gc
,
chkf
)
then
if
cond
(
e
,
g
,
gc
,
chkf
)
then
return
true
return
true
end
end
end
end
end
end
return
false
return
false
end
end
end
end
-- Helper: compute the minimal and maximal material counts across patterns
-- Helper: compute the minimal and maximal material counts across patterns
function
Fusion
.
PatternsMinMax
(
patterns
)
function
Fusion
.
PatternsMinMax
(
patterns
)
local
min_req
=
math.huge
local
min_req
=
math.huge
local
max_req
=
0
local
max_req
=
0
for
_
,
pat
in
ipairs
(
patterns
)
do
for
_
,
pat
in
ipairs
(
patterns
)
do
local
lo
,
hi
=
0
,
0
local
lo
,
hi
=
0
,
0
for
_
,
slot
in
ipairs
(
pat
.
slots
)
do
for
_
,
slot
in
ipairs
(
pat
.
slots
)
do
if
slot
.
group
then
if
slot
.
group
then
lo
=
lo
+
slot
.
group
.
min
lo
=
lo
+
slot
.
group
.
min
hi
=
hi
+
slot
.
group
.
max
hi
=
hi
+
slot
.
group
.
max
else
else
lo
=
lo
+
1
lo
=
lo
+
1
hi
=
hi
+
1
hi
=
hi
+
1
end
end
end
end
min_req
=
math.min
(
min_req
,
lo
)
min_req
=
math.min
(
min_req
,
lo
)
max_req
=
math.max
(
max_req
,
hi
)
max_req
=
math.max
(
max_req
,
hi
)
end
end
return
min_req
,
max_req
return
min_req
,
max_req
end
end
-- Helper: combine patterns into one operation function
-- Helper: combine patterns into one operation function
function
Fusion
.
MultiOperation
(
tc
,
patterns
)
function
Fusion
.
MultiOperation
(
tc
,
patterns
)
return
function
(
e
,
tp
,
eg
,
ep
,
ev
,
re
,
r
,
rp
,
gc
,
chkf
,
selected
)
return
function
(
e
,
tp
,
eg
,
ep
,
ev
,
re
,
r
,
rp
,
gc
,
chkf
,
selected
)
-- filter patterns by any locked codes, once per operation
-- filter patterns by any locked codes, once per operation
local
locked
=
Fusion
.
LockedCodes
local
locked
=
Fusion
.
LockedCodes
local
active_patterns
=
{}
local
active_patterns
=
{}
if
locked
then
if
locked
then
for
_
,
pat
in
ipairs
(
patterns
)
do
for
_
,
pat
in
ipairs
(
patterns
)
do
local
ok
=
true
local
ok
=
true
for
_
,
code
in
ipairs
(
locked
)
do
for
_
,
code
in
ipairs
(
locked
)
do
if
not
Fusion
.
PatternIncludesCode
(
pat
,
code
)
then
if
not
Fusion
.
PatternIncludesCode
(
pat
,
code
)
then
ok
=
false
ok
=
false
break
break
end
end
end
end
if
ok
then
table.insert
(
active_patterns
,
pat
)
end
if
ok
then
table.insert
(
active_patterns
,
pat
)
end
end
end
else
else
-- no locked codes: all patterns active
-- no locked codes: all patterns active
active_patterns
=
patterns
active_patterns
=
patterns
end
end
-- compute min/max over active patterns
-- compute min/max over active patterns
local
min_req
,
max_req
=
Fusion
.
PatternsMinMax
(
active_patterns
)
local
min_req
,
max_req
=
Fusion
.
PatternsMinMax
(
active_patterns
)
max_req
=
math.min
(
max_req
,
eg
:
GetCount
())
max_req
=
math.min
(
max_req
,
eg
:
GetCount
())
-- strict condition uses only active patterns
-- strict condition uses only active patterns
local
strict_cond
=
Fusion
.
MultiCondition
(
tc
,
active_patterns
,
false
)
local
strict_cond
=
Fusion
.
MultiCondition
(
tc
,
active_patterns
,
false
)
Duel
.
Hint
(
HINT_SELECTMSG
,
tp
,
HINTMSG_FMATERIAL
)
Duel
.
Hint
(
HINT_SELECTMSG
,
tp
,
HINTMSG_FMATERIAL
)
local
sg
=
selected
and
selected
:
Clone
()
or
Group
.
CreateGroup
()
local
sg
=
selected
and
selected
:
Clone
()
or
Group
.
CreateGroup
()
--- arraify eg
--- arraify eg
local
eg_arr
=
{}
local
eg_arr
=
{}
...
@@ -4465,66 +4374,66 @@ function Fusion.MultiOperation(tc,patterns)
...
@@ -4465,66 +4374,66 @@ function Fusion.MultiOperation(tc,patterns)
table.insert
(
eg_arr
,
mc
)
table.insert
(
eg_arr
,
mc
)
end
end
while
true
do
while
true
do
local
finishable
=
sg
:
GetCount
()
>=
min_req
and
strict_cond
(
e
,
sg
,
gc
,
chkf
)
local
finishable
=
sg
:
GetCount
()
>=
min_req
and
strict_cond
(
e
,
sg
,
gc
,
chkf
)
local
addable
=
Group
.
CreateGroup
()
local
addable
=
Group
.
CreateGroup
()
for
_
,
mc
in
ipairs
(
eg_arr
)
do
for
_
,
mc
in
ipairs
(
eg_arr
)
do
if
not
sg
:
IsContains
(
mc
)
then
if
not
sg
:
IsContains
(
mc
)
then
sg
:
AddCard
(
mc
)
sg
:
AddCard
(
mc
)
-- completion test against active patterns
-- completion test against active patterns
for
_
,
pat
in
ipairs
(
active_patterns
)
do
for
_
,
pat
in
ipairs
(
active_patterns
)
do
if
Fusion
.
CanCompleteFromMappings
(
e
,
sg
,
eg
,
pat
.
slots
,
pat
.
mat_filter
,
pat
.
fgoalcheck
,
tc
,
gc
,
chkf
)
then
if
Fusion
.
CanCompleteFromMappings
(
e
,
sg
,
eg
,
pat
.
slots
,
pat
.
mat_filter
,
pat
.
fgoalcheck
,
tc
,
gc
,
chkf
)
then
addable
:
AddCard
(
mc
)
addable
:
AddCard
(
mc
)
break
break
end
end
end
end
sg
:
RemoveCard
(
mc
)
sg
:
RemoveCard
(
mc
)
end
end
end
end
if
addable
:
GetCount
()
==
0
then
if
addable
:
GetCount
()
==
0
then
assert
(
finishable
)
assert
(
finishable
)
Duel
.
SetFusionMaterial
(
sg
)
Duel
.
SetFusionMaterial
(
sg
)
return
return
end
end
local
picked
=
Group
.
SelectUnselect
(
addable
,
sg
,
tp
,
finishable
,
true
,
min_req
,
max_req
)
local
picked
=
Group
.
SelectUnselect
(
addable
,
sg
,
tp
,
finishable
,
true
,
min_req
,
max_req
)
if
not
picked
then
if
not
picked
then
if
finishable
then
if
finishable
then
Duel
.
SetFusionMaterial
(
sg
)
Duel
.
SetFusionMaterial
(
sg
)
else
else
Duel
.
SetFusionMaterial
(
Group
.
CreateGroup
())
Duel
.
SetFusionMaterial
(
Group
.
CreateGroup
())
end
end
return
return
end
end
if
sg
:
IsContains
(
picked
)
then
if
sg
:
IsContains
(
picked
)
then
-- If EFFECT_MUST_BE_FMATERIAL is on your field, ensure it's in selected
-- If EFFECT_MUST_BE_FMATERIAL is on your field, ensure it's in selected
local
must_materials
=
Duel
.
GetMustMaterial
(
tp
,
EFFECT_MUST_BE_FMATERIAL
)
local
must_materials
=
Duel
.
GetMustMaterial
(
tp
,
EFFECT_MUST_BE_FMATERIAL
)
if
picked
~=
gc
and
not
must_materials
:
IsContains
(
picked
)
then
if
picked
~=
gc
and
not
must_materials
:
IsContains
(
picked
)
then
sg
:
RemoveCard
(
picked
)
sg
:
RemoveCard
(
picked
)
end
end
else
else
sg
:
AddCard
(
picked
)
sg
:
AddCard
(
picked
)
end
end
end
end
end
end
end
end
-- Helper: detect if a pattern has a slot for a given code
-- Helper: detect if a pattern has a slot for a given code
function
Fusion
.
PatternIncludesCode
(
pat
,
code
)
function
Fusion
.
PatternIncludesCode
(
pat
,
code
)
for
_
,
slot
in
ipairs
(
pat
.
slots
)
do
for
_
,
slot
in
ipairs
(
pat
.
slots
)
do
if
slot
.
match_code
==
code
then
if
slot
.
match_code
==
code
then
return
true
return
true
end
end
if
slot
.
match_codes
then
if
slot
.
match_codes
then
for
_
,
c
in
ipairs
(
slot
.
match_codes
)
do
for
_
,
c
in
ipairs
(
slot
.
match_codes
)
do
if
c
==
code
then
return
true
end
if
c
==
code
then
return
true
end
end
end
end
end
end
end
return
false
return
false
end
end
--- A “seeded” condition that first tries CanCompleteFromMappings on each pattern,
--- A “seeded” condition that first tries CanCompleteFromMappings on each pattern,
...
...
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