summaryrefslogtreecommitdiff
path: root/world/map/npc/magic
diff options
context:
space:
mode:
authormekolat <mekolat@users.noreply.github.com>2016-04-19 09:42:48 -0400
committermekolat <mekolat@users.noreply.github.com>2016-04-19 09:42:48 -0400
commitea7d999c39ead96efb6d9af7e68794c59290cf60 (patch)
tree771e06363fe2e7609847a0a63a2e499632d3d7a2 /world/map/npc/magic
parent9e7f46ac732851c1359a15837c82ebf67ea2be39 (diff)
parent91fe3711fcacdfe83794b4347595e56e90e9d3a7 (diff)
downloadserverdata-ea7d999c39ead96efb6d9af7e68794c59290cf60.tar.gz
serverdata-ea7d999c39ead96efb6d9af7e68794c59290cf60.tar.bz2
serverdata-ea7d999c39ead96efb6d9af7e68794c59290cf60.tar.xz
serverdata-ea7d999c39ead96efb6d9af7e68794c59290cf60.zip
Merge self-fork from mekolat/magic-v3
Magic v3 spells
Diffstat (limited to 'world/map/npc/magic')
-rw-r--r--world/map/npc/magic/README.md54
-rw-r--r--world/map/npc/magic/_import.txt42
-rw-r--r--world/map/npc/magic/_procedures.txt162
-rw-r--r--world/map/npc/magic/level0-wand.txt76
-rw-r--r--world/map/npc/magic/level1-aggravate.txt23
-rw-r--r--world/map/npc/magic/level1-detect-magic.txt30
-rw-r--r--world/map/npc/magic/level1-experience.txt41
-rw-r--r--world/map/npc/magic/level1-flare-dart.txt45
-rw-r--r--world/map/npc/magic/level1-grow-alizarin.txt38
-rw-r--r--world/map/npc/magic/level1-grow-cobalt.txt38
-rw-r--r--world/map/npc/magic/level1-grow-gamboge.txt38
-rw-r--r--world/map/npc/magic/level1-grow-mauve.txt38
-rw-r--r--world/map/npc/magic/level1-lesser-heal.txt51
-rw-r--r--world/map/npc/magic/level1-magic-blade.txt48
-rw-r--r--world/map/npc/magic/level1-make-sulphur.txt25
-rw-r--r--world/map/npc/magic/level1-sense-spouse.txt25
-rw-r--r--world/map/npc/magic/level1-summon-maggots.txt53
-rw-r--r--world/map/npc/magic/level1-transmute-wood.txt37
-rw-r--r--world/map/npc/magic/level2-arrow-hail.txt115
-rw-r--r--world/map/npc/magic/level2-barrier.txt50
-rw-r--r--world/map/npc/magic/level2-detect-players.txt33
-rw-r--r--world/map/npc/magic/level2-enchant-lifestone.txt36
-rw-r--r--world/map/npc/magic/level2-flying-backpack.txt41
-rw-r--r--world/map/npc/magic/level2-happy-curse.txt43
-rw-r--r--world/map/npc/magic/level2-hide.txt45
-rw-r--r--world/map/npc/magic/level2-lay-on-hands.txt61
-rw-r--r--world/map/npc/magic/level2-lightning-strike.txt80
-rw-r--r--world/map/npc/magic/level2-magic-knuckles.txt45
-rw-r--r--world/map/npc/magic/level2-make-arrows.txt27
-rw-r--r--world/map/npc/magic/level2-make-iron-powder.txt27
-rw-r--r--world/map/npc/magic/level2-make-shirt.txt25
-rw-r--r--world/map/npc/magic/level2-make-short-tanktop.txt25
-rw-r--r--world/map/npc/magic/level2-make-tanktop.txt25
-rw-r--r--world/map/npc/magic/level2-protect.txt50
-rw-r--r--world/map/npc/magic/level2-rain.txt104
-rw-r--r--world/map/npc/magic/level2-shear.txt60
-rw-r--r--world/map/npc/magic/level2-summon-fluffies.txt54
-rw-r--r--world/map/npc/magic/level2-summon-mouboo.txt54
-rw-r--r--world/map/npc/magic/level2-summon-pinkie.txt54
-rw-r--r--world/map/npc/magic/level2-summon-snakes.txt55
-rw-r--r--world/map/npc/magic/level2-summon-spiky-mushroom.txt54
-rw-r--r--world/map/npc/magic/level2-summon-wickedmushroom.txt55
-rw-r--r--world/map/npc/magic/level2-toxic-dart.txt51
-rw-r--r--world/map/npc/magic/level3-necromancy.txt55
44 files changed, 2188 insertions, 0 deletions
diff --git a/world/map/npc/magic/README.md b/world/map/npc/magic/README.md
new file mode 100644
index 00000000..57ab5131
--- /dev/null
+++ b/world/map/npc/magic/README.md
@@ -0,0 +1,54 @@
+- [ ] check the new builtins and make sure they work as intended
+ - [ ] `puppet`
+ - [ ] check what happens when making a puppet whose name already exist (maybe it replaces?)
+ - [ ] `destroy`
+ - [ ] `registercmd`
+ - [ ] check what happens when registering a command that was already registered
+ - [ ] `target`
+ - [ ] `get`
+ - [ ] the new `set`
+ - [ ] `min`
+ - [ ] `max`
+ - [ ] `pow`
+ - [ ] `sqrt`
+ - [ ] `cbrt`
+ - [ ] `elttype`
+ - [ ] `eltlvl`
+ - [ ] `injure`
+ - [ ] `elif`
+ - [ ] `else`
+ - [ ] `getnpcid`
+ - [ ] `overrideattack`
+ - [ ] `summon`
+ - [ ] `addnpctimer`
+ - [ ] `explode`
+ - [ ] `foreach`
+ - [ ] modified `areatimer`
+ - [ ] `aggravate`
+ - [ ] `getdir`
+ - [ ] `distance`
+ - [ ] `if_then_else`
+
+ - I do not like `void`, feels like an ugly workaround; we should make `puppet` and `call` work in both function context and statement context
+
+---
+- [ ] test the spells
+ - [ ] test with no target
+ - [ ] test with a npc target
+ - [ ] random npc not part of any quest
+ - [ ] injured mouboo
+ - [ ] also test on a **player** with the name `Mouboo` or `mouboo`
+ - [ ] druid tree
+ - [ ] test with a mob target
+ - [ ] mob with clear path (walkable)
+ - [ ] mob with no clear path (unwalkable, blocked by collision)
+ - [ ] mob out of attack range
+ - [ ] test with a player target
+ - [ ] both the caster and the target have pvp disabled
+ - [ ] both the caster and the target have pvp enabled
+ - [ ] the caster has pvp enabled and the target has pvp disabled
+ - [ ] the caster has pvp disabled and the target has pvp enabled
+ - [ ] test with the spouse as target
+
+---
+- [ ] Once everything is done, remove this file
diff --git a/world/map/npc/magic/_import.txt b/world/map/npc/magic/_import.txt
new file mode 100644
index 00000000..2ef595af
--- /dev/null
+++ b/world/map/npc/magic/_import.txt
@@ -0,0 +1,42 @@
+npc: npc/magic/_procedures.txt
+npc: npc/magic/level0-wand.txt
+npc: npc/magic/level1-aggravate.txt
+npc: npc/magic/level1-experience.txt
+npc: npc/magic/level1-lesser-heal.txt
+npc: npc/magic/level1-transmute-wood.txt
+npc: npc/magic/level1-make-sulphur.txt
+npc: npc/magic/level1-flare-dart.txt
+npc: npc/magic/level1-magic-blade.txt
+npc: npc/magic/level1-grow-mauve.txt
+npc: npc/magic/level1-grow-alizarin.txt
+npc: npc/magic/level1-grow-gamboge.txt
+npc: npc/magic/level1-grow-cobalt.txt
+npc: npc/magic/level1-summon-maggots.txt
+npc: npc/magic/level1-sense-spouse.txt
+npc: npc/magic/level1-detect-magic.txt
+npc: npc/magic/level2-arrow-hail.txt
+npc: npc/magic/level2-make-arrows.txt
+npc: npc/magic/level2-make-iron-powder.txt
+npc: npc/magic/level2-magic-knuckles.txt
+npc: npc/magic/level2-summon-snakes.txt
+npc: npc/magic/level2-summon-wickedmushroom.txt
+npc: npc/magic/level2-summon-spiky-mushroom.txt
+npc: npc/magic/level2-summon-fluffies.txt
+npc: npc/magic/level2-summon-mouboo.txt
+npc: npc/magic/level2-summon-pinkie.txt
+npc: npc/magic/level2-toxic-dart.txt
+npc: npc/magic/level2-enchant-lifestone.txt
+npc: npc/magic/level2-flying-backpack.txt
+npc: npc/magic/level2-protect.txt
+npc: npc/magic/level2-barrier.txt
+npc: npc/magic/level2-hide.txt
+npc: npc/magic/level2-happy-curse.txt
+npc: npc/magic/level2-detect-players.txt
+npc: npc/magic/level2-shear.txt
+npc: npc/magic/level2-lightning-strike.txt
+npc: npc/magic/level2-rain.txt
+npc: npc/magic/level2-lay-on-hands.txt
+npc: npc/magic/level2-make-short-tanktop.txt
+npc: npc/magic/level2-make-tanktop.txt
+npc: npc/magic/level2-make-shirt.txt
+npc: npc/magic/level3-necromancy.txt
diff --git a/world/map/npc/magic/_procedures.txt b/world/map/npc/magic/_procedures.txt
new file mode 100644
index 00000000..466d8ca8
--- /dev/null
+++ b/world/map/npc/magic/_procedures.txt
@@ -0,0 +1,162 @@
+function|script|magic_register
+{
+ //debugmes ">> Register " + .invocation$ + " @ " + strnpcinfo(0);
+ set .@ext$, if_then_else(getarg(0,"") != "", "::"+getarg(0), "");
+ registercmd .invocation$, strnpcinfo(0) + .@ext$; // register the spell
+ set .index, $@magic_index;
+ set $@magic_index, $@magic_index + 1;
+ return;
+}
+
+// this can only be done with a npc so...
+-|script|Magic Timer|32767
+{
+ end;
+OnClear:
+ set @_M_BLOCK, 0;
+ end;
+}
+
+// this function is call()-only
+function|script|magic_checks
+{
+ set .@r, 0;
+ if(getpvpflag(1)) set .@r, 1; // FIXME: make HIDDEN into a param
+ if(@_M_BLOCK) set .@r, 2; // check if last debuff ended
+ if(Hp < 1) set .@r, 3; // can not cast when dead
+ return .@r;
+}
+
+function|script|elt_damage
+{
+ // args are damage, dmgplus(mutation), bonus_elt, malus_elt, effect
+ set .@dmg, getarg(0) + rand(getarg(1));
+ if(get(ELTTYPE, @target_id) == getarg(3)) // malus
+ set .@dmg, .@dmg / 3;
+ if(get(ELTTYPE, @target_id) == getarg(2)) // bonus
+ set .@dmg, ((get(ELTLVL, @target_id) + 4) * .@dmg) / 4;
+ set .@source, .caster;
+ if (!.@source) set .@source, getcharid(3);
+
+ injure .@source, @target_id, .@dmg;
+ misceffect getarg(4), @target_id;
+ return;
+}
+
+function|script|melee_damage
+{
+ // args are spell power, target id, dmg
+ if ((getarg(0) - rand(100)) < (get(BaseLevel, getarg(1)) + get(MDEF1, getarg(1))))
+ injure BL_ID, getarg(1), 0;
+ else injure BL_ID, getarg(1), getarg(2);
+ return;
+}
+
+function|script|magic_create_item
+{
+ // FIXME / XXX: IMO, using Luk for this is very bad and unfair
+ set .@exp, (MAGIC_EXPERIENCE & (BYTE_0_MASK | BYTE_1_MASK)) >> BYTE_0_SHIFT;
+ set .@score, (.@exp + rand(min(@spellpower, ((.@exp / 3) + 1))));
+ set @create_params[2], 1; // success flag
+ if (.@score >= @create_params[1]) goto L_Perfect;
+ set @create_params[2], 0; // success flag
+ set .@score, .@score + rand(Luk) + rand(Luk);
+ if (.@score < (@create_params[1] / 3)) goto L_Backfire;
+ if (.@score < ((@create_params[1] * 2) / 3)) goto L_Iten;
+ message strcharinfo(0), "Magic : ##3##BYour spell takes on a mind of its own!";
+ if (rand(3) == 1) getitem @create_items$[1], 1; // bad item
+ return;
+
+L_Iten:
+ if (rand(5) != 2) goto L_Escape;
+ message strcharinfo(0), "Magic : ##3##BYour spell solidifies into the shape of a mysterious object!";
+ getitem "Iten", 1;
+ return;
+
+L_Escape:
+ message strcharinfo(0), "Magic : ##3##BYour spell escapes!";
+ return;
+
+L_Backfire:
+ message strcharinfo(0), "Magic : ##3##BYour spell backfires!";
+ if (rand(110) < Luk) heal 0 - ((BaseLevel+1)*(BaseLevel+2)*(rand(28)+3)), 0;
+ else heal 0 - (BaseLevel + 1), 0;
+ return;
+
+L_Perfect:
+ getitem @create_items$[0], @create_params[0]; // good item
+ return;
+}
+
+function|script|magic_exp
+{
+ set @last_index, (MAGIC_EXPERIENCE & BYTE_2_MASK) >> BYTE_2_SHIFT;
+ set @last_exp, (MAGIC_EXPERIENCE & (BYTE_0_MASK | BYTE_1_MASK)) >> BYTE_0_SHIFT;
+
+ //debugmes "old spell index: " + @last_index;
+ //debugmes "new spell index: " + .index;
+
+ if(getskilllv(SKILL_MAGIC) < (.level + 3) && .index != @last_index)
+ goto L_Gain;
+ //debugmes "same as last spell => don't proceed";
+ goto L_Return;
+
+L_Gain:
+ if(.exp_gain < 1) goto L_Return; // only the spells that have exp register here. If you
+ // remove this line then players can cast a spell with
+ // no cost, then a spell with a reagents, then another
+ // spell with no costs and still get the exp
+ set @new_exp, @last_exp + .exp_gain;
+ if(@new_exp > (BYTE_0_MASK | BYTE_1_MASK)) set @new_exp, (BYTE_0_MASK | BYTE_1_MASK);
+ //debugmes "old magic exp: "+ @last_exp;
+ //debugmes "new magic exp: "+ @new_exp;
+ set MAGIC_EXPERIENCE, (MAGIC_EXPERIENCE &~ (BYTE_0_MASK | BYTE_1_MASK)) | (@new_exp << BYTE_0_SHIFT);
+ set MAGIC_EXPERIENCE, (MAGIC_EXPERIENCE &~ BYTE_2_MASK) | (.index << BYTE_2_SHIFT);
+ goto L_Return;
+
+L_Return:
+ return;
+}
+
+function|script|adjust_spellpower
+{
+ set @spellpower, MATK1 + getskilllv(SKILL_MAGIC) + getskilllv(.school) + 10;
+ if((.school != SKILL_MAGIC_NATURE) && (.school != SKILL_MAGIC_LIFE)) goto L_Return;
+ if(@args$ == "" || !@args$ || getpartnerid2() == 0) goto L_Return;
+ if(getcharid(3, @args$) < 1 || getpartnerid2() != getcharid(3, @args$) || !(isloggedin(getcharid(3, @args$))))
+ goto L_Return;
+ //debugmes "You targeted your spouse!";
+ // XXX: the spell power increases when the target is the spouse so one could
+ // just do #modrilax (spouse) right?
+ //
+ // ... let's just forget about spouse for now
+ goto L_Return;
+
+L_Return:
+ return;
+}
+
+function|script|gain_heal_xp
+{
+ set @last_heal_xp, ((SCRIPT_XP & SCRIPT_HEALSPELL_MASK) >> SCRIPT_HEALSPELL_SHIFT);
+ if ((@target_id != BL_ID) && ((.@heal_value / .heal_xp_value_divisor) > (((10 + @last_heal_xp) + rand(@last_heal_xp + 1)) + rand(@last_heal_xp + 1))))
+ goto L_Block;
+ goto L_Return;
+
+L_Block:
+ set @heal_xp, (@last_heal_xp + @mexp);
+ if (@heal_xp > SCRIPT_HEALSPELL_MASK)
+ set @heal_xp, SCRIPT_HEALSPELL_MASK;
+ set SCRIPT_XP, (SCRIPT_XP & ~(SCRIPT_HEALSPELL_MASK) | (@heal_xp << SCRIPT_HEALSPELL_SHIFT));
+ goto L_Gain_Xp;
+
+L_Gain_Xp:
+ set @heal_exp, .@heal_value;
+ if (.@heal_value > get(HEALXP, @target_id))
+ set @heal_exp, get(HEALXP, @target_id);
+ getexp (.base_exp_factor * @heal_exp), 0;
+ goto L_Return;
+
+L_Return:
+ return;
+}
diff --git a/world/map/npc/magic/level0-wand.txt b/world/map/npc/magic/level0-wand.txt
new file mode 100644
index 00000000..07fbb025
--- /dev/null
+++ b/world/map/npc/magic/level0-wand.txt
@@ -0,0 +1,76 @@
+-|script|spell-wand|32767
+{
+ if(call("magic_checks")) goto L_Failed;
+ callsub S_CheckWand;
+ if(@WandAttack != 1) goto L_Failed;
+
+ // here we install
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 500, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ misceffect FX_MAGIC_GENERIC, strcharinfo(0);
+ set .@delay, (((200 - Agi) * 1200) / 200);
+ overrideattack (@Wand + (@spellpower / 10)), .@delay, 3, ATTACK_ICON_GENERIC, @WandID, strnpcinfo(0)+"::OnAttack";
+ callfunc "magic_exp";
+ end;
+
+OnAttack:
+ callsub S_CheckWand;
+ if(@WandAttack != 1) goto L_Failed;
+ if(target(BL_ID, @target_id, 22) != 22) goto L_Failed; // 0x02 | 0x04 | 0x10
+ set Sp, (Sp - @WandCost);
+ set @damage, (@Wand * (@spellpower / 3));
+ void call("elt_damage", @damage,@damage,ELT_NEUTRAL,ELT_NEUTRAL,FX_MAGIC_RED);
+ end;
+
+S_CheckWand:
+ set @Wand, 0;
+ set @wand_loop, 0;
+ goto S_Loop;
+
+S_Loop:
+ if ((getequipid(equip_hand1) == .Wands[@wand_loop]) || (getequipid(equip_hand2) == .Wands[@wand_loop]))
+ goto S_SetWand;
+ set @wand_loop, (@wand_loop + 1);
+ if (@wand_loop >= getarraysize(.Wands))
+ goto S_NoWand;
+ goto S_Loop;
+
+S_SetWand:
+ set @Wand, .WandsPwr[@wand_loop];
+ set @WandID, .WandsAnim[@wand_loop];
+ if (QL_MORGAN == 2)
+ set QL_MORGAN, 3;
+ set @WandCost, (@Wand * (BaseLevel / 15) + 2);
+ set @WandAttack, 0;
+ if (Sp < @WandCost)
+ goto S_LowSp;
+ set @WandAttack, 1; // everything is fine
+ return;
+
+S_NoWand:
+ message strcharinfo(0), "Wand : ##3##BYou need a wand Equipped!";
+ set @WandAttack, 0;
+ return;
+
+S_LowSp:
+ message strcharinfo(0), "Wand : ##3##BOut of Mana";
+ set @WandAttack, 0;
+ return;
+
+L_Failed:
+ //misceffect FX_ELECTRICITY_RED, strcharinfo(0); // XXX: do we show an effect on fail?
+ //debugmes "cast or attack failed";
+ end;
+
+OnInit:
+ setarray .Wands[0], 0, 758, 1171;
+ setarray .WandsPwr[0], 0, 2, 1;
+ setarray .WandsAnim[0], 0, 35, 33;
+ set .school, SKILL_MAGIC;
+ set .invocation$, chr(MAGIC_SYMBOL) + "confringo"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 0;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level1-aggravate.txt b/world/map/npc/magic/level1-aggravate.txt
new file mode 100644
index 00000000..05474a2a
--- /dev/null
+++ b/world/map/npc/magic/level1-aggravate.txt
@@ -0,0 +1,23 @@
+-|script|spell-aggravate|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 3) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 1000, "Magic Timer::OnClear"; // set the new debuff
+ set @args$, ""; callfunc "adjust_spellpower"; // we reset @args$ because this spell should not have a target
+ set @distance, (2 + (@spellpower / 50));
+ set Sp, Sp - 3;
+ misceffect FX_MAGIC_GREEN, strcharinfo(0);
+ callfunc "magic_exp";
+ aggravate getmap(), (POS_X - @distance), (POS_Y - @distance), (POS_X + @distance), (POS_Y + @distance), SFX_DEFAULT;
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_NATURE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "itenplz"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 1;
+ set .exp_gain, 0;
+ end;
+}
diff --git a/world/map/npc/magic/level1-detect-magic.txt b/world/map/npc/magic/level1-detect-magic.txt
new file mode 100644
index 00000000..f0c01592
--- /dev/null
+++ b/world/map/npc/magic/level1-detect-magic.txt
@@ -0,0 +1,30 @@
+-|script|detect-magic|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 3) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 6000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 3;
+ misceffect FX_MAGIC_GENERIC, strcharinfo(0);
+ set .@range, (@spellpower/50)+1;
+ foreach 1, getmap(), POS_X - .@range, POS_Y - .@range, POS_X + .@range, POS_Y + .@range,
+ strnpcinfo(0) + "::OnNearbyNpc";
+ callfunc "magic_exp";
+ end;
+
+OnNearbyNpc:
+ set .@e$, strnpcinfo(2,@target_id);
+ if(.@e$ == "#_M" || .@e$ == "#MAGIC" || get(.IS_MAGIC, @target_id))
+ misceffect FX_MAGIC_DEFAULT, @target_id;
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC;
+ set .invocation$, chr(MAGIC_SYMBOL) + "miteyo"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 1;
+ set .exp_gain, 0;
+ end;
+}
diff --git a/world/map/npc/magic/level1-experience.txt b/world/map/npc/magic/level1-experience.txt
new file mode 100644
index 00000000..36ad8179
--- /dev/null
+++ b/world/map/npc/magic/level1-experience.txt
@@ -0,0 +1,41 @@
+-|script|spell-experience|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 1) end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 1000, "Magic Timer::OnClear"; // set the new debuff
+ set @level, getskilllv(.school);
+ if (@level < .level) end;
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 1;
+ misceffect FX_MAGIC_GENERIC, strcharinfo(0);
+ callfunc "magic_exp";
+ set @ratio, ((@last_exp*10) - rand(.MAX_MAGIC_EXP[@level]/30))/.MAX_MAGIC_EXP[@level];
+
+ set @mes$, "You feel completely overwhelmed by your magic.";
+ if(@ratio == 1) set @mes$, "You feel quite overwhelmed by your magic, but are beginning to see patterns.";
+ if(@ratio == 2) set @mes$, "You feel that you have only the bare minimum of control over your magic.";
+ if(@ratio == 3) set @mes$, "Trying to control your magic is still rather troublesome.";
+ if(@ratio == 4) set @mes$, "You feel you still have a few difficulties in controlling your magic.";
+ if(@ratio == 5) set @mes$, "You feel somewhat in control of your magic.";
+ if(@ratio == 6) set @mes$, "You feel mostly in control of your magic.";
+ if(@ratio == 7) set @mes$, "You feel quite in control of your magic.";
+ if(@ratio == 8) set @mes$, "You feel that you have very good control of your magic.";
+ if(@ratio == 9) set @mes$, "You feel in almost perfect control of your magic.";
+ if(@ratio >= 10) set @mes$, "You feel in perfect control of your magic" + if_then_else(@level >= MAX_MAGIC_LEVEL, ".", ", and seem on the verge of something more... perhaps you should see the Mana Seed to ask for more magic?");
+ if(@ratio >= 20) set @mes$, "You have perfect control of what you understand now, but there is now a distinct sensation of something more, something indescribable. If only the Mana Seed would give more magic to you...";
+ if(@ratio >= 45) set @mes$, "Magic flows naturally from you, readily and with ease. Your understanding of what you can currently control at present is flawless, far beyond your requirements to cast magic at this level.";
+ if(@ratio >= 45 && @level < MAX_MAGIC_LEVEL) set @mes$, @mes$ + " Surely the Mana Seed will more than readily offer more magic for such a proficient user.";
+ if(@level >= 5) set @mes$, "You are as proficient at magic as you can possibly be."; // this is the maximum magic level
+ message strcharinfo(0), "Magic : ##3##B"+@mes$;
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC;
+ set .invocation$, chr(MAGIC_SYMBOL) + "abizit"; // used in npcs that refer to this spell
+ set .level, 1;
+ set .exp_gain, 0;
+ void call("magic_register");
+ setarray .MAX_MAGIC_EXP[0], 0, 0, 100, 1200, 8000, 40000, 65535;
+ end;
+}
diff --git a/world/map/npc/magic/level1-flare-dart.txt b/world/map/npc/magic/level1-flare-dart.txt
new file mode 100644
index 00000000..6c0ef303
--- /dev/null
+++ b/world/map/npc/magic/level1-flare-dart.txt
@@ -0,0 +1,45 @@
+-|script|flare-dart|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 10) end;
+ set @level, getskilllv(.school);
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (@level <= 2 && countitem("SulphurPowder") >= 1) delitem "SulphurPowder", 1;
+ elif (@level <= 2) end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 500, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 10;
+ misceffect FX_MAGIC_BLACK, strcharinfo(0);
+ setarray @flarspell[0],
+ sqrt(@spellpower) * 5, //dmg
+ (BaseLevel/3) + 5, // dmg bonus
+ (@spellpower/50) + 3, // charges
+ (((200 - Agi) * 1200) / 200); // delay
+ callfunc "magic_exp";
+ goto L_FreeRecast;
+
+OnAttack:
+ if (target(BL_ID, @target_id, 50) != 50) goto L_FreeRecast; // 0x20 | 0x02 | 0x10
+ misceffect FX_MAGIC_BLACK, strcharinfo(0);
+ void call("elt_damage", @flarspell[0], @flarspell[1], ELT_WATER, ELT_FIRE, FX_MAGIC_BLACK);
+ set @flarspell[2], @flarspell[2] - 1;
+ goto L_FreeRecast;
+
+L_FreeRecast:
+ if (@flarspell[2] > 0)
+ addtimer 0, strnpcinfo(0) + "::OnSetRecast";
+ end;
+
+OnSetRecast:
+ overrideattack 1, @flarspell[3], 4, ATTACK_ICON_GENERIC, 34, strnpcinfo(0)+"::OnAttack";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_WAR;
+ set .invocation$, chr(MAGIC_SYMBOL) + "flar"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 1;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level1-grow-alizarin.txt b/world/map/npc/magic/level1-grow-alizarin.txt
new file mode 100644
index 00000000..601f32c2
--- /dev/null
+++ b/world/map/npc/magic/level1-grow-alizarin.txt
@@ -0,0 +1,38 @@
+-|script|grow-alizarin|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 4) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (countitem("AlizarinHerb") < 1 || countitem("Root") < 1) end;
+ delitem "AlizarinHerb", 1;
+ delitem "Root", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 2000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 4;
+ misceffect FX_MAGIC_GREEN, strcharinfo(0);
+ misceffect FX_PENTAGRAM_BUILDUP, strcharinfo(0);
+ setarray @summon[0], 0, (getskilllv(.school)/2)+1;
+ callfunc "magic_exp";
+ addtimer 4000-(@spellpower-9), strnpcinfo(0)+"::OnSummon";
+ end;
+
+OnSummon:
+ misceffect FX_PENTAGRAM_BURST, strcharinfo(0);
+ callsub S_SummonAll;
+ end;
+
+S_SummonAll:
+ summon getmap(), rand(POS_X-2,POS_X+2), rand(POS_Y-2,POS_Y+2), BL_ID, 1032, 1, (@spellpower*50)+10000;
+ set @summon[0], @summon[0] + 1;
+ if (@summon[0] < @summon[1]) goto S_SummonAll;
+ return;
+
+OnInit:
+ set .school, SKILL_MAGIC_NATURE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "modriphoo"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 1;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level1-grow-cobalt.txt b/world/map/npc/magic/level1-grow-cobalt.txt
new file mode 100644
index 00000000..78ab602a
--- /dev/null
+++ b/world/map/npc/magic/level1-grow-cobalt.txt
@@ -0,0 +1,38 @@
+-|script|grow-cobalt|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 4) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (countitem("CobaltHerb") < 1 || countitem("Root") < 1) end;
+ delitem "CobaltHerb", 1;
+ delitem "Root", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 2000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 4;
+ misceffect FX_MAGIC_GREEN, strcharinfo(0);
+ misceffect FX_PENTAGRAM_BUILDUP, strcharinfo(0);
+ setarray @summon[0], 0, (getskilllv(.school)/2)+1;
+ callfunc "magic_exp";
+ addtimer 4000-(@spellpower-9), strnpcinfo(0)+"::OnSummon";
+ end;
+
+OnSummon:
+ misceffect FX_PENTAGRAM_BURST, strcharinfo(0);
+ callsub S_SummonAll;
+ end;
+
+S_SummonAll:
+ summon getmap(), rand(POS_X-2,POS_X+2), rand(POS_Y-2,POS_Y+2), BL_ID, 1030, 1, (@spellpower*50)+10000;
+ set @summon[0], @summon[0] + 1;
+ if (@summon[0] < @summon[1]) goto S_SummonAll;
+ return;
+
+OnInit:
+ set .school, SKILL_MAGIC_NATURE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "modrisump"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 1;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level1-grow-gamboge.txt b/world/map/npc/magic/level1-grow-gamboge.txt
new file mode 100644
index 00000000..eda23f70
--- /dev/null
+++ b/world/map/npc/magic/level1-grow-gamboge.txt
@@ -0,0 +1,38 @@
+-|script|grow-gamboge|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 4) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (countitem("GambogeHerb") < 1 || countitem("Root") < 1) end;
+ delitem "GambogeHerb", 1;
+ delitem "Root", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 2000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 4;
+ misceffect FX_MAGIC_GREEN, strcharinfo(0);
+ misceffect FX_PENTAGRAM_BUILDUP, strcharinfo(0);
+ setarray @summon[0], 0, (getskilllv(.school)/2)+1;
+ callfunc "magic_exp";
+ addtimer 4000-(@spellpower-9), strnpcinfo(0)+"::OnSummon";
+ end;
+
+OnSummon:
+ misceffect FX_PENTAGRAM_BURST, strcharinfo(0);
+ callsub S_SummonAll;
+ end;
+
+S_SummonAll:
+ summon getmap(), rand(POS_X-2,POS_X+2), rand(POS_Y-2,POS_Y+2), BL_ID, 1031, 1, (@spellpower*50)+10000;
+ set @summon[0], @summon[0] + 1;
+ if (@summon[0] < @summon[1]) goto S_SummonAll;
+ return;
+
+OnInit:
+ set .school, SKILL_MAGIC_NATURE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "modriyikam"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 1;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level1-grow-mauve.txt b/world/map/npc/magic/level1-grow-mauve.txt
new file mode 100644
index 00000000..951b71f0
--- /dev/null
+++ b/world/map/npc/magic/level1-grow-mauve.txt
@@ -0,0 +1,38 @@
+-|script|grow-mauve|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 4) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (countitem("MauveHerb") < 1 || countitem("Root") < 1) end;
+ delitem "MauveHerb", 1;
+ delitem "Root", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 2000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 4;
+ misceffect FX_MAGIC_GREEN, strcharinfo(0);
+ misceffect FX_PENTAGRAM_BUILDUP, strcharinfo(0);
+ setarray @summon[0], 0, (getskilllv(.school)/2)+1;
+ callfunc "magic_exp";
+ addtimer 4000-(@spellpower-9), strnpcinfo(0)+"::OnSummon";
+ end;
+
+OnSummon:
+ misceffect FX_PENTAGRAM_BURST, strcharinfo(0);
+ callsub S_SummonAll;
+ end;
+
+S_SummonAll:
+ summon getmap(), rand(POS_X-2,POS_X+2), rand(POS_Y-2,POS_Y+2), BL_ID, 1029, 1, (@spellpower*50)+10000;
+ set @summon[0], @summon[0] + 1;
+ if (@summon[0] < @summon[1]) goto S_SummonAll;
+ return;
+
+OnInit:
+ set .school, SKILL_MAGIC_NATURE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "modrilax"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 1;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level1-lesser-heal.txt b/world/map/npc/magic/level1-lesser-heal.txt
new file mode 100644
index 00000000..fcd9897a
--- /dev/null
+++ b/world/map/npc/magic/level1-lesser-heal.txt
@@ -0,0 +1,51 @@
+-|script|lesser-heal|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 6) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ set @target_id, getcharid(3, @args$);
+ if (@target_id < 1 || !(isloggedin(@target_id))) set @target_id, BL_ID; // fallback to self
+ if (@args$ == "Mouboo" || @args$ == "mouboo") set @target_id, getnpcid("Mouboo");
+ set .@range, (((MATK1 + getskilllv(SKILL_MAGIC) + getskilllv(.school) + 10) / 100) + 2);
+ if (distance(BL_ID, @target_id) >= .@range) end;
+ if (PVP_CHANNEL != get(PVP_CHANNEL, @target_id) && get(PVP_CHANNEL, @target_id) != 0) end;
+ if (countitem("Lifestone") < 1) end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 500, "Magic Timer::OnClear"; // set the new debuff
+ delitem "Lifestone", 1;
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 6;
+ misceffect FX_MAGIC_WHITE, strcharinfo(0);
+ set .@heal_value, get(HEALXP, @target_id);
+ set @mexp, .exp_gain;
+ callfunc "magic_exp";
+ if (.@heal_value > 200)
+ set .@heal_value, 200;
+ if (@args$ == "Mouboo" || @args$ == "mouboo") goto L_Mouboo;
+ if (@target_id != BL_ID) goto L_NotMe;
+ goto L_Continue;
+
+L_NotMe:
+ misceffect FX_MAGIC_WHITE, @target_id;
+ callfunc "gain_heal_xp";
+ goto L_Continue;
+
+L_Continue:
+ if (getskilllv(SKILL_MAGIC_DARK) >= 1) sc_start SC_HALT_REGENERATE, 2000, 0;
+ if (attachrid(@target_id) != 1) end; // XXX: to avoid the ugly attachrid method we would need some kind of `run_as` builtin
+ if (!(isdead())) heal 200, 0, 1;
+ end;
+
+L_Mouboo:
+ mes "Mouboo : ##3##BYour spell seems to have no effect on the mouboo.";
+ close;
+
+OnInit:
+ set .school, SKILL_MAGIC_LIFE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "lum"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 1;
+ set .exp_gain, 1;
+ set .heal_xp_value_divisor, 2;
+ end;
+}
diff --git a/world/map/npc/magic/level1-magic-blade.txt b/world/map/npc/magic/level1-magic-blade.txt
new file mode 100644
index 00000000..ad39c244
--- /dev/null
+++ b/world/map/npc/magic/level1-magic-blade.txt
@@ -0,0 +1,48 @@
+-|script|magic-blade|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 9) end;
+ set .@level, getskilllv(.school);
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (.@level <= 2 && countitem("SharpKnife") >= 1) set .@component$, "SharpKnife";
+ elif (.@level <= 2 && countitem("Knife") >= 1) set .@component$, "Knife";
+ elif (.@level <= 2) end;
+ if (.@component$ != "") delitem .@component$, 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 500, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 9;
+ misceffect FX_MAGIC_BLACK, strcharinfo(0);
+ setarray @chizaspell[0],
+ if_then_else(.@component$ == "Knife", 40, 60), // dmg
+ Str, // do not allow to equip light armor, cast, and then switch to heavy armor to get bonus str
+ (@spellpower/15) + 10, // charges
+ (((200 - Agi) * 1200) / 200), // delay
+ @spellpower;
+
+ callfunc "magic_exp";
+ goto L_FreeRecast;
+
+OnAttack:
+ if (target(BL_ID, @target_id, 22) != 22) goto L_FreeRecast; // 0x10 | 0x02 | 0x04
+ void call("melee_damage", @chizaspell[4], @target_id, (@chizaspell[0] + rand(@chizaspell[1] + 5)));
+ set @chizaspell[2], @chizaspell[2] - 1;
+ goto L_FreeRecast;
+
+L_FreeRecast:
+ if (@chizaspell[2] > 0)
+ addtimer 0, strnpcinfo(0) + "::OnSetRecast";
+ end;
+
+OnSetRecast:
+ overrideattack 1, @chizaspell[3], 1, ATTACK_ICON_GENERIC, 30, strnpcinfo(0)+"::OnAttack";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_WAR;
+ set .invocation$, chr(MAGIC_SYMBOL) + "chiza"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 1;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level1-make-sulphur.txt b/world/map/npc/magic/level1-make-sulphur.txt
new file mode 100644
index 00000000..4aab7e3b
--- /dev/null
+++ b/world/map/npc/magic/level1-make-sulphur.txt
@@ -0,0 +1,25 @@
+-|script|make-sulphur|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 4) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (countitem("PileOfAsh") >= 1) delitem "PileOfAsh", 1; else end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 4000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 4;
+ misceffect FX_MAGIC_RED, strcharinfo(0);
+ setarray @create_params[0], (@spellpower/100)+1+(rand(max(1,(800-@spellpower)))/180), 50;
+ setarray @create_items$[0], "SulphurPowder", "PileOfAsh";
+ callfunc "magic_create_item";
+ callfunc "magic_exp";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_TRANSMUTE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "gole"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 1;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level1-sense-spouse.txt b/world/map/npc/magic/level1-sense-spouse.txt
new file mode 100644
index 00000000..1766276c
--- /dev/null
+++ b/world/map/npc/magic/level1-sense-spouse.txt
@@ -0,0 +1,25 @@
+-|script|sense-spouse|32767
+{
+ set .@m, getpartnerid2();
+ if (.@m < 1)
+ goto L_NotMarried;
+ if (isloggedin(.@m) < 1)
+ goto L_NotOnline;
+ if (sc_check(SC_HIDE, .@m) || getpvpflag(1, .@m))
+ goto L_NotOnline;
+ message strcharinfo(0), "Spouse : Your spouse is... somewhere.";
+ end;
+
+L_NotOnline:
+ message strcharinfo(0), "Spouse : Your spouse is not online, or maybe just hiding from you.";
+ end;
+
+L_NotMarried:
+ message strcharinfo(0), "Spouse : You are not married, or no longer married (sorry for being the one telling you the bad news).";
+ end;
+
+OnInit:
+ set .invocation$, chr(MAGIC_SYMBOL) + "inzuwilt"; // used in npcs that refer to this spell
+ registercmd .invocation$, strnpcinfo(0);
+ end;
+}
diff --git a/world/map/npc/magic/level1-summon-maggots.txt b/world/map/npc/magic/level1-summon-maggots.txt
new file mode 100644
index 00000000..aa4e646f
--- /dev/null
+++ b/world/map/npc/magic/level1-summon-maggots.txt
@@ -0,0 +1,53 @@
+-|script|summon-maggots|32767
+{
+ end;
+
+OnCast:
+ if(call("magic_checks")) end;
+ if (Sp < 21) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (countitem("MaggotSlime") < 1 || countitem("Root") < 1) end;
+ delitem "MaggotSlime", 1;
+ delitem "Root", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 20000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 21;
+ misceffect FX_MAGIC_BLUE, strcharinfo(0);
+ misceffect FX_PENTAGRAM_BUILDUP, strcharinfo(0);
+ callfunc "magic_exp";
+ set .@puppet$, "#"+strnpcinfo(0)+"#"+BL_ID;
+ set .@puppet, puppet(getmap(), POS_X, POS_Y, .@puppet$, 127);
+ set .count, ((sqrt(@spellpower)+(@spellpower/15))/5)+1, .@puppet;
+ set .master, BL_ID, .@puppet;
+ set .lifetime, (@spellpower*50)+10000, .@puppet;
+ addnpctimer 5000-(@spellpower*5), .@puppet$+"::OnSummon";
+ addnpctimer 6000, .@puppet$+"::OnDestroy";
+ end;
+
+OnSummon:
+ specialeffect FX_PENTAGRAM_BURST;
+ set .@i, 0;
+ set .@x, getnpcx();
+ set .@y, getnpcy();
+ set .@map$, strnpcinfo(3);
+ callsub S_SummonAll;
+ end;
+
+OnDestroy:
+ destroy;
+
+S_SummonAll:
+ summon .@map$, rand(.@x-2,.@x+2), rand(.@y-2,.@y+2), .master, 1002, 2, .lifetime;
+ set .@i, .@i + 1;
+ if (.@i < .count) goto S_SummonAll;
+ return;
+
+OnInit:
+ set .school, SKILL_MAGIC_ASTRAL;
+ set .invocation$, chr(MAGIC_SYMBOL) + "kalmurk"; // used in npcs that refer to this spell
+ void call("magic_register", "OnCast");
+ set .level, 1;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level1-transmute-wood.txt b/world/map/npc/magic/level1-transmute-wood.txt
new file mode 100644
index 00000000..a7c86ecc
--- /dev/null
+++ b/world/map/npc/magic/level1-transmute-wood.txt
@@ -0,0 +1,37 @@
+-|script|spell-transmute-wood|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 5) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (countitem("RawLog") >= 1) delitem "RawLog", 1; else end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 4000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 5;
+ misceffect FX_MAGIC_RED, strcharinfo(0);
+ if (@args$ == "boo") goto L_Mouboo;
+ elif (@args$ == "lurk") goto L_Skytlurk;
+ else message strcharinfo(0), "Magic : ##3##BYou do not know how to transmute wood into this kind of animal."; // FIXME: write a better sentence
+ end;
+
+L_Mouboo:
+ setarray @create_params[0], 1, 40;
+ setarray @create_items$[0], "MoubooFigurine", "WarpedLog";
+ callfunc "magic_create_item";
+ callfunc "magic_exp";
+ end;
+
+L_Skytlurk:
+ if (rand(2) == 1) getitem "Iten", 1;
+ else getitem "WarpedLog", 1;
+ message strcharinfo(0), "Magic : ##3##BYou have no idea what a Skrytlurk looks like.";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_TRANSMUTE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "parum"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 1;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level2-arrow-hail.txt b/world/map/npc/magic/level2-arrow-hail.txt
new file mode 100644
index 00000000..3be14f1a
--- /dev/null
+++ b/world/map/npc/magic/level2-arrow-hail.txt
@@ -0,0 +1,115 @@
+-|script|arrow-hail|32767
+{
+ // we can not start here because for the puppets this is OnClick
+ end;
+
+OnCast:
+ if(call("magic_checks")) end;
+ if (getskilllv(.school) < .level) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (Sp < 25) end;
+ explode .@map_ext[0], getmap(), "-";
+ if (.@map_ext[1] != 1) end; // XXX this is fugly, in the future let's use MF_OUTSIDE to detect if a map is "outside" or "inside"
+ if (getmapflag(getmap(), MF_TOWN)) end;
+ if (countitem("Arrow") >= 20 && countitem("SulphurPowder") >= 1) delitem "Arrow", 20;
+ elif (countitem("IronArrow") >= 20 && countitem("SulphurPowder") >= 1) delitem "IronArrow", 20;
+ else end;
+ delitem "SulphurPowder", 1;
+ set Sp, Sp - 25;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 5000, "Magic Timer::OnClear"; // set the new debuff
+
+ setarray @away[0], POS_X, POS_Y, getdir(), (.range + 1), 0;
+ callsub S_AwayFrom;
+
+ set @nearby, 0;
+ foreach 1, getmap(), @away[0] - 14, @away[1] - 14, @away[0] + 14, @away[1] + 14,
+ strnpcinfo(0) + "::OnNearbyNpc";
+ if (@nearby) goto L_Absorb;
+
+ callfunc "adjust_spellpower";
+ set @new_npc_name$, "#" + strnpcinfo(0) + "#" + getcharid(3); // make a unique puppet name for every player
+ callfunc "magic_exp";
+ misceffect FX_MAGIC_BLACK, strcharinfo(0);
+ set @spell_npc, puppet(getmap(), POS_X, POS_Y, @new_npc_name$, 127); // clone npc => get puppet id
+ set .max_hit, (@spellpower / 8), @spell_npc; // set .max_hit in the puppet
+ set .caster, getcharid(3), @spell_npc; // tell the puppet who controls it
+ set .damage, 125, @spell_npc;
+ set .damage_bonus, (@spellpower / 5), @spell_npc;
+ set .area_x, @away[0], @spell_npc; set .area_y, @away[1], @spell_npc;
+ donpcevent @new_npc_name$+"::OnLaunch"; // start the puppet timer and strike
+ initnpctimer @new_npc_name$; // start the destroy timer
+ end;
+
+S_AwayFrom:
+ if(@away[2] == 6 && !(iscollision(getmap(), (@away[0] + 1), @away[1]))) // right
+ set @away[0], @away[0] + 1;
+ if(@away[2] == 4 && !(iscollision(getmap(), @away[0], (@away[1] - 1)))) // up
+ set @away[1], @away[1] - 1;
+ if(@away[2] == 2 && !(iscollision(getmap(), (@away[0] - 1), @away[1]))) // left
+ set @away[0], @away[0] - 1;
+ if(@away[2] == 0 && !(iscollision(getmap(), @away[0], (@away[1] + 1)))) // down
+ set @away[1], @away[1] + 1;
+ set @away[4], @away[4] + 1;
+ if(@away[4] < @away[3]) goto S_AwayFrom;
+ return;
+
+L_Absorb:
+ message strcharinfo(0), "##3Arrow Hail : ##BA nearby arrow hail absorbs your magic!";
+ end;
+
+OnNearbyNpc:
+ explode .@nearby$[0], strnpcinfo(0,@target_id), "#";
+ if(.@nearby$[0] == "arrow-hail" || .@nearby$[1] == "arrow-hail")
+ set @nearby, @nearby + 1;
+ end;
+
+OnLaunch:
+ if(attachrid(.caster) != 1) destroy; // destroy if caster is missing
+ if(getmap() != strnpcinfo(3)) destroy; // destroy if caster left the map
+ set .hit, .hit + 1;
+ if(.hit > .max_hit) destroy;
+ set .launch, 0;
+ callsub S_Launch;
+ addnpctimer 250 + rand(50) + rand(50), strnpcinfo(0)+"::OnLaunch"; // loop until max
+ end;
+
+S_Launch:
+ npcareawarp .area_x - 6, .area_y - 6, .area_x + 6, .area_y + 6, 0, strnpcinfo(0);
+ misceffect FX_ARROW_HAIL;
+ set .done, 0;
+ foreach 2, strnpcinfo(3), getnpcx(), getnpcy(), getnpcx(), getnpcy(), strnpcinfo(0) + "::OnHit";
+ if (PVP_CHANNEL || getmapflag(getmap(), MF_PVP))
+ foreach 0, strnpcinfo(3), getnpcx(), getnpcy(), getnpcx(), getnpcy(), strnpcinfo(0) + "::OnHit";
+ if(!.done && getx() == getnpcx() && gety() == getnpcy())
+ heal 0 - (.damage + rand(.damage_bonus) + rand(.damage_bonus)), 0; // injure caster
+ set .launch, .launch + 1;
+ if(.launch < 3) goto S_Launch;
+ return;
+
+OnTimer30000:
+ debugmes "frillyar timeout! [this shouldn't happen]";
+ destroy;
+
+OnHit:
+ if(attachrid(.caster) != 1) destroy; // destroy if caster is missing
+ if(getmap() != strnpcinfo(3)) destroy; // destroy if caster left the map
+
+ if(target(.caster, @target_id, 16) != 16 && .caster != @target_id) end;
+ if((get(BL_TYPE, @target_id) & 1) == 0) end; // either mob or pc
+ set .@damage, .damage + rand(.damage_bonus) + rand(.damage_bonus);
+ if(.caster != @target_id)
+ set .@damage, (.@damage * (100 - get(MDEF1, @target_id))) / 100;
+ injure .caster, @target_id, .@damage;
+ set .done, 1;
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_WAR;
+ set .range, 7;
+ set .invocation$, chr(MAGIC_SYMBOL) + "frillyar"; // used in npcs that refer to this spell
+ void call("magic_register", "OnCast");
+ set .level, 2;
+ set .exp_gain, 2;
+ end;
+}
diff --git a/world/map/npc/magic/level2-barrier.txt b/world/map/npc/magic/level2-barrier.txt
new file mode 100644
index 00000000..3f78677b
--- /dev/null
+++ b/world/map/npc/magic/level2-barrier.txt
@@ -0,0 +1,50 @@
+-|script|magic barrier|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 15) end;
+ set .@level, getskilllv(.school);
+ if (.@level < .level) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (.@level <= 3 && countitem("SmallMushroom") >= 1) delitem "SmallMushroom", 1;
+ elif (.@level <= 3) end;
+ set @target_id, getcharid(3, @args$);
+ if (@target_id < 1 || !(isloggedin(@target_id))) set @target_id, BL_ID; // fallback to self
+
+ set @asorm_caster, BL_ID, @target_id;
+ if (attachrid(@target_id) != 1) end;
+ set @target_hat, getequipid(equip_head), @asorm_caster;
+ if (attachrid(@asorm_caster) != 1) end;
+ if (@target_hat == 888) end; // FIXME: this whole 5 line block could be done with only one line if we modify getequipid OR make equip_ into params
+
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 1000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 15;
+ misceffect FX_MAGIC_BLUE, strcharinfo(0);
+ callfunc "magic_exp";
+
+ if (distance(BL_ID, @target_id) >= (@spellpower/30)+2) set @target_id, BL_ID;
+ if (BL_ID == @target_id) set @args$, strcharinfo(0);
+ if (BL_ID != @target_id) misceffect FX_MAGIC_DEFAULT, @args$;
+ set .@time, (@spellpower*200)+2000;
+ set @asorm_time, .@time, @target_id;
+ sc_start SC_MBARRIER, .@time, max(30,(@spellpower/8))+20, @target_id;
+ message @args$, "Barrier : You are surrounded by a magical barrier.";
+ if (attachrid(@target_id) != 1) end;
+ addtimer @asorm_time, strnpcinfo(0)+"::OnEnd";
+ end;
+
+OnEnd:
+ if (sc_check(SC_MBARRIER) != 1) end;
+ message strcharinfo(0), "Barrier : Your magical barrier dissipates.";
+ misceffect FX_MAGIC_DEFAULT, strcharinfo(0);
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_ASTRAL;
+ set .invocation$, chr(MAGIC_SYMBOL) + "asorm"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 3;
+ end;
+}
diff --git a/world/map/npc/magic/level2-detect-players.txt b/world/map/npc/magic/level2-detect-players.txt
new file mode 100644
index 00000000..9867b700
--- /dev/null
+++ b/world/map/npc/magic/level2-detect-players.txt
@@ -0,0 +1,33 @@
+-|script|detect-players|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 7) end;
+ if (getskilllv(.school) < .level) end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 300, "Magic Timer::OnClear";
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 7;
+ misceffect FX_MAGIC_GENERIC, strcharinfo(0);
+ callfunc "magic_exp";
+ set @inwilt$, "";
+ set .@d, @spellpower/2;
+ foreach 0, getmap(), POS_X - .@d, POS_Y - .@d, POS_X + .@d, POS_Y + .@d, strnpcinfo(0)+"::OnPC";
+ message strcharinfo(0), if_then_else(@inwilt$=="", "You sense no-one else nearby.", "You sense the following: "+@inwilt$);
+ end;
+
+OnPC:
+ if (@target_id == BL_ID) end; // do not count the caster
+ if (sc_check(SC_HIDE, @target_id)) end; // do not count players with anwiltyp
+ if (getpvpflag(1, @target_id)) end; // do not count invisible players
+ if (@inwilt$ != "") set @inwilt$, @inwilt$ + ", ";
+ set @inwilt$, @inwilt$ + strcharinfo(0, @target_id) + if_then_else(@spellpower > 99, "("+get(BaseLevel, @target_id)+")", "");
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC;
+ set .invocation$, chr(MAGIC_SYMBOL) + "inwilt"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 0;
+ end;
+}
diff --git a/world/map/npc/magic/level2-enchant-lifestone.txt b/world/map/npc/magic/level2-enchant-lifestone.txt
new file mode 100644
index 00000000..ba8536bf
--- /dev/null
+++ b/world/map/npc/magic/level2-enchant-lifestone.txt
@@ -0,0 +1,36 @@
+-|script|enchant-lifestone|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 15) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("BugLeg") >= 1) delitem "BugLeg", 1;
+ elif (countitem("MaggotSlime") >= 1) delitem "MaggotSlime", 1;
+ elif (countitem("MauveHerb") >= 1 && countitem("AlizarinHerb") >= 1 && countitem("CobaltHerb") >= 1 && countitem("GambogeHerb") >= 1) goto L_Herbs;
+ else end;
+ goto L_Proceed;
+
+L_Herbs:
+ delitem "MauveHerb", 1;
+ delitem "AlizarinHerb", 1;
+ delitem "CobaltHerb", 1;
+ delitem "GambogeHerb", 1;
+ goto L_Proceed;
+
+L_Proceed:
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 4000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 15;
+ misceffect FX_MAGIC_GENERIC, strcharinfo(0);
+ getitem "Lifestone", 1;
+ callfunc "magic_exp";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC;
+ set .invocation$, chr(MAGIC_SYMBOL) + "manpahil"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level2-flying-backpack.txt b/world/map/npc/magic/level2-flying-backpack.txt
new file mode 100644
index 00000000..b87fba02
--- /dev/null
+++ b/world/map/npc/magic/level2-flying-backpack.txt
@@ -0,0 +1,41 @@
+-|script|flying-backpack|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 12) end;
+ set .@level, getskilllv(.school);
+ if (.@level < .level) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (.@level <= 3 && countitem("SilkCocoon") >= 1) delitem "SilkCocoon", 1;
+ elif (.@level <= 3) end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 1000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 12;
+ misceffect FX_MAGIC_GREEN, strcharinfo(0);
+ callfunc "magic_exp";
+ set @target_id, getcharid(3, @args$);
+ if (@target_id < 1 || !(isloggedin(@target_id))) set @target_id, BL_ID; // fallback to self
+ if (distance(BL_ID, @target_id) >= (@spellpower/30)+2) set @target_id, BL_ID;
+ if (BL_ID != @target_id) misceffect FX_MAGIC_GENERIC, @args$;
+ if (BL_ID == @target_id) set @args$, strcharinfo(0);
+ set @plugh_time, (@spellpower*500)+5000, @target_id;
+ sc_start SC_FLYING_BACKPACK, @plugh_time, 0, @target_id;
+ message @args$, "Backpack : Your backpack is lifted by a mystical force; you no longer feel it pressing on your back.";
+ if (attachrid(@target_id) != 1) end;
+ addtimer @plugh_time, strnpcinfo(0)+"::OnEnd";
+ end;
+
+OnEnd:
+ if (sc_check(SC_FLYING_BACKPACK) != 1) end;
+ message strcharinfo(0), "Backpack : Your backpack is no longer levitating.";
+ misceffect FX_MAGIC_GENERIC, strcharinfo(0);
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_NATURE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "plugh"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level2-happy-curse.txt b/world/map/npc/magic/level2-happy-curse.txt
new file mode 100644
index 00000000..190c7d23
--- /dev/null
+++ b/world/map/npc/magic/level2-happy-curse.txt
@@ -0,0 +1,43 @@
+-|script|happy-curse|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 13) end;
+ set .@level, getskilllv(.school);
+ if (.@level < .level) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (.@level <= 3 && countitem("GingerBreadMan") >= 1) delitem "GingerBreadMan", 1;
+ elif (.@level <= 3) end;
+ set @target_id, getcharid(3, @args$);
+ if (@target_id < 1 || !(isloggedin(@target_id))) set @target_id, BL_ID; // fallback to self
+
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 1000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 13;
+ misceffect FX_MAGIC_GREEN, strcharinfo(0);
+ callfunc "magic_exp";
+
+ if (distance(BL_ID, @target_id) >= (@spellpower/100)+1) set @target_id, BL_ID;
+ if (BL_ID == @target_id) set @args$, strcharinfo(0);
+ set @joyplim_count, 1, @target_id;
+ set @joyplim_emote, if_then_else(getskilllv(SKILL_MAGIC_DARK) > 1, EMOTE_EVIL, EMOTE_HAPPY), @target_id;
+ set @joyplim_total, (@spellpower/10), @target_id;
+ if (attachrid(@target_id) != 1) end;
+ emotion @joyplim_emote, "self";
+ addtimer 500, strnpcinfo(0)+"::OnEmote";
+ end;
+
+OnEmote:
+ emotion @joyplim_emote, "self";
+ set @joyplim_count, @joyplim_count + 1;
+ if (@joyplim_count < @joyplim_total) addtimer 500, strnpcinfo(0)+"::OnEmote";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_NATURE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "joyplim"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level2-hide.txt b/world/map/npc/magic/level2-hide.txt
new file mode 100644
index 00000000..1895cff1
--- /dev/null
+++ b/world/map/npc/magic/level2-hide.txt
@@ -0,0 +1,45 @@
+-|script|spell-hide|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 11) end;
+ set .@level, getskilllv(.school);
+ if (.@level < .level) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (.@level <= 3 && countitem("CottonCloth") >= 1) delitem "CottonCloth", 1;
+ elif (.@level <= 3) end;
+ set @target_id, getcharid(3, @args$);
+ if (@target_id < 1 || !(isloggedin(@target_id))) set @target_id, BL_ID; // fallback to self
+
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 1000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 11;
+ misceffect FX_MAGIC_BLUE, strcharinfo(0);
+ callfunc "magic_exp";
+
+ if (distance(BL_ID, @target_id) >= (@spellpower/30)+2) set @target_id, BL_ID;
+ if (BL_ID == @target_id) set @args$, strcharinfo(0);
+ if (BL_ID != @target_id) misceffect FX_MAGIC_DEFAULT, @args$;
+ set .@time, (@spellpower*2500)+5000;
+ set @anwiltyp_time, .@time, @target_id;
+ sc_start SC_HIDE, .@time, 0, @target_id;
+ message @args$, "Magic : You are hidden!";
+ if (BL_ID != @target_id) message strcharinfo(0), "Magic : You hid someone!";
+ if (attachrid(@target_id) != 1) end;
+ addtimer @anwiltyp_time, strnpcinfo(0)+"::OnEnd";
+ end;
+
+OnEnd:
+ if (sc_check(SC_HIDE) != 1) end;
+ message strcharinfo(0), "Magic : You are no longer hidden.";
+ misceffect FX_MAGIC_GENERIC, strcharinfo(0);
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_ASTRAL;
+ set .invocation$, chr(MAGIC_SYMBOL) + "anwiltyp"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 2;
+ end;
+}
diff --git a/world/map/npc/magic/level2-lay-on-hands.txt b/world/map/npc/magic/level2-lay-on-hands.txt
new file mode 100644
index 00000000..4e3a1e9c
--- /dev/null
+++ b/world/map/npc/magic/level2-lay-on-hands.txt
@@ -0,0 +1,61 @@
+-|script|lay-on-hands|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 10) end;
+ if (getskilllv(.school) < .level) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (@args$ == "Mouboo" || @args$ == "mouboo") goto L_Mouboo;
+ set @target_id, getcharid(3, @args$);
+ if (@target_id < 1 || !(isloggedin(@target_id))) end;
+ if (Hp <= get(MaxHp, @target_id) / 20) end; // hp needs to be > 1/20 * target hp
+ callfunc "adjust_spellpower";
+ if (distance(BL_ID, @target_id) >= (((sqrt(@spellpower)*12)+@spellpower)/100)+2) end;
+ if (sc_check(SC_HALT_REGENERATE,@target_id)) end;
+ if (getequipid(equip_head, @args$) == 888) end; // magic gm top hat
+ set .@needed, get(MaxHp, @target_id) - get(Hp, @target_id);
+ goto L_Pay;
+
+L_Pay:
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 500, "Magic Timer::OnClear"; // XXX should this be 0 ?
+ set Sp, Sp - 10;
+ misceffect FX_MAGIC_WHITE, strcharinfo(0); // on caster
+ misceffect FX_MAGIC_WHITE, @args$; // on target
+
+ set .@fraction, max(80, 200 - (Vit + (@spellpower/10))); // pay at least 40%
+ set .@payment, (.@needed * .@fraction) / 200;
+ set .@available, Hp - (MaxHp / 20);
+ set .@heal_value, if_then_else(.@payment < .@available, .@needed+1-1, (.@available * 200) / .@fraction); // FIXME / XXX why the f do I need to do +1-1 ?
+ if (.@payment > .@available) set .@payment, .@available;
+
+ set @inma_power, .@heal_value, @target_id;
+
+ set @mexp, min(.exp_gain, .@payment/100);
+ callfunc "gain_heal_xp";
+ callfunc "magic_exp";
+
+ set .@dark, getskilllv(SKILL_MAGIC_DARK) >= 2; // true if dark magic user
+ set .@bad, (MaxHp/20)*(0-1);
+ if (.@dark) heal .@bad, 0;
+ sc_start SC_HALT_REGENERATE, if_then_else(.@dark, 5000, 10000), 0;
+
+ if (attachrid(@target_id) != 1) end;
+ if (!(isdead())) heal @inma_power, 0;
+ end;
+
+L_Mouboo:
+ set @spell, 1;
+ callfunc "QuestMoubooHeal";
+ set .@needed, 1000;
+ goto L_Pay;
+
+OnInit:
+ set .school, SKILL_MAGIC_LIFE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "inma"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 1; // this is MAX possible exp
+ set .heal_xp_value_divisor, 1;
+ set .base_exp_factor, 3;
+ end;
+}
diff --git a/world/map/npc/magic/level2-lightning-strike.txt b/world/map/npc/magic/level2-lightning-strike.txt
new file mode 100644
index 00000000..3032d5ab
--- /dev/null
+++ b/world/map/npc/magic/level2-lightning-strike.txt
@@ -0,0 +1,80 @@
+-|script|lightning-strike|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 20) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ set .@level, getskilllv(.school);
+ if (.@level < .level) end;
+ if (.@level <= 3 && countitem("IronPowder") >= 1) delitem "IronPowder", 1;
+ elif (.@level <= 3) end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 1000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 20;
+ misceffect FX_MAGIC_BLACK, strcharinfo(0);
+ setarray @ingravspell[0],
+ @spellpower,
+ Luk,
+ ((@spellpower/90)+1), // charges
+ (((200 - Agi) * 3000) / 200), // delay
+ 0, // in_rain
+ 0; // target id (tmp)
+
+ callfunc "magic_exp";
+ goto L_FreeRecast;
+
+OnAttack:
+ if (target(BL_ID, @target_id, 22) != 22) L_FreeRecast; // 0x10 | 0x02 | 0x04
+
+ set .@p, get(.max_radius, "rain") + 1;
+ set @ingravspell[5], @target_id; // store it because foreach overwrites it
+ foreach 1, getmap(), POS_X-.@p, POS_Y-.@p, POS_X+.@p, POS_Y+.@p, strnpcinfo(0)+"::OnNpc";
+ set @target_id, @ingravspell[5]; // now restore it
+
+ set @ingravspell[2], @ingravspell[2] - 1;
+ if (@ingravspell[4] & 1) goto L_InRain;
+ void call("elt_damage", @ingravspell[0], (@ingravspell[0]/2)+1, ELT_EARTH, ELT_WIND, FX_LIGHTNING1 + rand(3));
+ goto L_FreeRecast;
+
+L_FreeRecast:
+ if (@ingravspell[2] > 0)
+ addtimer 0, strnpcinfo(0) + "::OnSetRecast";
+ end;
+
+OnSetRecast:
+ overrideattack 1, @ingravspell[3], 8, ATTACK_ICON_GENERIC, 31, strnpcinfo(0)+"::OnAttack";
+ end;
+
+OnNpc:
+ set .@name$, strnpcinfo(0,@target_id);
+ explode .@nearby$[0], .@name$, "#";
+ if (.@nearby$[0] != "rain" && .@nearby$[1] != "rain") end;
+ setarray .@l[0], getnpcx(.@name$), getnpcy(.@name$), get(.radius, @target_id); // kaflosh x, y, radius
+ setarray @ingravspell[6], .@l[0]-.@l[2], .@l[1]-.@l[2], .@l[0]+.@l[2], .@l[1]+.@l[2]; // kaflosh x1, y1, x2, y2 <= this is "area"
+ if (POS_X >= @ingravspell[6] && POS_Y >= @ingravspell[7] && POS_X <= @ingravspell[8] && POS_Y <= @ingravspell[9])
+ set @ingravspell[4], @ingravspell[4] | 1;
+ end;
+
+L_InRain:
+ set @ingravspell[10], 0;
+ foreach 0, getmap(), @ingravspell[6], @ingravspell[7], @ingravspell[8], @ingravspell[9], strnpcinfo(0)+"::OnEntityInRain";
+ if (@ingravspell[10] >= 1 && (@ingravspell[1] + rand(200)) >= 150) goto L_FreeRecast;
+ misceffect FX_LIGHTNING1 + rand(3), strcharinfo(0);
+ heal 0 - @ingravspell[0], 0;
+ goto L_FreeRecast;
+
+OnEntityInRain:
+ if (target(BL_ID, @target_id, 16) != 16) end; // 0x10
+ if (@ingravspell[1] + rand(200) <= 175) end;
+ set @ingravspell[10], @ingravspell[10] + 1;
+ void call("elt_damage", @ingravspell[0]/6, (((@ingravspell[0]/2)+1)/3)+1, ELT_EARTH, ELT_WIND, FX_LIGHTNING1 + rand(3));
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_WAR;
+ set .invocation$, chr(MAGIC_SYMBOL) + "ingrav"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 2;
+ end;
+}
diff --git a/world/map/npc/magic/level2-magic-knuckles.txt b/world/map/npc/magic/level2-magic-knuckles.txt
new file mode 100644
index 00000000..0984a7fd
--- /dev/null
+++ b/world/map/npc/magic/level2-magic-knuckles.txt
@@ -0,0 +1,45 @@
+-|script|magic-knuckles|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 20) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ set .@level, getskilllv(.school);
+ if (.@level < .level) end;
+ if (.@level <= 3 && countitem("Beer") >= 1) delitem "Beer", 1;
+ elif (.@level <= 3) end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 500, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 20;
+ misceffect FX_MAGIC_BLACK, strcharinfo(0);
+ setarray @upmarmuspell[0],
+ @spellpower,
+ ((@spellpower/10) + 10), // charges
+ (((200 - Agi) * 1300) / 200), // delay
+ Str; // do not allow to equip light armor, cast, and then switch to heavy armor to get bonus str
+ callfunc "magic_exp";
+ goto L_FreeRecast;
+
+OnAttack:
+ if (target(BL_ID, @target_id, 22) != 22) goto L_FreeRecast; // 0x10 | 0x02 | 0x04
+ void call("melee_damage", @upmarmuspell[0], @target_id, (30 + rand((@upmarmuspell[3]*2) + 5)));
+ set @upmarmuspell[1], @upmarmuspell[1] - 1;
+ goto L_FreeRecast;
+
+L_FreeRecast:
+ if (@upmarmuspell[1] > 0)
+ addtimer 0, strnpcinfo(0) + "::OnSetRecast";
+ end;
+
+OnSetRecast:
+ overrideattack 1, @upmarmuspell[2], 1, ATTACK_ICON_GENERIC, 34, strnpcinfo(0)+"::OnAttack";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_WAR;
+ set .invocation$, chr(MAGIC_SYMBOL) + "upmarmu"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level2-make-arrows.txt b/world/map/npc/magic/level2-make-arrows.txt
new file mode 100644
index 00000000..5bad035b
--- /dev/null
+++ b/world/map/npc/magic/level2-make-arrows.txt
@@ -0,0 +1,27 @@
+-|script|make-arrows|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 8) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("RawLog") < 1) end;
+ delitem "RawLog", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 5000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 8;
+ misceffect FX_MAGIC_RED, strcharinfo(0);
+ setarray @create_params[0], (@spellpower/40)+1+(rand(max(1,(800-@spellpower)))/80), 500;
+ setarray @create_items$[0], "Arrow", "WarpedLog";
+ callfunc "magic_create_item";
+ callfunc "magic_exp";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_TRANSMUTE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "kularzufrill"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level2-make-iron-powder.txt b/world/map/npc/magic/level2-make-iron-powder.txt
new file mode 100644
index 00000000..55bdd0b9
--- /dev/null
+++ b/world/map/npc/magic/level2-make-iron-powder.txt
@@ -0,0 +1,27 @@
+-|script|make-iron-powder|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 8) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("IronOre") < 1) end;
+ delitem "IronOre", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 5000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 8;
+ misceffect FX_MAGIC_RED, strcharinfo(0);
+ setarray @create_params[0], (@spellpower/140)+1+(rand(max(1,(900-@spellpower)))/220), 700;
+ setarray @create_items$[0], "IronPowder", "IronOre";
+ callfunc "magic_create_item";
+ callfunc "magic_exp";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_TRANSMUTE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "zukminbirf"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 3;
+ end;
+}
diff --git a/world/map/npc/magic/level2-make-shirt.txt b/world/map/npc/magic/level2-make-shirt.txt
new file mode 100644
index 00000000..b7a1570f
--- /dev/null
+++ b/world/map/npc/magic/level2-make-shirt.txt
@@ -0,0 +1,25 @@
+-|script|make-shirt|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 25) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("CottonCloth") >= 5) delitem "CottonCloth", 5; else end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 5000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 25;
+ misceffect FX_MAGIC_RED, strcharinfo(0);
+ setarray @create_params[0], 1, 425;
+ setarray @create_items$[0], "CottonShirt", "CottonCloth";
+ callfunc "magic_create_item";
+ callfunc "magic_exp";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_TRANSMUTE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "patmuploo"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 2;
+ end;
+}
diff --git a/world/map/npc/magic/level2-make-short-tanktop.txt b/world/map/npc/magic/level2-make-short-tanktop.txt
new file mode 100644
index 00000000..eee48425
--- /dev/null
+++ b/world/map/npc/magic/level2-make-short-tanktop.txt
@@ -0,0 +1,25 @@
+-|script|make-short-tanktop|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 25) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("CottonCloth") >= 3) delitem "CottonCloth", 3; else end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 5000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 25;
+ misceffect FX_MAGIC_RED, strcharinfo(0);
+ setarray @create_params[0], 1, 250;
+ setarray @create_items$[0], "ShortTankTop", "CottonCloth";
+ callfunc "magic_create_item";
+ callfunc "magic_exp";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_TRANSMUTE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "patviloree"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 2;
+ end;
+}
diff --git a/world/map/npc/magic/level2-make-tanktop.txt b/world/map/npc/magic/level2-make-tanktop.txt
new file mode 100644
index 00000000..678cf650
--- /dev/null
+++ b/world/map/npc/magic/level2-make-tanktop.txt
@@ -0,0 +1,25 @@
+-|script|make-tanktop|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 25) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("CottonCloth") >= 4) delitem "CottonCloth", 4; else end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 5000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 25;
+ misceffect FX_MAGIC_RED, strcharinfo(0);
+ setarray @create_params[0], 1, 350;
+ setarray @create_items$[0], "TankTop", "CottonCloth";
+ callfunc "magic_create_item";
+ callfunc "magic_exp";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_TRANSMUTE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "patloree"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 2;
+ end;
+}
diff --git a/world/map/npc/magic/level2-protect.txt b/world/map/npc/magic/level2-protect.txt
new file mode 100644
index 00000000..e66aab3a
--- /dev/null
+++ b/world/map/npc/magic/level2-protect.txt
@@ -0,0 +1,50 @@
+-|script|protect|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 14) end;
+ set .@level, getskilllv(.school);
+ if (.@level < .level) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (.@level <= 3 && countitem("HardSpike") >= 1) delitem "HardSpike", 1;
+ elif (.@level <= 3) end;
+ set @target_id, getcharid(3, @args$);
+ if (@target_id < 1 || !(isloggedin(@target_id))) set @target_id, BL_ID; // fallback to self
+
+ set @betsanc_caster, BL_ID, @target_id;
+ if (attachrid(@target_id) != 1) end;
+ set @target_hat, getequipid(equip_head), @betsanc_caster;
+ if (attachrid(@betsanc_caster) != 1) end;
+ if (@target_hat == 888) end; // FIXME: this whole 5 line block could be done with only one line if we modify getequipid
+
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 1500, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 14;
+ misceffect FX_MAGIC_GREEN, strcharinfo(0);
+ callfunc "magic_exp";
+
+ if (distance(BL_ID, @target_id) >= (@spellpower/30)+2) set @target_id, BL_ID;
+ if (BL_ID == @target_id) set @args$, strcharinfo(0);
+ misceffect FX_MAGIC_SHIELD, @args$;
+ set .@time, (@spellpower*1000)+5000;
+ set @betsanc_time, .@time, @target_id;
+ sc_start SC_PHYS_SHIELD, .@time, max(15,(@spellpower/20))+5, @target_id;
+ message @args$, "Shield : You feel more protected.";
+ if (attachrid(@target_id) != 1) end;
+ addtimer @betsanc_time, strnpcinfo(0)+"::OnEnd";
+ end;
+
+OnEnd:
+ if (sc_check(SC_PHYS_SHIELD) != 1) end;
+ message strcharinfo(0), "Shield : You feel less protected.";
+ misceffect FX_MAGIC_SHIELD_ENDS, strcharinfo(0);
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_NATURE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "betsanc"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 2;
+ end;
+}
diff --git a/world/map/npc/magic/level2-rain.txt b/world/map/npc/magic/level2-rain.txt
new file mode 100644
index 00000000..d3718170
--- /dev/null
+++ b/world/map/npc/magic/level2-rain.txt
@@ -0,0 +1,104 @@
+-|script|rain|32767
+{
+ // we can not start here because for the puppets this is OnClick
+ end;
+
+OnCast:
+ if(call("magic_checks")) end;
+ if (getskilllv(.school) < .level) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (Sp < 17) end;
+ explode .@map_ext[0], getmap(), "-";
+ if (.@map_ext[1] != 1) end; // XXX this is fugly, in the future let's use MF_OUTSIDE to detect if a map is "outside" or "inside"
+ if (getmapflag(getmap(), MF_TOWN)) end;
+ if (getskilllv(.school) < 4 && countitem("BottleOfWater") >= 1) delitem "BottleOfWater", 1;
+ elif (getskilllv(.school) < 4) end;
+ set Sp, Sp - 17;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 3000, "Magic Timer::OnClear"; // set the new debuff
+
+ callfunc "adjust_spellpower";
+ set @krad, min(.max_radius,(min(@spellpower,200)/30)+3); // kaflosh radius
+
+ set @nearby, 0;
+ foreach 1, getmap(), POS_X - .max_radius, POS_Y - .max_radius, POS_X + .max_radius, POS_Y + .max_radius,
+ strnpcinfo(0) + "::OnNearbyNpc";
+ if (@nearby) goto L_Absorb;
+
+ set @new_npc_name$, "#" + strnpcinfo(0) + "#" + getcharid(3); // make a unique puppet name for every player
+ callfunc "magic_exp";
+ misceffect FX_MAGIC_GREEN, strcharinfo(0);
+ set @spell_npc, puppet(getmap(), POS_X, POS_Y, @new_npc_name$, 127); // clone npc => get puppet id
+ set .caster, getcharid(3), @spell_npc; // tell the puppet who controls it
+ set .radius, @krad, @spell_npc; // this is also used by ingrav, don't rename
+ set .initial_x, POS_X, @spell_npc;
+ set .initial_y, POS_Y, @spell_npc;
+ set .max, @spellpower/3, @spell_npc;
+ set .max_launch, min(200,@spellpower/2)/100, @spell_npc;
+ donpcevent @new_npc_name$+"::OnLaunch"; // start
+ addnpctimer 30000, @new_npc_name$+"::OnDestroy"; // this is just a failsafe in case the npc is not properly destroyed
+ if(isin("011-1", 85, 31, 103, 45)) goto L_Pumpkins;
+ end;
+
+L_Absorb:
+ message strcharinfo(0), "##3Rain : ##BA nearby raincloud absorbs your magic.";
+ end;
+
+OnNearbyNpc:
+ explode .@nearby$[0], strnpcinfo(0,@target_id), "#";
+ if(.@nearby$[1] == "DruidTree0" || .@nearby$[1] == "DruidTree0") goto L_Tree;
+ if(.@nearby$[0] == "rain" || .@nearby$[1] == "rain")
+ set @nearby, @nearby + 1;
+ end;
+
+L_Pumpkins:
+ callfunc "HalloweenQuestWaterPumpkins";
+ end;
+
+L_Tree:
+ set .@x, get(POS_X, @target_id); set .@y, get(POS_Y, @target_id);
+ if (.@x < POS_X-@krad || .@y < POS_Y-@krad || .@x > POS_X+@krad || .@y > POS_Y+@krad) end; // in max radius but not in puppet area
+ set @flag, 1;
+ callfunc "QuestTreeTrigger";
+ close;
+
+OnLaunch:
+ if(attachrid(.caster) != 1) destroy; // destroy if caster is missing
+ if(getmap() != strnpcinfo(3)) destroy; // destroy if caster left the map
+ set .count, .count + 1;
+ if(.count > .max) destroy;
+ set .launch, 0;
+ callsub S_Launch;
+ addnpctimer 400 + rand(100), strnpcinfo(0)+"::OnLaunch"; // loop until max
+ end;
+
+S_Launch:
+ npcareawarp .initial_x - .radius, .initial_y - .radius, .initial_x + .radius, .initial_y + .radius, 0, strnpcinfo(0);
+ misceffect FX_RAIN;
+ foreach 2, strnpcinfo(3), getnpcx()-1, getnpcy()-1, getnpcx()+1, getnpcy()+1, strnpcinfo(0) + "::OnHit";
+ set .launch, .launch + 1;
+ if(.launch < .max_launch) goto S_Launch;
+ return;
+
+OnHit:
+ if(attachrid(.caster) != 1) destroy; // destroy if caster is missing
+ if(getmap() != strnpcinfo(3)) destroy; // destroy if caster left the map
+ if(target(.caster, @target_id, 16) != 16 && .caster != @target_id) end;
+ if((get(BL_TYPE, @target_id) & 1) == 0) end; // either mob or pc
+ if(get(ELTTYPE, @target_id) == ELT_FIRE)
+ injure .caster, @target_id, rand((@spellpower/15)+5)+2;
+ end;
+
+OnDestroy:
+ debugmes "kaflosh timeout! [this shouldn't happen]";
+ destroy;
+
+OnInit:
+ set .school, SKILL_MAGIC_NATURE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "kaflosh"; // used in npcs that refer to this spell
+ void call("magic_register", "OnCast");
+ set .level, 2;
+ set .exp_gain, 1;
+ set .max_radius, 15;
+ end;
+}
diff --git a/world/map/npc/magic/level2-shear.txt b/world/map/npc/magic/level2-shear.txt
new file mode 100644
index 00000000..af825727
--- /dev/null
+++ b/world/map/npc/magic/level2-shear.txt
@@ -0,0 +1,60 @@
+-|script|shear|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 23) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (getskilllv(.school) < .level) end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 1000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 23;
+ misceffect FX_MAGIC_GREEN, strcharinfo(0);
+ setarray @chipchipspell[0],
+ @spellpower,
+ (((200 - Agi) * 2000) / 200); //delay
+ overrideattack 1, @chipchipspell[1], 1, ATTACK_ICON_SHEARING, 30, strnpcinfo(0)+"::OnAttack";
+ callfunc "magic_exp";
+ end;
+
+OnAttack:
+ if (isloggedin(@target_id)) goto L_FreeRecast; // can not shear a player
+ if (sc_check(SC_SHEARED, @target_id)) goto L_FreeRecast; // mob already sheared
+ if (target(BL_ID, @target_id, 22) != 22) goto L_FreeRecast; // 0x10 | 0x02 | 0x04
+ sc_start SC_SHEARED, 600000, 0, @target_id;
+ set .@score, rand(1000 - rand(@chipchipspell[0]));
+ set .@id, get(Class, @target_id); // get the mob ID
+
+ if (.@id == 1020 && .@score < 300) set .@item$, "WhiteFur"; // Fluffy
+ elif (.@id == 1027 && .@score < 300) set .@item$, "WhiteFur"; // EasterFluffy
+ elif (.@id == 1019 && .@score < 250) set .@item$, "HardSpike"; // SpikyMushroom
+ elif (.@id == 1028 && .@score < 175) set .@item$, "CottonCloth"; // Mouboo
+ elif (.@id == 1029 && .@score < 700) set .@item$, "MauveHerb"; // MauvePlant
+ elif (.@id == 1030 && .@score < 700) set .@item$, "CobaltHerb"; // CobaltPlant
+ elif (.@id == 1031 && .@score < 700) set .@item$, "GambogeHerb"; // GambogePlant
+ elif (.@id == 1032 && .@score < 700) set .@item$, "AlizarinHerb"; // AlizarinPlant
+ elif (.@id == 1035 && .@score < 300) set .@item$, "SilkCocoon"; // SilkWorm
+ elif (.@id == 1018 && .@score < 180) set .@item$, "PinkAntenna"; // Pinkie
+ else end;
+ makeitem .@item$, 1, getmap(), rand(POS_X - 1, POS_X + 1), rand(POS_Y - 1, POS_Y + 1);
+
+ if (.@id != 1020 && .@id != 1028 && .@id != 1018 && rand(2) != 1) end;
+ set @value, 1;
+ callfunc "QuestSagathaHappy";
+ end;
+
+L_FreeRecast:
+ addtimer 0, strnpcinfo(0) + "::OnSetRecast"; // we can't do it while already overridden, until it reaches a script terminator
+ end;
+
+OnSetRecast:
+ overrideattack 1, @chipchipspell[1], 1, ATTACK_ICON_SHEARING, 30, strnpcinfo(0)+"::OnAttack";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_NATURE;
+ set .invocation$, chr(MAGIC_SYMBOL) + "chipchip"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 0;
+ end;
+}
diff --git a/world/map/npc/magic/level2-summon-fluffies.txt b/world/map/npc/magic/level2-summon-fluffies.txt
new file mode 100644
index 00000000..cd754e33
--- /dev/null
+++ b/world/map/npc/magic/level2-summon-fluffies.txt
@@ -0,0 +1,54 @@
+-|script|smfluffies|32767
+{
+ end;
+
+OnCast:
+ if(call("magic_checks")) end;
+ if (Sp < 39) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("WhiteFur") < 1 || countitem("Root") < 1) end;
+ delitem "WhiteFur", 1;
+ delitem "Root", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 20000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 39;
+ misceffect FX_MAGIC_BLUE, strcharinfo(0);
+ misceffect FX_PENTAGRAM_BUILDUP, strcharinfo(0);
+ callfunc "magic_exp";
+ set .@puppet$, "#"+strnpcinfo(0)+"#"+BL_ID;
+ set .@puppet, puppet(getmap(), POS_X, POS_Y, .@puppet$, 127);
+ set .count, (@spellpower/170)+1+(@spellpower/430), .@puppet;
+ set .master, BL_ID, .@puppet;
+ set .lifetime, @spellpower*350, .@puppet;
+ addnpctimer 5000-(@spellpower*8), .@puppet$+"::OnSummon";
+ addnpctimer 6000, .@puppet$+"::OnDestroy";
+ end;
+
+OnSummon:
+ specialeffect FX_PENTAGRAM_BURST;
+ set .@i, 0;
+ set .@x, getnpcx();
+ set .@y, getnpcy();
+ set .@map$, strnpcinfo(3);
+ callsub S_SummonAll;
+ end;
+
+OnDestroy:
+ destroy;
+
+S_SummonAll:
+ summon .@map$, rand(.@x-2,.@x+2), rand(.@y-2,.@y+2), .master, 1020, 2, .lifetime;
+ set .@i, .@i + 1;
+ if (.@i < .count) goto S_SummonAll;
+ return;
+
+OnInit:
+ set .school, SKILL_MAGIC_ASTRAL;
+ set .invocation$, chr(MAGIC_SYMBOL) + "kalakarenk"; // used in npcs that refer to this spell
+ void call("magic_register", "OnCast");
+ set .level, 2;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level2-summon-mouboo.txt b/world/map/npc/magic/level2-summon-mouboo.txt
new file mode 100644
index 00000000..8eb074c6
--- /dev/null
+++ b/world/map/npc/magic/level2-summon-mouboo.txt
@@ -0,0 +1,54 @@
+-|script|smmouboo|32767
+{
+ end;
+
+OnCast:
+ if(call("magic_checks")) end;
+ if (Sp < 35) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("MoubooFigurine") < 1 || countitem("Root") < 1) end;
+ delitem "MoubooFigurine", 1;
+ delitem "Root", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 20000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 35;
+ misceffect FX_MAGIC_BLUE, strcharinfo(0);
+ misceffect FX_PENTAGRAM_BUILDUP, strcharinfo(0);
+ callfunc "magic_exp";
+ set .@puppet$, "#"+strnpcinfo(0)+"#"+BL_ID;
+ set .@puppet, puppet(getmap(), POS_X, POS_Y, .@puppet$, 127);
+ set .count, (@spellpower/270)+1, .@puppet;
+ set .master, BL_ID, .@puppet;
+ set .lifetime, @spellpower*100, .@puppet;
+ addnpctimer 4000-(@spellpower*9), .@puppet$+"::OnSummon";
+ addnpctimer 6000, .@puppet$+"::OnDestroy";
+ end;
+
+OnSummon:
+ specialeffect FX_PENTAGRAM_BURST;
+ set .@i, 0;
+ set .@x, getnpcx();
+ set .@y, getnpcy();
+ set .@map$, strnpcinfo(3);
+ callsub S_SummonAll;
+ end;
+
+OnDestroy:
+ destroy;
+
+S_SummonAll:
+ summon .@map$, rand(.@x-2,.@x+2), rand(.@y-2,.@y+2), .master, 1028, 2, .lifetime;
+ set .@i, .@i + 1;
+ if (.@i < .count) goto S_SummonAll;
+ return;
+
+OnInit:
+ set .school, SKILL_MAGIC_ASTRAL;
+ set .invocation$, chr(MAGIC_SYMBOL) + "kalboo"; // used in npcs that refer to this spell
+ void call("magic_register", "OnCast");
+ set .level, 2;
+ set .exp_gain, 2;
+ end;
+}
diff --git a/world/map/npc/magic/level2-summon-pinkie.txt b/world/map/npc/magic/level2-summon-pinkie.txt
new file mode 100644
index 00000000..b91ec1d9
--- /dev/null
+++ b/world/map/npc/magic/level2-summon-pinkie.txt
@@ -0,0 +1,54 @@
+-|script|smpinkie|32767
+{
+ end;
+
+OnCast:
+ if(call("magic_checks")) end;
+ if (Sp < 35) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("PinkAntenna") < 1 || countitem("Root") < 1) end;
+ delitem "PinkAntenna", 1;
+ delitem "Root", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 20000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 35;
+ misceffect FX_MAGIC_BLUE, strcharinfo(0);
+ misceffect FX_PENTAGRAM_BUILDUP, strcharinfo(0);
+ callfunc "magic_exp";
+ set .@puppet$, "#"+strnpcinfo(0)+"#"+BL_ID;
+ set .@puppet, puppet(getmap(), POS_X, POS_Y, .@puppet$, 127);
+ set .count, (@spellpower/120)+1, .@puppet;
+ set .master, BL_ID, .@puppet;
+ set .lifetime, @spellpower*150, .@puppet;
+ addnpctimer 5000-(@spellpower*9), .@puppet$+"::OnSummon";
+ addnpctimer 6000, .@puppet$+"::OnDestroy";
+ end;
+
+OnSummon:
+ specialeffect FX_PENTAGRAM_BURST;
+ set .@i, 0;
+ set .@x, getnpcx();
+ set .@y, getnpcy();
+ set .@map$, strnpcinfo(3);
+ callsub S_SummonAll;
+ end;
+
+OnDestroy:
+ destroy;
+
+S_SummonAll:
+ summon .@map$, rand(.@x-2,.@x+2), rand(.@y-2,.@y+2), .master, 1018, 2, .lifetime;
+ set .@i, .@i + 1;
+ if (.@i < .count) goto S_SummonAll;
+ return;
+
+OnInit:
+ set .school, SKILL_MAGIC_ASTRAL;
+ set .invocation$, chr(MAGIC_SYMBOL) + "kalgina"; // used in npcs that refer to this spell
+ void call("magic_register", "OnCast");
+ set .level, 2;
+ set .exp_gain, 2;
+ end;
+}
diff --git a/world/map/npc/magic/level2-summon-snakes.txt b/world/map/npc/magic/level2-summon-snakes.txt
new file mode 100644
index 00000000..7490c506
--- /dev/null
+++ b/world/map/npc/magic/level2-summon-snakes.txt
@@ -0,0 +1,55 @@
+-|script|summon-snakes|32767
+{
+ end;
+
+OnCast:
+ if(call("magic_checks")) end;
+ if (Sp < 40) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("DarkCrystal") < 1 || countitem("SnakeEgg") < 1) end;
+ if (OrumQuest <= 40) end;
+ delitem "DarkCrystal", 1;
+ delitem "SnakeEgg", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 15000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 40;
+ misceffect FX_MAGIC_DARKRED, strcharinfo(0);
+ misceffect FX_PENTAGRAM_BUILDUP, strcharinfo(0);
+ callfunc "magic_exp";
+ set .@puppet$, "#"+strnpcinfo(0)+"#"+BL_ID;
+ set .@puppet, puppet(getmap(), POS_X, POS_Y, .@puppet$, 127);
+ set .count, (@spellpower/300)+1, .@puppet;
+ set .master, BL_ID, .@puppet;
+ set .lifetime, @spellpower*80, .@puppet;
+ addnpctimer 4000-(@spellpower*9), .@puppet$+"::OnSummon";
+ addnpctimer 6000, .@puppet$+"::OnDestroy";
+ end;
+
+OnSummon:
+ specialeffect FX_PENTAGRAM_BURST;
+ set .@i, 0;
+ set .@x, getnpcx();
+ set .@y, getnpcy();
+ set .@map$, strnpcinfo(3);
+ callsub S_SummonAll;
+ end;
+
+OnDestroy:
+ destroy;
+
+S_SummonAll:
+ summon .@map$, rand(.@x-2,.@x+2), rand(.@y-2,.@y+2), .master, 1010, 2, .lifetime;
+ set .@i, .@i + 1;
+ if (.@i < .count) goto S_SummonAll;
+ return;
+
+OnInit:
+ set .school, SKILL_MAGIC_DARK;
+ set .invocation$, chr(MAGIC_SYMBOL) + "halhiss"; // used in npcs that refer to this spell
+ void call("magic_register", "OnCast");
+ set .level, 2;
+ set .exp_gain, 3;
+ end;
+}
diff --git a/world/map/npc/magic/level2-summon-spiky-mushroom.txt b/world/map/npc/magic/level2-summon-spiky-mushroom.txt
new file mode 100644
index 00000000..39ad9ecd
--- /dev/null
+++ b/world/map/npc/magic/level2-summon-spiky-mushroom.txt
@@ -0,0 +1,54 @@
+-|script|smsmushrooms|32767
+{
+ end;
+
+OnCast:
+ if(call("magic_checks")) end;
+ if (Sp < 33) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("HardSpike") < 1 || countitem("Root") < 1) end;
+ delitem "HardSpike", 1;
+ delitem "Root", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 20000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 33;
+ misceffect FX_MAGIC_BLUE, strcharinfo(0);
+ misceffect FX_PENTAGRAM_BUILDUP, strcharinfo(0);
+ callfunc "magic_exp";
+ set .@puppet$, "#"+strnpcinfo(0)+"#"+BL_ID;
+ set .@puppet, puppet(getmap(), POS_X, POS_Y, .@puppet$, 127);
+ set .count, (@spellpower/120)+1, .@puppet;
+ set .master, BL_ID, .@puppet;
+ set .lifetime, @spellpower*400, .@puppet;
+ addnpctimer 5000-(@spellpower*9), .@puppet$+"::OnSummon";
+ addnpctimer 6000, .@puppet$+"::OnDestroy";
+ end;
+
+OnSummon:
+ specialeffect FX_PENTAGRAM_BURST;
+ set .@i, 0;
+ set .@x, getnpcx();
+ set .@y, getnpcy();
+ set .@map$, strnpcinfo(3);
+ callsub S_SummonAll;
+ end;
+
+OnDestroy:
+ destroy;
+
+S_SummonAll:
+ summon .@map$, rand(.@x-2,.@x+2), rand(.@y-2,.@y+2), .master, 1019, 2, .lifetime;
+ set .@i, .@i + 1;
+ if (.@i < .count) goto S_SummonAll;
+ return;
+
+OnInit:
+ set .school, SKILL_MAGIC_ASTRAL;
+ set .invocation$, chr(MAGIC_SYMBOL) + "kalrenk"; // used in npcs that refer to this spell
+ void call("magic_register", "OnCast");
+ set .level, 2;
+ set .exp_gain, 1;
+ end;
+}
diff --git a/world/map/npc/magic/level2-summon-wickedmushroom.txt b/world/map/npc/magic/level2-summon-wickedmushroom.txt
new file mode 100644
index 00000000..df17742f
--- /dev/null
+++ b/world/map/npc/magic/level2-summon-wickedmushroom.txt
@@ -0,0 +1,55 @@
+-|script|smwmushroom|32767
+{
+ end;
+
+OnCast:
+ if(call("magic_checks")) end;
+ if (Sp < 35) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ if (getskilllv(.school) < .level) end;
+ if (countitem("DarkCrystal") < 1 || countitem("SmallMushroom") < 1) end;
+ if (OrumQuest <= 36) end;
+ delitem "DarkCrystal", 1;
+ delitem "SmallMushroom", 1;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 15000, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 35;
+ misceffect FX_MAGIC_DARKRED, strcharinfo(0);
+ misceffect FX_PENTAGRAM_BUILDUP, strcharinfo(0);
+ callfunc "magic_exp";
+ set .@puppet$, "#"+strnpcinfo(0)+"#"+BL_ID;
+ set .@puppet, puppet(getmap(), POS_X, POS_Y, .@puppet$, 127);
+ set .count, (@spellpower/250)+1, .@puppet;
+ set .master, BL_ID, .@puppet;
+ set .lifetime, @spellpower*80, .@puppet;
+ addnpctimer 4000-(@spellpower*9), .@puppet$+"::OnSummon";
+ addnpctimer 6000, .@puppet$+"::OnDestroy";
+ end;
+
+OnSummon:
+ specialeffect FX_PENTAGRAM_BURST;
+ set .@i, 0;
+ set .@x, getnpcx();
+ set .@y, getnpcy();
+ set .@map$, strnpcinfo(3);
+ callsub S_SummonAll;
+ end;
+
+OnDestroy:
+ destroy;
+
+S_SummonAll:
+ summon .@map$, rand(.@x-2,.@x+2), rand(.@y-2,.@y+2), .master, 1106, 2, .lifetime;
+ set .@i, .@i + 1;
+ if (.@i < .count) goto S_SummonAll;
+ return;
+
+OnInit:
+ set .school, SKILL_MAGIC_DARK;
+ set .invocation$, chr(MAGIC_SYMBOL) + "helorp"; // used in npcs that refer to this spell
+ void call("magic_register", "OnCast");
+ set .level, 2;
+ set .exp_gain, 3;
+ end;
+}
diff --git a/world/map/npc/magic/level2-toxic-dart.txt b/world/map/npc/magic/level2-toxic-dart.txt
new file mode 100644
index 00000000..357ae32b
--- /dev/null
+++ b/world/map/npc/magic/level2-toxic-dart.txt
@@ -0,0 +1,51 @@
+-|script|toxic-dart|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 15) end;
+ set .@level, getskilllv(.school);
+ if (.@level < .level) end;
+ if (OrumQuest <= 37) end;
+ if (.@level <= 2 && countitem("Root") >= 2) delitem "Root", 2;
+ elif (.@level <= 2) end;
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 500, "Magic Timer::OnClear"; // set the new debuff
+ callfunc "adjust_spellpower";
+ set Sp, Sp - 15;
+ misceffect FX_MAGIC_DARKRED, strcharinfo(0);
+ setarray @phlexspell[0],
+ (sqrt(@spellpower) * 5), // elt damage
+ ((BaseLevel/3) + 5), // elt damage bonus
+ (((200 - Agi) * 1200) / 200), // delay
+ ((@spellpower/75) + 3), // charges
+ (5000+(@spellpower*1200)), // poison duration
+ (max(15,@spellpower/15)+5); // poison strength
+
+ callfunc "magic_exp";
+ goto L_FreeRecast;
+
+OnAttack:
+ if (target(BL_ID, @target_id, 50) != 50) goto L_FreeRecast; // 0x20 | 0x02 | 0x10
+ misceffect FX_MAGIC_DARKRED, strcharinfo(0);
+ void call("elt_damage", @phlexspell[0], @phlexspell[1], ELT_NEUTRAL, ELT_POISON, FX_FIRE_BURST);
+ if(@target_id != BL_ID && isloggedin(@target_id)) // this is a dirty trick to check if the target is a player
+ sc_start sc_poison, @phlexspell[4], @phlexspell[5], @target_id;
+ set @phlexspell[3], @phlexspell[3] - 1;
+ goto L_FreeRecast;
+
+L_FreeRecast:
+ if (@phlexspell[3] > 0)
+ addtimer 0, strnpcinfo(0) + "::OnSetRecast";
+ end;
+
+OnSetRecast:
+ overrideattack 1, @phlexspell[2], 4, ATTACK_ICON_GENERIC, 31, strnpcinfo(0)+"::OnAttack";
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_DARK;
+ set .invocation$, chr(MAGIC_SYMBOL) + "phlex"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 2;
+ set .exp_gain, 3;
+ end;
+}
diff --git a/world/map/npc/magic/level3-necromancy.txt b/world/map/npc/magic/level3-necromancy.txt
new file mode 100644
index 00000000..ef2d761f
--- /dev/null
+++ b/world/map/npc/magic/level3-necromancy.txt
@@ -0,0 +1,55 @@
+// see https://tmworld.uservoice.com/forums/255809-general/suggestions/6051818-sacrifice
+// author: gumi
+-|script|necromancy|32767
+{
+ if(call("magic_checks")) end;
+ if (Sp < 50) end;
+ if (getskilllv(.school) < .level) end;
+ if (getskilllv(SKILL_MAGIC) < .level) end;
+ set @target_id, getcharid(3, @args$);
+ if (@target_id < 1 || !(isloggedin(@target_id))) end;
+ if (get(Hp, @target_id) > 0) end;
+ if (Hp < (get(MaxHp, @target_id) / 3)) end; // hp must be at least a third of the max hp of the target
+ callfunc "adjust_spellpower";
+ if (distance(BL_ID, @target_id) >= (((sqrt(@spellpower)*12)+@spellpower)/100)+2) end;
+ if (get(@necromancer, @target_id) > 0) end; // someone else is already trying to resurrect this player
+ if (getmapflag(getmap(), MF_NOSAVE)) end; // do not allow for maps like illia or candor
+ if (countitem("Soul") >= 1) delitem "Soul", 1; else end;
+
+ set @_M_BLOCK, 1; // block casting, until the timer clears it
+ addtimer 20000, "Magic Timer::OnClear";
+ set Sp, Sp - 50;
+ misceffect FX_MAGIC_DARKRED, strcharinfo(0); // on caster
+ misceffect FX_PENTAGRAM_BUILDUP, @args$; // on target
+
+ set @necromancer, CHAR_ID, @target_id; // tell the target who is reviving them
+
+ if (attachrid(@target_id) != 1) end;
+ addtimer 6000, strnpcinfo(0)+"::OnRevive"; // TODO: make it take more or less time depending on the spell power
+ end;
+
+OnRevive:
+ set .@necro, get(BL_ID, @necromancer);
+ if (.@necro < 1) goto L_Clean;
+ if (get(Hp, .@necro) < 1) end;
+ misceffect FX_PENTAGRAM_BURST, strcharinfo(0);
+ misceffect FX_CRITICAL, strcharinfo(0, .@necro);
+ heal 1, 0; // revive
+ set Hp, 1;
+ set Sp, 0;
+ set Hp, 1, .@necro;
+ set Sp, 0, .@necro;
+ goto L_Clean;
+
+L_Clean:
+ set @necromancer, 0;
+ end;
+
+OnInit:
+ set .school, SKILL_MAGIC_DARK;
+ set .invocation$, chr(MAGIC_SYMBOL) + "nevela"; // used in npcs that refer to this spell
+ void call("magic_register");
+ set .level, 3;
+ set .exp_gain, 1;
+ end;
+}