YGOPro Scripting Cookbook (Compatible with Omega)

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


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 in initial_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
    SINGLE applies to this single card (fields on it)
    FIELD applies to multiple cards / players (must be on the field)
    EQUIP granted to the equipped target
    ACTIONS can be activated as an action (auto-added by trigger types)
    ACTIVATE spell/trap activation (the printed text of a S/T)
    IGNITION “During your Main Phase: …” (active monster ability)
    TRIGGER_F mandatory trigger (“When/If X happens, …”)
    TRIGGER_O optional trigger (“When/If X happens, you can …”)
    QUICK_F mandatory quick effect (rare — Royal Decree etc.)
    QUICK_O optional quick effect (“During either player’s turn: you can …”)
    FLIP “FLIP: …”
    CONTINUOUS always-on / state machine (used for bookkeeping or grant chains)
    XMATERIAL effect that’s active only while this card is Xyz material
    GRANT gives an effect to other cards (Weather Painter etc.)
    TARGET influences 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 the EFFECT_* codes (e.g. EFFECT_UPDATE_ATTACK,
      EFFECT_INDESTRUCTABLE, EFFECT_CANNOT_ATTACK).
    • For EFFECT_TYPE_TRIGGER_*/QUICK_*/IGNITION/ACTIVATE, the code is
      an EVENT_* (e.g. EVENT_SUMMON_SUCCESS, EVENT_DESTROYED,
      EVENT_FREE_CHAIN, EVENT_CHAINING).
  • 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_ACTIVATE setup the engine handles the on-field too).
  • 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 optional code lets multiple cards
    share a counter (e.g. all “Snake-Eye” cards sharing 1, 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; or SUMMON_TYPE_FUSION etc. 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

  1. Always check chk==0 first in target and cost. 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.

  2. Always check IsRelateToEffect(e) on a target before acting on it
    in operation.
    Targets can leave the field between target time and
    resolution time.

  3. 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.

  4. Use EFFECT_FLAG_DELAY for “if” trigger effects. “When … you
    can …” is TRIGGER_O without DELAY. “If … you can …” is TRIGGER_O
    with EFFECT_FLAG_DELAY. The difference matters for missing timing.

  5. 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.

  6. 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, set CATEGORY_DESTROY. If it
    special summons, set CATEGORY_SPECIAL_SUMMON. If it does both,
    + them.

  7. 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).

  8. 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.

  9. 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).

  10. When a card “destroys this card by its own effect” on activation,
    use EFFECT_FLAG_DELAY + Duel.SendtoGrave(c, REASON_RULE)
    in
    the operation, not in the cost. Activation costs are not the
    self-destruct.

  11. Avoid Effect.GlobalEffect() for card-bound effects. Use
    Effect.CreateEffect(c) so the effect is owned by c and resets
    when c leaves play.

  12. SetRange matters. A trigger effect with no range only fires
    while the card is in MZONE by default for SINGLE_TRIGGER. For
    GY-triggered effects you must SetRange(LOCATION_GRAVE).

  13. 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:

  1. Read the card text on the printed card.
  2. Find the closest PSCT pattern in sections 7.1–7.14 below.
  3. Open the cited script (cXXXXXXXX.lua) and use it as a starting
    point. The structure of initial_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:

  1. LP cost via CheckLPCost/PayLPCost.
  2. After SpSummon, immediately Duel.Equip(tp, this, tc).
  3. Register an EFFECT_EQUIP_LIMIT so the equip can’t drift to another
    monster.
  4. Hook EVENT_LEAVE_FIELD so 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:

  1. aux.AddSynchroProcedure(c, tuner_filter, non_tuner_filter, min_non_tuners[, max_non_tuners[, level_check]]).
  2. c:EnableReviveLimit() so revive cards can’t bypass the procedure.
  3. EFFECT_TYPE_QUICK_O + EVENT_CHAINING is the classic “Quick
    Effect that responds to a chain link” shape.
  4. 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:

  1. aux.AddXyzProcedure(c, material_filter, required_level, required_count[, max]).
  2. aux.xyz_number[code] = N (purely cosmetic — the Number’s number).
  3. Detach material as cost: CheckRemoveOverlayCard + RemoveOverlayCard.
  4. 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:

  1. aux.AddLinkProcedure(c, material_filter, min[, max[, group_check]]).
  2. “Gains X for each monster it points to” → EFFECT_UPDATE_ATTACK
    SetValue returns GetLinkedGroupCount() * X.
  3. Tribute a linked monster: Duel.CheckReleaseGroup + SelectReleaseGroup
    filtered by GetLinkedGroup() 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”:

  1. EFFECT_DISABLE for cards already on the board (continuous Spells).
  2. EVENT_CHAIN_SOLVING interceptor for activations (Normal/Quick-Play
    Spells). Without (2) the activation’s chained effect still
    resolves; (2) calls Duel.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:

  1. Activation registers a CATEGORY_EQUIP effect that calls
    Duel.Equip(tp, e:GetHandler(), tc) in operation.
  2. EFFECT_EQUIP_LIMIT (SetValue returns a predicate) restricts which
    monsters can be equipped. (Duel.Equip itself doesn’t validate
    ownership — the limit does.)
  3. EFFECT_TYPE_EQUIP + EFFECT_SET_CONTROL flips 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

  1. IsRelateToEffect is not optional. Any operation that touches a
    targeted card has to re-check tc:IsRelateToEffect(e) because the
    target can be removed/banished/bounced between target and resolution.
    Skipping the check leads to ghost actions and engine errors.
  2. SetTargetPlayer + SetTargetParam are paired. If you set one
    you almost always set the other, and you pull both back in the
    operation via Duel.GetChainInfo(0, CHAININFO_TARGET_PLAYER, CHAININFO_TARGET_PARAM).
  3. EFFECT_FLAG_CARD_TARGET belongs 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.
  4. 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.
  5. 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.
  6. Duel.BreakEffect() between two Duel.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.).
  7. For “would be …” timing (Solemn Judgment-style), use the
    EVENT_SUMMON event (not EVENT_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.