Skip to content
bFormat edited this page Apr 28, 2025 · 2 revisions

SkillScript ์œ„ํ‚ค

์†Œ๊ฐœ

SkillScript๋Š” Bukkit/Spigot ์„œ๋ฒ„ ๊ด€๋ฆฌ์ž์™€ ๊ฐœ๋ฐœ์ž๊ฐ€ YAML ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปค์Šคํ…€ ์Šคํ‚ฌ์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋œ ํ”Œ๋Ÿฌ๊ทธ์ธ์ž…๋‹ˆ๋‹ค. ๋ณต์žกํ•œ Java ์ฝ”๋”ฉ ์—†์ด ๋‹ค์–‘ํ•œ ๊ฒŒ์ž„ ๋‚ด ๋™์ž‘(์•ก์…˜)์„ ์กฐํ•ฉํ•˜์—ฌ ๊ฐ•๋ ฅํ•˜๊ณ  ์œ ์—ฐํ•œ ์Šคํ‚ฌ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์‹œ์ž‘ํ•˜๊ธฐ: ์Šคํฌ๋ฆฝํŠธ ๊ธฐ๋ณธ ๊ตฌ์กฐ

SkillScript ์Šคํฌ๋ฆฝํŠธ๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ ํด๋” ๋‚ด scripts/ ๋””๋ ‰ํ† ๋ฆฌ์— .yaml ๋˜๋Š” .yml ํ™•์žฅ์ž๋ฅผ ๊ฐ€์ง„ ํŒŒ์ผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ๊ฐ ํŒŒ์ผ์€ ํ•˜๋‚˜์˜ ์Šคํ‚ฌ์„ ์ •์˜ํ•˜๋ฉฐ, ํŒŒ์ผ ์ด๋ฆ„(ํ™•์žฅ์ž ์ œ์™ธ)์ด ํ•ด๋‹น ์Šคํ‚ฌ์˜ ๊ณ ์œ ํ•œ ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, Fireball.yaml์ด๋ผ๋Š” ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๋ฉด, /cast Fireball ๋ช…๋ น์–ด๋กœ ํ•ด๋‹น ์Šคํ‚ฌ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ์˜ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

# scripts/Fireball.yaml

# ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์— ๋ฐ”๋กœ ์Šคํ‚ฌ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
OnCast: # ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์Šคํ‚ฌ์„ ์‹œ์ „ํ–ˆ์„ ๋•Œ(/cast Fireball) ๋ฐœ๋™๋˜๋Š” ํŠธ๋ฆฌ๊ฑฐ์ž…๋‹ˆ๋‹ค.
  # ์—ฌ๊ธฐ์— ์ด ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ๋ฐœ๋™๋  ๋•Œ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰๋  ์•ก์…˜ ๋ชฉ๋ก์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  - Variable.SetVariable: { name: "projectileSpeed", value: 1.5 }
  - TargetBehaviour.SendMessage:
      message: "&cLaunching Fireball!"
  - TargetBehaviour.PlayEffect:
      location: "@CasterLocation"
      particle: "LAVA"
      offset: { y: 1.5 } # ์‹œ์ „์ž ๋จธ๋ฆฌ ์œ„์—์„œ ๋ฐœ์‚ฌ๋˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ
  # ... Fireball ๊ด€๋ จ ๋‹ค๋ฅธ ์•ก์…˜๋“ค ...

# OnHit: # (ํ–ฅํ›„ ์ง€์› ์˜ˆ์ •) ์Šคํ‚ฌ ํˆฌ์‚ฌ์ฒด ๋“ฑ์ด ์ ์ค‘ํ–ˆ์„ ๋•Œ
#   - TargetBehaviour.Damage: { amount: 10 }
#   - TargetBehaviour.PlayEffect: { particle: "EXPLOSION_LARGE" }

# ๋‹ค๋ฅธ ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ์žˆ๋‹ค๋ฉด ์—ฌ๊ธฐ์— ์ถ”๊ฐ€...
  • ํ•˜๋‚˜์˜ ํŒŒ์ผ์€ ํ•˜๋‚˜์˜ ์Šคํ‚ฌ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ์Šคํ‚ฌ์„ ๋งŒ๋“ค๋ ค๋ฉด ์—ฌ๋Ÿฌ YAML ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”. (์˜ˆ: Heal.yaml, Teleport.yaml)
  • ํŒŒ์ผ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์—๋Š” ์Šคํ‚ฌ์ด ๋ฐœ๋™๋  ์กฐ๊ฑด์„ ๋‚˜ํƒ€๋‚ด๋Š” ํŠธ๋ฆฌ๊ฑฐ(Trigger) ํ‚ค(์˜ˆ: OnCast, OnHit)๊ฐ€ ์œ„์น˜ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ ํŠธ๋ฆฌ๊ฑฐ ์•„๋ž˜์—๋Š” ์‹คํ–‰๋  ์•ก์…˜(Action)๋“ค์˜ ๋ชฉ๋ก(List)์ด YAML ๋ฆฌ์ŠคํŠธ ํ˜•์‹(-)์œผ๋กœ ๋‚˜์—ด๋ฉ๋‹ˆ๋‹ค.
  • ํ˜„์žฌ ์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํŠธ๋ฆฌ๊ฑฐ๋Š” /cast ๋ช…๋ น์–ด์™€ ์—ฐ๋™๋˜๋Š” OnCast ์ž…๋‹ˆ๋‹ค.

์•ก์…˜ (Actions)

์•ก์…˜์€ ์Šคํ‚ฌ์ด ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฐœ๋ณ„์ ์ธ ๋™์ž‘ ๋‹จ์œ„์ž…๋‹ˆ๋‹ค. ๊ฐ ์•ก์…˜์€ ์ •ํ•ด์ง„ ์ด๋ฆ„๊ณผ ํ•„์š”ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ(Parameters)๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ํ˜•์‹:

- ActionRegistryName: # ActionRegistry์— ๋“ฑ๋ก๋œ ์•ก์…˜์˜ ์ „์ฒด ์ด๋ฆ„ (์˜ˆ: TargetBehaviour.SendMessage)
    parameter1: value1  # ํ•ด๋‹น ์•ก์…˜์ด ์š”๊ตฌํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ๊ฐ’
    parameter2: value2
    # ...

ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž…:

์•ก์…˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • String: ๋ฌธ์ž์—ด. ๋ณดํ†ต ๋”ฐ์˜ดํ‘œ๋Š” ์ƒ๋žต ๊ฐ€๋Šฅํ•˜๋‚˜, ํŠน์ˆ˜๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜๊ฑฐ๋‚˜ ๋ช…ํ™•์„ฑ์„ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. (์˜ˆ: message: "&aHello World!")
  • Integer: ์ •์ˆ˜ ์ˆซ์ž. (์˜ˆ: duration: 20)
  • Double: ์‹ค์ˆ˜ ์ˆซ์ž. (์˜ˆ: amount: 10.5)
  • Boolean: ๋…ผ๋ฆฌ๊ฐ’. true ๋˜๋Š” false. (์˜ˆ: ignoreArmor: true)
  • Location: ๊ฒŒ์ž„ ๋‚ด ์œ„์น˜ ์ขŒํ‘œ. ๋ณ€์ˆ˜, ์…€๋ ‰ํ„ฐ, ๋˜๋Š” ๋งต ํ˜•์‹์œผ๋กœ ์ง€์ • ๊ฐ€๋Šฅ. (์ž์„ธํ•œ ๋‚ด์šฉ์€ ์•„๋ž˜ ์ฐธ์กฐ)
  • Vector: 3์ฐจ์› ๋ฒกํ„ฐ (๋ฐฉํ–ฅ ๋˜๋Š” ์˜คํ”„์…‹). ๋ณ€์ˆ˜, ์…€๋ ‰ํ„ฐ, ๋ฆฌ์ŠคํŠธ [x, y, z], ๋˜๋Š” ๋งต {x: val, y: val, z: val} ํ˜•์‹์œผ๋กœ ์ง€์ • ๊ฐ€๋Šฅ.
  • List: ํ•ญ๋ชฉ์˜ ๋ชฉ๋ก. (์˜ˆ: tags: ["mytag", "effect_tag"])
  • Map: ํ‚ค-๊ฐ’ ์Œ์˜ ์ง‘ํ•ฉ. (์˜ˆ: particleData: { count: 5, speed: 0.1 })
  • Material: ๋งˆ์ธํฌ๋ž˜ํ”„ํŠธ ์•„์ดํ…œ/๋ธ”๋ก ์ข…๋ฅ˜ ์ด๋ฆ„ (๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„ ์—†์Œ). (์˜ˆ: material: "DIAMOND_SWORD")

์…€๋ ‰ํ„ฐ ๋ฐ ํ‚ค์›Œ๋“œ (Selectors & Keywords)

์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ์‹œ์ ์˜ ๋™์ ์ธ ๊ฐ’์„ ์ฐธ์กฐํ•˜๊ธฐ ์œ„ํ•ด @ ๊ธฐํ˜ธ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํŠน๋ณ„ํ•œ ํ‚ค์›Œ๋“œ(์…€๋ ‰ํ„ฐ)๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์…€๋ ‰ํ„ฐ:

  • @Caster: ์Šคํ‚ฌ์„ ์‹œ์ „ํ•œ ํ”Œ๋ ˆ์ด์–ด ์—”ํ‹ฐํ‹ฐ.
  • @Target ๋˜๋Š” @CurrentTarget: ํ˜„์žฌ ์•ก์…˜์˜ ๋Œ€์ƒ์ด ๋˜๋Š” ์—”ํ‹ฐํ‹ฐ ๋˜๋Š” ์œ„์น˜. (์•ก์…˜์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง, Target.* ์•ก์…˜์œผ๋กœ ์„ค์ • ๊ฐ€๋Šฅ)
  • @CastLocation: ์Šคํ‚ฌ์ด ์ฒ˜์Œ ์‹œ์ „๋œ ์œ„์น˜ (Location).
  • @CasterLocation: ํ˜„์žฌ ์‹œ์ „์ž์˜ ์‹ค์‹œ๊ฐ„ ์œ„์น˜ (Location).
  • @TargetLocation ๋˜๋Š” @CurrentTargetLocation: ํ˜„์žฌ ๋Œ€์ƒ์˜ ์œ„์น˜ (Location). ๋Œ€์ƒ์ด ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์•„๋‹ˆ๋ฉด ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ.
  • @CastDirection: ์Šคํ‚ฌ์ด ์ฒ˜์Œ ์‹œ์ „๋œ ๋ฐฉํ–ฅ (Vector).
  • @CasterDirection: ํ˜„์žฌ ์‹œ์ „์ž์˜ ์‹ค์‹œ๊ฐ„ ๋ฐฉํ–ฅ (Vector).
  • @TargetDirection ๋˜๋Š” @CurrentTargetDirection: ํ˜„์žฌ ๋Œ€์ƒ์˜ ๋ฐฉํ–ฅ (Vector). ๋Œ€์ƒ์ด ์—”ํ‹ฐํ‹ฐ์—ฌ์•ผ ํ•จ.

์†์„ฑ ์ ‘๊ทผ:

. (์ )์„ ์‚ฌ์šฉํ•˜์—ฌ ์…€๋ ‰ํ„ฐ๊ฐ€ ์ฐธ์กฐํ•˜๋Š” ๊ฐ์ฒด์˜ ํŠน์ • ์†์„ฑ ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ฃผ๋กœ variable.calculate ์•ก์…˜์˜ ํ‘œํ˜„์‹ ๋‚ด์—์„œ ์‚ฌ์šฉ)

  • Location ์†์„ฑ:
    • @CasterLocation.X, @TargetLocation.Y, @CastLocation.Z
  • Entity ์†์„ฑ:
    • @Caster.X, @Target.Y, @Caster.Z (์—”ํ‹ฐํ‹ฐ์˜ ํ˜„์žฌ ์œ„์น˜)
    • @Caster.Yaw, @Target.Pitch (์—”ํ‹ฐํ‹ฐ์˜ ๋ฐฉํ–ฅ)
    • @Caster.Health, @Target.Health (์—”ํ‹ฐํ‹ฐ์˜ ํ˜„์žฌ ์ฒด๋ ฅ, Damageable์ด์–ด์•ผ ํ•จ)
    • @Caster.MaxHealth, @Target.MaxHealth (์—”ํ‹ฐํ‹ฐ์˜ ์ตœ๋Œ€ ์ฒด๋ ฅ, LivingEntity์ด์–ด์•ผ ํ•จ)
    • (ํ–ฅํ›„ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ: @Caster.FoodLevel ๋“ฑ)

๋ณ€์ˆ˜ (Variables)

์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ์ค‘์— ๋ฐ์ดํ„ฐ๋ฅผ ์ž„์‹œ๋กœ ์ €์žฅํ•˜๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๋ณ€์ˆ˜๋Š” ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ ๋‚ด์—์„œ ์œ ํšจํ•˜๋ฉฐ, ๋‹ค๋ฅธ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ณ€์ˆ˜ ์ด๋ฆ„์€ ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋ณ€์ˆ˜ ์„ค์ •:

  • Variable.SetVariable ์•ก์…˜ ์‚ฌ์šฉ:
    - Variable.SetVariable:
        name: "myCounter" # ๋ณ€์ˆ˜ ์ด๋ฆ„
        value: 0          # ์ €์žฅํ•  ๊ฐ’ (๋ชจ๋“  ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž… ๊ฐ€๋Šฅ)
    - Variable.SetVariable:
        name: "targetEntity"
        value: "@Target"  # ์…€๋ ‰ํ„ฐ ๊ฒฐ๊ณผ(Entity)๋ฅผ ์ €์žฅ
    - Variable.SetVariable:
        name: "startPos"
        value: "@CasterLocation" # ์…€๋ ‰ํ„ฐ ๊ฒฐ๊ณผ(Location)๋ฅผ ์ €์žฅ
  • Variable.Calculate ์•ก์…˜ ์‚ฌ์šฉ (์ˆซ์ž): ์ˆ˜ํ•™์  ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. (์•„๋ž˜ 'ํ‘œํ˜„์‹' ์„น์…˜ ์ฐธ์กฐ)
    - Variable.Calculate:
        variable: "damageMultiplier"
        expression: "1.5 + @Caster.Health / 20"
  • Variable.GetLocation, Variable.GetDirection, Variable.GetOffsetLocation ์•ก์…˜ ์‚ฌ์šฉ: ํŠน์ • ์œ„์น˜/๋ฐฉํ–ฅ ๊ด€๋ จ ๊ฐ’์„ ๊ณ„์‚ฐํ•˜์—ฌ ๋ณ€์ˆ˜์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

๋ณ€์ˆ˜ ์‚ฌ์šฉ:

  • ์•ก์…˜ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ: ๋Œ€๋ถ€๋ถ„์˜ ์•ก์…˜ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ์ง์ ‘ ์ง€์ •ํ•˜์—ฌ ํ•ด๋‹น ๋ณ€์ˆ˜์˜ ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๋‹จ, ํ•ด๋‹น ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์š”๊ตฌํ•˜๋Š” ํƒ€์ž…๊ณผ ๋ณ€์ˆ˜ ํƒ€์ž…์ด ์ผ์น˜ํ•ด์•ผ ํ•จ)
    - Variable.SetVariable: { name: "targetToDamage", value: "@Target" }
    - Variable.SetVariable: { name: "calculatedDamage", value: 15.0 }
    - TargetBehaviour.Damage:
        target: targetToDamage     # Entity ํƒ€์ž… ๋ณ€์ˆ˜ ์‚ฌ์šฉ
        amount: calculatedDamage # Double ํƒ€์ž… ๋ณ€์ˆ˜ ์‚ฌ์šฉ
  • Variable.Calculate ํ‘œํ˜„์‹ ๋‚ด์—์„œ ์‚ฌ์šฉ: ์ˆซ์ž ํƒ€์ž… ๋ณ€์ˆ˜๋ฅผ ์ˆ˜ํ•™ ํ‘œํ˜„์‹ ๋‚ด์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    - Variable.Calculate:
        variable: "finalDamage"
        expression: "baseDamage * damageMultiplier + bonusDamage" # baseDamage, damageMultiplier, bonusDamage ๋ณ€์ˆ˜ ์‚ฌ์šฉ

๋ฌธ์ž์—ด ๋‚ด ๊ฐ’ ์‚ฝ์ž… (ํ”Œ๋ ˆ์ด์Šคํ™€๋”)

TargetBehaviour.SendMessage์™€ ๊ฐ™์ด ๋ฌธ์ž์—ด์„ ์‚ฌ์šฉํ•˜๋Š” ์ผ๋ถ€ ์•ก์…˜์—์„œ๋Š” ๋ฌธ์ž์—ด ๋‚ด์— ๋ณ€์ˆ˜ ๊ฐ’์ด๋‚˜ ์…€๋ ‰ํ„ฐ ๊ฐ’์„ ์ง์ ‘ ์‚ฝ์ž…ํ•˜์—ฌ ๋™์ ์ธ ๋‚ด์šฉ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ํ˜•์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • {var:๋ณ€์ˆ˜์ด๋ฆ„}: ์ง€์ •๋œ ์ด๋ฆ„์˜ ๋ณ€์ˆ˜ ๊ฐ’์„ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์‚ฝ์ž…ํ•ฉ๋‹ˆ๋‹ค.
  • {sel:์…€๋ ‰ํ„ฐ.์†์„ฑ}: ์ง€์ •๋œ ์…€๋ ‰ํ„ฐ์™€ ์†์„ฑ์„ ํ‰๊ฐ€ํ•˜์—ฌ ๊ทธ ๊ฐ’์„ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์‚ฝ์ž…ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ:

- Variable.SetVariable: { name: "kills", value: 10 }
- Target.SetSelf: {}
- TargetBehaviour.SendMessage:
    target: "@Caster"
    message: "&aYour Stats: &fHP: {sel:@Caster.Health}/{sel:@Caster.MaxHealth}, Kills: {var:kills}"
    # ์˜ˆ์ƒ ์ถœ๋ ฅ (์ฒด๋ ฅ์ด 15/20 ์ด๊ณ  kills ๋ณ€์ˆ˜๊ฐ€ 10์ผ ๋•Œ):
    # Your Stats: HP: 15/20, Kills: 10

- Variable.GetLocation: { target: "@Target", variable: "enemyPos" }
- TargetBehaviour.SendMessage:
    target: "@Caster"
    message: "&eTarget spotted at: {var:enemyPos}"
    # ์˜ˆ์ƒ ์ถœ๋ ฅ (enemyPos ๋ณ€์ˆ˜๊ฐ€ Location ๊ฐ์ฒด์ผ ๋•Œ):
    # Target spotted at: world, 123.5, 65.0, 45.2

์ฃผ์˜์‚ฌํ•ญ:

  • ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์•ˆ์˜ ๋ณ€์ˆ˜๋ช…์ด๋‚˜ ์…€๋ ‰ํ„ฐ ๋ฌธ์ž์—ด์—๋Š” ๊ณต๋ฐฑ์ด ํฌํ•จ๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
  • ๋ณ€์ˆ˜๋‚˜ ์…€๋ ‰ํ„ฐ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ, ๊ธฐ๋ณธ์ ์œผ๋กœ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ๋ฌธ์ž์—ด({var:nonExistentVar})์ด ๊ทธ๋Œ€๋กœ ๋‚จ๊ฑฐ๋‚˜ ๋นˆ ๋ฌธ์ž์—ด๋กœ ๋Œ€์ฒด๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์ •ํ™•ํ•œ ๋™์ž‘์€ ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์ •์ด๋‚˜ ๋ฒ„์ „์— ๋”ฐ๋ผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ)
  • ํ˜„์žฌ ๋ชจ๋“  ์•ก์…˜์˜ ๋ชจ๋“  ๋ฌธ์ž์—ด ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ์ด ๊ธฐ๋Šฅ์ด ์ง€์›๋˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ์ฃผ๋กœ ๋ฉ”์‹œ์ง€, ๋ช…๋ น์–ด ๊ด€๋ จ ์•ก์…˜์—์„œ ์ง€์›๋ฉ๋‹ˆ๋‹ค. (์ง€์› ์—ฌ๋ถ€๋Š” ๊ฐ ์•ก์…˜ ์„ค๋ช… ์ฐธ์กฐ)

ํ‘œํ˜„์‹ (Expressions) - variable.calculate

Variable.Calculate ์•ก์…˜์€ ๊ฐ•๋ ฅํ•œ ์ˆ˜ํ•™ ํ‘œํ˜„์‹ ๊ณ„์‚ฐ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๋ถ€์ ์œผ๋กœ mXparser ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๊ตฌ๋ฌธ:

- Variable.Calculate:
    variable: "resultVariableName" # ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ๋ณ€์ˆ˜ ์ด๋ฆ„
    expression: "Your mathematical expression here" # ๊ณ„์‚ฐํ•  ํ‘œํ˜„์‹ ๋ฌธ์ž์—ด

ํ‘œํ˜„์‹ ๋‚ด ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์š”์†Œ:

  • ์ˆซ์ž: 10, -5.2, 1.2E3 ๋“ฑ
  • ๊ธฐ๋ณธ ์—ฐ์‚ฐ์ž: +, -, *, / (๋‚˜๋ˆ—์…ˆ), ^ (๊ฑฐ๋“ญ์ œ๊ณฑ), % (๋‚˜๋จธ์ง€)
  • mXparser ๋‚ด์žฅ ํ•จ์ˆ˜:
    • ์‚ผ๊ฐํ•จ์ˆ˜: sin(rad), cos(rad), tan(rad), asin(x), acos(x), atan(x) ๋“ฑ (๊ฐ๋„๋Š” ๋ผ๋””์•ˆ ์‚ฌ์šฉ)
    • ์ง€์ˆ˜/๋กœ๊ทธ: exp(x), log(base, x), ln(x), log2(x), log10(x)
    • ๊ธฐํƒ€ ์œ ์šฉ ํ•จ์ˆ˜: sqrt(x) (์ œ๊ณฑ๊ทผ), abs(x) (์ ˆ๋Œ“๊ฐ’), round(value, places) (๋ฐ˜์˜ฌ๋ฆผ), floor(x) (๋‚ด๋ฆผ), ceil(x) (์˜ฌ๋ฆผ), min(a, b, ...) (์ตœ์†Ÿ๊ฐ’), max(a, b, ...) (์ตœ๋Œ“๊ฐ’), rand() (0~1 ์‚ฌ์ด ๋‚œ์ˆ˜), if(condition, true_expr, false_expr) ๋“ฑ ๋งค์šฐ ๋‹ค์–‘ํ•ฉ๋‹ˆ๋‹ค. (์ž์„ธํ•œ ๋‚ด์šฉ์€ mXparser ๋ฌธ์„œ ์ฐธ์กฐ)
  • mXparser ๋‚ด์žฅ ์ƒ์ˆ˜: pi, e, [deg] (๊ฐ๋„->๋ผ๋””์•ˆ ๋ณ€ํ™˜), [rad] (๋ผ๋””์•ˆ->๊ฐ๋„ ๋ณ€ํ™˜) ๋“ฑ
  • SkillScript ์…€๋ ‰ํ„ฐ: ํ‘œํ˜„์‹ ๋ฌธ์ž์—ด ์•ˆ์— @Caster.Health, @TargetLocation.Y ์™€ ๊ฐ™์ด ์ง์ ‘ ์ž‘์„ฑํ•˜์—ฌ ํ˜„์žฌ ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • SkillScript ๋ณ€์ˆ˜: ํ‘œํ˜„์‹ ๋ฌธ์ž์—ด ์•ˆ์— ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ์ง์ ‘ ์ž‘์„ฑํ•˜์—ฌ ํ•ด๋‹น ๋ณ€์ˆ˜์˜ ์ˆซ์ž ๊ฐ’์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: myVar, calculatedDamage)

์˜ˆ์‹œ:

- Variable.Calculate:
    variable: "launchAngle"
    expression: "atan(@TargetLocation.Y - @CasterLocation.Y, sqrt( (@TargetLocation.X-@CasterLocation.X)^2 + (@TargetLocation.Z-@CasterLocation.Z)^2 )) * [rad]" # ๋ผ๋””์•ˆ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ๋„๋กœ ๋ณ€ํ™˜
- Variable.Calculate:
    variable: "randomOffset"
    expression: "(rand() - 0.5) * 10" # -5 ~ 5 ์‚ฌ์ด์˜ ๋žœ๋ค ๊ฐ’
- Variable.Calculate:
    variable: "adjustedHealth"
    expression: "max(1, @Caster.Health - receivedDamage)" # ์ตœ์†Œ 1 ๋ณด์žฅ

์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์•ก์…˜ ๋ชฉ๋ก (Action Reference)

์—ฌ๊ธฐ์— ActionRegistry์— ๋“ฑ๋ก๋œ ๋ชจ๋“  ์•ก์…˜์˜ ์ด๋ฆ„, ํŒŒ๋ผ๋ฏธํ„ฐ, ์„ค๋ช…์„ ์ƒ์„ธํžˆ ๊ธฐ์ˆ ํ•ฉ๋‹ˆ๋‹ค.


(๊ฐ ์•ก์…˜๋ณ„ ์ƒ์„ธ ์„ค๋ช… ์ถ”๊ฐ€)

  • ControlFlow.Delay
    • duration (Integer, Required): ์ง€์—ฐ์‹œํ‚ฌ ์‹œ๊ฐ„ (์„œ๋ฒ„ ํ‹ฑ ๋‹จ์œ„, 1์ดˆ = 20ํ‹ฑ).
    • ์„ค๋ช…: ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰์„ ์ง€์ •๋œ ์‹œ๊ฐ„ ๋™์•ˆ ์ผ์‹œ ์ค‘์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • ControlFlow.IfCondition
    • condition (Boolean/String/Number, Required): ํ‰๊ฐ€ํ•  ์กฐ๊ฑด. true, "true", 1 ๋“ฑ์€ ์ฐธ์œผ๋กœ, ๋‚˜๋จธ์ง€๋Š” ๊ฑฐ์ง“์œผ๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค. (ํ–ฅํ›„ ํ‘œํ˜„์‹ ์ง€์› ์˜ˆ์ •)
    • Then (List, Optional): ์กฐ๊ฑด์ด ์ฐธ์ผ ๋•Œ ์‹คํ–‰ํ•  ์•ก์…˜ ๋ชฉ๋ก.
    • Else (List, Optional): ์กฐ๊ฑด์ด ๊ฑฐ์ง“์ผ ๋•Œ ์‹คํ–‰ํ•  ์•ก์…˜ ๋ชฉ๋ก.
    • ์„ค๋ช…: ์กฐ๊ฑด์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์•ก์…˜ ํ๋ฆ„์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. (์ฃผ์˜: ํ˜„์žฌ ์ค‘์ฒฉ๋œ ์ œ์–ด ํ๋ฆ„(If, Delay ๋“ฑ) ์‹คํ–‰์— ์ œ์•ฝ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)
  • Object.CreateObject
    • initialLocation (Location, Required): ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋  ์ดˆ๊ธฐ ์œ„์น˜.
    • objectId (String, Optional): ์ƒ์„ฑ๋  ์˜ค๋ธŒ์ ํŠธ์˜ ๊ณ ์œ  ID (์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ž๋™ ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ์Œ).
    • lifespan (Integer, Optional): ์˜ค๋ธŒ์ ํŠธ ์ง€์† ์‹œ๊ฐ„ (ํ‹ฑ). -1 ๋˜๋Š” ๋ฏธ์ง€์ • ์‹œ ์˜๊ตฌ.
    • appearance (Map, Optional): ์˜ค๋ธŒ์ ํŠธ ์™ธํ˜• ๊ด€๋ จ ๋ฐ์ดํ„ฐ (ํŒŒํ‹ฐํด, ๋ชจ๋ธ ๋“ฑ).
    • initialVector (Vector, Optional): ์˜ค๋ธŒ์ ํŠธ์˜ ์ดˆ๊ธฐ ์ด๋™ ๋ฒกํ„ฐ.
    • shapeDefinition (Map, Optional): ์˜ค๋ธŒ์ ํŠธ์˜ ์ถฉ๋Œ/ํ˜•ํƒœ ์ •์˜.
    • tags (List, Optional): ์˜ค๋ธŒ์ ํŠธ์— ๋ถ€์—ฌํ•  ํƒœ๊ทธ ๋ชฉ๋ก.
    • ObjectBehaviour.* (List, Optional): ์˜ค๋ธŒ์ ํŠธ ์ž์ฒด์˜ ๋™์ž‘ ์ •์˜ (์˜ˆ: ObjectBehaviour.OnTick, ObjectBehaviour.OnCollision).
    • ์„ค๋ช…: ๊ฒŒ์ž„ ์›”๋“œ์— ์Šคํ‚ฌ ์˜ค๋ธŒ์ ํŠธ(ํˆฌ์‚ฌ์ฒด, ์žฅํŒ ๋“ฑ)๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. (์ฃผ์˜: ํ˜„์žฌ ์‹ค์ œ ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ ๋กœ์ง์€ ๊ตฌํ˜„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.)
  • Target.SetSelf
    • (ํŒŒ๋ผ๋ฏธํ„ฐ ์—†์Œ)
    • ์„ค๋ช…: ํ˜„์žฌ ์•ก์…˜ ๋Œ€์ƒ์„ ์Šคํ‚ฌ ์‹œ์ „์ž ์ž์‹ (@Caster)์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • TargetBehaviour.Damage
    • target (Entity Selector/Variable, Optional): ํ”ผํ•ด๋ฅผ ์ž…ํž ๋Œ€์ƒ. ๋ฏธ์ง€์ • ์‹œ ํ˜„์žฌ ๋Œ€์ƒ(@Target).
    • amount (Double, Required): ์ž…ํž ํ”ผํ•ด๋Ÿ‰ (0๋ณด๋‹ค ์ปค์•ผ ํ•จ).
    • ignoreArmor (Boolean, Optional): ๋ฐฉ์–ด๋ ฅ ๋ฌด์‹œ ์—ฌ๋ถ€ (๊ธฐ๋ณธ๊ฐ’ false). (์ฃผ์˜: Bukkit API ์ œ์•ฝ์œผ๋กœ ์™„๋ฒฝํžˆ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Œ)
    • source (Entity Selector/Variable, Optional): ํ”ผํ•ด ๋ฐœ์ƒ์›. ๋ฏธ์ง€์ • ์‹œ ์‹œ์ „์ž(@Caster).
    • ์„ค๋ช…: ๋Œ€์ƒ ์—”ํ‹ฐํ‹ฐ์—๊ฒŒ ํ”ผํ•ด๋ฅผ ์ž…ํž™๋‹ˆ๋‹ค.
  • TargetBehaviour.PlayEffect
    • location (Location Selector/Variable, Required): ํšจ๊ณผ๊ฐ€ ์žฌ์ƒ๋  ์ค‘์‹ฌ ์œ„์น˜.
    • offset (Vector Selector/Variable/Literal, Optional): location์—์„œ ์ถ”๊ฐ€๋  ์œ„์น˜ ์˜คํ”„์…‹.
    • particle (String, Optional): ์žฌ์ƒํ•  ํŒŒํ‹ฐํด ์ด๋ฆ„ (Bukkit Particle enum ์ด๋ฆ„).
    • particleData (Map, Optional): ํŒŒํ‹ฐํด ์„ธ๋ถ€ ์„ค์ • (์•„๋ž˜ ์ฐธ์กฐ).
    • sound (String, Optional): ์žฌ์ƒํ•  ์‚ฌ์šด๋“œ ์ด๋ฆ„ (Bukkit Sound enum ์ด๋ฆ„).
    • soundData (Map, Optional): ์‚ฌ์šด๋“œ ์„ธ๋ถ€ ์„ค์ • (์•„๋ž˜ ์ฐธ์กฐ).
    • ์„ค๋ช…: ์ง€์ •๋œ ์œ„์น˜์— ํŒŒํ‹ฐํด ํšจ๊ณผ๋‚˜ ์‚ฌ์šด๋“œ๋ฅผ ์žฌ์ƒํ•ฉ๋‹ˆ๋‹ค.
    • particleData ํ•˜์œ„ ํŒŒ๋ผ๋ฏธํ„ฐ:
      • count (Integer, Optional, ๊ธฐ๋ณธ๊ฐ’ 1): ํŒŒํ‹ฐํด ์ƒ์„ฑ ๊ฐœ์ˆ˜.
      • speed (Double, Optional, ๊ธฐ๋ณธ๊ฐ’ 0.0): ํŒŒํ‹ฐํด ์†๋„/ํผ์ง ์ •๋„.
      • offset (Vector, Optional): ํŒŒํ‹ฐํด ์ƒ์„ฑ ์œ„์น˜์˜ ๋ถ„์‚ฐ ์˜คํ”„์…‹ (Location ์˜คํ”„์…‹๊ณผ ๋‹ค๋ฆ„!). ์˜ˆ: {x: 0.5, y: 0.5, z: 0.5}
      • color (Map, Optional, DustOptions ์ „์šฉ): {r: 0-255, g: 0-255, b: 0-255, size: float}.
      • material (String, Optional, ์•„์ดํ…œ/๋ธ”๋ก ํŒŒํ‹ฐํด ์ „์šฉ): Material ์ด๋ฆ„.
    • soundData ํ•˜์œ„ ํŒŒ๋ผ๋ฏธํ„ฐ:
      • volume (Float, Optional, ๊ธฐ๋ณธ๊ฐ’ 1.0): ์‚ฌ์šด๋“œ ํฌ๊ธฐ.
      • pitch (Float, Optional, ๊ธฐ๋ณธ๊ฐ’ 1.0): ์‚ฌ์šด๋“œ ๋†’๋‚ฎ์ด.
  • TargetBehaviour.SendMessage
    • message (String, Required): ์ „์†กํ•  ๋ฉ”์‹œ์ง€. Bukkit ์ƒ‰์ƒ ์ฝ”๋“œ(&) ์ง€์›.
    • target (Player/Console Selector/Variable, Optional): ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์„ ๋Œ€์ƒ. ๋ฏธ์ง€์ • ์‹œ ํ˜„์žฌ ๋Œ€์ƒ(@Target, ํ”Œ๋ ˆ์ด์–ด์—ฌ์•ผ ํ•จ), ๊ทธ ๋‹ค์Œ ์‹œ์ „์ž(@Caster).
    • ์„ค๋ช…: ๋Œ€์ƒ์—๊ฒŒ ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
  • Variable.Calculate
    • variable (String, Required): ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ๋ณ€์ˆ˜ ์ด๋ฆ„.
    • expression (String, Required): ๊ณ„์‚ฐํ•  ์ˆ˜ํ•™ ํ‘œํ˜„์‹. (์ž์„ธํ•œ ๋‚ด์šฉ์€ 'ํ‘œํ˜„์‹' ์„น์…˜ ์ฐธ์กฐ)
    • ์„ค๋ช…: ํ‘œํ˜„์‹์„ ๊ณ„์‚ฐํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ๋ณ€์ˆ˜์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • Variable.GetDirection
    • target (Entity/Location Selector/Variable, Required): ๋ฐฉํ–ฅ์„ ๊ฐ€์ ธ์˜ฌ ๋Œ€์ƒ. (์˜ˆ: @Caster, @Target, myLocationVar)
    • variable (String, Required): ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ๋ณ€์ˆ˜ ์ด๋ฆ„.
    • normalize (Boolean, Optional, ๊ธฐ๋ณธ๊ฐ’ true): ๊ฒฐ๊ณผ๋ฅผ ๋‹จ์œ„ ๋ฒกํ„ฐ(๊ธธ์ด 1)๋กœ ์ •๊ทœํ™”ํ• ์ง€ ์—ฌ๋ถ€.
    • ์„ค๋ช…: ๋Œ€์ƒ์˜ ํ˜„์žฌ ๋ฐ”๋ผ๋ณด๋Š” ๋ฐฉํ–ฅ(Vector)์„ ๊ฐ€์ ธ์™€ ๋ณ€์ˆ˜์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • Variable.GetLocation
    • target (Entity/Location Selector/Variable, Required): ์œ„์น˜๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋Œ€์ƒ. (์˜ˆ: @Caster, @Target, myLocationVar)
    • variable (String, Required): ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•  ๋ณ€์ˆ˜ ์ด๋ฆ„.
    • ์„ค๋ช…: ๋Œ€์ƒ์˜ ํ˜„์žฌ ์œ„์น˜(Location)๋ฅผ ๊ฐ€์ ธ์™€ ๋ณ€์ˆ˜์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • Variable.GetOffsetLocation
    • baseLocation (Location Selector/Variable, Required): ๊ธฐ์ค€ ์œ„์น˜.
    • offset (Vector Selector/Variable/Literal, Required): ๋”ํ•  ์˜คํ”„์…‹ ๋ฒกํ„ฐ.
    • variable (String, Required): ๊ณ„์‚ฐ๋œ ์ตœ์ข… ์œ„์น˜๋ฅผ ์ €์žฅํ•  ๋ณ€์ˆ˜ ์ด๋ฆ„.
    • ์„ค๋ช…: ๊ธฐ์ค€ ์œ„์น˜์— ์˜คํ”„์…‹ ๋ฒกํ„ฐ๋ฅผ ๋”ํ•œ ์ƒˆ ์œ„์น˜๋ฅผ ๊ณ„์‚ฐํ•˜์—ฌ ๋ณ€์ˆ˜์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • Variable.SetVariable
    • name (String, Required): ์„ค์ •ํ•  ๋ณ€์ˆ˜์˜ ์ด๋ฆ„.
    • value (Any, Required): ๋ณ€์ˆ˜์— ์ €์žฅํ•  ๊ฐ’ (๋ฌธ์ž์—ด, ์ˆซ์ž, ๋ถˆ๋ฆฌ์–ธ, ์…€๋ ‰ํ„ฐ ๊ฒฐ๊ณผ ๋“ฑ).
    • ์„ค๋ช…: ์ง€์ •๋œ ์ด๋ฆ„์˜ ๋ณ€์ˆ˜์— ๊ฐ’์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ๋ฎ์–ด์”๋‹ˆ๋‹ค.

์˜ˆ์ œ ์Šคํฌ๋ฆฝํŠธ

๋‹ค์Œ์€ ์—ฌ๋Ÿฌ ์•ก์…˜๊ณผ ๋ณ€์ˆ˜, ๊ณ„์‚ฐ์„ ํ™œ์šฉํ•˜๋Š” ์˜ˆ์ œ ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค.

# scripts/examples.yaml
Skills:
  AdvancedProjectile:
    Triggers:
      OnCast:
        # 1. ์ดˆ๊ธฐ ์„ค์ •
        - Target.SetSelf: {} # ๋Œ€์ƒ์„ ์‹œ์ „์ž๋กœ ์ดˆ๊ธฐํ™”
        - Variable.SetVariable: { name: "baseDamage", value: 10.0 }
        - Variable.SetVariable: { name: "speedMultiplier", value: 1.2 }
        - Variable.GetLocation: { target: "@Caster", variable: "spawnLoc" }
        - Variable.GetDirection: { target: "@Caster", variable: "spawnDir" } # ์ •๊ทœํ™”๋œ ๋ฐฉํ–ฅ ๋ฒกํ„ฐ

        # 2. ์‹œ์ „์ž ์ฒด๋ ฅ ๋น„๋ก€ ์ถ”๊ฐ€ ๋ฐ๋ฏธ์ง€ ๊ณ„์‚ฐ
        - Variable.Calculate:
            variable: "healthBonusDmg"
            expression: "max(0, @Caster.Health / 5)" # ์ฒด๋ ฅ 5๋‹น 1์˜ ๋ณด๋„ˆ์Šค ๋ฐ๋ฏธ์ง€, ์ตœ์†Œ 0
        - Variable.Calculate:
            variable: "finalDamage"
            expression: "baseDamage + healthBonusDmg"

        # 3. ๋ฐœ์‚ฌ ์œ„์น˜ ์กฐ์ • (๋จธ๋ฆฌ ์œ„ ์•ฝ๊ฐ„ ์•ž)
        - Variable.GetOffsetLocation:
            baseLocation: spawnLoc
            offset: { x: 0, y: 1.8, z: 0 } # ๋ˆˆ ๋†’์ด ๊ทผ์ฒ˜
            variable: "eyeLoc"
        - Variable.Calculate: # ๋ฐฉํ–ฅ ๋ฒกํ„ฐ์— ์†๋„ ๊ณฑํ•˜๊ธฐ
            variable: "velocity"
            expression: "spawnDir * speedMultiplier * 20" # mXparser๋Š” ๋ฒกํ„ฐ*์Šค์นผ๋ผ ์ž๋™ ์ง€์› ์•ˆํ•  ์ˆ˜ ์žˆ์Œ. ํ™•์ธ ํ•„์š”
            # ๋งŒ์•ฝ ์œ„ ํ‘œํ˜„์‹์ด ์•ˆ๋œ๋‹ค๋ฉด, ๊ฐ ์š”์†Œ๋ณ„ ๊ณ„์‚ฐ ํ•„์š”:
            # variable: velocityX, expression: spawnDir.X * speedMultiplier * 20
            # variable: velocityY, expression: spawnDir.Y * speedMultiplier * 20
            # variable: velocityZ, expression: spawnDir.Z * speedMultiplier * 20
            # ์ดํ›„ Vector ์ƒ์„ฑ ์•ก์…˜ ํ•„์š” (ํ˜„์žฌ ์—†์Œ)

        # 4. ๋ฐœ์‚ฌ ํšจ๊ณผ ๋ฐ ๋ฉ”์‹œ์ง€
        - TargetBehaviour.SendMessage:
            message: "&eLaunching projectile! Damage: {var:finalDamage}" # ๋ณ€์ˆ˜ ์น˜ํ™˜ ์•ˆ๋จ! ๋ฉ”์‹œ์ง€ ๋ถ„๋ฆฌ ํ•„์š”
        - TargetBehaviour.SendMessage:
            target: "@Caster"
            message: "&eCalculated Damage: &f" # ๊ฐ’ ๋ถ€๋ถ„์€ ๋‹ค์Œ ์•ก์…˜์—์„œ? (ํ˜„์žฌ ๋ถˆํŽธํ•จ)
            # ๋˜๋Š” ์ตœ์ข… ๋ฐ๋ฏธ์ง€๋ฅผ ๋‹ค์Œ ๋‹จ๊ณ„ ์•ก์…˜์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ๋งŒ ์‚ฌ์šฉ

        - TargetBehaviour.PlayEffect:
            location: eyeLoc # ์กฐ์ •๋œ ์œ„์น˜์—์„œ ํšจ๊ณผ ์žฌ์ƒ
            particle: "FLAME"
            particleData: { count: 20, speed: 0.1, offset: {x: 0.2, y: 0.2, z: 0.2} }
            sound: "ENTITY_GHAST_SHOOT"

        # 5. ์‹ค์ œ ํˆฌ์‚ฌ์ฒด ์ƒ์„ฑ (CreateObject ๊ตฌํ˜„ ์‹œ)
        # - Object.CreateObject:
        #     initialLocation: eyeLoc
        #     initialVector: velocity # Vector ํƒ€์ž… ๋ณ€์ˆ˜ ์‚ฌ์šฉ
        #     # ... ํˆฌ์‚ฌ์ฒด ์™ธํ˜•, ์ถฉ๋Œ ์‹œ ๋™์ž‘ ๋“ฑ ์ •์˜ ...
        #     # ์˜ˆ: ObjectBehaviour.OnHit: [ { TargetBehaviour.Damage: { amount: finalDamage } } ]

(์ฃผ์˜: ์œ„ ์˜ˆ์ œ๋Š” ํ˜„์žฌ ๊ธฐ๋Šฅ ๋ฐ ์ œ์•ฝ์„ ๋ฐ˜์˜ํ–ˆ์œผ๋ฉฐ, ํŠนํžˆ ๋ฌธ์ž์—ด ๋‚ด ๋ณ€์ˆ˜ ์น˜ํ™˜ ๋ฐ ๋ฒกํ„ฐ ์—ฐ์‚ฐ, ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ ๋ถ€๋ถ„์€ ํ–ฅํ›„ ๊ฐœ์„ ์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)