Commit 6c193bc3 authored by Vury Leo's avatar Vury Leo

tuning 白の枢機竜

parent 0d056a5a
...@@ -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,
......
...@@ -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,
}) })
} }
......
...@@ -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 idx,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 _,c2 in ipairs(slot.match_codes) do
if c==code then if c2==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 --- FindAllMappings
--- @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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment