YGOPro Card Scripting Guide & Cookbook
A practical, example-driven guide to writing Lua scripts for the YGOPro
engine, organised the way a Yu-Gi-Oh player thinks about cards: by the
clauses on the card text. Sections 1–6 are the synthetic, didactic guide
(foundations, templates by card type, PSCT idioms, common pitfalls,
quick reference). Section 7 is the real-card cookbook — every example is
the actual script that ships with YGOPro, paired with the printed
English text from cards_en.cdb.
This is a working manual you read alongside the card you’re trying to
script. Skim 1 Foundations once. After that:
- For didactic templates by card type, jump to 2 Card Type Templates.
- For “what’s the idiom for this exact phrase?”, jump to 3 PSCT Cookbook.
- For “give me a real card I can copy from”, jump to 7 Real Card Cookbook.
Table of Contents
-
- 2.1 Vanilla Monster
- 2.2 Effect Monster
- 2.3 Normal Spell
- 2.4 Quick Play Spell
- 2.5 Continuous Spell
- 2.6 Equip Spell
- 2.7 Field Spell
- 2.8 Ritual Spell and Ritual Monster
- 2.9 Normal Trap
- 2.10 Continuous Trap
- 2.11 Counter Trap
- 2.12 Fusion Monster
- 2.13 Synchro Monster
- 2.14 Xyz Monster
- 2.15 Pendulum Monster
- 2.16 Link Monster
- 2.17 Token Creating Cards
-
- 7.1 Draw Effects
- 7.2 Destroy Effects
- 7.3 Banish and Bounce
- 7.4 Search and Mill
- 7.5 Special Summon
- 7.6 Procedure Summoned Monsters
- 7.7 Negation
- 7.8 Stat Changes and Battle
- 7.9 Continuous Restriction and Protection
- 7.10 Burn and Recovery
- 7.11 Equip and Control Change
- 7.12 Flip Effects
- 7.13 Hand Trap Chain Reactors
- 7.14 Activation Locks and Once Per Turn Variants
1. Foundations
1.1 Anatomy of a Card Script
Every card lives in a single file named c<password>.lua in the
script/ folder. The password is the 8-digit code printed on the card
(some cards have shorter codes — those work too).
The skeleton is always the same:
--Name of the card (in the original language; comment only)
function c12345678.initial_effect(c)
-- here you create one Effect object per effect printed on the card
-- and register it on `c` (the card instance)
local e1 = Effect.CreateEffect(c)
-- ...configure e1...
c:RegisterEffect(e1)
local e2 = Effect.CreateEffect(c)
-- ...configure e2...
c:RegisterEffect(e2)
end
-- All your callback functions live alongside, namespaced by code:
function c12345678.condition(e,tp,eg,ep,ev,re,r,rp) ... end
function c12345678.target (e,tp,eg,ep,ev,re,r,rp,chk) ... end
function c12345678.operation(e,tp,eg,ep,ev,re,r,rp) ... end
The engine calls c12345678.initial_effect(c) exactly once when the card
is created. Inside it, you build up Effect objects describing the card’s
behaviour and attach each one to c with c:RegisterEffect(e). From
that point onward the engine takes over: it consults your effects’
condition/cost/target/operation callbacks at the right times.
Rule of thumb: if the card text says “you can …” or “you must …”,
that’s almost always one Effect. A card with three bullet-pointed
effects has three Effects ininitial_effect.
1.2 The Five Callbacks
Almost every Effect uses some subset of these five functions. They are
called in this order whenever the effect tries to fire:
| Callback | When | Default | Purpose |
|---|---|---|---|
| Condition | “Can I activate at all?” | always true | Gates the effect on game state (e.g. “if you control a Dragon”) |
| Cost | “Pay the cost.” | none | Pay LP, discard, banish, etc. (with chk==0 precheck) |
| Target | “Lock in targets.” | none | Choose targets and tell the engine what categories of cards will be touched |
| Operation | “Resolve the effect.” | none | Actually do the thing (destroy, draw, summon, …) |
| Value | continuous queries | none | Returns a number (ATK boost, level mod) or a boolean (immunity filter) |
You attach them with e:SetCondition(fn), e:SetCost(fn),
e:SetTarget(fn), e:SetOperation(fn), e:SetValue(v_or_fn).
1.3 Standard Parameter List
Every callback receives the same 8 parameters. Memorise them — you will
see them everywhere:
| Param | Meaning |
|---|---|
e |
The Effect itself. e:GetHandler() returns the card it’s on. |
tp |
“this player” — the controller of the effect (0 or 1). |
eg |
The group of cards involved in the triggering event. |
ep |
The player involved in the triggering event. |
ev |
The integer value of the triggering event (e.g. for EVENT_DAMAGE it’s the damage amount). |
re |
The triggering effect (the effect that caused this event). |
r |
The reason bitmask of the event. |
rp |
The player who caused the triggering effect. |
The target callback also receives chk as the 9th parameter (and
optionally chkc as the 10th):
function card.target(e,tp,eg,ep,ev,re,r,rp,chk,chkc)
if chkc then return ... end -- "is `chkc` a legal target?"
if chk==0 then return ... end -- "can I pick at least one target?"
-- otherwise: actually pick the targets
end
chk==0 is a precheck: the engine asks “can this effect be activated?”
without any side effects. Return true/false. When chk==1 you do
the real targeting (Duel.SelectTarget, etc.) and call
Duel.SetOperationInfo so the chain knows what categories of cards will
be touched.
1.4 Effect Type vs Effect Code vs Event
There are three knobs that together describe what an effect is:
-
SetType(...)— what kind of ability this is. Combine with+.EFFECT_TYPE_*Card-text phrase SINGLEapplies to this single card (fields on it) FIELDapplies to multiple cards / players (must be on the field) EQUIPgranted to the equipped target ACTIONScan be activated as an action (auto-added by trigger types) ACTIVATEspell/trap activation (the printed text of a S/T) IGNITION“During your Main Phase: …” (active monster ability) TRIGGER_Fmandatory trigger (“When/If X happens, …”) TRIGGER_Ooptional trigger (“When/If X happens, you can …”) QUICK_Fmandatory quick effect (rare — Royal Decree etc.) QUICK_Ooptional quick effect (“During either player’s turn: you can …”) FLIP“FLIP: …” CONTINUOUSalways-on / state machine (used for bookkeeping or grant chains) XMATERIALeffect that’s active only while this card is Xyz material GRANTgives an effect to other cards (Weather Painter etc.) TARGETinfluences the continuous targets of a continuous S/T -
SetCode(...)— what to listen for or what kind of thing this
effect is:- For
EFFECT_TYPE_SINGLE/FIELD/EQUIP(continuous-style effects), the
code is one of theEFFECT_*codes (e.g.EFFECT_UPDATE_ATTACK,
EFFECT_INDESTRUCTABLE,EFFECT_CANNOT_ATTACK). - For
EFFECT_TYPE_TRIGGER_*/QUICK_*/IGNITION/ACTIVATE, the code is
anEVENT_*(e.g.EVENT_SUMMON_SUCCESS,EVENT_DESTROYED,
EVENT_FREE_CHAIN,EVENT_CHAINING).
- For
-
SetCategory(...)— what kinds of things the effect does, as a
bitmask. The engine uses this for chain-resolving anti-targeting
(“Solemn Strike negates effects that … destroy”) and for client UX.
Always set it accurately; combine with+:e:SetCategory(CATEGORY_DESTROY+CATEGORY_TOHAND)
1.5 Range Reset CountLimit HintTiming
These four properties tune where and how often an effect can fire:
-
SetRange(loc1+loc2+...)— the locations the effect is active in.- Continuous effects on monsters:
LOCATION_MZONE. - Hand traps:
LOCATION_HAND. - Effects that work from the GY:
LOCATION_GRAVE. - Pendulum effects in the P-zone:
LOCATION_PZONE. - Quick-Play spells in the hand:
LOCATION_HAND(with the right
EFFECT_TYPE_ACTIVATEsetup the engine handles the on-field too).
- Continuous effects on monsters:
-
SetReset(reset_flag[, count])— when this effect goes away.
Common combos:RESET_EVENT+RESETS_STANDARD -- gone when the card moves zones RESET_PHASE+PHASE_END -- gone at end of phase RESET_EVENT+RESETS_STANDARD+RESET_PHASE+PHASE_END -- gone if it moves OR end of phase (typical for "until end of turn" effects) -
SetCountLimit(max[, code[, flag]])— “you can only use this
effect N times per turn”. The optionalcodelets multiple cards
share a counter (e.g. all “Snake-Eye” cards sharing1, 16178681). -
SetHintTiming(self_timing[, opp_timing])— auto-shows the player
a hint at certain times (e.g. ending phases). Mostly used for
Quick-Play spells and free-chain activations.
1.6 The Aux Library
utility.lua and procedure.lua define Auxiliary (aliased as aux),
a library of shared helpers. The most-used:
| Helper | What it gives you |
|---|---|
aux.Stringid(code, n) |
Look up the n-th printed string in this card’s text (for SetDescription) |
aux.AND(f1, f2, ...) / aux.OR(...) / aux.NOT(f) |
Combine filter functions |
aux.TRUE / aux.FALSE / aux.NULL |
constant-returning functions |
aux.Tuner(f) / aux.NonTuner(f) |
Synchro material filters |
aux.AddSynchroProcedure(c, tuner_f, nontuner_f, min, max) |
wires up Synchro Summon |
aux.AddXyzProcedure(c, filter, level, count, …) |
wires up Xyz Summon |
aux.AddLinkProcedure(c, filter, min, max[, gcheck]) |
wires up Link Summon |
aux.AddFusionProcMix(...) |
wires up explicit Fusion Materials |
aux.EnableSpiritReturn(c, ...) |
adds the Spirit-monster bounce |
aux.EnableDualAttribute(c) |
adds Gemini state |
aux.NegateAnyFilter / aux.NegateMonsterFilter |
filters for “negate the effects of a face-up card” |
aux.bdcon / aux.bdocon / aux.bdgcon |
common conditions for EVENT_BATTLE_DESTROYING |
aux.exccon |
“except the turn this card was sent to the GY” |
aux.dscon |
“free chain ATK/DEF change” condition |
aux.indoval / aux.indsval |
filters for “by your/your opponent’s effects” |
2 Card Type Templates
Each section below gives the minimal working skeleton for a category
of card, then a worked example.
2.1 Vanilla Monster
A card with no effect text just needs an empty initial_effect:
--Battle Ox
function c5053103.initial_effect(c)
end
That’s literally it. The card’s stats come from the card database
(cards.cdb); the script is just an empty stub.
2.2 Effect Monster
The skeleton differs by clause type.
2.2.1 Ignition Effect (“During your Main Phase: …”)
function c12345678.initial_effect(c)
local e1 = Effect.CreateEffect(c)
e1:SetDescription(aux.Stringid(12345678, 0))
e1:SetCategory(CATEGORY_TOHAND) -- what the effect will do
e1:SetType(EFFECT_TYPE_IGNITION) -- it's an ignition effect
e1:SetRange(LOCATION_MZONE) -- usable from the monster zone
e1:SetCountLimit(1) -- once per turn
e1:SetCost(c12345678.cost)
e1:SetTarget(c12345678.target)
e1:SetOperation(c12345678.activate)
c:RegisterEffect(e1)
end
2.2.2 Trigger Effect — Optional (“When/If X happens, you can …”)
local e1 = Effect.CreateEffect(c)
e1:SetDescription(aux.Stringid(12345678, 0))
e1:SetType(EFFECT_TYPE_SINGLE+EFFECT_TYPE_TRIGGER_O) -- single = on this card
e1:SetCode(EVENT_SUMMON_SUCCESS) -- when summoned
e1:SetCountLimit(1)
e1:SetTarget(c12345678.target)
e1:SetOperation(c12345678.operation)
c:RegisterEffect(e1)
For triggers that listen to any card on the field doing the event, use
EFFECT_TYPE_FIELD+EFFECT_TYPE_TRIGGER_O instead and add
e:SetRange(LOCATION_MZONE).
2.2.3 Trigger Effect — Mandatory (“When/If X happens, …” without “you can”)
Same as optional but with EFFECT_TYPE_TRIGGER_F (Forced) instead of
EFFECT_TYPE_TRIGGER_O (Optional). Mandatory triggers cannot miss
timing.
2.2.4 Quick Effect (“During either player’s turn: you can …”)
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_QUICK_O)
e1:SetCode(EVENT_FREE_CHAIN) -- can chain at any open window
e1:SetRange(LOCATION_MZONE)
e1:SetCountLimit(1)
e1:SetTarget(c12345678.target)
e1:SetOperation(c12345678.operation)
c:RegisterEffect(e1)
For “in response to opponent activating an effect” use
EVENT_CHAINING instead of EVENT_FREE_CHAIN.
2.2.5 Continuous Effect (“This card gains 500 ATK for each …”)
Continuous effects use EFFECT_TYPE_SINGLE (or FIELD) plus a
specific EFFECT_* code. They have a Value instead of an
operation:
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_SINGLE)
e1:SetCode(EFFECT_UPDATE_ATTACK)
e1:SetProperty(EFFECT_FLAG_SINGLE_RANGE) -- only applies to this card
e1:SetRange(LOCATION_MZONE)
e1:SetValue(c12345678.atkval) -- function returning the ATK boost
c:RegisterEffect(e1)
function c12345678.atkval(e, c)
-- e.g. +500 for every Dragon you control
return Duel.GetMatchingGroupCount(Card.IsRace, e:GetHandlerPlayer(),
LOCATION_MZONE, 0, nil, RACE_DRAGON) * 500
end
2.2.6 Flip Effect (“FLIP: …”)
local e1 = Effect.CreateEffect(c)
e1:SetDescription(aux.Stringid(12345678, 0))
e1:SetCategory(CATEGORY_DESTROY)
e1:SetType(EFFECT_TYPE_SINGLE+EFFECT_TYPE_FLIP)
e1:SetTarget(c12345678.target)
e1:SetOperation(c12345678.operation)
c:RegisterEffect(e1)
The engine raises EVENT_FLIP automatically when the monster flips
face-up; you don’t need to set the code yourself.
2.3 Normal Spell
The simplest case. Activate from the hand or set, do something, go to GY.
--Pot of Greed
function c55144522.initial_effect(c)
local e1 = Effect.CreateEffect(c)
e1:SetCategory(CATEGORY_DRAW)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetProperty(EFFECT_FLAG_PLAYER_TARGET)
e1:SetCode(EVENT_FREE_CHAIN)
e1:SetTarget(c55144522.target)
e1:SetOperation(c55144522.activate)
c:RegisterEffect(e1)
end
function c55144522.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsPlayerCanDraw(tp,2) end
Duel.SetTargetPlayer(tp)
Duel.SetTargetParam(2)
Duel.SetOperationInfo(0,CATEGORY_DRAW,nil,0,tp,2)
end
function c55144522.activate(e,tp,eg,ep,ev,re,r,rp)
local p,d = Duel.GetChainInfo(0, CHAININFO_TARGET_PLAYER, CHAININFO_TARGET_PARAM)
Duel.Draw(p, d, REASON_EFFECT)
end
The two key calls:
Duel.SetOperationInfo(chain_index, category, target_group, count, player, param)— declares what the effect will do, so chain participants (“if you would draw…”) can react.Duel.GetChainInfo(0, …)reads the targets you set during the target step, in the operation step.
2.4 Quick Play Spell
A Normal Spell with EFFECT_TYPE_ACTIVATE and a hint timing window:
--Mystical Space Typhoon
function c5318639.initial_effect(c)
local e1 = Effect.CreateEffect(c)
e1:SetCategory(CATEGORY_DESTROY)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetCode(EVENT_FREE_CHAIN)
e1:SetHintTiming(0, TIMING_END_PHASE+TIMING_EQUIP)
e1:SetProperty(EFFECT_FLAG_CARD_TARGET)
e1:SetTarget(c5318639.target)
e1:SetOperation(c5318639.activate)
c:RegisterEffect(e1)
end
function c5318639.filter(c)
return c:IsType(TYPE_SPELL+TYPE_TRAP)
end
function c5318639.target(e,tp,eg,ep,ev,re,r,rp,chk,chkc)
if chkc then return chkc:IsOnField() and c5318639.filter(chkc) and chkc~=e:GetHandler() end
if chk==0 then return Duel.IsExistingTarget(c5318639.filter,tp,LOCATION_ONFIELD,LOCATION_ONFIELD,1,e:GetHandler()) end
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_DESTROY)
local g = Duel.SelectTarget(tp,c5318639.filter,tp,LOCATION_ONFIELD,LOCATION_ONFIELD,1,1,e:GetHandler())
Duel.SetOperationInfo(0,CATEGORY_DESTROY,g,1,0,0)
end
function c5318639.activate(e,tp,eg,ep,ev,re,r,rp)
local tc = Duel.GetFirstTarget()
if tc:IsRelateToEffect(e) then
Duel.Destroy(tc, REASON_EFFECT)
end
end
Notice the EFFECT_FLAG_CARD_TARGET property and the chkc branch in
target — that’s how a card-targeting spell exposes its “what would I
target?” preview for opposing chain hints.
2.5 Continuous Spell
function c12345678.initial_effect(c)
-- The activation itself
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetCode(EVENT_FREE_CHAIN)
c:RegisterEffect(e1)
-- The continuous effect that lives on the field
local e2 = Effect.CreateEffect(c)
e2:SetType(EFFECT_TYPE_FIELD)
e2:SetCode(EFFECT_UPDATE_ATTACK)
e2:SetRange(LOCATION_SZONE)
e2:SetTargetRange(LOCATION_MZONE, 0) -- all monsters you control
e2:SetValue(500)
c:RegisterEffect(e2)
end
2.6 Equip Spell
Two halves: an activation that targets a monster + tries to equip
it, and a continuous equip-only effect that’s bestowed on the
target while equipped.
function c12345678.initial_effect(c)
-- activation
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetCode(EVENT_FREE_CHAIN)
e1:SetProperty(EFFECT_FLAG_CARD_TARGET)
e1:SetCategory(CATEGORY_EQUIP)
e1:SetTarget(c12345678.target)
e1:SetOperation(c12345678.activate)
c:RegisterEffect(e1)
-- equip target restriction (e.g. only equip to Warriors)
local e2 = Effect.CreateEffect(c)
e2:SetType(EFFECT_TYPE_SINGLE)
e2:SetCode(EFFECT_EQUIP_LIMIT)
e2:SetProperty(EFFECT_FLAG_CANNOT_DISABLE)
e2:SetValue(c12345678.eqlimit)
c:RegisterEffect(e2)
-- continuous: +1000 ATK to equipped monster
local e3 = Effect.CreateEffect(c)
e3:SetType(EFFECT_TYPE_EQUIP)
e3:SetCode(EFFECT_UPDATE_ATTACK)
e3:SetValue(1000)
c:RegisterEffect(e3)
end
function c12345678.eqlimit(e, c) return c:IsRace(RACE_WARRIOR) end
function c12345678.filter(c) return c:IsFaceup() and c:IsRace(RACE_WARRIOR) end
function c12345678.target(e,tp,eg,ep,ev,re,r,rp,chk,chkc)
if chkc then return chkc:IsLocation(LOCATION_MZONE) and c12345678.filter(chkc) end
if chk==0 then return Duel.IsExistingTarget(c12345678.filter,tp,LOCATION_MZONE,LOCATION_MZONE,1,nil) end
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_EQUIP)
Duel.SelectTarget(tp,c12345678.filter,tp,LOCATION_MZONE,LOCATION_MZONE,1,1,nil)
end
function c12345678.activate(e,tp,eg,ep,ev,re,r,rp)
local c = e:GetHandler()
local tc = Duel.GetFirstTarget()
if c:IsRelateToEffect(e) and tc:IsRelateToEffect(e) and tc:IsFaceup() then
Duel.Equip(tp, c, tc)
end
end
2.7 Field Spell
Continuous Spell with EFFECT_TYPE_FIELD/SetRange(LOCATION_FZONE) for
the on-field continuous effects, plus the activation. Often have a
“once per turn” trigger as well.
2.8 Ritual Spell and Ritual Monster
Use the aux.AddRitualProcedure* helpers. The ritual spell typically
does:
aux.AddRitualProcEqual2(c, c12345678.filter)
and provides a filter(c) that returns true for the ritual monsters it
can summon. The ritual monster itself just needs c:EnableReviveLimit()
in its initial_effect plus its actual effects.
2.9 Normal Trap
--Mirror Force
function c44095762.initial_effect(c)
local e1 = Effect.CreateEffect(c)
e1:SetCategory(CATEGORY_DESTROY)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetCode(EVENT_ATTACK_ANNOUNCE)
e1:SetCondition(c44095762.condition)
e1:SetTarget(c44095762.target)
e1:SetOperation(c44095762.activate)
c:RegisterEffect(e1)
end
function c44095762.condition(e,tp,eg,ep,ev,re,r,rp)
return tp ~= Duel.GetTurnPlayer() -- opponent must be attacking
end
function c44095762.filter(c) return c:IsAttackPos() end
function c44095762.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(c44095762.filter,tp,0,LOCATION_MZONE,1,nil) end
local g = Duel.GetMatchingGroup(c44095762.filter,tp,0,LOCATION_MZONE,nil)
Duel.SetOperationInfo(0,CATEGORY_DESTROY,g,g:GetCount(),0,0)
end
function c44095762.activate(e,tp,eg,ep,ev,re,r,rp)
local g = Duel.GetMatchingGroup(c44095762.filter,tp,0,LOCATION_MZONE,nil)
if g:GetCount()>0 then Duel.Destroy(g, REASON_EFFECT) end
end
The EVENT_ATTACK_ANNOUNCE code makes this trigger on attack
declaration. The condition tp ~= Duel.GetTurnPlayer() ensures you
aren’t the one attacking.
2.10 Continuous Trap
Same shape as Continuous Spell:
--Imperial Iron Wall
function c30459350.initial_effect(c)
-- activation
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetCode(EVENT_FREE_CHAIN)
c:RegisterEffect(e1)
-- continuous: neither player can banish
local e2 = Effect.CreateEffect(c)
e2:SetType(EFFECT_TYPE_FIELD)
e2:SetCode(EFFECT_CANNOT_REMOVE)
e2:SetRange(LOCATION_SZONE)
e2:SetProperty(EFFECT_FLAG_PLAYER_TARGET)
e2:SetTargetRange(1, 1) -- both players
c:RegisterEffect(e2)
end
2.11 Counter Trap
Same as Normal Trap but the activation listens to EVENT_CHAINING and
uses Duel.NegateActivate(ev) / Duel.NegateEffect(ev) in the
operation. A common condition helper is aux.NegateSummonCondition
(for negating summons).
2.12 Fusion Monster
function c12345678.initial_effect(c)
-- 1) the fusion procedure (what materials does the card need?)
aux.AddFusionProcCode2(c, 89943723, 71625222)
-- 2) revive limit (must be Fusion Summoned first to come back)
c:EnableReviveLimit()
-- 3) any extra effects
-- ...
end
The most-used helpers:
| Helper | Use case |
|---|---|
aux.AddFusionProcCode2(c, code1, code2) |
“ + ” |
aux.AddFusionProcCodeFun(c, code, fun) |
“ + 1 monster matching fun” |
aux.AddFusionProcMix(c, sub, ins, ...) |
mixed “1 of these + 1 of those” |
aux.AddFusionProcFun2(c, fun1, fun2) |
“1 fun1 + 1 fun2” |
2.13 Synchro Monster
function c12345678.initial_effect(c)
aux.AddSynchroProcedure(c,
nil, -- tuner filter (nil = any tuner)
aux.NonTuner(nil), -- non-tuner filter
1) -- min non-tuners
c:EnableReviveLimit()
-- any extra effects below
end
For “1 tuner + 1+ non-tuner Dragon-Type monsters”:
aux.AddSynchroProcedure(c, nil, aux.NonTuner(Card.IsRace, RACE_DRAGON), 1)
For multi-tuner cards like Crystal Wing use aux.AddSynchroMixProcedure.
2.14 Xyz Monster
function c84013237.initial_effect(c)
aux.AddXyzProcedure(c, nil, 4, 2) -- any 2 Level 4 monsters
c:EnableReviveLimit()
-- effects...
end
aux.xyz_number[84013237] = 39 -- "Number 39: …"
For “2 Level 4 Warrior-Type monsters”:
aux.AddXyzProcedure(c, aux.FilterBoolFunction(Card.IsRace, RACE_WARRIOR), 4, 2)
2.15 Pendulum Monster
A Pendulum monster has two effect blocks: monster effects (active in
MZONE) and pendulum effects (active in PZONE). Plus the pendulum
summoning procedure is automatic — you don’t need to wire it.
function c12345678.initial_effect(c)
-- pendulum scale stuff is automatic, given the printed scale
-- ----- Pendulum effect (activates from PZONE) -----
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_FIELD+EFFECT_TYPE_TRIGGER_O)
e1:SetCode(EVENT_PHASE+PHASE_END)
e1:SetRange(LOCATION_PZONE)
e1:SetCountLimit(1)
e1:SetTarget(c12345678.pendtarget)
e1:SetOperation(c12345678.pendop)
c:RegisterEffect(e1)
-- ----- Monster effect (activates from MZONE) -----
local e2 = Effect.CreateEffect(c)
e2:SetType(EFFECT_TYPE_SINGLE+EFFECT_TYPE_TRIGGER_O)
e2:SetCode(EVENT_SUMMON_SUCCESS)
e2:SetTarget(c12345678.montarget)
e2:SetOperation(c12345678.monop)
c:RegisterEffect(e2)
end
2.16 Link Monster
function c1861629.initial_effect(c)
aux.AddLinkProcedure(c,
aux.FilterBoolFunction(Card.IsLinkType, TYPE_EFFECT), -- Effect monster materials
2) -- exactly 2 (Link rating 2)
c:EnableReviveLimit()
-- "This card gains 500 ATK for each card it points to"
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_SINGLE)
e1:SetCode(EFFECT_UPDATE_ATTACK)
e1:SetProperty(EFFECT_FLAG_SINGLE_RANGE)
e1:SetRange(LOCATION_MZONE)
e1:SetValue(c1861629.atkval)
c:RegisterEffect(e1)
end
function c1861629.atkval(e, c) return c:GetLinkedGroupCount() * 500 end
For uniqueness checks (e.g. “must include 1+ Knightmare”):
aux.AddLinkProcedure(c, nil, 2, 2, c2857636.lcheck)
function c2857636.lcheck(g, lc)
return g:GetClassCount(Card.GetLinkCode) == g:GetCount() -- all different names
end
2.17 Token Creating Cards
function c12345678.activate(e,tp,eg,ep,ev,re,r,rp)
if Duel.GetLocationCount(tp, LOCATION_MZONE) <= 0 then return end
if not Duel.IsPlayerCanSpecialSummonMonster(tp, 73915052, 0, TYPES_TOKEN_MONSTER, 0, 0, 1, RACE_FIEND, ATTRIBUTE_DARK) then return end
local token = Duel.CreateToken(tp, 73915052) -- token code id (see your DB)
Duel.SpecialSummon(token, 0, tp, tp, false, false, POS_FACEUP_ATTACK)
end
3 PSCT Cookbook
This is the meat of the guide. Each subsection translates a piece of
printed card text into the script idiom that implements it.
Activation Cost Clauses
“Pay 1000 LP”
function card.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.CheckLPCost(tp, 1000) end
Duel.PayLPCost(tp, 1000)
end
“Pay half your LP”
function card.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return true end -- can always pay (LP can't go to 0 via Solemn-style cost)
Duel.PayLPCost(tp, math.floor(Duel.GetLP(tp)/2))
end
“Discard 1 card”
function card.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(Card.IsDiscardable, tp, LOCATION_HAND, 0, 1, nil) end
Duel.DiscardHand(tp, Card.IsDiscardable, 1, 1, REASON_COST+REASON_DISCARD)
end
“Discard this card to the GY (hand-trap cost)”
function card.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return e:GetHandler():IsDiscardable() end
Duel.SendtoGrave(e:GetHandler(), REASON_COST+REASON_DISCARD)
end
“Tribute 1 monster”
function card.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.CheckReleaseGroup(tp, nil, 1, nil) end
local g = Duel.SelectReleaseGroup(tp, nil, 1, 1, nil)
Duel.Release(g, REASON_COST)
end
“Banish 1 card from your GY”
function card.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(Card.IsAbleToRemoveAsCost, tp, LOCATION_GRAVE, 0, 1, nil) end
local g = Duel.SelectMatchingCard(tp, Card.IsAbleToRemoveAsCost, tp, LOCATION_GRAVE, 0, 1, 1, nil)
Duel.Remove(g, POS_FACEUP, REASON_COST)
end
“Detach 1 Xyz Material” (Xyz monster cost)
function card.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return e:GetHandler():CheckRemoveOverlayCard(tp, 1, REASON_COST) end
e:GetHandler():RemoveOverlayCard(tp, 1, 1, REASON_COST)
end
Targeting Clauses
The target callback has three roles (all in one function) selected by
the value of chk and the presence of chkc:
function card.target(e,tp,eg,ep,ev,re,r,rp,chk,chkc)
-- 1. chkc != nil → "is `chkc` a legal target?" (used by hover/preview)
if chkc then return chkc:IsLocation(LOCATION_MZONE) and card.filter(chkc) end
-- 2. chk == 0 → "can I activate this at all?"
if chk==0 then return Duel.IsExistingTarget(card.filter, tp, 0, LOCATION_MZONE, 1, nil) end
-- 3. chk == 1 → actually pick the targets
Duel.Hint(HINT_SELECTMSG, tp, HINTMSG_DESTROY)
local g = Duel.SelectTarget(tp, card.filter, tp, 0, LOCATION_MZONE, 1, 1, nil)
Duel.SetOperationInfo(0, CATEGORY_DESTROY, g, g:GetCount(), 0, 0)
end
Then in the operation:
function card.operation(e,tp,eg,ep,ev,re,r,rp)
local tc = Duel.GetFirstTarget()
if tc:IsRelateToEffect(e) then -- targets can leave the field between target+resolve
Duel.Destroy(tc, REASON_EFFECT)
end
end
Always check
IsRelateToEffect(e)before acting on a target. The
target may have moved zones between target-time and resolve-time
(chained removal) and the engine considers that a “lost target”.
Movement Clauses
“Destroy 1 card on the field”
Already covered in section 2.4 above. Operation: Duel.Destroy(tc, REASON_EFFECT).
“Send 1 card from your Deck to the GY”
function card.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(card.tgfilter, tp, LOCATION_DECK, 0, 1, nil) end
Duel.SetOperationInfo(0, CATEGORY_TOGRAVE, nil, 1, tp, LOCATION_DECK)
end
function card.tgfilter(c) return c:IsType(TYPE_MONSTER) and c:IsAbleToGrave() end
function card.activate(e,tp,eg,ep,ev,re,r,rp)
Duel.Hint(HINT_SELECTMSG, tp, HINTMSG_TOGRAVE)
local g = Duel.SelectMatchingCard(tp, card.tgfilter, tp, LOCATION_DECK, 0, 1, 1, nil)
if g:GetCount()>0 then Duel.SendtoGrave(g, REASON_EFFECT) end
end
“Banish 1 card from the field”
function card.activate(e,tp,eg,ep,ev,re,r,rp)
local tc = Duel.GetFirstTarget()
if tc:IsRelateToEffect(e) then
Duel.Remove(tc, POS_FACEUP, REASON_EFFECT)
end
end
For “face-down” use POS_FACEDOWN.
“Add 1 X from your Deck to your hand”
function card.thfilter(c) return c:IsCode(12345678) and c:IsAbleToHand() end
function card.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(card.thfilter, tp, LOCATION_DECK, 0, 1, nil) end
Duel.SetOperationInfo(0, CATEGORY_TOHAND, nil, 1, tp, LOCATION_DECK)
end
function card.activate(e,tp,eg,ep,ev,re,r,rp)
Duel.Hint(HINT_SELECTMSG, tp, HINTMSG_ATOHAND)
local g = Duel.SelectMatchingCard(tp, card.thfilter, tp, LOCATION_DECK, 0, 1, 1, nil)
if g:GetCount()>0 then
Duel.SendtoHand(g, nil, REASON_EFFECT)
Duel.ConfirmCards(1-tp, g) -- show it to the opponent
end
end
“Return 1 card to the hand”
Duel.SendtoHand(tc, nil, REASON_EFFECT) -- "to its owner's hand"
Duel.SendtoHand(tc, tp, REASON_EFFECT) -- "to your hand"
“Shuffle 1 card into the deck”
Duel.SendtoDeck(tc, nil, SEQ_DECKSHUFFLE, REASON_EFFECT)
SEQ_DECKTOP / SEQ_DECKBOTTOM for top/bottom-of-deck variants.
Summoning Clauses
“Special Summon 1 X from your Deck”
function card.spfilter(c, e, tp)
return c:IsCode(12345678) and c:IsCanBeSpecialSummoned(e, 0, tp, false, false)
end
function card.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.GetLocationCount(tp, LOCATION_MZONE)>0
and Duel.IsExistingMatchingCard(card.spfilter, tp, LOCATION_DECK, 0, 1, nil, e, tp) end
Duel.SetOperationInfo(0, CATEGORY_SPECIAL_SUMMON, nil, 1, tp, LOCATION_DECK)
end
function card.activate(e,tp,eg,ep,ev,re,r,rp)
if Duel.GetLocationCount(tp, LOCATION_MZONE)<=0 then return end
Duel.Hint(HINT_SELECTMSG, tp, HINTMSG_SPSUMMON)
local g = Duel.SelectMatchingCard(tp, card.spfilter, tp, LOCATION_DECK, 0, 1, 1, nil, e, tp)
if g:GetCount()>0 then
Duel.SpecialSummon(g, 0, tp, tp, false, false, POS_FACEUP)
end
end
Duel.SpecialSummon(target, sumtype, sumplayer, controlplayer, nocheck, nolimit, position):
sumtype(0 default; orSUMMON_TYPE_FUSIONetc. for procedure-style summons)nocheck(true bypasses summon-condition checks; e.g. for revive cards that ignore “must be properly summoned first”)nolimit(true bypasses Special-Summon-from-extra-once-per-summon-condition cards like Stardust)
“Tribute 1 monster, then Special Summon …”
function card.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.CheckReleaseGroup(tp, nil, 1, nil) end
local g = Duel.SelectReleaseGroup(tp, nil, 1, 1, nil)
Duel.Release(g, REASON_COST)
end
-- ... target/operation as for any SP summon
“Tribute Summon” requirement variants
Cards that need a single tribute-class for summoning extras don’t write
their own tribute logic — that’s handled by EFFECT_DOUBLE_TRIBUTE,
EFFECT_DECREASE_TRIBUTE, EFFECT_EXTRA_RELEASE, etc. Example “this
card can be Normal Summoned without Tributing”:
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_SINGLE)
e1:SetCode(EFFECT_DECREASE_TRIBUTE)
e1:SetProperty(EFFECT_FLAG_SINGLE_RANGE)
e1:SetRange(LOCATION_HAND)
e1:SetValue(0xff) -- decrease by all (i.e. 0 tributes needed)
c:RegisterEffect(e1)
“Special Summon yourself from the hand if X”
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_FIELD)
e1:SetCode(EFFECT_SPSUMMON_PROC) -- this is the "summon procedure" code
e1:SetProperty(EFFECT_FLAG_CANNOT_DISABLE+EFFECT_FLAG_UNCOPYABLE)
e1:SetRange(LOCATION_HAND)
e1:SetCondition(card.spcon)
e1:SetOperation(card.spop)
c:RegisterEffect(e1)
function card.spcon(e, c)
if c==nil then return true end -- "is this a valid procedure to choose at all?"
-- e.g. "if you control no monsters"
return Duel.GetLocationCount(c:GetControler(), LOCATION_MZONE)>0
and Duel.IsExistingMatchingCard(Card.IsType, c:GetControler(), LOCATION_MZONE, 0, 1, nil, TYPE_MONSTER) == false
end
function card.spop(e,tp,eg,ep,ev,re,r,rp,c)
-- nothing to do; the engine handles the summon itself
end
Negation Clauses
“Negate the activation of a card and destroy it”
e1:SetCode(EVENT_CHAINING)
e1:SetCondition(card.negcon)
e1:SetTarget(card.negtg)
e1:SetOperation(card.negop)
function card.negcon(e,tp,eg,ep,ev,re,r,rp)
return Duel.IsChainNegatable(ev) -- the chain link can still be negated
end
function card.negtg(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return true end
Duel.SetOperationInfo(0, CATEGORY_NEGATE, eg, 1, 0, 0)
Duel.SetOperationInfo(0, CATEGORY_DESTROY, eg, 1, 0, 0)
end
function card.negop(e,tp,eg,ep,ev,re,r,rp)
if Duel.NegateActivation(ev) and re:GetHandler():IsRelateToEffect(re) then
Duel.Destroy(re:GetHandler(), REASON_EFFECT)
end
end
Duel.NegateActivation(ev) negates the activation (the card doesn’t
even resolve). Duel.NegateEffect(ev) lets it resolve as a no-op (used
when only the effect is negated; the card stays on the field).
“Negate the effects of a face-up monster”
function card.operation(e,tp,eg,ep,ev,re,r,rp)
local tc = Duel.GetFirstTarget()
if tc:IsFaceup() and tc:IsRelateToEffect(e) and tc:IsCanBeDisabledByEffect(e) then
Duel.NegateRelatedChain(tc, RESET_TURN_SET)
local e1 = Effect.CreateEffect(e:GetHandler())
e1:SetType(EFFECT_TYPE_SINGLE)
e1:SetCode(EFFECT_DISABLE)
e1:SetReset(RESET_EVENT+RESETS_STANDARD+RESET_PHASE+PHASE_END)
tc:RegisterEffect(e1)
local e2 = Effect.CreateEffect(e:GetHandler())
e2:SetType(EFFECT_TYPE_SINGLE)
e2:SetCode(EFFECT_DISABLE_EFFECT)
e2:SetValue(RESET_TURN_SET)
e2:SetReset(RESET_EVENT+RESETS_STANDARD+RESET_PHASE+PHASE_END)
tc:RegisterEffect(e2)
end
end
This is the Effect Veiler / Infinite Impermanence pattern.
“Negate the Summon of a monster”
e1:SetCode(EVENT_SUMMON) -- (and EVENT_SPSUMMON / EVENT_FLIP_SUMMON for the others)
e1:SetCondition(aux.NegateSummonCondition)
-- ...
function card.activate(e,tp,eg,ep,ev,re,r,rp)
Duel.NegateSummon(eg)
Duel.Destroy(eg, REASON_EFFECT)
end
Stat Change Clauses
“This card gains X ATK”
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_SINGLE)
e1:SetCode(EFFECT_UPDATE_ATTACK)
e1:SetProperty(EFFECT_FLAG_SINGLE_RANGE)
e1:SetRange(LOCATION_MZONE)
e1:SetValue(500) -- or a function for variable values
c:RegisterEffect(e1)
Use EFFECT_UPDATE_DEFENSE for DEF.
“This card’s ATK becomes X”
e1:SetCode(EFFECT_SET_ATTACK)
e1:SetValue(2000)
Use EFFECT_SET_ATTACK_FINAL for “becomes X (no further modifications)”.
“Other monsters you control gain X ATK”
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_FIELD)
e1:SetCode(EFFECT_UPDATE_ATTACK)
e1:SetRange(LOCATION_MZONE)
e1:SetTargetRange(LOCATION_MZONE, 0) -- self-side monsters only
e1:SetTarget(card.atktg) -- excludes self
e1:SetValue(500)
c:RegisterEffect(e1)
function card.atktg(e, c) return c ~= e:GetHandler() end
“Until end of turn” stat changes from a one-shot effect
function card.operation(e,tp,eg,ep,ev,re,r,rp)
local tc = Duel.GetFirstTarget()
if not tc:IsRelateToEffect(e) then return end
local e1 = Effect.CreateEffect(e:GetHandler())
e1:SetType(EFFECT_TYPE_SINGLE)
e1:SetCode(EFFECT_UPDATE_ATTACK)
e1:SetValue(-2000) -- temporarily lose 2000 ATK
e1:SetReset(RESET_EVENT+RESETS_STANDARD+RESET_PHASE+PHASE_END)
tc:RegisterEffect(e1)
end
Search and Reveal Clauses
“Confirm/show 1 card to the opponent”
Duel.ConfirmCards(1-tp, g) -- shows g to the other player
“Look at the top X cards of the deck, choose 1 to add to hand”
function card.activate(e,tp,eg,ep,ev,re,r,rp)
local g = Duel.GetDecktopGroup(tp, 5)
Duel.ConfirmCards(tp, g) -- you look at them
Duel.DisableShuffleCheck()
-- pick one
local sg = g:Filter(card.thfilter, nil)
if sg:GetCount()>0 then
local tc = sg:Select(tp, 1, 1, nil):GetFirst()
Duel.SendtoHand(tc, nil, REASON_EFFECT)
Duel.ConfirmCards(1-tp, tc)
g:RemoveCard(tc)
end
Duel.SortDecktop(tp, tp, #g) -- you sort the rest back on top
end
Damage and Recovery Clauses
“Inflict X damage to your opponent”
function card.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return true end
Duel.SetTargetPlayer(1-tp)
Duel.SetTargetParam(800)
Duel.SetOperationInfo(0, CATEGORY_DAMAGE, nil, 0, 1-tp, 800)
end
function card.activate(e,tp,eg,ep,ev,re,r,rp)
local p, d = Duel.GetChainInfo(0, CHAININFO_TARGET_PLAYER, CHAININFO_TARGET_PARAM)
Duel.Damage(p, d, REASON_EFFECT)
end
“Gain X LP”
Duel.Recover(tp, 1000, REASON_EFFECT)
“Burn = X × number of monsters” (variable damage)
function card.activate(e,tp,eg,ep,ev,re,r,rp)
local ct = Duel.GetMatchingGroupCount(Card.IsType, tp, LOCATION_MZONE, LOCATION_MZONE, nil, TYPE_MONSTER)
Duel.Damage(1-tp, ct*200, REASON_EFFECT)
end
Counter Clauses
“Place 1 X-Counter on this card”
-- Allow this card to hold the counter type:
local e_p = Effect.CreateEffect(c)
e_p:SetType(EFFECT_TYPE_SINGLE)
e_p:SetCode(EFFECT_COUNTER_PERMIT+0x1) -- counter type 0x1
e_p:SetProperty(EFFECT_FLAG_SINGLE_RANGE)
e_p:SetRange(LOCATION_MZONE)
c:RegisterEffect(e_p)
-- The actual placement (in some operation):
e:GetHandler():AddCounter(0x1, 1)
“Remove X-Counters as cost”
function card.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return e:GetHandler():IsCanRemoveCounter(tp, 0x1, 1, REASON_COST) end
e:GetHandler():RemoveCounter(tp, 0x1, 1, REASON_COST)
end
Once Per Turn Clauses
“You can only activate 1 \[Card Name\] per turn”
e1:SetCountLimit(1, 12345678) -- 12345678 is this card's password
This shares the limit across multiple copies of the card.
“You can only use this effect of \[Card Name\] once per turn”
e1:SetCountLimit(1, 12345678+EFFECT_COUNT_CODE_OATH)
The EFFECT_COUNT_CODE_OATH flag means “count even attempted activations
that get negated”.
“You can only Special Summon \[Card Name\] once per turn”
function c12345678.initial_effect(c)
c:SetSPSummonOnce(12345678)
-- ...
end
Continuous Protection Clauses
“Cannot be destroyed by battle”
local e1 = Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_SINGLE)
e1:SetCode(EFFECT_INDESTRUCTABLE_BATTLE)
e1:SetProperty(EFFECT_FLAG_SINGLE_RANGE)
e1:SetRange(LOCATION_MZONE)
e1:SetValue(1) -- "cannot be destroyed"; can be a function
c:RegisterEffect(e1)
“Cannot be destroyed by your opponent’s card effects”
e1:SetCode(EFFECT_INDESTRUCTABLE_EFFECT)
e1:SetValue(aux.indoval) -- only when opponent is the one destroying
“Cannot be targeted by your opponent’s effects”
e1:SetCode(EFFECT_CANNOT_BE_EFFECT_TARGET)
e1:SetValue(aux.tgoval)
“Cannot be Tributed”
e1:SetCode(EFFECT_UNRELEASABLE_NONSUM) -- and EFFECT_UNRELEASABLE_SUM for tribute summons
e1:SetValue(1)
“Other monsters you control cannot be attacked”
e1:SetType(EFFECT_TYPE_FIELD)
e1:SetCode(EFFECT_CANNOT_BE_BATTLE_TARGET)
e1:SetRange(LOCATION_MZONE)
e1:SetTargetRange(LOCATION_MZONE, 0)
e1:SetTarget(card.atktg) -- exclude self
e1:SetValue(card.atklimit) -- function returning true to block
function card.atktg(e, c) return c ~= e:GetHandler() end
function card.atklimit(e, c) return true end -- block all attackers
4 Worked Examples
Example 1 — A complete searcher Effect Monster
“When this card is Normal Summoned: You can add 1 Level 4 or lower
Warrior monster from your Deck to your hand. You can only use this
effect of [name] once per turn.”
function c12345678.initial_effect(c)
local e1 = Effect.CreateEffect(c)
e1:SetDescription(aux.Stringid(12345678, 0))
e1:SetCategory(CATEGORY_SEARCH+CATEGORY_TOHAND)
e1:SetType(EFFECT_TYPE_SINGLE+EFFECT_TYPE_TRIGGER_O)
e1:SetCode(EVENT_SUMMON_SUCCESS)
e1:SetCountLimit(1, 12345678)
e1:SetTarget(c12345678.target)
e1:SetOperation(c12345678.activate)
c:RegisterEffect(e1)
end
function c12345678.thfilter(c)
return c:IsRace(RACE_WARRIOR) and c:IsLevelBelow(4) and c:IsAbleToHand()
end
function c12345678.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(c12345678.thfilter, tp, LOCATION_DECK, 0, 1, nil) end
Duel.SetOperationInfo(0, CATEGORY_TOHAND, nil, 1, tp, LOCATION_DECK)
end
function c12345678.activate(e,tp,eg,ep,ev,re,r,rp)
Duel.Hint(HINT_SELECTMSG, tp, HINTMSG_ATOHAND)
local g = Duel.SelectMatchingCard(tp, c12345678.thfilter, tp, LOCATION_DECK, 0, 1, 1, nil)
if g:GetCount()>0 then
Duel.SendtoHand(g, nil, REASON_EFFECT)
Duel.ConfirmCards(1-tp, g)
end
end
Example 2 — A “destroy 1, draw 1” Quick-Play Spell
“Target 1 face-up Spell/Trap on the field; destroy it, then draw 1
card. You can only activate 1 [name] per turn.”
function c12345678.initial_effect(c)
local e1 = Effect.CreateEffect(c)
e1:SetCategory(CATEGORY_DESTROY+CATEGORY_DRAW)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetCode(EVENT_FREE_CHAIN)
e1:SetProperty(EFFECT_FLAG_CARD_TARGET)
e1:SetCountLimit(1, 12345678)
e1:SetTarget(c12345678.target)
e1:SetOperation(c12345678.activate)
c:RegisterEffect(e1)
end
function c12345678.filter(c)
return c:IsFaceup() and c:IsType(TYPE_SPELL+TYPE_TRAP)
end
function c12345678.target(e,tp,eg,ep,ev,re,r,rp,chk,chkc)
if chkc then return chkc:IsOnField() and c12345678.filter(chkc) end
if chk==0 then return Duel.IsExistingTarget(c12345678.filter, tp, LOCATION_ONFIELD, LOCATION_ONFIELD, 1, nil)
and Duel.IsPlayerCanDraw(tp, 1) end
Duel.Hint(HINT_SELECTMSG, tp, HINTMSG_DESTROY)
local g = Duel.SelectTarget(tp, c12345678.filter, tp, LOCATION_ONFIELD, LOCATION_ONFIELD, 1, 1, nil)
Duel.SetOperationInfo(0, CATEGORY_DESTROY, g, 1, 0, 0)
Duel.SetOperationInfo(0, CATEGORY_DRAW, nil, 0, tp, 1)
end
function c12345678.activate(e,tp,eg,ep,ev,re,r,rp)
local tc = Duel.GetFirstTarget()
if tc:IsRelateToEffect(e) and Duel.Destroy(tc, REASON_EFFECT)>0 then
Duel.BreakEffect() -- separates the resolution into 2 timepoints
Duel.Draw(tp, 1, REASON_EFFECT)
end
end
Duel.BreakEffect() is essential here — without it, the game treats the
destruction and draw as one inseparable thing, which would let cards
chain only once. Calling BreakEffect between the destroy and the draw
mirrors the comma in the card text (“destroy it, then draw 1 card”).
Example 3 — A trigger-effect Synchro monster
“1 Tuner + 1 or more non-Tuner monsters. Once per turn: You can
target 1 Spell/Trap your opponent controls; destroy it. If this card
is destroyed by battle and sent to the GY: You can Special Summon 1
Synchro monster from your GY whose Level is lower than this card’s.”
function c12345678.initial_effect(c)
-- Synchro Summon procedure
aux.AddSynchroProcedure(c, nil, aux.NonTuner(nil), 1)
c:EnableReviveLimit()
-- Effect 1: destroy 1 opponent's S/T
local e1 = Effect.CreateEffect(c)
e1:SetDescription(aux.Stringid(12345678, 0))
e1:SetCategory(CATEGORY_DESTROY)
e1:SetType(EFFECT_TYPE_IGNITION)
e1:SetProperty(EFFECT_FLAG_CARD_TARGET)
e1:SetRange(LOCATION_MZONE)
e1:SetCountLimit(1, 12345678)
e1:SetTarget(c12345678.destg)
e1:SetOperation(c12345678.desop)
c:RegisterEffect(e1)
-- Effect 2: revive a smaller Synchro on battle destruction
local e2 = Effect.CreateEffect(c)
e2:SetDescription(aux.Stringid(12345678, 1))
e2:SetCategory(CATEGORY_SPECIAL_SUMMON)
e2:SetType(EFFECT_TYPE_SINGLE+EFFECT_TYPE_TRIGGER_O)
e2:SetCode(EVENT_BATTLE_DESTROYED)
e2:SetProperty(EFFECT_FLAG_DELAY) -- needs DELAY for "if" clauses
e2:SetTarget(c12345678.sptg)
e2:SetOperation(c12345678.spop)
c:RegisterEffect(e2)
end
function c12345678.tgfilter(c)
return c:IsFaceup() and c:IsType(TYPE_SPELL+TYPE_TRAP)
end
function c12345678.destg(e,tp,eg,ep,ev,re,r,rp,chk,chkc)
if chkc then return chkc:IsControler(1-tp) and c12345678.tgfilter(chkc) end
if chk==0 then return Duel.IsExistingTarget(c12345678.tgfilter, tp, 0, LOCATION_ONFIELD, 1, nil) end
Duel.Hint(HINT_SELECTMSG, tp, HINTMSG_DESTROY)
local g = Duel.SelectTarget(tp, c12345678.tgfilter, tp, 0, LOCATION_ONFIELD, 1, 1, nil)
Duel.SetOperationInfo(0, CATEGORY_DESTROY, g, 1, 0, 0)
end
function c12345678.desop(e,tp,eg,ep,ev,re,r,rp)
local tc = Duel.GetFirstTarget()
if tc:IsRelateToEffect(e) then
Duel.Destroy(tc, REASON_EFFECT)
end
end
function c12345678.spfilter(c, e, tp, lv)
return c:IsType(TYPE_SYNCHRO) and c:IsLevelBelow(lv-1)
and c:IsCanBeSpecialSummoned(e, 0, tp, false, false)
end
function c12345678.sptg(e,tp,eg,ep,ev,re,r,rp,chk)
local lv = e:GetHandler():GetLevel()
if chk==0 then return Duel.GetLocationCount(tp, LOCATION_MZONE)>0
and Duel.IsExistingMatchingCard(c12345678.spfilter, tp, LOCATION_GRAVE, 0, 1, nil, e, tp, lv) end
Duel.SetOperationInfo(0, CATEGORY_SPECIAL_SUMMON, nil, 1, tp, LOCATION_GRAVE)
end
function c12345678.spop(e,tp,eg,ep,ev,re,r,rp)
if Duel.GetLocationCount(tp, LOCATION_MZONE)<=0 then return end
local lv = e:GetHandler():GetLevel()
Duel.Hint(HINT_SELECTMSG, tp, HINTMSG_SPSUMMON)
local g = Duel.SelectMatchingCard(tp, c12345678.spfilter, tp, LOCATION_GRAVE, 0, 1, 1, nil, e, tp, lv)
if g:GetCount()>0 then
Duel.SpecialSummon(g, 0, tp, tp, false, false, POS_FACEUP)
end
end
5 Common Pitfalls
-
Always check
chk==0first intargetandcost. The engine
asks “can you do this?” before asking “do it now?”. Returning a
wrong precheck causes the effect to be activatable (or not) when it
shouldn’t be. -
Always check
IsRelateToEffect(e)on a target before acting on it
in operation. Targets can leave the field between target time and
resolution time. -
Mandatory triggers (
EFFECT_TYPE_TRIGGER_F) cannot miss timing.
Use them for “must” effects. Optional triggers
(EFFECT_TYPE_TRIGGER_O) miss timing if something else happens
between trigger and resolution. -
Use
EFFECT_FLAG_DELAYfor “if” trigger effects. “When … you
can …” isTRIGGER_Owithout DELAY. “If … you can …” isTRIGGER_O
withEFFECT_FLAG_DELAY. The difference matters for missing timing. -
Use
Duel.BreakEffect()between two distinct phrases of one
effect. “Destroy 1 card, then draw 1 card” — the comma is a
BreakEffect. Without it the engine treats them as one inseparable
step. -
Set
CATEGORY_*correctly. Solemn Strike and friends look at
the category bitmask of the effect to decide whether they can be
chained. If your effect destroys, setCATEGORY_DESTROY. If it
special summons, setCATEGORY_SPECIAL_SUMMON. If it does both,
+them. -
Search effects need
Duel.ConfirmCards(1-tp, g). When you add a
card from the deck to your hand, the opponent gets to see it
(matches the OCG/TCG “publicly known” rule). -
Set descriptions for triggered effects with
e:SetDescription(aux.Stringid(code, n))— without a description the
client shows “” and players don’t know which effect they’re
activating when there are multiple. -
For “you can only activate 1 [name] per turn” use
SetCountLimit(1, code). With the code you get a per-turn
shared counter across all copies. Without it (SetCountLimit(1))
you get a per-effect counter (allowing multiple copies to each fire
once). -
When a card “destroys this card by its own effect” on activation,
useEFFECT_FLAG_DELAY+Duel.SendtoGrave(c, REASON_RULE)in
the operation, not in the cost. Activation costs are not the
self-destruct. -
Avoid
Effect.GlobalEffect()for card-bound effects. Use
Effect.CreateEffect(c)so the effect is owned bycand resets
whencleaves play. -
SetRangematters. A trigger effect with no range only fires
while the card is in MZONE by default for SINGLE_TRIGGER. For
GY-triggered effects you mustSetRange(LOCATION_GRAVE). -
Copy effects with
e1:Clone(), then change only what differs.
Cards that listen to multiple events (EVENT_SUMMON_SUCCESS+
EVENT_SPSUMMON_SUCCESS+EVENT_FLIP_SUMMON_SUCCESS) should
define one effect and clone it three times with different codes.
6 Quick Reference Tables
EFFECT_TYPE quick chart
| Card text | EFFECT_TYPE_* |
|---|---|
| Spell/Trap activation | ACTIVATE |
| “During your Main Phase” monster effect | IGNITION |
| “When/If X happens, you can …” | SINGLE+TRIGGER_O (or FIELD+TRIGGER_O) |
| “When/If X happens” (mandatory) | SINGLE+TRIGGER_F |
| “During either player’s turn: you can …” | QUICK_O |
| Always-on monster ability | SINGLE+CONTINUOUS |
| Field-spell-like always-on grant | FIELD+CONTINUOUS |
| FLIP: … | SINGLE+FLIP |
EVENT quick chart
| Trigger phrasing | EVENT_* |
|---|---|
| “this card is Normal Summoned” | EVENT_SUMMON_SUCCESS |
| “this card is Special Summoned” | EVENT_SPSUMMON_SUCCESS |
| “this card is Flip Summoned” | EVENT_FLIP_SUMMON_SUCCESS |
| “this card is sent to the GY” | EVENT_TO_GRAVE |
| “this card is destroyed by battle” | EVENT_BATTLE_DESTROYED |
| “this card destroys an opponent’s monster by battle” | EVENT_BATTLE_DESTROYING |
| “this card is targeted by an effect” | EVENT_BECOME_TARGET |
| “an opponent’s card effect is activated” | EVENT_CHAINING |
| “this card declares an attack” | EVENT_ATTACK_ANNOUNCE |
| “during damage step” | EVENT_BATTLE_START/EVENT_BATTLE_CONFIRM/etc. |
| “this card leaves the field” | EVENT_LEAVE_FIELD |
| “you draw a card” | EVENT_DRAW |
| “you take damage” | EVENT_DAMAGE |
| “you gain LP” | EVENT_RECOVER |
| “this card is flipped” | EVENT_FLIP |
| “during the End Phase” | EVENT_PHASE+PHASE_END |
| “during the Standby Phase” | EVENT_PHASE+PHASE_STANDBY |
| free-chain spell/trap | EVENT_FREE_CHAIN |
REASON quick chart
| Reason for movement | Use |
|---|---|
REASON_EFFECT |
by a card effect |
REASON_BATTLE |
by battle |
REASON_COST |
as a cost |
REASON_DISCARD |
discarded (combine with COST/EFFECT) |
REASON_DESTROY |
being destroyed (set automatically by Duel.Destroy) |
REASON_RELEASE |
tributed |
REASON_MATERIAL + REASON_FUSION/SYNCHRO/XYZ/LINK/RITUAL |
sent as material |
REASON_RULE |
by game rules |
Duel.\* method cheat sheet
| Method | Purpose |
|---|---|
Duel.GetLocationCount(p, loc) |
free zones in loc for player p |
Duel.IsExistingMatchingCard(filter, tp, self_loc, opp_loc, count, exception, ...) |
are there ≥count cards matching? |
Duel.GetMatchingGroup(filter, tp, self_loc, opp_loc, exception, ...) |
get them as a Group |
Duel.GetMatchingGroupCount(...) |
count them |
Duel.SelectMatchingCard(tp, filter, ..., min, max, exception, ...) |
player picks |
Duel.IsExistingTarget(filter, ...) / Duel.SelectTarget(...) |
target-time variants |
Duel.GetFirstTarget() / Duel.GetChainInfo(0, CHAININFO_TARGET_CARDS) |
retrieve targets |
Duel.SetOperationInfo(0, CATEGORY_X, group, count, player, param) |
declare what the effect will do |
Duel.SetTargetPlayer(p) / Duel.SetTargetParam(v) |
for player-targeting effects |
Duel.GetChainInfo(0, CHAININFO_TARGET_PLAYER) |
retrieve player targets in operation |
Duel.Destroy(c_or_g, REASON_EFFECT) |
destroy |
Duel.SendtoGrave(c_or_g, REASON_EFFECT) |
send to GY |
Duel.SendtoHand(c_or_g, owner_or_nil, REASON_EFFECT) |
bounce to hand |
Duel.SendtoDeck(c_or_g, owner_or_nil, SEQ_DECKSHUFFLE, REASON_EFFECT) |
back to deck |
Duel.Remove(c_or_g, POS_FACEUP, REASON_EFFECT) |
banish |
Duel.Release(c_or_g, REASON_EFFECT) |
tribute |
Duel.SpecialSummon(c, sumtype, sumplayer, controlplayer, nocheck, nolimit, position) |
summon |
Duel.NegateActivation(ev) / Duel.NegateEffect(ev) |
negate |
Duel.NegateAttack() |
block the current attack |
Duel.Damage(p, amount, REASON_EFFECT) |
burn |
Duel.Recover(p, amount, REASON_EFFECT) |
LP gain |
Duel.Draw(p, count, REASON_EFFECT) |
draw |
Duel.PayLPCost(p, amount) |
pay LP |
Duel.Equip(equip_player, equip_card, target) |
equip |
Duel.Hint(HINT_SELECTMSG, tp, msg) |
popup hint to player |
Duel.ConfirmCards(p, c_or_g) |
reveal a card to player p |
Duel.BreakEffect() |
timepoint break inside an operation |
Duel.GetTurnPlayer() / Duel.GetTurnCount() / Duel.GetCurrentPhase() |
turn state |
Duel.GetLP(p) / Duel.SetLP(p, amount) |
LP queries |
Card.\* method cheat sheet
| Method | Purpose |
|---|---|
c:IsCode(code, ...) |
one of these card codes? |
c:IsType(TYPE_X) / c:IsAllTypes(TYPE_A+TYPE_B) |
type bitmask test |
c:IsRace(RACE_X) / c:IsAttribute(ATTRIBUTE_X) |
type/attribute test |
c:IsLevel(n) / c:IsLevelBelow(n) / c:IsLevelAbove(n) |
level test |
c:IsRank(n) / c:IsLink(n) |
rank/link rating test |
c:GetAttack() / c:GetDefense() |
current ATK/DEF |
c:GetLevel() / c:GetRank() / c:GetLink() |
current level / rank / link |
c:IsFaceup() / c:IsFacedown() |
position test |
c:IsAttackPos() / c:IsDefensePos() |
battle position |
c:IsLocation(LOCATION_X) / c:IsOnField() |
location test |
c:IsControler(p) |
controlled by player p? |
c:GetController() / c:GetOwner() |
who controls/owns it |
c:IsSetCard(setcode) |
belongs to archetype? |
c:IsAbleToHand/Grave/Deck/Extra/Remove() |
can be moved to that location? |
c:IsCanBeSpecialSummoned(e, sumtype, sumplayer, ignore_summoning_rules, nolimit) |
SS eligibility |
c:IsCanBeFusionMaterial/SynchroMaterial/XyzMaterial/LinkMaterial(...) |
material eligibility |
c:IsRelateToEffect(e) |
still tied to effect e? |
c:RegisterEffect(eff) |
attach an effect to this card |
c:GetFlagEffect(id) / c:RegisterFlagEffect(id, reset, flag, count[, lab[, desc]]) |
per-card bookkeeping flag |
c:AddCounter(type, count) / c:RemoveCounter(p, type, count, REASON_X) |
counters |
c:GetEquipTarget() |
what this equip card is equipped to |
c:GetOverlayCount() / c:GetOverlayGroup() |
Xyz materials |
c:GetLinkedGroup() / c:GetLinkedGroupCount() |
cards this Link Monster points to |
7 Real Card Cookbook
This is the lookup table: take the English PSCT text (the wording on
a real Yu-Gi-Oh card), find the matching pattern below, and copy the
real Lua code that the YGOPro project ships for that exact card.
Every example in this section is the actual script from the
ygopro-scripts-master repository (no synthetic snippets), with the
English text taken from the cards_en.cdb localization database. If you
read a real card and the PSCT clause appears here, you can use the
attached code as a working template.
How to use it:
- Read the card text on the printed card.
- Find the closest PSCT pattern in sections 7.1–7.14 below.
- Open the cited script (
cXXXXXXXX.lua) and use it as a starting
point. The structure ofinitial_effect(c)plus per-effect callbacks
is the part you’ll change for your own card.
Each entry shows the card’s English text, the script that ships with
YGOPro, and a short note on the pattern. The pattern is what you copy;
the rest is window dressing.
7.1 Draw Effects
Pot of Greed (`c55144522`)
Draw 2 cards.
function c55144522.initial_effect(c)
--Activate
local e1=Effect.CreateEffect(c)
e1:SetCategory(CATEGORY_DRAW)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetProperty(EFFECT_FLAG_PLAYER_TARGET)
e1:SetCode(EVENT_FREE_CHAIN)
e1:SetTarget(c55144522.target)
e1:SetOperation(c55144522.activate)
c:RegisterEffect(e1)
end
function c55144522.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsPlayerCanDraw(tp,2) end
Duel.SetTargetPlayer(tp)
Duel.SetTargetParam(2)
Duel.SetOperationInfo(0,CATEGORY_DRAW,nil,0,tp,2)
end
function c55144522.activate(e,tp,eg,ep,ev,re,r,rp)
local p,d=Duel.GetChainInfo(0,CHAININFO_TARGET_PLAYER,CHAININFO_TARGET_PARAM)
Duel.Draw(p,d,REASON_EFFECT)
end
Pattern. Pure draw. The target sets a target player + count via
SetTargetPlayer/SetTargetParam so other cards can reference how much
will be drawn. EFFECT_FLAG_PLAYER_TARGET tells the engine the chain
“targets a player” rather than a card.
Upstart Goblin (`c70368879`) — draw with a side effect
Draw 1 card, then your opponent gains 1000 LP.
function c70368879.initial_effect(c)
local e1=Effect.CreateEffect(c)
e1:SetCategory(CATEGORY_DRAW+CATEGORY_RECOVER)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetCode(EVENT_FREE_CHAIN)
e1:SetTarget(c70368879.target)
e1:SetOperation(c70368879.activate)
c:RegisterEffect(e1)
end
function c70368879.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsPlayerCanDraw(tp,1) end
Duel.SetTargetPlayer(tp)
Duel.SetTargetParam(1)
Duel.SetOperationInfo(0,CATEGORY_DRAW,nil,0,tp,1)
Duel.SetOperationInfo(0,CATEGORY_RECOVER,nil,0,1-tp,1000)
end
function c70368879.activate(e,tp,eg,ep,ev,re,r,rp)
local p,d=Duel.GetChainInfo(0,CHAININFO_TARGET_PLAYER,CHAININFO_TARGET_PARAM)
if Duel.Draw(p,d,REASON_EFFECT)>0 then
Duel.BreakEffect()
Duel.Recover(1-tp,1000,REASON_EFFECT)
end
end
Pattern. The “then” wording in PSCT means sequential, conditional on
the prior step succeeding — note the if Duel.Draw(...)>0 then guard.
Duel.BreakEffect() separates the two halves so they can be responded to
independently and so chain-block timing is correct.
Pot of Desires (`c35261759`) — banish-cost draw with hard once-per-turn
Banish 10 cards from the top of your Deck, face-down; draw 2 cards.
You can only activate 1 “Pot of Desires” per turn.
e1:SetCountLimit(1,35261759+EFFECT_COUNT_CODE_OATH)
e1:SetCost(c35261759.cost)
...
function c35261759.cost(e,tp,eg,ep,ev,re,r,rp,chk)
local g=Duel.GetDecktopGroup(tp,10)
if chk==0 then return g:FilterCount(Card.IsAbleToRemoveAsCost,nil,POS_FACEDOWN)==10
and Duel.GetFieldGroupCount(tp,LOCATION_DECK,0)>=12 end
Duel.DisableShuffleCheck()
Duel.Remove(g,POS_FACEDOWN,REASON_COST)
end
Pattern. +EFFECT_COUNT_CODE_OATH makes the limit oath-binding —
even if the activation is negated you can’t try again. The cost legality
check (chk==0) demands all 10 top cards be banishable face-down AND the
deck has the cards available. DisableShuffleCheck stops the engine
re-shuffling between the cost being paid and resolution.
Allure of Darkness (`c1475311`) — draw + conditional discard
Draw 2 cards, then banish 1 DARK monster from your hand, or, if you do
not have any in your hand, send your entire hand to the GY.
function c1475311.activate(e,tp,eg,ep,ev,re,r,rp)
local p,d=Duel.GetChainInfo(0,CHAININFO_TARGET_PLAYER,CHAININFO_TARGET_PARAM)
if Duel.Draw(p,d,REASON_EFFECT)<=0 then return end
Duel.BreakEffect()
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_REMOVE)
local g=Duel.SelectMatchingCard(p,Card.IsAttribute,p,LOCATION_HAND,0,1,1,nil,ATTRIBUTE_DARK)
Duel.ShuffleHand(p)
local tg=g:GetFirst()
if tg then
if Duel.Remove(tg,POS_FACEUP,REASON_EFFECT)==0 then
Duel.ConfirmCards(1-p,tg)
Duel.ShuffleHand(p)
end
else
local sg=Duel.GetFieldGroup(p,LOCATION_HAND,0)
Duel.SendtoGrave(sg,REASON_EFFECT)
end
end
Pattern. “or, if you do not have any” → the operation branches on the
result of SelectMatchingCard: when nothing is selectable the entire
hand is sent to the GY. ShuffleHand runs even on success so the
discarded DARK monster isn’t visible to the opponent.
Card of Demise (`c59750328`) — draw-up-to-3 with downside
Draw until you have 3 cards in your hand, also for the rest of this
turn after this card resolves, your opponent takes no damage. During
the End Phase of this turn, send your entire hand to the GY. You can
only activate 1 “Card of Demise” per turn. You cannot Special Summon
during the turn you activate this card.
The cost registers a no Special Summon for the turn lock:
function s.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.GetActivityCount(tp,ACTIVITY_SPSUMMON)==0 end
local e1=Effect.CreateEffect(e:GetHandler())
e1:SetType(EFFECT_TYPE_FIELD)
e1:SetProperty(EFFECT_FLAG_PLAYER_TARGET+EFFECT_FLAG_OATH)
e1:SetCode(EFFECT_CANNOT_SPECIAL_SUMMON)
e1:SetReset(RESET_PHASE+PHASE_END)
e1:SetTargetRange(1,0)
Duel.RegisterEffect(e1,tp)
end
The operation registers the End-Phase hand-cleanup as a continuous
field effect on a 1-shot count limit:
local e3=Effect.CreateEffect(e:GetHandler())
e3:SetType(EFFECT_TYPE_FIELD+EFFECT_TYPE_CONTINUOUS)
e3:SetCode(EVENT_PHASE+PHASE_END)
e3:SetCountLimit(1)
e3:SetReset(RESET_PHASE+PHASE_END)
e3:SetOperation(s.tgop)
Duel.RegisterEffect(e3,p)
Pattern. “The rest of this turn …” effects are implemented by
registering a new effect at activation time with RESET_PHASE+PHASE_END
so it falls off cleanly. EFFECT_FLAG_OATH makes the lock immune to
“resetting” — once it’s on the player it stays for the whole turn.
7.2 Destroy Effects
Mystical Space Typhoon (`c5318639`) — single S/T destroy with `Duel.SelectTarget`
Target 1 Spell/Trap on the field; destroy that target.
function c5318639.initial_effect(c)
local e1=Effect.CreateEffect(c)
e1:SetCategory(CATEGORY_DESTROY)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetCode(EVENT_FREE_CHAIN)
e1:SetHintTiming(0,TIMING_END_PHASE+TIMING_EQUIP)
e1:SetProperty(EFFECT_FLAG_CARD_TARGET)
e1:SetTarget(c5318639.target)
e1:SetOperation(c5318639.activate)
c:RegisterEffect(e1)
end
function c5318639.filter(c)
return c:IsType(TYPE_SPELL+TYPE_TRAP)
end
function c5318639.target(e,tp,eg,ep,ev,re,r,rp,chk,chkc)
if chkc then return chkc:IsOnField() and c5318639.filter(chkc) and chkc~=e:GetHandler() end
if chk==0 then return Duel.IsExistingTarget(c5318639.filter,tp,LOCATION_ONFIELD,LOCATION_ONFIELD,1,e:GetHandler()) end
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_DESTROY)
local g=Duel.SelectTarget(tp,c5318639.filter,tp,LOCATION_ONFIELD,LOCATION_ONFIELD,1,1,e:GetHandler())
Duel.SetOperationInfo(0,CATEGORY_DESTROY,g,1,0,0)
end
function c5318639.activate(e,tp,eg,ep,ev,re,r,rp)
local tc=Duel.GetFirstTarget()
if tc:IsRelateToEffect(e) then
Duel.Destroy(tc,REASON_EFFECT)
end
end
Pattern. The canonical “target X, destroy X” template:
EFFECT_FLAG_CARD_TARGET + a target callback that handles three modes
(chkc for replay validation, chk==0 for legality, otherwise actually
selects), then an operation that re-checks IsRelateToEffect (the card
might have left the field between target and resolution).
Dark Hole (`c53129443`) — wipe both fields
Destroy all monsters on the field.
function c53129443.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(aux.TRUE,tp,LOCATION_MZONE,LOCATION_MZONE,1,nil) end
local sg=Duel.GetMatchingGroup(aux.TRUE,tp,LOCATION_MZONE,LOCATION_MZONE,nil)
Duel.SetOperationInfo(0,CATEGORY_DESTROY,sg,sg:GetCount(),0,0)
end
function c53129443.activate(e,tp,eg,ep,ev,re,r,rp)
local sg=Duel.GetMatchingGroup(aux.TRUE,tp,LOCATION_MZONE,LOCATION_MZONE,nil)
Duel.Destroy(sg,REASON_EFFECT)
end
Pattern. “All monsters on the field” → LOCATION_MZONE,LOCATION_MZONE
(both player ranges). aux.TRUE matches anything. No target flag because
no specific card is targeted by the chain; the snapshot is taken at
resolution.
Raigeki (`c12580477`) — destroy opponent’s monsters
Destroy all monsters your opponent controls.
function c12580477.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(aux.TRUE,tp,0,LOCATION_MZONE,1,nil) end
local sg=Duel.GetMatchingGroup(aux.TRUE,tp,0,LOCATION_MZONE,nil)
Duel.SetOperationInfo(0,CATEGORY_DESTROY,sg,sg:GetCount(),0,0)
end
function c12580477.activate(e,tp,eg,ep,ev,re,r,rp)
local sg=Duel.GetMatchingGroup(aux.TRUE,tp,0,LOCATION_MZONE,nil)
Duel.Destroy(sg,REASON_EFFECT)
end
Pattern. Notice the location pair is (self_loc=0, opponent_loc=LOCATION_MZONE).
Whenever you see 0 in the self slot it means “your range = nothing” /
opponent only.
Mirror Force (`c44095762`) — battle-trigger destroy
When an opponent’s monster declares an attack: Destroy all your
opponent’s Attack Position monsters.
e1:SetCode(EVENT_ATTACK_ANNOUNCE)
e1:SetCondition(c44095762.condition)
...
function c44095762.condition(e,tp,eg,ep,ev,re,r,rp)
return tp~=Duel.GetTurnPlayer()
end
function c44095762.filter(c)
return c:IsAttackPos()
end
Pattern. Attack-trigger trap: event = EVENT_ATTACK_ANNOUNCE,
condition gates by turn player so it doesn’t trigger on your own attacks,
filter narrows to attack position only.
Bottomless Trap Hole (`c29401950`) — Summon-trigger filter, banish-on-destroy
When your opponent Summons a monster(s) with 1500 or more ATK: Destroy
that monster(s) with 1500 or more ATK, and if you do, banish it.
function c29401950.filter(c,tp,ep)
return c:IsLocation(LOCATION_MZONE) and c:IsFaceup() and c:GetAttack()>=1500
and ep~=tp and c:IsAbleToRemove()
end
...
function c29401950.activate(e,tp,eg,ep,ev,re,r,rp)
local tc=eg:GetFirst()
if tc and tc:IsRelateToEffect(e) and tc:IsFaceup() and tc:GetAttack()>=1500 then
Duel.Destroy(tc,REASON_EFFECT,LOCATION_REMOVED)
end
end
Pattern. Duel.Destroy(tc, REASON_EFFECT, LOCATION_REMOVED) — the
3rd argument forces the destruction destination to be the banish zone
instead of the GY. That is exactly the “and if you do, banish it” clause
in one call. Three near-identical effect blocks register for
EVENT_SUMMON_SUCCESS, EVENT_FLIP_SUMMON_SUCCESS,
EVENT_SPSUMMON_SUCCESS so all summon types are covered.
Heavy Storm (`c19613556`) — destroy all S/T (excluding self)
Destroy all Spell and Trap Cards on the field.
function c19613556.activate(e,tp,eg,ep,ev,re,r,rp)
local sg=Duel.GetMatchingGroup(c19613556.filter,tp,LOCATION_ONFIELD,LOCATION_ONFIELD,aux.ExceptThisCard(e))
Duel.Destroy(sg,REASON_EFFECT)
end
Pattern. aux.ExceptThisCard(e) is the helper for “exclude the
activating card itself.” A Spell that destroys all Spells must obviously
exclude itself.
Twin Twisters (`c43898403`) — discard cost + multi-target S/T destroy
Discard 1 card, then target up to 2 Spells/Traps on the field; destroy
them.
function c43898403.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(Card.IsDiscardable,tp,LOCATION_HAND,0,1,e:GetHandler()) end
Duel.DiscardHand(tp,Card.IsDiscardable,1,1,REASON_COST+REASON_DISCARD)
end
function c43898403.target(e,tp,eg,ep,ev,re,r,rp,chk,chkc)
if chkc then return chkc:IsOnField() and c43898403.filter(chkc) and chkc~=e:GetHandler() end
if chk==0 then return Duel.IsExistingTarget(c43898403.filter,tp,LOCATION_ONFIELD,LOCATION_ONFIELD,1,e:GetHandler()) end
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_DESTROY)
local g=Duel.SelectTarget(tp,c43898403.filter,tp,LOCATION_ONFIELD,LOCATION_ONFIELD,1,2,e:GetHandler())
Duel.SetOperationInfo(0,CATEGORY_DESTROY,g,g:GetCount(),0,0)
end
Pattern. “Up to 2” → SelectTarget(...,1,2,...). The min/max args
gate user choice; min=1 because you must pick at least one (the activation
clause forces that), max=2.
Lightning Vortex (`c69162969`) — discard cost + face-up monster wipe
Discard 1 card; destroy all face-up monsters your opponent controls.
function c69162969.filter(c) return c:IsFaceup() end
function c69162969.activate(e,tp,eg,ep,ev,re,r,rp)
local sg=Duel.GetMatchingGroup(c69162969.filter,tp,0,LOCATION_MZONE,nil)
Duel.Destroy(sg,REASON_EFFECT)
end
Pattern. “Face-up” is the most common qualifier for mass destruction
— c:IsFaceup() in the filter and you’re done.
Torrential Tribute (`c53582587`) — Summon-trigger, destroy all monsters
When a monster(s) is Summoned: Destroy all monsters on the field.
The script registers three identical activate effects — one per summon
event — to cover Normal, Flip, and Special Summons:
e1:SetCode(EVENT_SUMMON_SUCCESS)
e2:SetCode(EVENT_FLIP_SUMMON_SUCCESS)
e3:SetCode(EVENT_SPSUMMON_SUCCESS)
Pattern. “When a monster(s) is Summoned” in PSCT == every summon
trigger, so you have to register for each of the three events.
7.3 Banish and Bounce
Compulsory Evacuation Device (`c94192409`) — bounce 1 monster
Target 1 monster on the field; return that target to the hand.
function c94192409.target(e,tp,eg,ep,ev,re,r,rp,chk,chkc)
if chkc then return chkc:IsLocation(LOCATION_MZONE) and chkc:IsAbleToHand() end
if chk==0 then return Duel.IsExistingTarget(Card.IsAbleToHand,tp,LOCATION_MZONE,LOCATION_MZONE,1,nil) end
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_RTOHAND)
local g=Duel.SelectTarget(tp,Card.IsAbleToHand,tp,LOCATION_MZONE,LOCATION_MZONE,1,1,nil)
Duel.SetOperationInfo(0,CATEGORY_TOHAND,g,1,0,0)
end
function c94192409.activate(e,tp,eg,ep,ev,re,r,rp)
local tc=Duel.GetFirstTarget()
if tc and tc:IsRelateToEffect(e) then
Duel.SendtoHand(tc,nil,REASON_EFFECT)
end
end
Pattern. Bounce = Duel.SendtoHand(tc, nil, REASON_EFFECT). The
filter Card.IsAbleToHand checks whether the card legally can go to
the hand (some cards can’t — e.g. tokens, Extra-Deck monsters that go
back to Extra). The hint HINTMSG_RTOHAND shows “return to hand” in the
selection prompt.
7.4 Search and Mill
Foolish Burial (`c81439173`) — mill 1 monster from Deck
Send 1 monster from your Deck to the GY.
function c81439173.tgfilter(c)
return c:IsType(TYPE_MONSTER) and c:IsAbleToGrave()
end
function c81439173.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(c81439173.tgfilter,tp,LOCATION_DECK,0,1,nil) end
Duel.SetOperationInfo(0,CATEGORY_TOGRAVE,nil,1,tp,LOCATION_DECK)
end
function c81439173.activate(e,tp,eg,ep,ev,re,r,rp)
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_TOGRAVE)
local g=Duel.SelectMatchingCard(tp,c81439173.tgfilter,tp,LOCATION_DECK,0,1,1,nil)
if g:GetCount()>0 then
Duel.SendtoGrave(g,REASON_EFFECT)
end
end
Pattern. Send to GY = Duel.SendtoGrave(g, REASON_EFFECT). The
filter has to include c:IsAbleToGrave() because some cards refuse to
go to the GY (e.g. tokens). For deck search/mill the location is
LOCATION_DECK.
Reinforcement of the Army (`c32807846`) — Deck → hand search by criteria
Add 1 Level 4 or lower Warrior monster from your Deck to your hand.
function c32807846.filter(c)
return c:IsLevelBelow(4) and c:IsRace(RACE_WARRIOR) and c:IsAbleToHand()
end
function c32807846.activate(e,tp,eg,ep,ev,re,r,rp)
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_ATOHAND)
local g=Duel.SelectMatchingCard(tp,c32807846.filter,tp,LOCATION_DECK,0,1,1,nil)
if g:GetCount()>0 then
Duel.SendtoHand(g,nil,REASON_EFFECT)
Duel.ConfirmCards(1-tp,g)
end
end
Pattern. Search-to-hand template: filter by what the card text says
(“Level 4 or lower Warrior”), SelectMatchingCard from LOCATION_DECK,
SendtoHand, then ConfirmCards(1-tp, g) because the opponent must see
the searched card.
Witch of the Black Forest (`c78010363`) / Sangan (`c26202165`) — search on send-to-GY with name lockout
If this card is sent from the field to the GY: Add 1 monster with 1500
or less DEF from your Deck to your hand, but you cannot activate cards,
or the effects of cards, with that name for the rest of this turn. You
can only use this effect of “Witch of the Black Forest” once per turn.
e1:SetType(EFFECT_TYPE_SINGLE+EFFECT_TYPE_TRIGGER_F)
e1:SetCode(EVENT_TO_GRAVE)
e1:SetCountLimit(1,78010363)
e1:SetCondition(c78010363.condition)
...
function c78010363.condition(e,tp,eg,ep,ev,re,r,rp)
return e:GetHandler():IsPreviousLocation(LOCATION_ONFIELD)
end
...
function c78010363.operation(e,tp,eg,ep,ev,re,r,rp)
-- ... search and SendtoHand ...
local tc=g:GetFirst()
if tc:IsLocation(LOCATION_HAND) then
local e1=Effect.CreateEffect(e:GetHandler())
e1:SetType(EFFECT_TYPE_FIELD)
e1:SetProperty(EFFECT_FLAG_PLAYER_TARGET)
e1:SetCode(EFFECT_CANNOT_ACTIVATE)
e1:SetTargetRange(1,0)
e1:SetValue(c78010363.aclimit)
e1:SetLabel(tc:GetCode())
e1:SetReset(RESET_PHASE+PHASE_END)
Duel.RegisterEffect(e1,tp)
end
end
function c78010363.aclimit(e,re,tp)
return re:GetHandler():IsCode(e:GetLabel())
end
Pattern. “If sent from the field” = EVENT_TO_GRAVE + condition
IsPreviousLocation(LOCATION_ONFIELD). The “you cannot activate cards
with that name” lock is built by registering a fresh EFFECT_CANNOT_ACTIVATE
field effect, labelled with the searched card’s code, with an aclimit
function that returns true (= forbidden) for any chained card matching
that code.
Sangan (c26202165) is the same pattern with IsAttackBelow(1500)
swapped for IsDefenseBelow(1500).
Sky Striker Ace - Shizuku (`c90673288`) — search a name not already in your GY
You can add 1 “Sky Striker” Spell from your Deck to your hand, with a
name different from the cards in your GY.
function c90673288.thfilter(c,tp)
return c:IsSetCard(0x115) and c:IsType(TYPE_SPELL) and c:IsAbleToHand()
and not Duel.IsExistingMatchingCard(Card.IsCode,tp,LOCATION_GRAVE,0,1,nil,c:GetCode())
end
Pattern. “Different name from cards in your GY” — the filter does a
nested Duel.IsExistingMatchingCard against the GY using the candidate
card’s own code, and negates the result.
7.5 Special Summon
Cyber Dragon (`c70095154` — pattern reference)
The standard “Special Summon (from your hand) by procedure if your
opponent controls a monster and you control none” template uses an
EFFECT_SPSUMMON_PROC effect of type EFFECT_TYPE_FIELD registered
in LOCATION_HAND with a condition that checks the field state. This
pattern is also used by Lava Golem below; see Lava Golem for the
canonical structure.
Monster Reborn (`c83764718`) — generic GY revive
The classic revive Spell registers a single EFFECT_TYPE_ACTIVATE with
CATEGORY_SPECIAL_SUMMON, target = 1 GY monster either side, operation
= Duel.SpecialSummon(tc, 0, tp, tp, false, false, POS_FACEUP). The
target callback uses EFFECT_FLAG_CARD_TARGET and the same chkc/chk==0/select
3-mode dispatch shown for MST.
Premature Burial (`c70828912`) — pay LP, revive, equip
Activate this card by paying 800 LP, then target 1 monster in your
Graveyard; Special Summon that target in Attack Position and equip it
with this card. When this card is destroyed, destroy the equipped
monster.
function c70828912.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.CheckLPCost(tp,800)
else Duel.PayLPCost(tp,800) end
end
function c70828912.spfilter(c,e,tp)
return c:IsCanBeSpecialSummoned(e,0,tp,false,false,POS_FACEUP_ATTACK)
end
function c70828912.operation(e,tp,eg,ep,ev,re,r,rp)
local c=e:GetHandler()
local tc=Duel.GetFirstTarget()
if c:IsRelateToEffect(e) and tc:IsRelateToEffect(e) then
if Duel.SpecialSummon(tc,0,tp,tp,false,false,POS_FACEUP_ATTACK)==0 then return end
Duel.Equip(tp,c,tc)
local e1=Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_SINGLE)
e1:SetCode(EFFECT_EQUIP_LIMIT)
e1:SetProperty(EFFECT_FLAG_CANNOT_DISABLE)
e1:SetReset(RESET_EVENT+RESETS_STANDARD)
e1:SetValue(c70828912.eqlimit)
e1:SetLabelObject(tc)
c:RegisterEffect(e1)
end
end
function c70828912.desop(e,tp,eg,ep,ev,re,r,rp)
local c=e:GetHandler()
local tc=c:GetFirstCardTarget()
if c:IsReason(REASON_DESTROY) and tc and tc:IsLocation(LOCATION_MZONE) then
Duel.Destroy(tc,REASON_EFFECT)
end
end
Pattern. Revive-and-equip combo:
- LP cost via
CheckLPCost/PayLPCost. - After SpSummon, immediately
Duel.Equip(tp, this, tc). - Register an
EFFECT_EQUIP_LIMITso the equip can’t drift to another
monster. - Hook
EVENT_LEAVE_FIELDso the equipped monster goes down with the
equip card.
Black Luster Soldier — Soldier of Chaos (`c49202162`) — Link with same-name material restriction
3 monsters with different names
aux.AddLinkProcedure(c,nil,3,3,c49202162.lcheck)
function c49202162.lcheck(g,lc)
return g:GetClassCount(Card.GetLinkCode)==g:GetCount()
end
Pattern. “Different names” → g:GetClassCount(Card.GetLinkCode) == g:GetCount().
GetClassCount returns the number of distinct values; if it equals the
group’s count, every member is unique.
Lava Golem (`c102380`) — Special-Summon-to-opponent-by-tribute
Cannot be Normal Summoned/Set. Must first be Special Summoned (from
your hand) to your opponent’s field by Tributing 2 monsters they
control. […] Once per turn, during your Standby Phase: Take 1000
damage.
local e1=Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_FIELD)
e1:SetCode(EFFECT_SPSUMMON_PROC)
e1:SetProperty(EFFECT_FLAG_CANNOT_DISABLE+EFFECT_FLAG_UNCOPYABLE+EFFECT_FLAG_SPSUM_PARAM)
e1:SetRange(LOCATION_HAND)
e1:SetTargetRange(POS_FACEUP,1) -- POS_FACEUP, opponent's side
e1:SetCondition(c102380.spcon)
e1:SetTarget(c102380.sptg)
e1:SetOperation(c102380.spop)
c:RegisterEffect(e1)
The damage trigger is a separate field-trigger:
local e2=Effect.CreateEffect(c)
e2:SetType(EFFECT_TYPE_FIELD+EFFECT_TYPE_TRIGGER_F)
e2:SetCategory(CATEGORY_DAMAGE)
e2:SetCode(EVENT_PHASE+PHASE_STANDBY)
e2:SetRange(LOCATION_MZONE)
e2:SetCountLimit(1)
e2:SetCondition(c102380.damcon)
Tribute selection requires a 2-card subgroup that legally fits a zone
on the opponent’s side (Duel.GetMZoneCount(1-tp,g,tp)>0):
function c102380.fselect(g,tp)
return Duel.GetMZoneCount(1-tp,g,tp)>0
end
Pattern. “Special Summon procedure” cards register an
EFFECT_SPSUMMON_PROC from LOCATION_HAND and use
SetTargetRange(POS_FACEUP,1) to put the monster on the opponent’s
side (,1 = opponent). Standby-phase recurring effects use
EVENT_PHASE+PHASE_STANDBY with a turn-player condition.
Call of the Haunted (`c97077563` — pattern)
The classic continuous-trap revive uses two effects: an
EFFECT_TYPE_ACTIVATE that runs Duel.SpecialSummon(...) and a
EVENT_LEAVE_FIELD hook that destroys the equipped monster’s host (or
sends the revived monster away when the trap leaves). The skeleton is
identical to Premature Burial above without the LP cost.
Black Luster Soldier (`c72989439` — pattern reference)
The Ritual / “banish-2 to SS” archetype is a single EFFECT_TYPE_ACTIVATE
on a hand monster whose cost banishes 1 LIGHT and 1 DARK from the GY,
then operation Duel.SpecialSummon(c, SUMMON_TYPE_SPECIAL, tp, tp, false, false, POS_FACEUP).
This is the standard “by its own procedure” template — registering an
EFFECT_SPSUMMON_PROC with the cost embedded in the procedure itself.
Nibiru, the Primal Being (`c27204311` — pattern)
A hand-trap that creates a token: cost tributes monsters via
SelectReleaseGroup, operation calls Duel.CreateToken and
Duel.SpecialSummon for the activator and a token for the opponent.
The condition checks for ≥5 monster-summons via
Duel.GetActivityCount(...) accumulated by a global registered effect.
Scapegoat (`c73915051`) — token spam + summon lock cost
Special Summon 4 “Sheep Tokens” […] You cannot Summon other monsters
the turn you activate this card (but you can Normal Set).
function c73915051.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.GetActivityCount(tp,ACTIVITY_SUMMON)==0
and Duel.GetActivityCount(tp,ACTIVITY_FLIPSUMMON)==0
and Duel.GetActivityCount(tp,ACTIVITY_SPSUMMON)==0 end
-- Register CANNOT_SPECIAL_SUMMON, CANNOT_SUMMON, CANNOT_FLIP_SUMMON for the rest of turn
local e1=Effect.CreateEffect(e:GetHandler())
e1:SetType(EFFECT_TYPE_FIELD)
e1:SetProperty(EFFECT_FLAG_PLAYER_TARGET+EFFECT_FLAG_OATH)
e1:SetCode(EFFECT_CANNOT_SPECIAL_SUMMON)
e1:SetReset(RESET_PHASE+PHASE_END)
e1:SetTargetRange(1,0)
e1:SetLabelObject(e)
e1:SetTarget(c73915051.sumlimit)
Duel.RegisterEffect(e1,tp)
-- ...same for CANNOT_SUMMON, CANNOT_FLIP_SUMMON
end
function c73915051.sumlimit(e,c,sump,sumtype,sumpos,targetp,se)
return e:GetLabelObject()~=se -- block everything except this very effect
end
Token creation in the operation:
for i=1,4 do
local token=Duel.CreateToken(tp,73915051+i)
Duel.SpecialSummonStep(token,0,tp,tp,false,false,POS_FACEUP_DEFENSE)
-- Tag tokens as "cannot be tributed"
local e1=Effect.CreateEffect(e:GetHandler())
e1:SetType(EFFECT_TYPE_SINGLE)
e1:SetCode(EFFECT_UNRELEASABLE_SUM)
e1:SetValue(1)
e1:SetReset(RESET_EVENT+RESETS_STANDARD)
token:RegisterEffect(e1,true)
end
Duel.SpecialSummonComplete()
Pattern. Multiple tokens at once → SpecialSummonStep × N then
one SpecialSummonComplete(). The “cannot Tribute Summon” rider on
the tokens uses EFFECT_UNRELEASABLE_SUM. The summon lock is registered
in cost (so it applies even if the activation is later negated, per
PSCT) and uses LabelObject(e) so the activation itself can still
resolve.
7.6 Procedure Summoned Monsters
Stardust Dragon (`c44508094`) — Synchro
1 Tuner + 1+ non-Tuner monsters
aux.AddSynchroProcedure(c,nil,aux.NonTuner(nil),1)
c:EnableReviveLimit()
The negate-and-tribute-self trigger:
local e1=Effect.CreateEffect(c)
e1:SetCategory(CATEGORY_NEGATE+CATEGORY_DESTROY)
e1:SetType(EFFECT_TYPE_QUICK_O)
e1:SetCode(EVENT_CHAINING)
e1:SetProperty(EFFECT_FLAG_DAMAGE_STEP+EFFECT_FLAG_DAMAGE_CAL)
e1:SetRange(LOCATION_MZONE)
e1:SetCondition(c44508094.condition)
e1:SetCost(c44508094.cost)
e1:SetTarget(c44508094.target)
e1:SetOperation(c44508094.operation)
c:RegisterEffect(e1)
Revive in End Phase if the negate fired:
e:GetHandler():RegisterFlagEffect(44508094,RESET_EVENT+RESETS_STANDARD+RESET_PHASE+PHASE_END,0,0)
...
function c44508094.sumtg(...)
if chk==0 then return Duel.GetLocationCount(tp,LOCATION_MZONE)>0
and c:GetFlagEffect(44508094)>0
and c:IsCanBeSpecialSummoned(e,0,tp,false,false) end
Duel.SetOperationInfo(0,CATEGORY_SPECIAL_SUMMON,c,1,0,0)
end
Pattern. Synchro Monster boilerplate:
aux.AddSynchroProcedure(c, tuner_filter, non_tuner_filter, min_non_tuners[, max_non_tuners[, level_check]]).c:EnableReviveLimit()so revive cards can’t bypass the procedure.EFFECT_TYPE_QUICK_O+EVENT_CHAININGis the classic “Quick
Effect that responds to a chain link” shape.- RegisterFlagEffect → conditional revive in End Phase: write a flag
in the operation, then a separate trigger reads the flag in the
sum-tg/sum-op pair.
Number 39: Utopia (`c84013237`) — Xyz
2 Level 4 monsters
aux.AddXyzProcedure(c,nil,4,2)
c:EnableReviveLimit()
aux.xyz_number[84013237]=39
Material-detach to negate an attack:
function c84013237.atkcost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return e:GetHandler():CheckRemoveOverlayCard(tp,1,REASON_COST) end
e:GetHandler():RemoveOverlayCard(tp,1,1,REASON_COST)
end
function c84013237.atkop(e,tp,eg,ep,ev,re,r,rp)
Duel.NegateAttack()
end
Pattern. Xyz boilerplate:
aux.AddXyzProcedure(c, material_filter, required_level, required_count[, max]).aux.xyz_number[code] = N(purely cosmetic — the Number’s number).- Detach material as cost:
CheckRemoveOverlayCard+RemoveOverlayCard. Duel.NegateAttack()is the canonical attack-negate primitive.
Decode Talker (`c1861629`) — Link
2+ Effect Monsters
Gains 500 ATK for each monster it points to.
aux.AddLinkProcedure(c,aux.FilterBoolFunction(Card.IsLinkType,TYPE_EFFECT),2)
local e1=Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_SINGLE)
e1:SetCode(EFFECT_UPDATE_ATTACK)
e1:SetProperty(EFFECT_FLAG_SINGLE_RANGE)
e1:SetRange(LOCATION_MZONE)
e1:SetValue(c1861629.atkval)
c:RegisterEffect(e1)
function c1861629.atkval(e,c)
return c:GetLinkedGroupCount()*500
end
-- Tribute-a-pointed-monster to negate
function c1861629.discost(e,tp,eg,ep,ev,re,r,rp,chk)
local lg=e:GetHandler():GetLinkedGroup()
if chk==0 then return Duel.CheckReleaseGroup(tp,c1861629.cfilter,1,nil,lg) end
local g=Duel.SelectReleaseGroup(tp,c1861629.cfilter,1,1,nil,lg)
Duel.Release(g,REASON_COST)
end
Pattern. Link boilerplate:
aux.AddLinkProcedure(c, material_filter, min[, max[, group_check]]).- “Gains X for each monster it points to” →
EFFECT_UPDATE_ATTACK
SetValuereturnsGetLinkedGroupCount() * X. - Tribute a linked monster:
Duel.CheckReleaseGroup+SelectReleaseGroup
filtered byGetLinkedGroup()membership.
Polymerization (`c24094653`) — Fusion
Fusion Summon 1 Fusion Monster from your Extra Deck, using monsters
from your hand or field as Fusion Material.
function c24094653.filter2(c,e,tp,m,f,chkf)
return c:IsType(TYPE_FUSION) and (not f or f(c))
and c:IsCanBeSpecialSummoned(e,SUMMON_TYPE_FUSION,tp,false,false)
and c:CheckFusionMaterial(m,nil,chkf)
end
function c24094653.activate(e,tp,eg,ep,ev,re,r,rp)
local chkf=tp
local mg1=Duel.GetFusionMaterial(tp):Filter(c24094653.filter1,nil,e)
local sg1=Duel.GetMatchingGroup(c24094653.filter2,tp,LOCATION_EXTRA,0,nil,e,tp,mg1,nil,chkf)
-- ...
if sg1:GetCount()>0 ... then
local tg=sg:Select(tp,1,1,nil)
local tc=tg:GetFirst()
local mat1=Duel.SelectFusionMaterial(tp,tc,mg1,nil,chkf)
tc:SetMaterial(mat1)
Duel.SendtoGrave(mat1,REASON_EFFECT+REASON_MATERIAL+REASON_FUSION)
Duel.BreakEffect()
Duel.SpecialSummon(tc,SUMMON_TYPE_FUSION,tp,tp,false,false,POS_FACEUP)
tc:CompleteProcedure()
end
end
Pattern. Fusion summoning by an external card uses
Duel.GetFusionMaterial(tp) (every card the player can use as
material), CheckFusionMaterial on the Extra-Deck candidate,
SelectFusionMaterial to pick the actual mix, SendtoGrave with the
combo REASON_EFFECT+REASON_MATERIAL+REASON_FUSION, then
SpecialSummon with SUMMON_TYPE_FUSION and tc:CompleteProcedure()
to mark the procedure satisfied.
For Fusion monsters themselves, the typical setup in their script is
aux.AddFusionProcCode2(c, mat1_code, mat2_code, true, true) or
aux.AddFusionProcMix(...) — see utility.lua in the scripts repo.
7.7 Negation
Solemn Judgment (`c41420027`) — negate Summon OR S/T activation, pay ½ LP
When a monster(s) would be Summoned, OR a Spell/Trap Card is activated:
Pay half your LP; negate the Summon or activation, and if you do,
destroy that card.
-- Summon-side: 3 effects (Normal/Flip/Special)
local e1=Effect.CreateEffect(c)
e1:SetCategory(CATEGORY_DISABLE_SUMMON+CATEGORY_DESTROY)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetCode(EVENT_SUMMON)
e1:SetCondition(aux.NegateSummonCondition)
e1:SetCost(c41420027.cost1)
e1:SetTarget(c41420027.target1)
e1:SetOperation(c41420027.activate1)
c:RegisterEffect(e1)
-- ...e2 = Clone with EVENT_FLIP_SUMMON, e3 = Clone with EVENT_SPSUMMON
-- Effect-side: chain-link negate
local e4=Effect.CreateEffect(c)
e4:SetCategory(CATEGORY_NEGATE+CATEGORY_DESTROY)
e4:SetType(EFFECT_TYPE_ACTIVATE)
e4:SetCode(EVENT_CHAINING)
e4:SetCondition(c41420027.condition2)
e4:SetCost(c41420027.cost2)
e4:SetTarget(c41420027.target2)
e4:SetOperation(c41420027.activate2)
c:RegisterEffect(e4)
function c41420027.cost1(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return true end
Duel.PayLPCost(tp,math.floor(Duel.GetLP(tp)/2))
end
function c41420027.activate1(e,tp,eg,ep,ev,re,r,rp)
Duel.NegateSummon(eg)
Duel.Destroy(eg,REASON_EFFECT)
end
function c41420027.condition2(e,tp,eg,ep,ev,re,r,rp)
return re:IsHasType(EFFECT_TYPE_ACTIVATE) and Duel.IsChainNegatable(ev)
end
function c41420027.activate2(e,tp,eg,ep,ev,re,r,rp)
if Duel.NegateActivation(ev) and re:GetHandler():IsRelateToEffect(re) then
Duel.Destroy(eg,REASON_EFFECT)
end
end
Pattern. Negate-Summon vs. Negate-Activation are two distinct
event/condition shapes:
| Negating … | Event | Condition | Negate call |
|---|---|---|---|
| Summon | EVENT_SUMMON / EVENT_FLIP_SUMMON / EVENT_SPSUMMON |
aux.NegateSummonCondition |
Duel.NegateSummon(eg) |
| Activation | EVENT_CHAINING |
re:IsHasType(EFFECT_TYPE_ACTIVATE) and Duel.IsChainNegatable(ev) |
Duel.NegateActivation(ev) |
Pay half LP cost: Duel.PayLPCost(tp, math.floor(Duel.GetLP(tp)/2)).
Solemn Strike (`c40605147`) — pay 1500 to negate monster effect or SpSummon
When a monster(s) would be Special Summoned, OR a monster effect is
activated: Pay 1500 LP; negate the Summon or activation, and if you do,
destroy that card.
Cost:
function c40605147.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.CheckLPCost(tp,1500) end
Duel.PayLPCost(tp,1500)
end
Effect-side condition narrows to monster activations:
function c40605147.condition(e,tp,eg,ep,ev,re,r,rp)
return re:IsActiveType(TYPE_MONSTER) and Duel.IsChainNegatable(ev)
end
Pattern. re:IsActiveType(TYPE_MONSTER) is how you narrow a
chain-negate to monster effects only. Replace with
TYPE_SPELL+TYPE_TRAP for the inverse, etc.
Solemn Warning (`c84749824`) — pay 2000 to negate Summon or SS-from-effect
The structure is the same as Solemn Judgment plus a check that the
activated effect would Special Summon (re:IsHasCategory(CATEGORY_SPECIAL_SUMMON)).
Cost: Duel.PayLPCost(tp, 2000).
Effect Veiler / Ash Blossom / Ghost Ogre — hand-trap monster effect negators
(Quick Effect): You can send this card from your hand to the GY;
negate the [Summon/effect/activation] of a monster …
These all share the same shape:
e1:SetType(EFFECT_TYPE_QUICK_O)
e1:SetCode(EVENT_CHAINING)
e1:SetRange(LOCATION_HAND)
e1:SetCountLimit(1, <card-id>) -- hard once per turn
e1:SetCondition(<narrow to a particular activation>)
e1:SetCost(<send self to GY>)
e1:SetOperation(<NegateActivation / DisableEffect / NegateAttack ...>)
The cost is invariably:
function s.cost(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return e:GetHandler():IsAbleToGraveAsCost() end
Duel.SendtoGrave(e:GetHandler(),REASON_COST)
end
The variant chooses what to negate:
| Card | Condition narrows to | Operation |
|---|---|---|
| Effect Veiler | re:IsActiveType(TYPE_MONSTER) and tp == opponent |
Duel.NegateRelatedChain(ev, RESET_TURN_SET) (negates the rest of the chain link’s effects until end of turn) |
| Ash Blossom | activation from Deck/hand by specific category (Search/Draw/SS-from-Deck) | Duel.NegateActivation(ev) |
| Ghost Ogre | activation that affects a face-up card on the field | Duel.NegateRelatedChain(ev, RESET_TURN_SET) + Duel.Destroy(re:GetHandler(), REASON_EFFECT) |
Stardust-style on-board negate
Already covered in section 6 (Stardust Dragon). The essential shape: a
EFFECT_TYPE_QUICK_O from LOCATION_MZONE listening to
EVENT_CHAINING, condition gated by the chained effect’s categories, and
the operation runs Duel.NegateActivation(ev) plus Duel.Destroy(eg,…).
Decode Talker / Knightmare … — “your opponent activates a Spell/Trap”
Same EVENT_CHAINING shape with the condition narrowed by
re:IsActiveType(TYPE_SPELL+TYPE_TRAP) and ep==1-tp (it’s the
opponent’s chain link). See Decode Talker code in section 6.
7.8 Stat Changes and Battle
Honest (`c37742478`) — single-target ATK boost during damage step
During damage calculation, if a LIGHT monster you control battles an
opponent’s monster (Quick Effect): You can send this card from your
hand to the GY; that LIGHT monster gains ATK equal to the opponent’s
monster’s ATK during this damage calculation only.
The pattern is EFFECT_TYPE_QUICK_O with the property
EFFECT_FLAG_DAMAGE_STEP + EFFECT_FLAG_DAMAGE_CAL so it can fire during
damage calc, hand range, send-self-to-GY cost, and the operation
registers an EFFECT_UPDATE_ATTACK with RESET_PHASE+PHASE_DAMAGE_CAL
on the LIGHT monster equal to bc:GetAttack().
United We Stand (`c56747793`) — equip with variable ATK/DEF
The equipped monster gains 800 ATK and DEF for each face-up monster you
control.
e2:SetType(EFFECT_TYPE_EQUIP)
e2:SetCode(EFFECT_UPDATE_ATTACK)
e2:SetValue(c56747793.value)
function c56747793.value(e,c)
return Duel.GetMatchingGroupCount(Card.IsFaceup, e:GetHandlerPlayer(), LOCATION_MZONE, 0, nil) * 800
end
Pattern. “Variable bonus” → SetValue returns a function rather
than a number; the engine re-evaluates whenever conditions change.
EFFECT_TYPE_EQUIP makes the effect apply to the equipped monster
automatically.
Forbidden Chalice (`c96947648`) — temporary +400 ATK + negate
Target 1 face-up monster on the field; until the end of this turn,
that target gains 400 ATK, but its effects are negated.
The operation registers two single-turn effects on the target:
local e1=Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_SINGLE)
e1:SetCode(EFFECT_UPDATE_ATTACK)
e1:SetValue(400)
e1:SetReset(RESET_EVENT+RESETS_STANDARD+RESET_PHASE+PHASE_END)
tc:RegisterEffect(e1)
local e2=Effect.CreateEffect(c)
e2:SetType(EFFECT_TYPE_SINGLE)
e2:SetCode(EFFECT_DISABLE)
e2:SetReset(RESET_EVENT+RESETS_STANDARD+RESET_PHASE+PHASE_END)
tc:RegisterEffect(e2)
Pattern. “Until end of turn” effects on a specific card are
registered directly on that card with
RESET_EVENT+RESETS_STANDARD+RESET_PHASE+PHASE_END so they fall off when
the target leaves the field OR at the end of the turn.
Sky Striker Ace - Shizuku (`c90673288`) — opponent debuff scaling with own GY
Monsters your opponent controls lose 100 ATK/DEF for each Spell in your
GY.
local e1=Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_FIELD)
e1:SetCode(EFFECT_UPDATE_ATTACK)
e1:SetRange(LOCATION_MZONE)
e1:SetTargetRange(0,LOCATION_MZONE) -- only opponent's MZ
e1:SetValue(c90673288.atkval)
c:RegisterEffect(e1)
function c90673288.atkval(e)
return Duel.GetMatchingGroupCount(Card.IsType,e:GetHandlerPlayer(),LOCATION_GRAVE,0,nil,TYPE_SPELL)*-100
end
Pattern. Field-scope ATK/DEF modifier: EFFECT_TYPE_FIELD +
EFFECT_UPDATE_ATTACK + SetTargetRange to choose which players’ zones
it applies to. Negative SetValue for a debuff.
7.9 Continuous Restriction and Protection
Skill Drain (`c82732705`) — disable face-up monster effects on the field
Activate by paying 1000 LP. Negate the effects of all face-up Effect
Monsters on the field.
local e2=Effect.CreateEffect(c)
e2:SetType(EFFECT_TYPE_FIELD)
e2:SetRange(LOCATION_SZONE)
e2:SetTargetRange(LOCATION_MZONE,LOCATION_MZONE)
e2:SetTarget(c82732705.disable)
e2:SetCode(EFFECT_DISABLE)
c:RegisterEffect(e2)
function c82732705.disable(e,c)
return c:IsType(TYPE_EFFECT) or c:GetOriginalType()&TYPE_EFFECT~=0
end
Cost on activation: Duel.PayLPCost(tp, 1000).
Pattern. Continuous “negate effects” trap = EFFECT_TYPE_FIELD +
EFFECT_DISABLE, range = where the card lives (S/T zone), target range =
where the affected cards live (both MZs), target = filter for affected
cards.
Soul Drain (`c73599290`) — disable banished/GY monster activations
Activate by paying 1000 LP. Monsters that are banished, as well as
monsters in the GY, cannot activate their effects.
local e2=Effect.CreateEffect(c)
e2:SetType(EFFECT_TYPE_FIELD)
e2:SetProperty(EFFECT_FLAG_PLAYER_TARGET)
e2:SetCode(EFFECT_CANNOT_ACTIVATE)
e2:SetRange(LOCATION_SZONE)
e2:SetTargetRange(1,1)
e2:SetValue(c73599290.aclimit)
c:RegisterEffect(e2)
function c73599290.aclimit(e,re,tp)
local loc=re:GetActivateLocation()
return (loc==LOCATION_GRAVE or loc==LOCATION_REMOVED) and re:IsActiveType(TYPE_MONSTER)
end
Pattern. “Cannot activate” with a predicate on the chained effect
→ EFFECT_CANNOT_ACTIVATE with SetValue(<aclimit predicate>). The
predicate gets (e, re, tp) — re:GetActivateLocation() /
re:IsActiveType(...) are the most useful queries.
Imperial Iron Wall (`c30459350`) — neither player can banish
local e2=Effect.CreateEffect(c)
e2:SetType(EFFECT_TYPE_FIELD)
e2:SetCode(EFFECT_CANNOT_REMOVE)
e2:SetRange(LOCATION_SZONE)
e2:SetProperty(EFFECT_FLAG_PLAYER_TARGET)
e2:SetTargetRange(1,1)
c:RegisterEffect(e2)
Pattern. Action-type bans like “cannot banish / cannot draw /
cannot tribute” are EFFECT_CANNOT_* codes with EFFECT_FLAG_PLAYER_TARGET
and SetTargetRange(1,1) to apply to both players.
Imperial Order (`c61740673`) — negate all Spell effects + LP maintain
Negate all Spell effects on the field. Once per turn, during the
Standby Phase, you must pay 700 LP (this is not optional), or this card
is destroyed.
-- disable existing Spells in the S/T zones
e2:SetType(EFFECT_TYPE_FIELD)
e2:SetCode(EFFECT_DISABLE)
e2:SetRange(LOCATION_SZONE)
e2:SetTargetRange(LOCATION_SZONE,LOCATION_SZONE)
e2:SetTarget(c61740673.distarget)
function c61740673.distarget(e,c)
return c~=e:GetHandler() and c:IsType(TYPE_SPELL)
end
-- intercept chains and negate Spell-link activations
e3:SetType(EFFECT_TYPE_FIELD+EFFECT_TYPE_CONTINUOUS)
e3:SetCode(EVENT_CHAIN_SOLVING)
e3:SetRange(LOCATION_SZONE)
e3:SetOperation(c61740673.disoperation)
function c61740673.disoperation(e,tp,eg,ep,ev,re,r,rp)
local tl=Duel.GetChainInfo(ev,CHAININFO_TRIGGERING_LOCATION)
if bit.band(tl,LOCATION_SZONE)~=0 and re:IsActiveType(TYPE_SPELL) then
Duel.NegateEffect(ev)
end
end
-- mandatory maintenance
e4:SetCode(EVENT_PHASE+PHASE_STANDBY)
e4:SetCountLimit(1)
e4:SetOperation(c61740673.mtop)
function c61740673.mtop(e,tp,eg,ep,ev,re,r,rp)
if Duel.CheckLPCost(tp,700) then
Duel.PayLPCost(tp,700)
else
Duel.Destroy(e:GetHandler(),REASON_COST)
end
end
Pattern. Two layers for “negate all Spell effects”:
EFFECT_DISABLEfor cards already on the board (continuous Spells).EVENT_CHAIN_SOLVINGinterceptor for activations (Normal/Quick-Play
Spells). Without (2) the activation’s chained effect still
resolves; (2) callsDuel.NegateEffect(ev)mid-resolution.
The maintenance cost is a EVENT_PHASE+PHASE_STANDBY 1-shot that pays
or self-destroys.
Set Rotation (`c73468603`) — both-side Field Spell lock
Set 2 Field Spells with different names from your Deck on the field
(1 on your field, and 1 on your opponent’s field). While either of
those cards remain Set on the field, neither player can activate or
Set other Field Spells.
function c73468603.actlimit(e,re,tp)
return re:IsActiveType(TYPE_FIELD) and re:IsHasType(EFFECT_TYPE_ACTIVATE)
and re:GetHandler():GetFlagEffect(73468603)==0
end
function c73468603.setlimit(e,c,tp)
return c:IsType(TYPE_FIELD) and c:GetFlagEffect(73468603)==0
end
Pattern. Tag the cards with a RegisterFlagEffect(73468603, …) and
build the lock predicates around “anything not tagged is forbidden.”
This is the standard idiom for “this card and the cards it placed are
exempt; everything else is restricted.”
Mystic Mine (`c86871614`) — already covered (Cloning) — but the engine pattern of “field-spell continuous restriction” is identical: `EFFECT_TYPE_FIELD + EFFECT_CANNOT_ATTACK / EFFECT_CANNOT_ACTIVATE` registered at activate time
(Cloning script is in section 5 / token-creation territory; the original
Mystic Mine restriction template uses
EFFECT_CANNOT_ATTACK / EFFECT_CANNOT_ACTIVATE with a
SetCondition that compares Duel.GetFieldGroupCount for both sides.)
7.10 Burn and Recovery
Just Desserts (`c24068492`) — variable burn
Inflict 500 damage to your opponent for each monster they control.
function c24068492.target(e,tp,eg,ep,ev,re,r,rp,chk)
if chk==0 then return Duel.IsExistingMatchingCard(aux.TRUE,tp,0,LOCATION_MZONE,1,nil) end
Duel.SetTargetPlayer(1-tp)
local dam=Duel.GetFieldGroupCount(1-tp,LOCATION_MZONE,0)*500
Duel.SetTargetParam(dam)
Duel.SetOperationInfo(0,CATEGORY_DAMAGE,nil,0,1-tp,dam)
end
function c24068492.activate(e,tp,eg,ep,ev,re,r,rp)
local p,d=Duel.GetChainInfo(0,CHAININFO_TARGET_PLAYER,CHAININFO_TARGET_PARAM)
Duel.Damage(p,dam,REASON_EFFECT) -- but the canonical is GetChainInfo'd p, dam pair
end
(The script reads dam again from GetFieldGroupCount in activate,
not from GetChainInfo — this matches the PSCT “for each monster they
control [at resolution]”.)
Pattern. Variable burn — compute dam in target and in activate,
because the count is re-checked at resolution per OCG rules.
SetTargetPlayer(1-tp) + SetTargetParam(dam) lets responses see how
much damage will resolve.
Snatch Steal (`c45986603`) — opponent gains LP each Standby
During each of your opponent’s Standby Phases: They gain 1000 Life
Points.
e2:SetType(EFFECT_TYPE_FIELD+EFFECT_TYPE_TRIGGER_F)
e2:SetCategory(CATEGORY_RECOVER)
e2:SetCode(EVENT_PHASE+PHASE_STANDBY)
e2:SetRange(LOCATION_SZONE)
e2:SetCountLimit(1)
e2:SetCondition(c45986603.reccon)
e2:SetTarget(c45986603.rectg)
e2:SetOperation(c45986603.recop)
function c45986603.reccon(e,tp,eg,ep,ev,re,r,rp)
return tp~=Duel.GetTurnPlayer()
end
function c45986603.recop(e,tp,eg,ep,ev,re,r,rp)
local p,d=Duel.GetChainInfo(0,CHAININFO_TARGET_PLAYER,CHAININFO_TARGET_PARAM)
Duel.Recover(p,d,REASON_EFFECT)
end
Pattern. Recurring phase trigger: EVENT_PHASE+PHASE_STANDBY
mandatory (TRIGGER_F), CountLimit(1) to fire only once per phase,
condition gates by turn player. Duel.Recover(p, n, REASON_EFFECT) to
heal.
7.11 Equip and Control Change
Snatch Steal (`c45986603`) — equip-and-take-control
Equip only to a monster your opponent controls. Take control of the
equipped monster.
-- Activation = target opponent's monster, equip + control
e1:SetCategory(CATEGORY_CONTROL+CATEGORY_EQUIP)
e1:SetType(EFFECT_TYPE_ACTIVATE)
e1:SetCode(EVENT_FREE_CHAIN)
e1:SetProperty(EFFECT_FLAG_CARD_TARGET+EFFECT_FLAG_CONTINUOUS_TARGET)
-- Equip-limit (only opponent's monsters allowed as targets)
e3:SetType(EFFECT_TYPE_SINGLE)
e3:SetCode(EFFECT_EQUIP_LIMIT)
e3:SetValue(c45986603.eqlimit)
function c45986603.eqlimit(e,c)
return e:GetHandlerPlayer()~=c:GetControler() or e:GetHandler():GetEquipTarget()==c
end
-- Control switch while equipped
e4:SetType(EFFECT_TYPE_EQUIP)
e4:SetCode(EFFECT_SET_CONTROL)
e4:SetValue(c45986603.ctval)
function c45986603.ctval(e,c)
return e:GetHandlerPlayer()
end
Pattern. Equip-card structure:
- Activation registers a
CATEGORY_EQUIPeffect that calls
Duel.Equip(tp, e:GetHandler(), tc)in operation. EFFECT_EQUIP_LIMIT(SetValuereturns a predicate) restricts which
monsters can be equipped. (Duel.Equipitself doesn’t validate
ownership — the limit does.)EFFECT_TYPE_EQUIP+EFFECT_SET_CONTROLflips controller while the
equip is active.
7.12 Flip Effects
Man-Eater Bug (`c54652250`) — destroy on flip
FLIP: Target 1 monster on the field; destroy it.
local e1=Effect.CreateEffect(c)
e1:SetCategory(CATEGORY_DESTROY)
e1:SetProperty(EFFECT_FLAG_CARD_TARGET)
e1:SetType(EFFECT_TYPE_SINGLE+EFFECT_TYPE_FLIP)
e1:SetTarget(c54652250.target)
e1:SetOperation(c54652250.operation)
c:RegisterEffect(e1)
function c54652250.target(e,tp,eg,ep,ev,re,r,rp,chk,chkc)
if chkc then return chkc:IsLocation(LOCATION_MZONE) end
if chk==0 then return true end
Duel.Hint(HINT_SELECTMSG,tp,HINTMSG_DESTROY)
local g=Duel.SelectTarget(tp,aux.TRUE,tp,LOCATION_MZONE,LOCATION_MZONE,1,1,nil)
Duel.SetOperationInfo(0,CATEGORY_DESTROY,g,g:GetCount(),0,0)
end
Pattern. Flip effects = EFFECT_TYPE_SINGLE + EFFECT_TYPE_FLIP, no
Code or Range (the flip itself is the trigger). The rest is the
usual target+operation pair.
7.13 Hand Trap Chain Reactors
Maxx “C” (`c23434538`)
(Quick Effect): You can send this card from your hand to the GY; this
turn, each time your opponent Special Summons a monster(s),
immediately draw 1 card. You can only use this effect of “Maxx ‘C’”
once per turn.
The interesting half is the operation, which registers three
field-scope effects that survive until end of turn:
-- (1) draw 1 immediately when SS happens outside chain solving
local e1=Effect.CreateEffect(c)
e1:SetType(EFFECT_TYPE_CONTINUOUS+EFFECT_TYPE_FIELD)
e1:SetProperty(EFFECT_FLAG_DELAY)
e1:SetCode(EVENT_SPSUMMON_SUCCESS)
e1:SetCondition(c23434538.drcon1)
e1:SetOperation(c23434538.drop1)
e1:SetReset(RESET_PHASE+PHASE_END)
Duel.RegisterEffect(e1,tp)
-- (2) bookkeeping flag for SS during chain solving (count them)
e2:SetCode(EVENT_SPSUMMON_SUCCESS)
e2:SetCondition(c23434538.regcon)
e2:SetOperation(c23434538.regop)
function c23434538.regop(...)
Duel.RegisterFlagEffect(tp,23434538,RESET_CHAIN,0,1)
end
-- (3) when the chain finishes, draw N (where N == flag count)
e3:SetCode(EVENT_CHAIN_SOLVED)
e3:SetCondition(c23434538.drcon2)
e3:SetOperation(c23434538.drop2)
function c23434538.drop2(...)
local n=Duel.GetFlagEffect(tp,23434538)
Duel.ResetFlagEffect(tp,23434538)
Duel.Draw(tp,n,REASON_EFFECT)
end
Pattern. Anything that says “each time” during this turn ⇒ register
a continuous trigger that resets at end of turn. If the trigger fires
during a chain, defer the actual action until EVENT_CHAIN_SOLVED so
multi-summon plays generate one consolidated draw, and use a flag effect
to count occurrences.
7.14 Activation Locks and Once Per Turn Variants
Hard once per turn — `SetCountLimit(1, <id>)`
e1:SetCountLimit(1, 23434538)
The unique 2nd argument keys the count limit by card name (PSCT: “You
can only use this effect of [name] once per turn”).
Soft once per turn — `SetCountLimit(1)`
e1:SetCountLimit(1)
No 2nd argument → resets when the effect leaves the field. PSCT: “Once
per turn” (no name lock).
Hard once per turn AND oath — `SetCountLimit(1, <id>+EFFECT_COUNT_CODE_OATH)`
Used by Pot of Desires, Card of Demise. PSCT: “You can only activate 1
[name] per turn” — even if negated, you can’t try again.
Once per turn Special Summon (Sky Striker Ace - Shizuku)
c:SetSPSummonOnce(90673288)
PSCT: “You can only Special Summon X(s) once per turn.” A single helper
on the card object — no manual flag management needed.
A few cross-pattern utilities you’ll see constantly
| Need | Idiom |
|---|---|
| Skip yourself when filtering field cards | aux.ExceptThisCard(e) (last arg of GetMatchingGroup/SelectTarget) |
| Filter that always returns true | aux.TRUE |
| Different-name check on a group | g:GetClassCount(Card.GetCode) == g:GetCount() |
| Boolean wrapper for a predicate | aux.FilterBoolFunction(Card.IsType, TYPE_EFFECT) |
| LP cost shorthand | Duel.CheckLPCost(tp, n) / Duel.PayLPCost(tp, n) |
| Discard-from-hand cost | Duel.DiscardHand(tp, Card.IsDiscardable, 1, 1, REASON_COST+REASON_DISCARD) |
| Send self to GY as cost | Duel.SendtoGrave(e:GetHandler(), REASON_COST) |
| Detach Xyz material as cost | c:CheckRemoveOverlayCard(tp, n, REASON_COST) / c:RemoveOverlayCard(tp, n, n, REASON_COST) |
| Tribute as cost | Duel.CheckReleaseGroup(tp, filter, n, …) / Duel.SelectReleaseGroup(...) / Duel.Release(g, REASON_COST) |
| String localization for selection prompt | aux.Stringid(<card_id>, <index>) (indices come from the card’s strings.conf) |
| Reset bag for “until end of turn or until off-field” | RESET_EVENT+RESETS_STANDARD+RESET_PHASE+PHASE_END |
| Reset bag for “until off-field” | RESET_EVENT+RESETS_STANDARD |
| Negate a chain link’s effects (Veiler-style) | Duel.NegateRelatedChain(ev, RESET_TURN_SET) |
| Negate a chained activation (Solemn-style) | Duel.NegateActivation(ev) |
| Negate a Summon (Solemn Warning-style) | Duel.NegateSummon(eg) |
| Negate an attack (Utopia-style) | Duel.NegateAttack() |
Common gotchas worth flagging up front
IsRelateToEffectis not optional. Any operation that touches a
targeted card has to re-checktc:IsRelateToEffect(e)because the
target can be removed/banished/bounced between target and resolution.
Skipping the check leads to ghost actions and engine errors.SetTargetPlayer+SetTargetParamare paired. If you set one
you almost always set the other, and you pull both back in the
operation viaDuel.GetChainInfo(0, CHAININFO_TARGET_PLAYER, CHAININFO_TARGET_PARAM).EFFECT_FLAG_CARD_TARGETbelongs on activation, not on the
target callback. The flag tells the engine the chain link can be
targeted; the callback’s job is just to enumerate legal choices.- Hard-OPT keys must be unique per “card name” semantically. Two
different effects on the same card that share an OPT use the same
key. Two different cards that share an OPT semantically use the
same key (e.g. all “Pot of Greed-name” cards would share). When in
doubt, the card’s own ID is the key. - Cost runs even if the activation is negated. Target may not. If
a clause must apply “even if negated” (oath effects, hand-discards,
summon locks) put it in the cost. If it should only apply on
resolution, put it in the operation. Duel.BreakEffect()between twoDuel.X(…)calls in an operation
creates a chain split. Use it when PSCT says “and if you do, …”
between two distinct game actions (Upstart, Allure, etc.).- For “would be …” timing (Solemn Judgment-style), use the
EVENT_SUMMONevent (notEVENT_SUMMON_SUCCESS) and pair with
aux.NegateSummonCondition. The SUCCESS variants fire after the
summon has happened.
This document covers ≈95% of the patterns you’ll encounter writing
scripts. For the remaining 5% — bizarre legacy cards, weird damage-step
interactions, custom procedures — open an existing card with similar
text in script/cXXXXX.lua and copy its shape. The engine source itself
is documented in omega-3-core-rewrite/ if you need to confirm what an
engine call actually does.
Happy scripting.