From 8ac49c6058db5bf7f06662b8370b1d0fdf17d578 Mon Sep 17 00:00:00 2001
From: wushin <pasekei@gmail.com>
Date: Mon, 8 Jun 2015 00:40:20 -0500
Subject: new/modified builtins

areatimer
foreach
aggravate
Override attack animation
injure
summon
---
 src/map/map.hpp        |   4 +
 src/map/mob.cpp        |  10 ++
 src/map/mob.hpp        |   1 +
 src/map/npc.cpp        |  88 +++++++++++++
 src/map/npc.hpp        |   1 +
 src/map/pc.cpp         |  67 +++++++++-
 src/map/pc.hpp         |   2 +
 src/map/script-fun.cpp | 329 +++++++++++++++++++++++++++++++++++++++++++++++--
 8 files changed, 484 insertions(+), 18 deletions(-)

(limited to 'src')

diff --git a/src/map/map.hpp b/src/map/map.hpp
index 47eef57..a2f2ff2 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -208,6 +208,7 @@ struct map_session_data : block_list, SessionData
     tick_t cast_tick;     // [Fate] Next tick at which spellcasting is allowed
     dumb_ptr<magic::invocation> active_spells;   // [Fate] Singly-linked list of active spells linked to this PC
     BlockId attack_spell_override; // [Fate] When an attack spell is active for this player, they trigger it
+    NpcEvent magic_attack;
     // like a weapon.  Check pc_attack_timer() for details.
     // Weapon equipment slot (slot 4) item override
     StatusChange attack_spell_icon_override;
@@ -479,6 +480,9 @@ struct mob_data : block_list
     // [Fate] mob-specific stats
     earray<unsigned short, mob_stat, mob_stat::LAST> stats;
     short size;
+    // Npc Runscripts
+    std::list<RString> eventqueuel;
+    Array<Timer, MAX_EVENTTIMER> eventtimer;
 };
 
 struct BlockLists
diff --git a/src/map/mob.cpp b/src/map/mob.cpp
index 7566e03..1be40f0 100644
--- a/src/map/mob.cpp
+++ b/src/map/mob.cpp
@@ -1396,6 +1396,16 @@ int mob_target(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl, int dist)
     return 0;
 }
 
+int mob_aggravate(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl)
+{
+    if (md->bl_type != BL::MOB)
+        return 0;
+    mob_target(md, bl, battle_get_range(bl));
+    md->target_id = bl->bl_id;
+    md->attacked_id = bl->bl_id;
+    return 1;
+}
+
 /*==========================================
  * The ?? routine of an active monster
  *------------------------------------------
diff --git a/src/map/mob.hpp b/src/map/mob.hpp
index 6d87228..a466d0b 100644
--- a/src/map/mob.hpp
+++ b/src/map/mob.hpp
@@ -100,6 +100,7 @@ BlockId mob_once_spawn_area(dumb_ptr<map_session_data> sd,
         NpcEvent event);
 
 int mob_target(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl, int dist);
+int mob_aggravate(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl);
 int mob_stop_walking(dumb_ptr<mob_data> md, int type);
 int mob_stopattack(dumb_ptr<mob_data>);
 int mob_spawn(BlockId);
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index 4296432..1707c1c 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -286,6 +286,94 @@ int npc_event_do_oninit(void)
     return 0;
 }
 
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void npc_eventtimer(TimerData *, tick_t, BlockId id, NpcEvent data)
+{
+    Option<P<struct event_data>> ev_ = ev_db.search(data);
+    dumb_ptr<npc_data_script> nd;
+    dumb_ptr<block_list> bl = map_id2bl(id);
+
+    if (ev_.is_none() && data.label == stringish<ScriptLabel>("OnTouch"_s))
+        return;
+
+    P<struct event_data> ev = TRY_UNWRAP(ev_,
+    {
+        if (battle_config.error_log)
+            PRINTF("npc_event: event not found [%s]\n"_fmt,
+                    data);
+        return;
+    });
+    if ((nd = ev->nd) == nullptr)
+    {
+        if (battle_config.error_log)
+            PRINTF("npc_event: event not found [%s]\n"_fmt,
+                    data);
+        return;
+    }
+
+    if (nd->scr.event_needs_map)
+    {
+        int xs = nd->scr.xs;
+        int ys = nd->scr.ys;
+        if (nd->bl_m != bl->bl_m)
+            return;
+        if (xs > 0
+            && (bl->bl_x < nd->bl_x - xs / 2 || nd->bl_x + xs / 2 < bl->bl_x))
+            return;
+        if (ys > 0
+            && (bl->bl_y < nd->bl_y - ys / 2 || nd->bl_y + ys / 2 < bl->bl_y))
+            return;
+    }
+
+    run_script(ScriptPointer(borrow(*nd->scr.script), ev->pos), id, nd->bl_id);
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+int npc_addeventtimer(dumb_ptr<block_list> bl, interval_t tick, NpcEvent name)
+{
+    int i;
+
+    nullpo_retz(bl);
+    if (bl->bl_type == BL::MOB)
+    {
+        dumb_ptr<mob_data> md = bl->is_mob();
+        for (i = 0; i < MAX_EVENTTIMER; i++)
+            if (!md->eventtimer[i])
+                break;
+
+        if (i < MAX_EVENTTIMER)
+        {
+            md->eventtimer[i] = Timer(gettick() + tick,
+                    std::bind(npc_eventtimer, ph::_1, ph::_2,
+                        md->bl_id, name));
+            return 1;
+        }
+    }
+    if (bl->bl_type == BL::NPC)
+    {
+        dumb_ptr<npc_data> nd = bl->is_npc();
+        for (i = 0; i < MAX_EVENTTIMER; i++)
+            if (!nd->eventtimer[i])
+                break;
+
+        if (i < MAX_EVENTTIMER)
+        {
+            nd->eventtimer[i] = Timer(gettick() + tick,
+                    std::bind(npc_eventtimer, ph::_1, ph::_2,
+                        nd->bl_id, name));
+            return 1;
+        }
+    }
+    return 0;
+}
+
 /// Callback for npc OnTimer*: labels.
 /// This will be called later if you call npc_timerevent_start.
 /// This function may only expire, but not deactivate, the counter.
diff --git a/src/map/npc.hpp b/src/map/npc.hpp
index b587f5f..7a96bf9 100644
--- a/src/map/npc.hpp
+++ b/src/map/npc.hpp
@@ -45,6 +45,7 @@ constexpr Species INVISIBLE_CLASS = wrap<Species>(32767);
 
 int npc_event_dequeue(dumb_ptr<map_session_data> sd);
 int npc_event(dumb_ptr<map_session_data> sd, NpcEvent npcname, int);
+int npc_addeventtimer(dumb_ptr<block_list> bl, interval_t tick, NpcEvent name);
 int npc_touch_areanpc(dumb_ptr<map_session_data>, Borrowed<map_local>, int, int);
 int npc_click(dumb_ptr<map_session_data>, BlockId);
 int npc_scriptcont(dumb_ptr<map_session_data>, BlockId);
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index c47925f..c4c3ad9 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -635,6 +635,51 @@ int pc_isequip(dumb_ptr<map_session_data> sd, IOff0 n)
     return 1;
 }
 
+void pc_set_weapon_icon(dumb_ptr<map_session_data> sd, int count,
+        StatusChange icon, ItemNameId look)
+{
+    const StatusChange old_icon = sd->attack_spell_icon_override;
+
+    sd->attack_spell_icon_override = icon;
+    sd->attack_spell_look_override = look;
+
+    if (old_icon != StatusChange::ZERO && old_icon != icon)
+        clif_status_change(sd, old_icon, 0);
+
+    clif_fixpcpos(sd);
+    if (count)
+    {
+        clif_changelook(sd, LOOK::WEAPON, unwrap<ItemNameId>(look));
+        if (icon != StatusChange::ZERO)
+            clif_status_change(sd, icon, 1);
+    }
+    else
+    {
+        /* Set it to `normal' */
+        clif_changelook(sd, LOOK::WEAPON,
+                static_cast<uint16_t>(sd->status.weapon));
+    }
+}
+
+void pc_set_attack_info(dumb_ptr<map_session_data> sd, interval_t speed, int range)
+{
+    sd->attack_spell_delay = speed;
+    sd->attack_spell_range = range;
+
+    if (speed == interval_t::zero())
+    {
+        pc_calcstatus(sd, 1);
+        clif_updatestatus(sd, SP::ASPD);
+        clif_updatestatus(sd, SP::ATTACKRANGE);
+    }
+    else
+    {
+        sd->aspd = speed;
+        clif_updatestatus(sd, SP::ASPD);
+        clif_updatestatus(sd, SP::ATTACKRANGE);
+    }
+}
+
 /*==========================================
  * session idに問題無し
  * char鯖から送られてきたステータスを設定
@@ -2608,13 +2653,23 @@ void pc_attack_timer(TimerData *, tick_t tick, BlockId id)
     if (sd->attackabletime > tick)
         return;               // cannot attack yet
 
-    interval_t attack_spell_delay = sd->attack_spell_delay;
-    if (sd->attack_spell_override   // [Fate] If we have an active attack spell, use that
-        && magic::spell_attack(id, sd->attacktarget))
+    if (sd->attack_spell_override)   // [Fate] If we have an active attack spell, use that
     {
-        // Return if the spell succeeded.  If the spell had disspiated, spell_attack() may fail.
-        sd->attackabletime = tick + attack_spell_delay;
-
+        // call_spell_event_script
+        argrec_t arg[1] =
+        {
+            {"@target_id"_s, static_cast<int32_t>(unwrap<BlockId>(bl->bl_id))},
+        };
+        npc_event_do_l(sd->magic_attack, sd->bl_id, arg);
+        sd->attackabletime = tick + sd->attack_spell_delay;
+        sd->attack_spell_charges--;
+        if (!sd->attack_spell_charges)
+        {
+            sd->attack_spell_override = BlockId();
+            pc_set_weapon_icon(sd, 0, StatusChange::ZERO, ItemNameId());
+            pc_set_attack_info(sd, interval_t::zero(), 0);
+            pc_calcstatus(sd, 0);
+        }
     }
     else
     {
diff --git a/src/map/pc.hpp b/src/map/pc.hpp
index db1d5be..3a35330 100644
--- a/src/map/pc.hpp
+++ b/src/map/pc.hpp
@@ -80,6 +80,8 @@ int pc_counttargeted(dumb_ptr<map_session_data> sd, dumb_ptr<block_list> src,
 int pc_setrestartvalue(dumb_ptr<map_session_data> sd, int type);
 void pc_makesavestatus(dumb_ptr<map_session_data>);
 int pc_setnewpc(dumb_ptr<map_session_data>, AccountId, CharId, int, uint32_t /*tick_t*/, SEX);
+void pc_set_weapon_icon(dumb_ptr<map_session_data> sd, int count,StatusChange icon, ItemNameId look);
+void pc_set_attack_info(dumb_ptr<map_session_data> sd, interval_t speed, int range);
 int pc_authok(AccountId, int, ClientVersion, const CharKey *, const CharData *);
 int pc_authfail(AccountId accid);
 
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 07076d5..9f9652e 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -70,6 +70,13 @@ namespace map
 #define AARG(n) (st->stack->stack_datav[st->start + 2 + (n)])
 #define HARG(n) (st->end > st->start + 2 + (n))
 
+enum class MonsterAttitude
+{
+    HOSTILE     = 0,
+    FRIENDLY    = 1,
+    SERVANT     = 2,
+    FROZEN      = 3,
+};
 //
 // 埋め込み関数
 //
@@ -498,6 +505,81 @@ void builtin_heal(ScriptState *st)
         pc_heal(sd, hp, sp);
 }
 
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_elttype(ScriptState *st)
+{
+    int element_type = static_cast<int>(battle_get_element(map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0))))).element);
+    push_int<ScriptDataInt>(st->stack, element_type);
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_eltlvl(ScriptState *st)
+{
+    int element_lvl = static_cast<int>(battle_get_element(map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0))))).level);
+    push_int<ScriptDataInt>(st->stack, element_lvl);
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_injure(ScriptState *st)
+{
+    dumb_ptr<block_list> caster = map_id2bl(st->rid);
+    dumb_ptr<block_list> target = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0))));
+    int damage_caused = conv_num(st, &AARG(1));
+    int mp_damage = conv_num(st, &AARG(2));
+    int target_hp = battle_get_hp(target);
+    int mdef = battle_get_mdef(target);
+
+    if (target->bl_type == BL::PC
+        && !target->bl_m->flag.get(MapFlag::PVP)
+        && (caster->bl_type == BL::PC)
+        && ((caster->is_player()->state.pvpchannel > 1) && (target->is_player()->state.pvpchannel != caster->is_player()->state.pvpchannel)))
+        return;               /* Cannot damage other players outside of pvp */
+
+    if (target != caster)
+    {
+        /* Not protected against own spells */
+        damage_caused = (damage_caused * (100 - mdef)) / 100;
+        mp_damage = (mp_damage * (100 - mdef)) / 100;
+    }
+
+    damage_caused = (damage_caused > target_hp) ? target_hp : damage_caused;
+
+    if (damage_caused < 0)
+        damage_caused = 0;
+
+    // display damage first, because dealing damage may deallocate the target.
+    clif_damage(caster, target,
+            gettick(), interval_t::zero(), interval_t::zero(),
+            damage_caused, 0, DamageType::NORMAL);
+
+    if (caster->bl_type == BL::PC)
+    {
+        dumb_ptr<map_session_data> caster_pc = caster->is_player();
+        if (target->bl_type == BL::MOB)
+        {
+            dumb_ptr<mob_data> mob = target->is_mob();
+            dumb_ptr<npc_data> nd = map_id_is_npc(st->oid);
+            MAP_LOG_PC(caster_pc, "SPELLDMG MOB%d %d FOR %d BY %s"_fmt,
+                    mob->bl_id, mob->mob_class, damage_caused, nd->name);
+        }
+    }
+    battle_damage(caster, target, damage_caused, mp_damage);
+
+    return;
+}
+
 /*==========================================
  *
  *------------------------------------------
@@ -639,6 +721,61 @@ void builtin_elif (ScriptState *st)
     run_func(st);
 }
 
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_foreach_sub(dumb_ptr<block_list> bl, NpcEvent event, BlockId caster)
+{
+    // call_spell_event_script
+    argrec_t arg[1] =
+    {
+        {"@target_id"_s, static_cast<int32_t>(unwrap<BlockId>(bl->bl_id))},
+    };
+    npc_event_do_l(event, caster, arg);
+}
+static
+void builtin_foreach(ScriptState *st)
+{
+    int x0, y0, x1, y1, bl_num;
+
+    dumb_ptr<block_list> caster = map_id2bl(st->rid);
+    bl_num = conv_num(st, &AARG(0));
+    MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(1))));
+    x0 = conv_num(st, &AARG(2));
+    y0 = conv_num(st, &AARG(3));
+    x1 = conv_num(st, &AARG(4));
+    y1 = conv_num(st, &AARG(5));
+    ZString event_ = ZString(conv_str(st, &AARG(6)));
+    BL block_type;
+    NpcEvent event;
+    extract(event_, &event);
+
+    P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return);
+
+    switch (bl_num)
+    {
+        case 0:
+            block_type = BL::PC;
+            break;
+        case 1:
+            block_type = BL::NPC;
+            break;
+        case 2:
+            block_type = BL::MOB;
+            break;
+        default:
+            return;
+    }
+
+    map_foreachinarea(std::bind(builtin_foreach_sub, ph::_1, event, caster->bl_id),
+            m,
+            x0, y0,
+            x1, y1,
+            block_type);
+}
+
 /*==========================================
  * 変数設定
  *------------------------------------------
@@ -1396,6 +1533,31 @@ void builtin_getskilllv(ScriptState *st)
     push_int<ScriptDataInt>(st->stack, pc_checkskill(script_rid2sd(st), id));
 }
 
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_overrideattack(ScriptState *st)
+{
+    dumb_ptr<map_session_data> sd = script_rid2sd(st);
+    int charges = conv_num(st, &AARG(0));
+    interval_t attack_delay = static_cast<interval_t>(conv_num(st, &AARG(1)));
+    int attack_range = conv_num(st, &AARG(2));
+    StatusChange icon = StatusChange(conv_num(st, &AARG(3)));
+    ItemNameId look = wrap<ItemNameId>(static_cast<uint16_t>(conv_num(st, &AARG(4))));
+    ZString event_ = ZString(conv_str(st, &AARG(5)));
+
+    NpcEvent event;
+    extract(event_, &event);
+
+    sd->attack_spell_override = st->oid;
+    sd->attack_spell_charges = charges;
+    sd->magic_attack = event;
+    pc_set_weapon_icon(sd, charges, icon, look);
+    pc_set_attack_info(sd, attack_delay, attack_range);
+}
+
 /*==========================================
  *
  *------------------------------------------
@@ -1594,6 +1756,72 @@ void builtin_getexp(ScriptState *st)
 
 }
 
+static
+void builtin_summon(ScriptState *st)
+{
+    NpcEvent event;
+    MapName map = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+    int x = conv_num(st, &AARG(1));
+    int y = conv_num(st, &AARG(2));
+    dumb_ptr<block_list> owner_e = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(3))));
+    dumb_ptr<map_session_data> owner = nullptr;
+    Species monster_id = wrap<Species>(conv_num(st, &AARG(4)));
+    MonsterAttitude monster_attitude = static_cast<MonsterAttitude>(conv_num(st, &AARG(5)));
+    interval_t lifespan = static_cast<interval_t>(conv_num(st, &AARG(6)));
+    if (HARG(7))
+        extract(ZString(conv_str(st, &AARG(7))), &event);
+
+    if (monster_attitude == MonsterAttitude::SERVANT
+        && owner_e->bl_type == BL::PC)
+        owner = owner_e->is_player(); // XXX in the future this should also work with mobs as owner
+
+    BlockId mob_id = mob_once_spawn(owner, map, x, y, MobName(), monster_id, 1, event);
+    dumb_ptr<mob_data> mob = map_id_is_mob(mob_id);
+
+    if (mob)
+    {
+        mob->mode = get_mob_db(monster_id).mode;
+
+        switch (monster_attitude)
+        {
+            case MonsterAttitude::SERVANT:
+                mob->state.special_mob_ai = 1;
+                mob->mode |= MobMode::AGGRESSIVE;
+                break;
+
+            case MonsterAttitude::FRIENDLY:
+                mob->mode = MobMode::CAN_ATTACK | (mob->mode & MobMode::CAN_MOVE);
+                break;
+
+            case MonsterAttitude::HOSTILE:
+                mob->mode = MobMode::CAN_ATTACK | MobMode::AGGRESSIVE | (mob->mode & MobMode::CAN_MOVE);
+                if (owner)
+                {
+                    mob->target_id = owner->bl_id;
+                    mob->attacked_id = owner->bl_id;
+                }
+                break;
+
+            case MonsterAttitude::FROZEN:
+                mob->mode = MobMode::ZERO;
+                break;
+        }
+
+        mob->mode |=
+            MobMode::SUMMONED | MobMode::TURNS_AGAINST_BAD_MASTER;
+
+        mob->deletetimer = Timer(gettick() + lifespan,
+                std::bind(mob_timer_delete, ph::_1, ph::_2,
+                    mob_id));
+
+        if (owner)
+        {
+            mob->master_id = owner->bl_id;
+            mob->master_dist = 6;
+        }
+    }
+}
+
 /*==========================================
  * モンスター発生
  *------------------------------------------
@@ -1981,6 +2209,38 @@ void builtin_getmapusers(ScriptState *st)
     push_int<ScriptDataInt>(st->stack, users);
 }
 
+static
+void builtin_aggravate_sub(dumb_ptr<block_list> bl, dumb_ptr<block_list> target, int effect)
+{
+    dumb_ptr<mob_data> md = bl->is_mob();
+
+    if (mob_aggravate(md, target))
+        clif_misceffect(bl, effect);
+}
+
+static
+void builtin_aggravate(ScriptState *st)
+{
+    dumb_ptr<block_list> target = map_id2bl(st->rid);
+    MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+    int x0 = conv_num(st, &AARG(1));
+    int y0 = conv_num(st, &AARG(2));
+    int x1 = conv_num(st, &AARG(3));
+    int y1 = conv_num(st, &AARG(4));
+    int effect = conv_num(st, &AARG(5));
+    P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str),
+    {
+        push_int<ScriptDataInt>(st->stack, -1);
+        return;
+    });
+
+    map_foreachinarea(std::bind(builtin_aggravate_sub, ph::_1, target, effect),
+            m,
+            x0, y0,
+            x1, y1,
+            BL::MOB);
+}
+
 /*==========================================
  * エリア指定ユーザー数所得
  *------------------------------------------
@@ -3068,6 +3328,18 @@ void builtin_npctalk(ScriptState *st)
         clif_message(nd, str);
 }
 
+/*==========================================
+  * casttime
+  *------------------------------------------
+  */
+static
+void builtin_casttime(ScriptState *st)
+{
+    dumb_ptr<map_session_data> sd = script_rid2sd(st);
+    interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(0)));
+    sd->cast_tick = gettick() + tick;
+}
+
 /*==========================================
   * getlook char info. getlook(arg)
   *------------------------------------------
@@ -3152,31 +3424,56 @@ void builtin_getsavepoint(ScriptState *st)
 static
 void builtin_areatimer_sub(dumb_ptr<block_list> bl, interval_t tick, NpcEvent event)
 {
-    pc_addeventtimer(bl->is_player(), tick, event);
+    if (bl->bl_type == BL::PC)
+    {
+        dumb_ptr<map_session_data> sd = map_id_is_player(bl->bl_id);
+        pc_addeventtimer(sd, tick, event);
+    }
+    else
+    {
+        npc_addeventtimer(bl, tick, event);
+    }
 }
 
 static
 void builtin_areatimer(ScriptState *st)
 {
-    int x0, y0, x1, y1;
-
-    MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
-    x0 = conv_num(st, &AARG(1));
-    y0 = conv_num(st, &AARG(2));
-    x1 = conv_num(st, &AARG(3));
-    y1 = conv_num(st, &AARG(4));
-    interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(5)));
-    ZString event_ = ZString(conv_str(st, &AARG(6)));
+    int x0, y0, x1, y1, bl_num;
+
+    bl_num = conv_num(st, &AARG(0));
+    MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(1))));
+    x0 = conv_num(st, &AARG(2));
+    y0 = conv_num(st, &AARG(3));
+    x1 = conv_num(st, &AARG(4));
+    y1 = conv_num(st, &AARG(5));
+    interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(6)));
+    ZString event_ = conv_str(st, &AARG(7));
+    BL block_type;
     NpcEvent event;
     extract(event_, &event);
 
     P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return);
 
+    switch (bl_num)
+    {
+        case 0:
+            block_type = BL::PC;
+            break;
+        case 1:
+            block_type = BL::NPC;
+            break;
+        case 2:
+            block_type = BL::MOB;
+            break;
+        default:
+            return;
+    }
+
     map_foreachinarea(std::bind(builtin_areatimer_sub, ph::_1, tick, event),
             m,
             x0, y0,
             x1, y1,
-            BL::PC);
+            block_type);
 }
 
 /*==========================================
@@ -3431,6 +3728,9 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(warp, "Mxy"_s, '\0'),
     BUILTIN(areawarp, "MxyxyMxy"_s, '\0'),
     BUILTIN(heal, "ii?"_s, '\0'),
+    BUILTIN(elttype, "i"_s, 'i'),
+    BUILTIN(eltlvl, "i"_s, 'i'),
+    BUILTIN(injure, "iii"_s, '\0'),
     BUILTIN(input, "N"_s, '\0'),
     BUILTIN(if, "iF*"_s, '\0'),
     BUILTIN(elif, "iF*"_s, '\0'),
@@ -3456,6 +3756,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(skill, "ii?"_s, '\0'),
     BUILTIN(setskill, "ii"_s, '\0'),
     BUILTIN(getskilllv, "i"_s, 'i'),
+    BUILTIN(overrideattack, "iiiiiE"_s, '\0'),
     BUILTIN(getgmlevel, ""_s, 'i'),
     BUILTIN(end, ""_s, '\0'),
     BUILTIN(getopt2, ""_s, 'i'),
@@ -3465,6 +3766,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(gettime, "i"_s, 'i'),
     BUILTIN(openstorage, ""_s, '\0'),
     BUILTIN(getexp, "ii"_s, '\0'),
+    BUILTIN(summon, "Mxysmii?"_s, '\0'),
     BUILTIN(monster, "Mxysmi?"_s, '\0'),
     BUILTIN(areamonster, "Mxyxysmi?"_s, '\0'),
     BUILTIN(killmonster, "ME"_s, '\0'),
@@ -3525,6 +3827,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(npcareawarp, "xyxyis"_s, '\0'),
     BUILTIN(message, "Ps"_s, '\0'),
     BUILTIN(npctalk, "ss?"_s, '\0'),
+    BUILTIN(casttime, "i"_s, '\0'),
     BUILTIN(title, "s"_s, '\0'),
     BUILTIN(smsg, "e??"_s, '\0'),
     BUILTIN(remotecmd, "s?"_s, '\0'),
@@ -3533,11 +3836,13 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(getmask, ""_s, 'i'),
     BUILTIN(getlook, "i"_s, 'i'),
     BUILTIN(getsavepoint, "i"_s, '.'),
-    BUILTIN(areatimer, "MxyxytE"_s, '\0'),
+    BUILTIN(areatimer, "MxyxytEi"_s, '\0'),
+    BUILTIN(foreach, "iMxyxyE"_s, '\0'),
     BUILTIN(isin, "Mxyxy"_s, 'i'),
     BUILTIN(iscollision, "Mxy"_s, 'i'),
     BUILTIN(shop, "s"_s, '\0'),
     BUILTIN(isdead, ""_s, 'i'),
+    BUILTIN(aggravate, "Mxyxyi"_s, '\0'),
     BUILTIN(fakenpcname, "ssi"_s, '\0'),
     BUILTIN(getx, ""_s, 'i'),
     BUILTIN(gety, ""_s, 'i'),
-- 
cgit v1.2.3-70-g09d2


From a8640e1df61c06faf6edb89c5c4b9f025c0dff33 Mon Sep 17 00:00:00 2001
From: wushin <pasekei@gmail.com>
Date: Tue, 9 Jun 2015 00:53:20 -0500
Subject: Magic -> Map Npcs

---
 src/map/clif.cpp       |  2 +-
 src/map/globals.cpp    |  1 +
 src/map/globals.hpp    |  1 +
 src/map/npc.cpp        | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/map/npc.hpp        |  1 +
 src/map/script-fun.cpp | 19 +++++++++++++-
 6 files changed, 91 insertions(+), 2 deletions(-)

(limited to 'src')

diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index df21a88..a2a2a33 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -3842,7 +3842,7 @@ RecvResult clif_parse_GlobalMessage(Session *s, dumb_ptr<map_session_data> sd)
     if (is_atcommand(s, sd, mbuf, GmLevel()))
         return rv;
 
-    if (!magic::magic_message(sd, mbuf))
+    if (!magic_message(sd, mbuf))
     {
         /* Don't send chat that results in an automatic ban. */
         if (tmw_CheckChatSpam(sd, mbuf))
diff --git a/src/map/globals.cpp b/src/map/globals.cpp
index dce3906..4a54843 100644
--- a/src/map/globals.cpp
+++ b/src/map/globals.cpp
@@ -85,6 +85,7 @@ namespace tmwa
         BlockId npc_id = START_NPC_NUM;
         Map<NpcEvent, struct event_data> ev_db;
         DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name;
+        DMap<RString, NpcEvent> spells_by_name;
         // used for clock-based event triggers
         // only tm_min, tm_hour, and tm_mday are used
         tm ev_tm_b =
diff --git a/src/map/globals.hpp b/src/map/globals.hpp
index b457b4e..5a4ec82 100644
--- a/src/map/globals.hpp
+++ b/src/map/globals.hpp
@@ -79,6 +79,7 @@ namespace tmwa
         extern BlockId npc_id;
         extern Map<NpcEvent, event_data> ev_db;
         extern DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name;
+        extern DMap<RString, NpcEvent> spells_by_name;
         extern tm ev_tm_b;
         extern Map<PartyId, PartyMost> party_db;
         extern std::map<AccountId, GmLevel> gm_accountm;
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index 1707c1c..0175916 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -145,6 +145,75 @@ dumb_ptr<npc_data> npc_name2id(NpcName name)
     return npcs_by_name.get(name);
 }
 
+/*==========================================
+ * NPCを名前で探す
+ *------------------------------------------
+ */
+NpcEvent spell_name2id(RString name)
+{
+    return spells_by_name.get(name);
+}
+
+/*==========================================
+ * Spell Toknise
+ * Return a pair of strings, {spellname, parameter}
+ * Parameter may be empty.
+ *------------------------------------------
+ */
+static
+std::pair<XString, XString> magic_tokenise(XString src)
+{
+    auto seeker = std::find(src.begin(), src.end(), ' ');
+
+    if (seeker == src.end())
+    {
+        return {src, XString()};
+    }
+    else
+    {
+        XString rv1 = src.xislice_h(seeker);
+        ++seeker;
+
+        while (seeker != src.end() && *seeker == ' ')
+            ++seeker;
+
+        // Note: this very well could be empty
+        XString rv2 = src.xislice_t(seeker);
+        return {rv1, rv2};
+    }
+}
+
+/*==========================================
+ * NPC Spell
+ *------------------------------------------
+ */
+int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
+{
+    if (pc_isdead(caster))
+        return 0;
+    if (bool(caster->status.option & Opt0::HIDE))
+        return 0;
+    if (caster->cast_tick > gettick())
+        return 0;
+
+    auto pair = magic_tokenise(source_invocation);
+    // Spell Cast
+    NpcName spell_name = stringish<NpcName>(pair.first);
+    RString spell_params = pair.second;
+
+    NpcEvent event = spell_name2id(spell_name);
+
+    if (event)
+    {
+        PRINTF("Cast:  %s\n"_fmt, spell_name);
+        PRINTF("event:  %s\n"_fmt, event);
+        PRINTF("Params:  %s\n"_fmt, spell_params);
+        npc_event(caster, event, 0);
+        return 1;
+    }
+    return 0;
+}
+
 /*==========================================
  * イベントキューのイベント処理
  *------------------------------------------
diff --git a/src/map/npc.hpp b/src/map/npc.hpp
index 7a96bf9..be4686a 100644
--- a/src/map/npc.hpp
+++ b/src/map/npc.hpp
@@ -58,6 +58,7 @@ dumb_ptr<npc_data> npc_name2id(NpcName name);
 
 BlockId npc_get_new_npc_id(void);
 
+int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation);
 /**
  * Uninstalls and frees an NPC
  */
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 9f9652e..35e119b 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -572,7 +572,7 @@ void builtin_injure(ScriptState *st)
             dumb_ptr<mob_data> mob = target->is_mob();
             dumb_ptr<npc_data> nd = map_id_is_npc(st->oid);
             MAP_LOG_PC(caster_pc, "SPELLDMG MOB%d %d FOR %d BY %s"_fmt,
-                    mob->bl_id, mob->mob_class, damage_caused, nd->name);
+                    mob->bl_id, mob->mob_class, damage_caused, caster->is_player()->magic_attack);
         }
     }
     battle_damage(caster, target, damage_caused, mp_damage);
@@ -3340,6 +3340,22 @@ void builtin_casttime(ScriptState *st)
     sd->cast_tick = gettick() + tick;
 }
 
+/*==========================================
+  * register cmd
+  *------------------------------------------
+  */
+static
+void builtin_registercmd(ScriptState *st)
+{
+    dumb_ptr<npc_data> nd = map_id_is_npc(st->oid);
+    RString evoke = conv_str(st, &AARG(0));
+    ZString event_ = conv_str(st, &AARG(1));
+    NpcEvent event;
+    extract(event_, &event);
+
+    spells_by_name.put(evoke, event);
+}
+
 /*==========================================
   * getlook char info. getlook(arg)
   *------------------------------------------
@@ -3828,6 +3844,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(message, "Ps"_s, '\0'),
     BUILTIN(npctalk, "ss?"_s, '\0'),
     BUILTIN(casttime, "i"_s, '\0'),
+    BUILTIN(registercmd, "sE"_s, '\0'),
     BUILTIN(title, "s"_s, '\0'),
     BUILTIN(smsg, "e??"_s, '\0'),
     BUILTIN(remotecmd, "s?"_s, '\0'),
-- 
cgit v1.2.3-70-g09d2


From 506a41d6926405b2753894f0b40130b4077828b3 Mon Sep 17 00:00:00 2001
From: wushin <pasekei@gmail.com>
Date: Tue, 9 Jun 2015 01:06:22 -0500
Subject: Remove old Magic

---
 src/map/clif.cpp                   |   13 +-
 src/map/fwd.hpp                    |   18 -
 src/map/globals.cpp                |   19 +-
 src/map/globals.hpp                |   14 +-
 src/map/magic-expr-eval.hpp        |   54 --
 src/map/magic-expr.cpp             | 1874 ------------------------------------
 src/map/magic-expr.hpp             |  107 --
 src/map/magic-expr.py              |   38 -
 src/map/magic-interpreter-base.cpp |  553 -----------
 src/map/magic-interpreter-base.hpp |   84 --
 src/map/magic-interpreter.hpp      |  630 ------------
 src/map/magic-interpreter.py       |  215 -----
 src/map/magic-interpreter.t.hpp    |   85 --
 src/map/magic-stmt.cpp             | 1547 -----------------------------
 src/map/magic-stmt.hpp             |   93 --
 src/map/magic-stmt.py              |   37 -
 src/map/magic-v2.cpp               | 1295 -------------------------
 src/map/magic-v2.hpp               |   37 -
 src/map/magic.cpp                  |  130 ---
 src/map/magic.hpp                  |   48 -
 src/map/map.cpp                    |   20 +-
 src/map/map.hpp                    |   15 +-
 src/map/map.t.hpp                  |    1 -
 src/map/npc.cpp                    |   58 +-
 src/map/pc.cpp                     |    9 +-
 src/map/script-fun.cpp             |   38 +-
 src/map/skill.cpp                  |   13 -
 src/mmo/cxxstdio_enums.hpp         |    7 -
 src/mmo/skill.t.hpp                |    4 -
 29 files changed, 61 insertions(+), 6995 deletions(-)
 delete mode 100644 src/map/magic-expr-eval.hpp
 delete mode 100644 src/map/magic-expr.cpp
 delete mode 100644 src/map/magic-expr.hpp
 delete mode 100644 src/map/magic-expr.py
 delete mode 100644 src/map/magic-interpreter-base.cpp
 delete mode 100644 src/map/magic-interpreter-base.hpp
 delete mode 100644 src/map/magic-interpreter.hpp
 delete mode 100644 src/map/magic-interpreter.py
 delete mode 100644 src/map/magic-interpreter.t.hpp
 delete mode 100644 src/map/magic-stmt.cpp
 delete mode 100644 src/map/magic-stmt.hpp
 delete mode 100644 src/map/magic-stmt.py
 delete mode 100644 src/map/magic-v2.cpp
 delete mode 100644 src/map/magic-v2.hpp
 delete mode 100644 src/map/magic.cpp
 delete mode 100644 src/map/magic.hpp

(limited to 'src')

diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index a2a2a33..33de3e6 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -62,8 +62,6 @@
 #include "globals.hpp"
 #include "intif.hpp"
 #include "itemdb.hpp"
-#include "magic.hpp"
-#include "magic-stmt.hpp"
 #include "map.hpp"
 #include "map_conf.hpp"
 #include "npc.hpp"
@@ -2641,12 +2639,6 @@ void clif_getareachar(dumb_ptr<block_list> bl, dumb_ptr<map_session_data> sd)
         case BL::ITEM:
             clif_getareachar_item(sd, bl->is_item());
             break;
-        case BL::SPELL:
-            // spell objects are not visible
-            // (at least, I *think* that's what this code is for)
-            // in any case, this is not a behavior change, just silencing
-            // the below warning
-            break;
         default:
             if (battle_config.error_log)
                 PRINTF("get area char ??? %d\n"_fmt,
@@ -3792,7 +3784,6 @@ RecvResult clif_parse_GetCharNameRequest(Session *s, dumb_ptr<map_session_data>
             send_fpacket<0x0095, 30>(s, fixed_95);
         }
             break;
-        // case BL::SPELL
         default:
             if (battle_config.error_log)
                 PRINTF("clif_parse_GetCharNameRequest : bad type %d (%d)\n"_fmt,
@@ -4273,8 +4264,8 @@ RecvResult clif_parse_TakeItem(Session *s, dumb_ptr<map_session_data> sd)
         || abs(sd->bl_y - fitem->bl_y) >= 2)
         return rv;                 // too far away to pick up
 
-    if (sd->state.shroud_active && sd->state.shroud_disappears_on_pickup)
-        magic::magic_unshroud(sd);
+//    if (sd->state.shroud_active && sd->state.shroud_disappears_on_pickup)
+//        magic_unshroud(sd);
 
     pc_takeitem(sd, fitem);
 
diff --git a/src/map/fwd.hpp b/src/map/fwd.hpp
index 911d566..de65216 100644
--- a/src/map/fwd.hpp
+++ b/src/map/fwd.hpp
@@ -57,7 +57,6 @@ struct map_session_data;
 struct npc_data;
 struct mob_data;
 struct flooritem_data;
-//struct magic::invocation;
 struct map_local;
 class npc_data_script;
 class npc_data_shop;
@@ -71,22 +70,5 @@ struct ScriptState;
 struct str_data_t;
 class SIR;
 
-namespace magic
-{
-struct fun_t;
-struct op_t;
-struct expr_t;
-struct val_t;
-struct location_t;
-struct area_t;
-struct spell_t;
-struct invocation;
-struct teleport_anchor_t;
-struct env_t;
-struct magic_conf_t;
-struct component_t;
-struct effect_set_t;
-struct proc_t;
-} // namespace magic
 } // namespace map
 } // namespace tmwa
diff --git a/src/map/globals.cpp b/src/map/globals.cpp
index 4a54843..c96037e 100644
--- a/src/map/globals.cpp
+++ b/src/map/globals.cpp
@@ -27,7 +27,6 @@
 #include "battle_conf.hpp"
 #include "itemdb.hpp"
 #include "quest.hpp"
-#include "magic-interpreter.hpp"
 #include "map_conf.hpp"
 #include "mob.hpp"
 #include "npc-internal.hpp"
@@ -51,17 +50,6 @@ namespace tmwa
         std::map<MapName, RString> resnametable;
         Map<ItemNameId, item_data> item_db;
         Map<QuestId, quest_data> quest_db;
-        namespace magic
-        {
-            // Global magic conf
-            magic_conf_t magic_conf;
-            env_t magic_default_env = { &magic_conf, nullptr };
-            namespace magic_v2
-            {
-                std::map<RString, proc_t> procs;
-                std::map<RString, val_t> const_defm;
-            } // namespace magic_v2
-        } // namespace magic
 
         DMap<BlockId, dumb_ptr<block_list>> id_db;
         UPMap<MapName, map_abstract> maps_db;
@@ -85,7 +73,8 @@ namespace tmwa
         BlockId npc_id = START_NPC_NUM;
         Map<NpcEvent, struct event_data> ev_db;
         DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name;
-        DMap<RString, NpcEvent> spells_by_name;
+        DMap<RString, NpcName> spells_by_name;
+        DMap<RString, NpcEvent> spells_by_events;
         // used for clock-based event triggers
         // only tm_min, tm_hour, and tm_mday are used
         tm ev_tm_b =
@@ -142,9 +131,5 @@ namespace tmwa
         //      BuiltinFunction builtin_functions[];
         //  src/map/clif.cpp:
         //      func_table clif_parse_func_table[0x0220];
-        //  src/map/magic-expr.cpp:
-        //      std::map<ZString, fun_t> functions;
-        //  src/map/magic-stmt.cpp:
-        //      std::map<ZString, op_t> operations;
     } // namespace map
 } // namespace tmwa
diff --git a/src/map/globals.hpp b/src/map/globals.hpp
index 5a4ec82..84e4765 100644
--- a/src/map/globals.hpp
+++ b/src/map/globals.hpp
@@ -49,17 +49,6 @@ namespace tmwa
         extern std::map<MapName, RString> resnametable;
         extern Map<ItemNameId, item_data> item_db;
         extern Map<QuestId, quest_data> quest_db;
-        namespace magic
-        {
-            // Global magic conf
-            extern magic_conf_t magic_conf;
-            extern env_t magic_default_env;
-            namespace magic_v2
-            {
-                extern std::map<RString, proc_t> procs;
-                extern std::map<RString, val_t> const_defm;
-            } // namespace magic_v2
-        } // namespace magic
         extern DMap<BlockId, dumb_ptr<block_list>> id_db;
         extern UPMap<MapName, map_abstract> maps_db;
         extern DMap<CharName, dumb_ptr<map_session_data>> nick_db;
@@ -79,7 +68,8 @@ namespace tmwa
         extern BlockId npc_id;
         extern Map<NpcEvent, event_data> ev_db;
         extern DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name;
-        extern DMap<RString, NpcEvent> spells_by_name;
+        extern DMap<RString, NpcName> spells_by_name;
+        extern DMap<RString, NpcEvent> spells_by_events;
         extern tm ev_tm_b;
         extern Map<PartyId, PartyMost> party_db;
         extern std::map<AccountId, GmLevel> gm_accountm;
diff --git a/src/map/magic-expr-eval.hpp b/src/map/magic-expr-eval.hpp
deleted file mode 100644
index e8ed4aa..0000000
--- a/src/map/magic-expr-eval.hpp
+++ /dev/null
@@ -1,54 +0,0 @@
-#pragma once
-//    magic-expr-eval.hpp - Utilities for evaluating magic.
-//
-//    Copyright © 2004-2011 The Mana World Development Team
-//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include "fwd.hpp"
-
-#include "../strings/zstring.hpp"
-
-#include "magic-interpreter.t.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-// TODO soon kill this unlike I killed VAR
-#define ARGINT(x) args[x].get_if<ValInt>()->v_int
-#define ARGDIR(x) args[x].get_if<ValDir>()->v_dir
-#define ARGSTR(x) ZString(args[x].get_if<ValString>()->v_string)
-#define ARGENTITY(x) args[x].get_if<ValEntityPtr>()->v_entity
-#define ARGLOCATION(x) args[x].get_if<ValLocation>()->v_location
-#define ARGAREA(x) args[x].get_if<ValArea>()->v_area
-#define ARGSPELL(x) args[x].get_if<ValSpell>()->v_spell
-#define ARGINVOCATION(x) args[x].get_if<ValInvocationPtr>()->v_invocation
-
-#define ENTITY_TYPE(x) ARGENTITY(x)->bl_type
-
-#define ARGPC(x)  (ARGENTITY(x)->is_player())
-#define ARGNPC(x)  (ARGENTITY(x)->is_npc())
-#define ARGMOB(x)  (ARGENTITY(x)->is_mob())
-
-#define ARG_MAY_BE_AREA(x) (args[x].is<ValArea>() || args[x].is<ValArea>())
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic-expr.cpp b/src/map/magic-expr.cpp
deleted file mode 100644
index 197727e..0000000
--- a/src/map/magic-expr.cpp
+++ /dev/null
@@ -1,1874 +0,0 @@
-#include "magic-expr.hpp"
-//    magic-expr.cpp - Pure functions for the old magic backend.
-//
-//    Copyright © 2004-2011 The Mana World Development Team
-//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include <cassert>
-
-#include <algorithm>
-
-#include "../strings/mstring.hpp"
-#include "../strings/astring.hpp"
-#include "../strings/zstring.hpp"
-#include "../strings/vstring.hpp"
-#include "../strings/literal.hpp"
-
-#include "../generic/dumb_ptr.hpp"
-#include "../generic/random.hpp"
-
-#include "../io/cxxstdio.hpp"
-
-#include "../mmo/cxxstdio_enums.hpp"
-
-#include "battle.hpp"
-#include "itemdb.hpp"
-#include "magic-expr-eval.hpp"
-#include "magic-interpreter.hpp"
-#include "magic-interpreter-base.hpp"
-#include "npc.hpp"
-#include "pc.hpp"
-#include "script-call.hpp"
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-static
-void free_area(dumb_ptr<area_t> area)
-{
-    if (!area)
-        return;
-
-    MATCH_BEGIN (*area)
-    {
-        MATCH_CASE (const AreaUnion&, a)
-        {
-            free_area(a.a_union[0]);
-            free_area(a.a_union[1]);
-        }
-    }
-    MATCH_END ();
-
-    area.delete_();
-}
-
-static
-dumb_ptr<area_t> dup_area(dumb_ptr<area_t> area)
-{
-    MATCH_BEGIN (*area)
-    {
-        MATCH_CASE (const location_t&, loc)
-        {
-            return dumb_ptr<area_t>::make(loc);
-        }
-        MATCH_CASE (const AreaUnion&, a)
-        {
-            AreaUnion u;
-            u.a_union[0] = dup_area(a.a_union[0]);
-            u.a_union[1] = dup_area(a.a_union[1]);
-            return dumb_ptr<area_t>::make(u);
-        }
-        MATCH_CASE (const AreaRect&, rect)
-        {
-            return dumb_ptr<area_t>::make(rect);
-        }
-        MATCH_CASE (const AreaBar&, bar)
-        {
-            return dumb_ptr<area_t>::make(bar);
-        }
-    }
-    MATCH_END ();
-
-    abort();
-}
-
-void magic_copy_var(val_t *dest, const val_t *src)
-{
-    MATCH_BEGIN (*src)
-    {
-        MATCH_DEFAULT ()
-        {
-            abort();
-        }
-        MATCH_CASE (const ValUndef&, s)
-        {
-            *dest = s;
-        }
-        MATCH_CASE (const ValInt&, s)
-        {
-            *dest = s;
-        }
-        MATCH_CASE (const ValDir&, s)
-        {
-            *dest = s;
-        }
-        MATCH_CASE (const ValString&, s)
-        {
-            *dest = ValString{s.v_string};
-        }
-        MATCH_CASE (const ValEntityInt&, s)
-        {
-            *dest = s;
-        }
-        MATCH_CASE (const ValEntityPtr&, s)
-        {
-            *dest = s;
-        }
-        MATCH_CASE (const ValLocation&, s)
-        {
-            *dest = s;
-        }
-        MATCH_CASE (const ValArea&, s)
-        {
-            *dest = ValArea{dup_area(s.v_area)};
-        }
-        MATCH_CASE (const ValSpell&, s)
-        {
-            *dest = s;
-        }
-        MATCH_CASE (const ValInvocationInt&, s)
-        {
-            *dest = s;
-        }
-        MATCH_CASE (const ValInvocationPtr&, s)
-        {
-            *dest = s;
-        }
-        MATCH_CASE (const ValFail&, s)
-        {
-            *dest = s;
-        }
-        MATCH_CASE (const ValNegative1&, s)
-        {
-            *dest = s;
-        }
-    }
-    MATCH_END ();
-}
-
-void magic_clear_var(val_t *v)
-{
-    MATCH_BEGIN (*v)
-    {
-        MATCH_CASE (ValString&, s)
-        {
-            (void)s;
-        }
-        MATCH_CASE (const ValArea&, a)
-        {
-            free_area(a.v_area);
-        }
-    }
-    MATCH_END ();
-}
-
-static
-AString show_entity(dumb_ptr<block_list> entity)
-{
-    switch (entity->bl_type)
-    {
-        case BL::PC:
-            return entity->is_player()->status_key.name.to__actual();
-        case BL::NPC:
-            return entity->is_npc()->name;
-        case BL::MOB:
-            return entity->is_mob()->name;
-        case BL::ITEM:
-            assert (0 && "There is no way this code did what it was supposed to do!"_s);
-            /* Sorry about this one... */
-            // WTF? item_data is a Item, not a struct item_data
-            // return ((struct item_data *) (&entity->is_item()->item_data))->name;
-            abort();
-        case BL::SPELL:
-            return "%invocation(ERROR:this-should-not-be-an-entity)"_s;
-        default:
-            return "%unknown-entity"_s;
-    }
-}
-
-static
-void stringify(val_t *v)
-{
-    static earray<LString, DIR, DIR::COUNT> dirs //=
-    {{
-        "south"_s, "south-west"_s,
-        "west"_s, "north-west"_s,
-        "north"_s, "north-east"_s,
-        "east"_s, "south-east"_s,
-    }};
-    AString buf;
-
-    MATCH_BEGIN (*v)
-    {
-        MATCH_DEFAULT ()
-        {
-            abort();
-        }
-        MATCH_CASE (const ValUndef&, x)
-        {
-            (void)x;
-            buf = "UNDEF"_s;
-        }
-        MATCH_CASE (const ValInt&, x)
-        {
-            buf = STRPRINTF("%i"_fmt, x.v_int);
-        }
-        MATCH_CASE (const ValString&, x)
-        {
-            (void)x;
-            return;
-        }
-        MATCH_CASE (const ValDir&, x)
-        {
-            buf = dirs[x.v_dir];
-        }
-        MATCH_CASE (const ValEntityPtr&, x)
-        {
-            buf = show_entity(x.v_entity);
-        }
-        MATCH_CASE (const ValLocation&, x)
-        {
-            buf = STRPRINTF("<\"%s\", %d, %d>"_fmt,
-                    x.v_location.m->name_,
-                    x.v_location.x,
-                    x.v_location.y);
-        }
-        MATCH_CASE (const ValArea&, x)
-        {
-            buf = "%area"_s;
-            free_area(x.v_area);
-        }
-        MATCH_CASE (const ValSpell&, x)
-        {
-            buf = x.v_spell->name;
-        }
-        MATCH_CASE (const ValInvocationInt&, x)
-        {
-            dumb_ptr<invocation> invocation_ =
-                map_id2bl(x.v_iid)->is_spell();
-            buf = invocation_->spell->name;
-        }
-        MATCH_CASE (const ValInvocationPtr&, x)
-        {
-            dumb_ptr<invocation> invocation_ =
-                x.v_invocation;
-            buf = invocation_->spell->name;
-        }
-    }
-    MATCH_END ();
-
-    *v = ValString{buf};
-}
-
-static
-void intify(val_t *v)
-{
-    if (v->is<ValInt>())
-        return;
-
-    magic_clear_var(v);
-    *v = ValInt{1};
-}
-
-static
-dumb_ptr<area_t> area_union(dumb_ptr<area_t> area, dumb_ptr<area_t> other_area)
-{
-    AreaUnion a;
-    a.a_union[0] = area;
-    a.a_union[1] = other_area;
-    return dumb_ptr<area_t>::make(a);
-}
-
-/**
- * Turns location into area, leaves other types untouched
- */
-static
-void make_area(val_t *v)
-{
-    if (ValLocation *l = v->get_if<ValLocation>())
-    {
-        auto a = dumb_ptr<area_t>::make(l->v_location);
-        *v = ValArea{a};
-    }
-}
-
-static
-void make_location(val_t *v)
-{
-    if (ValArea *a = v->get_if<ValArea>())
-    {
-        MATCH_BEGIN (*a->v_area)
-        {
-            MATCH_CASE (const location_t&, location)
-            {
-                free_area(a->v_area);
-                *v = ValLocation{location};
-            }
-        }
-        MATCH_END ();
-    }
-}
-
-static
-void make_spell(val_t *v)
-{
-    assert(!v->is<ValInvocationInt>());
-    if (ValInvocationPtr *p = v->get_if<ValInvocationPtr>())
-    {
-        dumb_ptr<invocation> invoc = p->v_invocation;
-        if (!invoc)
-        {
-            *v = ValFail{};
-        }
-        else
-        {
-            *v = ValSpell{invoc->spell};
-        }
-    }
-}
-
-static
-int fun_add(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (args[0].is<ValInt>() && args[1].is<ValInt>())
-    {
-        /* Integer addition */
-        *result = ValInt{ARGINT(0) + ARGINT(1)};
-    }
-    else if (ARG_MAY_BE_AREA(0) && ARG_MAY_BE_AREA(1))
-    {
-        /* Area union */
-        make_area(&args[0]);
-        make_area(&args[1]);
-        *result = ValArea{area_union(ARGAREA(0), ARGAREA(1))};
-        ARGAREA(0) = nullptr; args[0] = ValUndef{};
-        ARGAREA(1) = nullptr; args[1] = ValUndef{};
-    }
-    else
-    {
-        /* Anything else -> string concatenation */
-        stringify(&args[0]);
-        stringify(&args[1]);
-        /* Yes, we could speed this up. */
-        // ugh
-        MString m;
-        m += ARGSTR(0);
-        m += ARGSTR(1);
-        *result = ValString{AString(m)};
-    }
-    return 0;
-}
-
-static
-int fun_sub(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{ARGINT(0) - ARGINT(1)};
-    return 0;
-}
-
-static
-int fun_mul(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{ARGINT(0) * ARGINT(1)};
-    return 0;
-}
-
-static
-int fun_div(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (!ARGINT(1))
-        return 1;               /* division by zero */
-    *result = ValInt{ARGINT(0) / ARGINT(1)};
-    return 0;
-}
-
-static
-int fun_mod(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (!ARGINT(1))
-        return 1;               /* division by zero */
-    *result = ValInt{ARGINT(0) % ARGINT(1)};
-    return 0;
-}
-
-static
-int fun_or(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{ARGINT(0) || ARGINT(1)};
-    return 0;
-}
-
-static
-int fun_and(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{ARGINT(0) && ARGINT(1)};
-    return 0;
-}
-
-static
-int fun_not(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{!ARGINT(0)};
-    return 0;
-}
-
-static
-int fun_neg(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{~ARGINT(0)};
-    return 0;
-}
-
-static
-int fun_gte(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (args[0].is<ValString>() || args[1].is<ValString>())
-    {
-        stringify(&args[0]);
-        stringify(&args[1]);
-        *result = ValInt{ARGSTR(0) >= ARGSTR(1)};
-    }
-    else
-    {
-        intify(&args[0]);
-        intify(&args[1]);
-        *result = ValInt{ARGINT(0) >= ARGINT(1)};
-    }
-    return 0;
-}
-
-static
-int fun_lt(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args)
-{
-    fun_gte(env, result, args);
-    result->get_if<ValInt>()->v_int ^= 1;
-    return 0;
-}
-
-static
-int fun_gt(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (args[0].is<ValString>() || args[1].is<ValString>())
-    {
-        stringify(&args[0]);
-        stringify(&args[1]);
-        *result = ValInt{ARGSTR(0) > ARGSTR(1)};
-    }
-    else
-    {
-        intify(&args[0]);
-        intify(&args[1]);
-        *result = ValInt{ARGINT(0) > ARGINT(1)};
-    }
-    return 0;
-}
-
-static
-int fun_lte(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args)
-{
-    fun_gt(env, result, args);
-    result->get_if<ValInt>()->v_int ^= 1;
-    return 0;
-}
-
-static
-int fun_eq(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (args[0].is<ValString>() || args[1].is<ValString>())
-    {
-        stringify(&args[0]);
-        stringify(&args[1]);
-        *result = ValInt{ARGSTR(0) == ARGSTR(1)};
-    }
-    else if (args[0].is<ValDir>() && args[1].is<ValDir>())
-        *result = ValInt{ARGDIR(0) == ARGDIR(1)};
-    else if (args[0].is<ValEntityPtr>() && args[1].is<ValEntityPtr>())
-        *result = ValInt{ARGENTITY(0) == ARGENTITY(1)};
-    else if (args[0].is<ValLocation>() && args[1].is<ValLocation>())
-        *result = ValInt{(ARGLOCATION(0).x == ARGLOCATION(1).x
-                     && ARGLOCATION(0).y == ARGLOCATION(1).y
-                     && ARGLOCATION(0).m == ARGLOCATION(1).m)};
-    else if (args[0].is<ValArea>() && args[1].is<ValArea>())
-        *result = ValInt{ARGAREA(0) == ARGAREA(1)}; /* Probably not that great an idea... */
-    else if (args[0].is<ValSpell>() && args[1].is<ValSpell>())
-        *result = ValInt{ARGSPELL(0) == ARGSPELL(1)};
-    else if (args[0].is<ValInvocationPtr>() && args[1].is<ValInvocationPtr>())
-        *result = ValInt{ARGINVOCATION(0) == ARGINVOCATION(1)};
-    else
-    {
-        intify(&args[0]);
-        intify(&args[1]);
-        *result = ValInt{ARGINT(0) == ARGINT(1)};
-    }
-    return 0;
-}
-
-static
-int fun_ne(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args)
-{
-    fun_eq(env, result, args);
-    result->get_if<ValInt>()->v_int ^= 1;
-    return 0;
-}
-
-static
-int fun_bitand(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{ARGINT(0) & ARGINT(1)};
-    return 0;
-}
-
-static
-int fun_bitor(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{ARGINT(0) | ARGINT(1)};
-    return 0;
-}
-
-static
-int fun_bitxor(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{ARGINT(0) ^ ARGINT(1)};
-    return 0;
-}
-
-static
-int fun_bitshl(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{ARGINT(0) << ARGINT(1)};
-    return 0;
-}
-
-static
-int fun_bitshr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{ARGINT(0) >> ARGINT(1)};
-    return 0;
-}
-
-static
-int fun_max(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{std::max(ARGINT(0), ARGINT(1))};
-    return 0;
-}
-
-static
-int fun_min(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{std::min(ARGINT(0), ARGINT(1))};
-    return 0;
-}
-
-static
-int fun_if_then_else(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (ARGINT(0))
-        magic_copy_var(result, &args[1]);
-    else
-        magic_copy_var(result, &args[2]);
-    return 0;
-}
-
-Borrowed<map_local> magic_area_rect(int *x, int *y, int *width, int *height,
-        area_t& area_)
-{
-    MATCH_BEGIN (area_)
-    {
-        MATCH_CASE (const AreaUnion&, a)
-        {
-            (void)a;
-            abort();
-        }
-        MATCH_CASE (const location_t&, a_loc)
-        {
-            P<map_local> m = a_loc.m;
-            *x = a_loc.x;
-            *y = a_loc.y;
-            *width = 1;
-            *height = 1;
-            return m;
-        }
-        MATCH_CASE (const AreaRect&, a_rect)
-        {
-            P<map_local> m = a_rect.loc.m;
-            *x = a_rect.loc.x;
-            *y = a_rect.loc.y;
-            *width = a_rect.width;
-            *height = a_rect.height;
-            return m;
-        }
-        MATCH_CASE (const AreaBar&, a_bar)
-        {
-            int tx = a_bar.loc.x;
-            int ty = a_bar.loc.y;
-            int twidth = a_bar.width;
-            int tdepth = a_bar.width;
-            P<map_local> m = a_bar.loc.m;
-
-            switch (a_bar.dir)
-            {
-                case DIR::S:
-                    *x = tx - twidth;
-                    *y = ty;
-                    *width = twidth * 2 + 1;
-                    *height = tdepth;
-                    break;
-
-                case DIR::W:
-                    *x = tx - tdepth;
-                    *y = ty - twidth;
-                    *width = tdepth;
-                    *height = twidth * 2 + 1;
-                    break;
-
-                case DIR::N:
-                    *x = tx - twidth;
-                    *y = ty - tdepth;
-                    *width = twidth * 2 + 1;
-                    *height = tdepth;
-                    break;
-
-                case DIR::E:
-                    *x = tx;
-                    *y = ty - twidth;
-                    *width = tdepth;
-                    *height = twidth * 2 + 1;
-                    break;
-
-                default:
-                    FPRINTF(stderr,
-                            "Error: Trying to compute area of NE/SE/NW/SW-facing bar"_fmt);
-                    *x = tx;
-                    *y = ty;
-                    *width = *height = 1;
-            }
-            return m;
-        }
-    }
-    MATCH_END ();
-    abort();
-}
-
-int magic_location_in_area(Borrowed<map_local> m, int x, int y, dumb_ptr<area_t> area)
-{
-    MATCH_BEGIN (*area)
-    {
-        MATCH_CASE (const AreaUnion&, a)
-        {
-            return magic_location_in_area(m, x, y, a.a_union[0])
-                || magic_location_in_area(m, x, y, a.a_union[1]);
-        }
-        MATCH_CASE (const location_t&, a_loc)
-        {
-            (void)a_loc;
-            // TODO this can be simplified
-            int ax, ay, awidth, aheight;
-            P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area);
-            return (am == m
-                    && (x >= ax) && (y >= ay)
-                    && (x < ax + awidth) && (y < ay + aheight));
-        }
-        MATCH_CASE (const AreaRect&, a_rect)
-        {
-            (void)a_rect;
-            // TODO this is too complicated
-            int ax, ay, awidth, aheight;
-            P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area);
-            return (am == m
-                    && (x >= ax) && (y >= ay)
-                    && (x < ax + awidth) && (y < ay + aheight));
-        }
-        MATCH_CASE (const AreaBar&, a_bar)
-        {
-            (void)a_bar;
-            // TODO this is wrong
-            int ax, ay, awidth, aheight;
-            P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area);
-            return (am == m
-                    && (x >= ax) && (y >= ay)
-                    && (x < ax + awidth) && (y < ay + aheight));
-        }
-    }
-    MATCH_END ();
-    abort();
-}
-
-static
-int fun_is_in(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{magic_location_in_area(ARGLOCATION(0).m,
-                                        ARGLOCATION(0).x,
-                                        ARGLOCATION(0).y, ARGAREA(1))};
-    return 0;
-}
-
-static
-int fun_skill(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (ENTITY_TYPE(0) != BL::PC
-        // don't convert to enum until after the range check
-        // (actually it would be okay, I checked)
-        || ARGINT(1) < 0
-        || ARGINT(1) >= static_cast<uint16_t>(MAX_SKILL))
-    {
-        *result = ValInt{0};
-    }
-    else
-    {
-        SkillID id = static_cast<SkillID>(ARGINT(1));
-        *result = ValInt{ARGPC(0)->status.skill[id].lv};
-    }
-    return 0;
-}
-
-static
-int fun_his_shroud(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{(ENTITY_TYPE(0) == BL::PC && ARGPC(0)->state.shroud_active)};
-    return 0;
-}
-
-#define BATTLE_GETTER(name)                                             \
-static                                                                  \
-int fun_get_##name(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)   \
-{                                                                       \
-    *result = ValInt{battle_get_##name(ARGENTITY(0))};                  \
-    return 0;                                                           \
-}
-
-BATTLE_GETTER(str)
-BATTLE_GETTER(agi)
-BATTLE_GETTER(vit)
-BATTLE_GETTER(dex)
-BATTLE_GETTER(luk)
-BATTLE_GETTER(int)
-BATTLE_GETTER(lv)
-BATTLE_GETTER(hp)
-BATTLE_GETTER(mdef)
-BATTLE_GETTER(def)
-BATTLE_GETTER(max_hp)
-static
-int fun_get_dir(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValDir{battle_get_dir(ARGENTITY(0))};
-    return 0;
-}
-
-#define MMO_GETTER(name)                                                \
-static                                                                  \
-int fun_get_##name(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)   \
-{                                                                       \
-    if (ENTITY_TYPE(0) == BL::PC)                                       \
-        *result = ValInt{ARGPC(0)->status.name};                        \
-    else                                                                \
-        *result = ValInt{0};                                            \
-    return 0;                                                           \
-}
-
-MMO_GETTER(sp)
-MMO_GETTER(max_sp)
-
-static
-int fun_name_of(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (args[0].is<ValEntityPtr>())
-    {
-        *result = ValString{show_entity(ARGENTITY(0))};
-        return 0;
-    }
-    else if (args[0].is<ValSpell>())
-    {
-        *result = ValString{ARGSPELL(0)->name};
-        return 0;
-    }
-    else if (args[0].is<ValInvocationPtr>())
-    {
-        *result = ValString{ARGINVOCATION(0)->spell->name};
-        return 0;
-    }
-    return 1;
-}
-
-/* [Freeyorp] I'm putting this one in as name_of seems to have issues with summoned or spawned mobs. */
-static
-int fun_mob_id(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (ENTITY_TYPE(0) != BL::MOB)
-        return 1;
-    *result = ValInt{unwrap<Species>(ARGMOB(0)->mob_class)};
-    return 0;
-}
-
-inline
-void COPY_LOCATION(block_list& dest, location_t& src)
-{
-    dest.bl_x = src.x;
-    dest.bl_y = src.y;
-    dest.bl_m = src.m;
-}
-
-inline
-void COPY_LOCATION(location_t& dest, block_list& src)
-{
-    dest.x = src.bl_x;
-    dest.y = src.bl_y;
-    dest.m = src.bl_m;
-}
-
-static
-int fun_location(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    location_t loc;
-    COPY_LOCATION(loc, *(ARGENTITY(0)));
-    *result = ValLocation{loc};
-    return 0;
-}
-
-static
-int fun_random(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    int delta = ARGINT(0);
-    if (delta < 0)
-        delta = -delta;
-    if (delta == 0)
-    {
-        *result = ValInt{0};
-        return 0;
-    }
-    *result = ValInt{random_::to(delta)};
-
-    if (ARGINT(0) < 0)
-        result->get_if<ValInt>()->v_int *= -1;
-    return 0;
-}
-
-static
-int fun_random_dir(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (ARGINT(0))
-        *result = ValDir{random_::choice({DIR::S, DIR::SW, DIR::W, DIR::NW, DIR::N, DIR::NE, DIR::E, DIR::SE})};
-    else
-        *result = ValDir{random_::choice({DIR::S, DIR::W, DIR::N, DIR::E})};
-    return 0;
-}
-
-static
-int fun_hash_entity(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{static_cast<int32_t>(unwrap<BlockId>(ARGENTITY(0)->bl_id))};
-    return 0;
-}
-
-// ret -1: not a string, ret 1: no such item, ret 0: OK
-int magic_find_item(Slice<val_t> args, int index, Item *item_, int *stackable)
-{
-    Option<P<struct item_data>> item_data_ = None;
-    int must_add_sequentially;
-
-    if (args[index].is<ValInt>())
-        item_data_ = itemdb_exists(wrap<ItemNameId>(static_cast<uint16_t>(ARGINT(index))));
-    else if (args[index].is<ValString>())
-        item_data_ = itemdb_searchname(ARGSTR(index));
-    else
-        return -1;
-
-    P<struct item_data> item_data = TRY_UNWRAP(item_data_, return 1);
-
-    // Very elegant.
-    must_add_sequentially = (
-            item_data->type == ItemType::WEAPON
-            || item_data->type == ItemType::ARMOR
-            || item_data->type == ItemType::_7
-            || item_data->type == ItemType::_8);
-
-    if (stackable)
-        *stackable = !must_add_sequentially;
-
-    *item_ = Item();
-    item_->nameid = item_data->nameid;
-
-    return 0;
-}
-
-static
-int fun_count_item(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    dumb_ptr<map_session_data> chr = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr;
-    int stackable;
-    Item item;
-
-    GET_ARG_ITEM(1, item, stackable);
-
-    if (!chr)
-        return 1;
-
-    *result = ValInt{pc_count_all_items(chr, item.nameid)};
-    return 0;
-}
-
-static
-int fun_is_equipped(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    dumb_ptr<map_session_data> chr = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr;
-    int stackable;
-    Item item;
-    bool retval = false;
-
-    GET_ARG_ITEM(1, item, stackable);
-
-    if (!chr)
-        return 1;
-
-    for (EQUIP i : EQUIPs)
-    {
-        IOff0 idx = chr->equip_index_maybe[i];
-        if (idx.ok() && chr->status.inventory[idx].nameid == item.nameid)
-        {
-            retval = true;
-            break;
-        }
-    }
-
-    *result = ValInt{retval};
-    return 0;
-}
-
-static
-int fun_is_married(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{(ENTITY_TYPE(0) == BL::PC && ARGPC(0)->status.partner_id)};
-    return 0;
-}
-
-static
-int fun_is_dead(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{(ENTITY_TYPE(0) == BL::PC && pc_isdead(ARGPC(0)))};
-    return 0;
-}
-
-static
-int fun_is_pc(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{(ENTITY_TYPE(0) == BL::PC)};
-    return 0;
-}
-
-static
-int fun_partner(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (ENTITY_TYPE(0) == BL::PC && ARGPC(0)->status.partner_id)
-    {
-        *result =
-            ValEntityPtr{map_nick2sd(map_charid2nick(ARGPC(0)->status.partner_id))};
-        return 0;
-    }
-    else
-        return 1;
-}
-
-static
-int fun_awayfrom(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    location_t *loc = &ARGLOCATION(0);
-    int dx = dirx[ARGDIR(1)];
-    int dy = diry[ARGDIR(1)];
-    int distance = ARGINT(2);
-    while (distance--
-        && !bool(read_gatp(loc->m, loc->x + dx, loc->y + dy)
-            & MapCell::UNWALKABLE))
-    {
-        loc->x += dx;
-        loc->y += dy;
-    }
-
-    *result = ValLocation{*loc};
-    return 0;
-}
-
-static
-int fun_failed(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{args[0].is<ValFail>()};
-    return 0;
-}
-
-static
-int fun_npc(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    NpcName name = stringish<NpcName>(ARGSTR(0));
-    dumb_ptr<npc_data> npc = npc_name2id(name);
-    *result = ValEntityPtr{npc};
-    return npc == nullptr;
-}
-
-static
-int fun_pc(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    CharName name = stringish<CharName>(ARGSTR(0));
-    dumb_ptr<map_session_data> chr = map_nick2sd(name);
-    *result = ValEntityPtr{chr};
-    return chr == nullptr;
-}
-
-static
-int fun_distance(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (ARGLOCATION(0).m != ARGLOCATION(1).m)
-        *result = ValInt{0x7fffffff};
-    else
-        *result = ValInt{std::max(abs(ARGLOCATION(0).x - ARGLOCATION(1).x),
-                         abs(ARGLOCATION(0).y - ARGLOCATION(1).y))};
-    return 0;
-}
-
-static
-int fun_rdistance(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (ARGLOCATION(0).m != ARGLOCATION(1).m)
-        *result = ValInt{0x7fffffff};
-    else
-    {
-        int dx = ARGLOCATION(0).x - ARGLOCATION(1).x;
-        int dy = ARGLOCATION(0).y - ARGLOCATION(1).y;
-        *result = ValInt{static_cast<int>(sqrt((dx * dx) + (dy * dy)))};
-    }
-    return 0;
-}
-
-static
-int fun_anchor(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args)
-{
-    dumb_ptr<teleport_anchor_t> anchor = magic_find_anchor(ARGSTR(0));
-
-    if (!anchor)
-        return 1;
-
-    magic_eval(env, result, anchor->location);
-
-    make_area(result);
-    if (!result->is<ValArea>())
-    {
-        magic_clear_var(result);
-        return 1;
-    }
-
-    return 0;
-}
-
-static
-int fun_line_of_sight(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    block_list e1, e2;
-
-    COPY_LOCATION(e1, ARGLOCATION(0));
-    COPY_LOCATION(e2, ARGLOCATION(1));
-
-    *result = ValInt{battle_check_range(dumb_ptr<block_list>(&e1), dumb_ptr<block_list>(&e2), 0)};
-
-    return 0;
-}
-
-void magic_random_location(location_t *dest, dumb_ptr<area_t> area)
-{
-    MATCH_BEGIN (*area)
-    {
-        MATCH_CASE (const AreaUnion&, a)
-        {
-            if (random_::chance({a.a_union[0]->size, area->size}))
-                magic_random_location(dest, a.a_union[0]);
-            else
-                magic_random_location(dest, a.a_union[1]);
-        }
-        MATCH_CASE (const location_t&, a_loc)
-        {
-            (void)a_loc;
-            // TODO this can be simplified
-            int x, y, w, h;
-            P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area);
-
-            if (w <= 1)
-                w = 1;
-
-            if (h <= 1)
-                h = 1;
-
-            // This is not exactly the same as the old logic,
-            // but it's better.
-            auto pair = map_randfreecell(m, x, y, w, h);
-
-            dest->m = m;
-            dest->x = pair.first;
-            dest->y = pair.second;
-        }
-        MATCH_CASE (const AreaRect&, a_rect)
-        {
-            (void)a_rect;
-            // TODO this can be simplified
-            int x, y, w, h;
-            P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area);
-
-            if (w <= 1)
-                w = 1;
-
-            if (h <= 1)
-                h = 1;
-
-            // This is not exactly the same as the old logic,
-            // but it's better.
-            auto pair = map_randfreecell(m, x, y, w, h);
-
-            dest->m = m;
-            dest->x = pair.first;
-            dest->y = pair.second;
-        }
-        MATCH_CASE (const AreaBar&, a_bar)
-        {
-            (void)a_bar;
-            // TODO this is wrong
-            int x, y, w, h;
-            P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area);
-
-            if (w <= 1)
-                w = 1;
-
-            if (h <= 1)
-                h = 1;
-
-            // This is not exactly the same as the old logic,
-            // but it's better.
-            auto pair = map_randfreecell(m, x, y, w, h);
-
-            dest->m = m;
-            dest->x = pair.first;
-            dest->y = pair.second;
-        }
-    }
-    MATCH_END ();
-}
-
-static
-int fun_pick_location(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    location_t loc;
-    magic_random_location(&loc, ARGAREA(0));
-    *result = ValLocation{loc};
-    return 0;
-}
-
-static
-int fun_read_script_int(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    dumb_ptr<block_list> subject_p = ARGENTITY(0);
-    VarName var_name = stringish<VarName>(ARGSTR(1));
-    int array_index = 0;
-
-    if (subject_p->bl_type != BL::PC)
-        return 1;
-
-    *result = ValInt{get_script_var_i(subject_p->is_player(), var_name, array_index)};
-    return 0;
-}
-
-static
-int fun_read_script_str(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    dumb_ptr<block_list> subject_p = ARGENTITY(0);
-    VarName var_name = stringish<VarName>(ARGSTR(1));
-    int array_index = 0;
-
-    if (subject_p->bl_type != BL::PC)
-        return 1;
-
-    *result = ValString{get_script_var_s(subject_p->is_player(), var_name, array_index)};
-    return 0;
-}
-
-static
-int fun_rbox(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    location_t loc = ARGLOCATION(0);
-    int radius = ARGINT(1);
-
-    AreaRect a_rect;
-    a_rect.loc.m = loc.m;
-    a_rect.loc.x = loc.x - radius;
-    a_rect.loc.y = loc.y - radius;
-    a_rect.width = radius * 2 + 1;
-    a_rect.height = radius * 2 + 1;
-    *result = ValArea{dumb_ptr<area_t>::make(a_rect)};
-
-    return 0;
-}
-
-static
-int fun_running_status_update(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    if (ENTITY_TYPE(0) != BL::PC && ENTITY_TYPE(0) != BL::MOB)
-        return 1;
-
-    StatusChange sc = static_cast<StatusChange>(ARGINT(1));
-    *result = ValInt{bool(battle_get_sc_data(ARGENTITY(0))[sc].timer)};
-    return 0;
-}
-
-static
-int fun_status_option(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{(bool((ARGPC(0))->status.option & static_cast<Opt0>(ARGINT(1))))};
-    return 0;
-}
-
-static
-int fun_element(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{static_cast<int>(battle_get_element(ARGENTITY(0)).element)};
-    return 0;
-}
-
-static
-int fun_element_level(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{battle_get_element(ARGENTITY(0)).level};
-    return 0;
-}
-
-static
-int fun_is_exterior(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-#warning "Evil assumptions!"
-    *result = ValInt{ARGLOCATION(0).m->name_[4] == '1'};
-    return 0;
-}
-
-static
-int fun_contains_string(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{nullptr != strstr(ARGSTR(0).c_str(), ARGSTR(1).c_str())};
-    return 0;
-}
-
-static
-int fun_strstr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    const char *offset = strstr(ARGSTR(0).c_str(), ARGSTR(1).c_str());
-    *result = ValInt{static_cast<int32_t>(offset - ARGSTR(0).c_str())};
-    return offset == nullptr;
-}
-
-static
-int fun_strlen(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{static_cast<int32_t>(strlen(ARGSTR(0).c_str()))};
-    return 0;
-}
-
-static
-int fun_substr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    RString src = ARGSTR(0);
-    int offset = ARGINT(1);
-    int len = ARGINT(2);
-
-    if (len < 0)
-        len = 0;
-    if (offset < 0)
-        offset = 0;
-
-    if (offset > src.size())
-        offset = src.size();
-
-    if (offset + len > src.size())
-        len = src.size() - offset;
-
-    auto begin = src.begin() + offset;
-    auto end = begin + len;
-    *result = ValString{RString(begin, end)};
-
-    return 0;
-}
-
-static
-int fun_sqrt(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    *result = ValInt{static_cast<int>(sqrt(ARGINT(0)))};
-    return 0;
-}
-
-static
-int fun_map_level(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-#warning "Evil assumptions!"
-    *result = ValInt{ARGLOCATION(0).m->name_[4] - '0'};
-    return 0;
-}
-
-static
-int fun_map_nr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-#warning "Evil assumptions!"
-    MapName mapname = ARGLOCATION(0).m->name_;
-
-    *result = ValInt{((mapname[0] - '0') * 100)
-        + ((mapname[1] - '0') * 10) + ((mapname[2] - '0'))};
-    return 0;
-}
-
-static
-int fun_dir_towards(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    int dx;
-    int dy;
-
-    if (ARGLOCATION(0).m != ARGLOCATION(1).m)
-        return 1;
-
-    dx = ARGLOCATION(1).x - ARGLOCATION(0).x;
-    dy = ARGLOCATION(1).y - ARGLOCATION(0).y;
-
-    if (ARGINT(2))
-    {
-        /* 8-direction mode */
-        if (abs(dx) > abs(dy) * 2)
-        {                       /* east or west */
-            if (dx < 0)
-                *result = ValDir{DIR::W};
-            else
-                *result = ValDir{DIR::E};
-        }
-        else if (abs(dy) > abs(dx) * 2)
-        {                       /* north or south */
-            if (dy > 0)
-                *result = ValDir{DIR::S};
-            else
-                *result = ValDir{DIR::N};
-        }
-        else if (dx < 0)
-        {                       /* north-west or south-west */
-            if (dy < 0)
-                *result = ValDir{DIR::NW};
-            else
-                *result = ValDir{DIR::SW};
-        }
-        else
-        {                       /* north-east or south-east */
-            if (dy < 0)
-                *result = ValDir{DIR::NE};
-            else
-                *result = ValDir{DIR::SE};
-        }
-    }
-    else
-    {
-        /* 4-direction mode */
-        if (abs(dx) > abs(dy))
-        {                       /* east or west */
-            if (dx < 0)
-                *result = ValDir{DIR::W};
-            else
-                *result = ValDir{DIR::E};
-        }
-        else
-        {                       /* north or south */
-            if (dy > 0)
-                *result = ValDir{DIR::S};
-            else
-                *result = ValDir{DIR::N};
-        }
-    }
-
-    return 0;
-}
-
-static
-int fun_extract_healer_xp(dumb_ptr<env_t>, val_t *result, Slice<val_t> args)
-{
-    dumb_ptr<map_session_data> sd = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr;
-
-    if (!sd)
-        *result = ValInt{0};
-    else
-        *result = ValInt{pc_extract_healer_exp(sd, ARGINT(1))};
-    return 0;
-}
-
-#define MAGIC_FUNCTION(name, args, ret, impl) {name, {name, args, ret, impl}}
-#define MAGIC_FUNCTION1(name, args, ret) MAGIC_FUNCTION(#name##_s, args, ret, fun_##name)
-static // should be LString, but no heterogenous lookup yet
-std::map<ZString, fun_t> functions =
-{
-    MAGIC_FUNCTION("+"_s, ".."_s, '.', fun_add),
-    MAGIC_FUNCTION("-"_s, "ii"_s, 'i', fun_sub),
-    MAGIC_FUNCTION("*"_s, "ii"_s, 'i', fun_mul),
-    MAGIC_FUNCTION("/"_s, "ii"_s, 'i', fun_div),
-    MAGIC_FUNCTION("%"_s, "ii"_s, 'i', fun_mod),
-    MAGIC_FUNCTION("||"_s, "ii"_s, 'i', fun_or),
-    MAGIC_FUNCTION("&&"_s, "ii"_s, 'i', fun_and),
-    MAGIC_FUNCTION("<"_s, ".."_s, 'i', fun_lt),
-    MAGIC_FUNCTION(">"_s, ".."_s, 'i', fun_gt),
-    MAGIC_FUNCTION("<="_s, ".."_s, 'i', fun_lte),
-    MAGIC_FUNCTION(">="_s, ".."_s, 'i', fun_gte),
-    MAGIC_FUNCTION("=="_s, ".."_s, 'i', fun_eq),
-    MAGIC_FUNCTION("!="_s, ".."_s, 'i', fun_ne),
-    MAGIC_FUNCTION("|"_s, ".."_s, 'i', fun_bitor),
-    MAGIC_FUNCTION("&"_s, "ii"_s, 'i', fun_bitand),
-    MAGIC_FUNCTION("^"_s, "ii"_s, 'i', fun_bitxor),
-    MAGIC_FUNCTION("<<"_s, "ii"_s, 'i', fun_bitshl),
-    MAGIC_FUNCTION(">>"_s, "ii"_s, 'i', fun_bitshr),
-    MAGIC_FUNCTION1(not, "i"_s, 'i'),
-    MAGIC_FUNCTION1(neg, "i"_s, 'i'),
-    MAGIC_FUNCTION1(max, "ii"_s, 'i'),
-    MAGIC_FUNCTION1(min, "ii"_s, 'i'),
-    MAGIC_FUNCTION1(is_in, "la"_s, 'i'),
-    MAGIC_FUNCTION1(if_then_else, "i__"_s, '_'),
-    MAGIC_FUNCTION1(skill, "ei"_s, 'i'),
-    MAGIC_FUNCTION("str"_s, "e"_s, 'i', fun_get_str),
-    MAGIC_FUNCTION("agi"_s, "e"_s, 'i', fun_get_agi),
-    MAGIC_FUNCTION("vit"_s, "e"_s, 'i', fun_get_vit),
-    MAGIC_FUNCTION("dex"_s, "e"_s, 'i', fun_get_dex),
-    MAGIC_FUNCTION("luk"_s, "e"_s, 'i', fun_get_luk),
-    MAGIC_FUNCTION("int"_s, "e"_s, 'i', fun_get_int),
-    MAGIC_FUNCTION("level"_s, "e"_s, 'i', fun_get_lv),
-    MAGIC_FUNCTION("mdef"_s, "e"_s, 'i', fun_get_mdef),
-    MAGIC_FUNCTION("def"_s, "e"_s, 'i', fun_get_def),
-    MAGIC_FUNCTION("hp"_s, "e"_s, 'i', fun_get_hp),
-    MAGIC_FUNCTION("max_hp"_s, "e"_s, 'i', fun_get_max_hp),
-    MAGIC_FUNCTION("sp"_s, "e"_s, 'i', fun_get_sp),
-    MAGIC_FUNCTION("max_sp"_s, "e"_s, 'i', fun_get_max_sp),
-    MAGIC_FUNCTION("dir"_s, "e"_s, 'd', fun_get_dir),
-    MAGIC_FUNCTION1(name_of, "."_s, 's'),
-    MAGIC_FUNCTION1(mob_id, "e"_s, 'i'),
-    MAGIC_FUNCTION1(location, "e"_s, 'l'),
-    MAGIC_FUNCTION1(random, "i"_s, 'i'),
-    MAGIC_FUNCTION1(random_dir, "i"_s, 'd'),
-    MAGIC_FUNCTION1(hash_entity, "e"_s, 'i'),
-    MAGIC_FUNCTION1(is_married, "e"_s, 'i'),
-    MAGIC_FUNCTION1(partner, "e"_s, 'e'),
-    MAGIC_FUNCTION1(awayfrom, "ldi"_s, 'l'),
-    MAGIC_FUNCTION1(failed, "_"_s, 'i'),
-    MAGIC_FUNCTION1(pc, "s"_s, 'e'),
-    MAGIC_FUNCTION1(npc, "s"_s, 'e'),
-    MAGIC_FUNCTION1(distance, "ll"_s, 'i'),
-    MAGIC_FUNCTION1(rdistance, "ll"_s, 'i'),
-    MAGIC_FUNCTION1(anchor, "s"_s, 'a'),
-    MAGIC_FUNCTION("random_location"_s, "a"_s, 'l', fun_pick_location),
-    MAGIC_FUNCTION("script_int"_s, "es"_s, 'i', fun_read_script_int),
-    MAGIC_FUNCTION("script_str"_s, "es"_s, 's', fun_read_script_str),
-    MAGIC_FUNCTION1(rbox, "li"_s, 'a'),
-    MAGIC_FUNCTION1(count_item, "e."_s, 'i'),
-    MAGIC_FUNCTION1(line_of_sight, "ll"_s, 'i'),
-    MAGIC_FUNCTION1(running_status_update, "ei"_s, 'i'),
-    MAGIC_FUNCTION1(status_option, "ei"_s, 'i'),
-    MAGIC_FUNCTION1(element, "e"_s, 'i'),
-    MAGIC_FUNCTION1(element_level, "e"_s, 'i'),
-    MAGIC_FUNCTION1(his_shroud, "e"_s, 'i'),
-    MAGIC_FUNCTION1(is_equipped, "e."_s, 'i'),
-    MAGIC_FUNCTION1(is_exterior, "l"_s, 'i'),
-    MAGIC_FUNCTION1(contains_string, "ss"_s, 'i'),
-    MAGIC_FUNCTION1(strstr, "ss"_s, 'i'),
-    MAGIC_FUNCTION1(strlen, "s"_s, 'i'),
-    MAGIC_FUNCTION1(substr, "sii"_s, 's'),
-    MAGIC_FUNCTION1(sqrt, "i"_s, 'i'),
-    MAGIC_FUNCTION1(map_level, "l"_s, 'i'),
-    MAGIC_FUNCTION1(map_nr, "l"_s, 'i'),
-    MAGIC_FUNCTION1(dir_towards, "lli"_s, 'd'),
-    MAGIC_FUNCTION1(is_dead, "e"_s, 'i'),
-    MAGIC_FUNCTION1(is_pc, "e"_s, 'i'),
-    MAGIC_FUNCTION("extract_healer_experience"_s, "ei"_s, 'i', fun_extract_healer_xp),
-};
-
-fun_t *magic_get_fun(ZString name)
-{
-    auto it = functions.find(name);
-    if (it == functions.end())
-        return nullptr;
-    return &it->second;
-}
-
-// 1 on failure
-static
-int eval_location(dumb_ptr<env_t> env, location_t *dest, const e_location_t *expr)
-{
-    val_t m, x, y;
-    magic_eval(env, &m, expr->m);
-    magic_eval(env, &x, expr->x);
-    magic_eval(env, &y, expr->y);
-
-    if (m.is<ValString>()
-        && x.is<ValInt>() && y.is<ValInt>())
-    {
-        MapName name = VString<15>(ZString(m.get_if<ValString>()->v_string));
-        magic_clear_var(&m);
-        P<map_local> map_id = TRY_UNWRAP(map_mapname2mapid(name), return 1);
-        dest->m = map_id;
-        dest->x = x.get_if<ValInt>()->v_int;
-        dest->y = y.get_if<ValInt>()->v_int;
-        return 0;
-    }
-    else
-    {
-        magic_clear_var(&m);
-        magic_clear_var(&x);
-        magic_clear_var(&y);
-        return 1;
-    }
-}
-
-static
-dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_)
-{
-    MATCH_BEGIN (expr_)
-    {
-        MATCH_CASE (const e_location_t&, a_loc)
-        {
-            location_t loc;
-            if (eval_location(env, &loc, &a_loc))
-            {
-                return nullptr;
-            }
-            else
-            {
-                return dumb_ptr<area_t>::make(loc);
-            }
-        }
-        MATCH_CASE (const ExprAreaUnion&, a)
-        {
-            AreaUnion u;
-            bool fail = false;
-            for (int i = 0; i < 2; i++)
-            {
-                u.a_union[i] = eval_area(env, *a.a_union[i]);
-                if (!u.a_union[i])
-                    fail = true;
-            }
-
-            if (fail)
-            {
-                for (int i = 0; i < 2; i++)
-                {
-                    if (u.a_union[i])
-                        free_area(u.a_union[i]);
-                }
-                return nullptr;
-            }
-            return dumb_ptr<area_t>::make(u);
-        }
-        MATCH_CASE (const ExprAreaRect&, a_rect)
-        {
-            val_t width, height;
-            magic_eval(env, &width, a_rect.width);
-            magic_eval(env, &height, a_rect.height);
-
-            AreaRect a_rect_;
-            if (width.is<ValInt>()
-                && height.is<ValInt>()
-                && !eval_location(env, &(a_rect_.loc),
-                                   &a_rect.loc))
-            {
-                a_rect_.width = width.get_if<ValInt>()->v_int;
-                a_rect_.height = height.get_if<ValInt>()->v_int;
-
-                magic_clear_var(&width);
-                magic_clear_var(&height);
-                return dumb_ptr<area_t>::make(a_rect_);
-            }
-            else
-            {
-                magic_clear_var(&width);
-                magic_clear_var(&height);
-                return nullptr;
-            }
-        }
-        MATCH_CASE (const ExprAreaBar&, a_bar)
-        {
-            val_t width, depth, dir;
-            magic_eval(env, &width, a_bar.width);
-            magic_eval(env, &depth, a_bar.depth);
-            magic_eval(env, &dir, a_bar.dir);
-
-            AreaBar a_bar_;
-            if (width.is<ValInt>()
-                && depth.is<ValInt>()
-                && dir.is<ValDir>()
-                && !eval_location(env, &a_bar_.loc,
-                                   &a_bar.loc))
-            {
-                a_bar_.width = width.get_if<ValInt>()->v_int;
-                a_bar_.depth = depth.get_if<ValInt>()->v_int;
-                a_bar_.dir = dir.get_if<ValDir>()->v_dir;
-
-                magic_clear_var(&width);
-                magic_clear_var(&depth);
-                magic_clear_var(&dir);
-                return dumb_ptr<area_t>::make(a_bar_);
-            }
-            else
-            {
-                magic_clear_var(&width);
-                magic_clear_var(&depth);
-                magic_clear_var(&dir);
-                return nullptr;
-            }
-        }
-    }
-    MATCH_END ();
-    abort();
-}
-
-// This is called on arguments with begin=true,
-// and on the return value with begin=false.
-// In both cases, the ambiguous types are in pointer mode.
-static
-bool type_key_matches(char ty_key, val_t *arg, bool begin)
-{
-    switch (ty_key)
-    {
-        case 'i':
-            if (begin)
-                intify(arg);
-            return arg->is<ValInt>();
-        case 'd':
-            return arg->is<ValDir>();
-        case 's':
-            if (begin)
-                stringify(arg);
-            return arg->is<ValString>();
-        case 'e':
-            return arg->is<ValEntityPtr>();
-        case 'l':
-            if (begin)
-                make_location(arg);
-            return arg->is<ValLocation>();
-        case 'a':
-            if (begin)
-                make_area(arg);
-            return arg->is<ValArea>();
-        case 'S':
-            if (begin)
-                make_spell(arg);
-            return arg->is<ValSpell>();
-        case 'I':
-            return arg->is<ValInvocationPtr>();
-        default:
-            return true;
-    }
-}
-
-int magic_signature_check(ZString opname, ZString funname, ZString signature,
-        Slice<val_t> args, int line, int column)
-{
-    int i;
-    for (i = 0; i < args.size(); i++)
-    {
-        val_t *arg = &args[i];
-
-        // whoa, it turns out the second p *does* shadow this one
-        if (ValEntityInt *p1 = arg->get_if<ValEntityInt>())
-        {
-            /* Dereference entities in preparation for calling function */
-            dumb_ptr<block_list> ent = map_id2bl(p1->v_eid);
-            if (ent)
-            {
-                *arg = ValEntityPtr{ent};
-            }
-            else
-            {
-                *arg = ValFail{};
-            }
-        }
-        else if (ValInvocationInt *p2 = arg->get_if<ValInvocationInt>())
-        {
-            dumb_ptr<invocation> invoc = map_id2bl(p2->v_iid)->is_spell();
-            if (invoc)
-            {
-                *arg = ValInvocationPtr{invoc};
-            }
-            else
-            {
-                *arg = ValFail();
-            }
-        }
-
-        char ty_key = signature[i];
-        if (!ty_key)
-        {
-            FPRINTF(stderr,
-                    "[magic-eval]:  L%d:%d: Too many arguments (%zu) to %s `%s'\n"_fmt,
-                    line, column, args.size(), opname, funname);
-            return 1;
-        }
-
-        if (arg->is<ValFail>() && ty_key != '_')
-            return 1;           /* Fail `in a sane way':  This is a perfectly permissible error */
-
-        // this also does conversions now
-        if (type_key_matches(ty_key, arg, true))
-            continue;
-
-        if (arg->is<ValUndef>())
-        {
-            FPRINTF(stderr,
-                    "[magic-eval]:  L%d:%d: Argument #%d to %s `%s' undefined\n"_fmt,
-                    line, column, i + 1, opname, funname);
-            return 1;
-        }
-
-
-        {                       /* Coercion failed? */
-            if (!arg->is<ValFail>())
-            {
-                FPRINTF(stderr,
-                        "[magic-eval]:  L%d:%d: Argument #%d to %s `%s' of incorrect type (sorry, types aren't integers anymore)\n"_fmt,
-                        line, column, i + 1, opname, funname);
-            }
-            return 1;
-        }
-    }
-
-    return 0;
-}
-
-void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr)
-{
-    MATCH_BEGIN (*expr)
-    {
-        MATCH_CASE (const val_t&, e_val)
-        {
-            magic_copy_var(dest, &e_val);
-        }
-
-        MATCH_CASE (const e_location_t&, e_location)
-        {
-            location_t loc;
-            if (eval_location(env, &loc, &e_location))
-                *dest = ValFail();
-            else
-                *dest = ValLocation{loc};
-        }
-        MATCH_CASE (const e_area_t&, e_area)
-        {
-            if (dumb_ptr<area_t> area = eval_area(env, e_area))
-                *dest = ValArea{area};
-            else
-                *dest = ValFail();
-        }
-        MATCH_CASE (const ExprFunApp&, e_funapp)
-        {
-            val_t arguments[MAX_ARGS];
-            int args_nr = e_funapp.args_nr;
-            int i;
-            fun_t *f = e_funapp.funp;
-
-            for (i = 0; i < args_nr; ++i)
-                magic_eval(env, &arguments[i], e_funapp.args[i]);
-            if (magic_signature_check("function"_s, f->name, f->signature, Slice<val_t>(arguments, args_nr),
-                        e_funapp.line_nr, e_funapp.column)
-                    || f->fun(env, dest, Slice<val_t>(arguments, args_nr)))
-                *dest = ValFail();
-            else
-            {
-                assert (!dest->is<ValInvocationPtr>());
-                assert (!dest->is<ValInvocationInt>());
-                assert (!dest->is<ValEntityInt>());
-                assert (type_key_matches(f->ret_ty, dest, false));
-
-                /* translate entity back into persistent int */
-                if (ValEntityPtr *ent = dest->get_if<ValEntityPtr>())
-                {
-                    if (ent->v_entity)
-                        *dest = ValEntityInt{ent->v_entity->bl_id};
-                    else
-                        *dest = ValFail();
-                }
-                // what about invocation?
-            }
-
-            for (i = 0; i < args_nr; ++i)
-                magic_clear_var(&arguments[i]);
-        }
-        MATCH_CASE (const ExprId&, e)
-        {
-            val_t& v = env->VAR(e.e_id);
-            magic_copy_var(dest, &v);
-        }
-        MATCH_CASE (const ExprField&, e_field)
-        {
-            val_t v;
-            int id = e_field.id;
-            magic_eval(env, &v, e_field.expr);
-
-            assert(!v.is<ValInvocationPtr>());
-            if (ValInvocationInt *ii = v.get_if<ValInvocationInt>())
-            {
-                dumb_ptr<invocation> t = map_id2bl(ii->v_iid)->is_spell();
-
-                if (!t)
-                    *dest = ValUndef();
-                else
-                {
-                    val_t& val = t->env->VAR(id);
-                    magic_copy_var(dest, &val);
-                }
-            }
-            else
-            {
-                FPRINTF(stderr,
-                        "[magic] Attempt to access field %s on non-spell\n"_fmt,
-                        env->base_env->varv[id].name);
-                *dest = ValFail();
-            }
-        }
-    }
-    MATCH_END ();
-}
-
-int magic_eval_int(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr)
-{
-    val_t result;
-    magic_eval(env, &result, expr);
-
-    if (result.is<ValFail>() || result.is<ValUndef>())
-        return 0;
-
-    intify(&result);
-
-    return result.get_if<ValInt>()->v_int;
-}
-
-AString magic_eval_str(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr)
-{
-    val_t result;
-    magic_eval(env, &result, expr);
-
-    if (result.is<ValFail>() || result.is<ValUndef>())
-        return "?"_s;
-
-    stringify(&result);
-
-    return result.get_if<ValString>()->v_string;
-}
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic-expr.hpp b/src/map/magic-expr.hpp
deleted file mode 100644
index 055f37b..0000000
--- a/src/map/magic-expr.hpp
+++ /dev/null
@@ -1,107 +0,0 @@
-#pragma once
-//    magic-expr.hpp - Pure functions for the old magic backend.
-//
-//    Copyright © 2004-2011 The Mana World Development Team
-//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include "fwd.hpp"
-
-#include "../strings/zstring.hpp"
-#include "../strings/literal.hpp"
-
-#include "magic-interpreter.t.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-/*
- * Argument types:
- *  i : int
- *  d : dir
- *  s : string
- *  e : entity
- *  l : location
- *  a : area
- *  S : spell
- *  I : invocation
- *  . : any, except for fail/undef
- *  _ : any, including fail, but not undef
- */
-struct fun_t
-{
-    LString name;
-    LString signature;
-    char ret_ty;
-    int (*fun)(dumb_ptr<env_t> env, val_t *result, Slice<val_t> arga);
-};
-
-/**
- * Retrieves a function by name
- * @param name The name to look up
- * @return A function of that name, or nullptr.
- */
-fun_t *magic_get_fun(ZString name);
-
-/**
- * Evaluates an expression and stores the result in `dest'
- */
-void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr);
-
-/**
- * Evaluates an expression and coerces the result into an integer
- */
-int magic_eval_int(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr);
-
-/**
- * Evaluates an expression and coerces the result into a string
- */
-AString magic_eval_str(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr);
-
-void magic_clear_var(val_t *v);
-
-void magic_copy_var(val_t *dest, const val_t *src);
-
-void magic_random_location(location_t *dest, dumb_ptr<area_t> area);
-
-// ret -1: not a string, ret 1: no such item, ret 0: OK
-int magic_find_item(Slice<val_t> args, int index, Item *item, int *stackable);
-
-#define GET_ARG_ITEM(index, dest, stackable)                    \
-     switch (magic_find_item(args, index, &dest, &stackable))   \
-    {                                                           \
-        case -1: return 1;                                      \
-        case 1: return 0;                                       \
-        default: break;                                         \
-    }
-
-int magic_location_in_area(Borrowed<map_local> m, int x, int y, dumb_ptr<area_t> area);
-
-/* Helper definitions for dealing with functions and operations */
-
-int magic_signature_check(ZString opname, ZString funname, ZString signature,
-        Slice<val_t> args, int line, int column);
-
-Borrowed<map_local> magic_area_rect(int *x, int *y, int *width, int *height,
-        area_t& area);
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic-expr.py b/src/map/magic-expr.py
deleted file mode 100644
index f53ddc8..0000000
--- a/src/map/magic-expr.py
+++ /dev/null
@@ -1,38 +0,0 @@
-class fun_t(object):
-    __slots__ = ('_value')
-
-    name = 'tmwa::map::magic::fun_t'
-    depth = 1
-    enabled = True
-
-    def __init__(self, value):
-        if not value:
-            value = None
-        self._value = value
-
-    def to_string(self):
-        value = self._value
-        if value is None:
-            return '(fun_t *) nullptr'
-        return '(fun_t *)'
-
-    def children(self):
-        value = self._value
-        if value is None:
-            return
-        value = value.dereference()
-        yield '->name', value['name']
-        yield '->signature', value['signature']
-        yield '->ret_ty', value['ret_ty']
-        yield '->fun', value['fun']
-
-    test_extra = '''
-    using tmwa::operator "" _s;
-    '''
-
-    tests = [
-            ('static_cast<tmwa::map::magic::fun_t *>(nullptr)',
-                '(fun_t *) nullptr'),
-            ('new tmwa::map::magic::fun_t{"name"_s, "sig"_s, \'\\0\', nullptr}',
-                '(fun_t *) = {->name = "name", ->signature = "sig", ->ret_ty = 0 \'\\000\', ->fun = nullptr}'),
-    ]
diff --git a/src/map/magic-interpreter-base.cpp b/src/map/magic-interpreter-base.cpp
deleted file mode 100644
index c2be363..0000000
--- a/src/map/magic-interpreter-base.cpp
+++ /dev/null
@@ -1,553 +0,0 @@
-#include "magic-interpreter-base.hpp"
-//    magic-interpreter-base.cpp - Core of the old magic system.
-//
-//    Copyright © 2004-2011 The Mana World Development Team
-//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include <algorithm>
-
-#include "../strings/astring.hpp"
-#include "../strings/xstring.hpp"
-
-#include "../io/cxxstdio.hpp"
-
-#include "../mmo/cxxstdio_enums.hpp"
-
-#include "../net/timer.hpp"
-
-#include "globals.hpp"
-#include "magic.hpp"
-#include "magic-expr.hpp"
-#include "magic-interpreter.hpp"
-#include "pc.hpp"
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-static
-void set_int(val_t *v, int i)
-{
-    *v = ValInt{i};
-}
-
-static __attribute__((unused))
-void set_dir(val_t *v, DIR d)
-{
-    *v = ValDir{d};
-}
-
-
-static
-void set_string(val_t *v, RString x)
-{
-    *v = ValString{x};
-}
-
-static
-void set_entity(val_t *v, dumb_ptr<block_list> e)
-{
-    *v = ValEntityInt{e->bl_id};
-}
-
-static
-void set_invocation(val_t *v, dumb_ptr<invocation> i)
-{
-    *v = ValInvocationInt{i->bl_id};
-}
-
-static
-void set_spell(val_t *v, dumb_ptr<spell_t> x)
-{
-    *v = ValSpell{x};
-}
-
-AString magic_find_invocation(XString spellname)
-{
-    auto it = magic_conf.spells_by_name.find(spellname);
-    if (it != magic_conf.spells_by_name.end())
-        return it->second->invocation;
-
-    return AString();
-}
-
-dumb_ptr<spell_t> magic_find_spell(XString invocation)
-{
-    auto it = magic_conf.spells_by_invocation.find(invocation);
-    if (it != magic_conf.spells_by_invocation.end())
-        return it->second;
-
-    return nullptr;
-}
-
-/* -------------------------------------------------------------------------------- */
-/* Spell anchors */
-/* -------------------------------------------------------------------------------- */
-
-AString magic_find_anchor_invocation(XString anchor_name)
-{
-    auto it = magic_conf.anchors_by_name.find(anchor_name);
-
-    if (it != magic_conf.anchors_by_name.end())
-        return it->second->invocation;
-
-    return AString();
-}
-
-dumb_ptr<teleport_anchor_t> magic_find_anchor(XString name)
-{
-    auto it = magic_conf.anchors_by_invocation.find(name);
-    if (it != magic_conf.anchors_by_invocation.end())
-        return it->second;
-
-    return nullptr;
-}
-
-/* -------------------------------------------------------------------------------- */
-/* Spell guard checks */
-/* -------------------------------------------------------------------------------- */
-
-static
-dumb_ptr<env_t> alloc_env(magic_conf_t *conf)
-{
-    auto env = dumb_ptr<env_t>::make();
-    env->varu = make_unique<val_t[]>(conf->varv.size());
-    env->base_env = conf;
-    return env;
-}
-
-static
-dumb_ptr<env_t> clone_env(dumb_ptr<env_t> src)
-{
-    dumb_ptr<env_t> retval = alloc_env(src->base_env);
-
-    for (int i = 0; i < src->base_env->varv.size(); i++)
-        magic_copy_var(&retval->varu[i], &src->varu[i]);
-
-    return retval;
-}
-
-void magic_free_env(dumb_ptr<env_t> env)
-{
-    for (int i = 0; i < env->base_env->varv.size(); i++)
-        magic_clear_var(&env->varu[i]);
-    // handled by std::unique_ptr now. Was a memory leak before.
-    // delete[] env->vars;
-    env.delete_();
-}
-
-dumb_ptr<env_t> spell_create_env(magic_conf_t *conf, dumb_ptr<spell_t> spell,
-        dumb_ptr<map_session_data> caster, int spellpower, XString param)
-{
-    dumb_ptr<env_t> env = alloc_env(conf);
-
-    switch (spell->spellarg_ty)
-    {
-
-        case SPELLARG::STRING:
-            set_string(&env->varu[spell->arg], param);
-            break;
-
-        case SPELLARG::PC:
-        {
-            CharName name = stringish<CharName>(param);
-            dumb_ptr<map_session_data> subject = map_nick2sd(name);
-            if (!subject)
-                subject = caster;
-            set_entity(&env->varu[spell->arg], subject);
-            break;
-        }
-
-        case SPELLARG::NONE:
-            break;
-
-        default:
-            FPRINTF(stderr, "Unexpected spellarg type %d\n"_fmt,
-                    spell->spellarg_ty);
-    }
-
-    set_entity(&env->varu[VAR_CASTER], caster);
-    set_int(&env->varu[VAR_SPELLPOWER], spellpower);
-    set_spell(&env->varu[VAR_SPELL], spell);
-
-    return env;
-}
-
-static
-void free_components(dumb_ptr<component_t> *component_holder)
-{
-    if (*component_holder == nullptr)
-        return;
-    free_components(&(*component_holder)->next);
-    (*component_holder).delete_();
-    *component_holder = nullptr;
-}
-
-void magic_add_component(dumb_ptr<component_t> *component_holder, ItemNameId id, int count)
-{
-    if (count <= 0)
-        return;
-
-    if (*component_holder == nullptr)
-    {
-        auto component = dumb_ptr<component_t>::make();
-        component->next = nullptr;
-        component->item_id = id;
-        component->count = count;
-        *component_holder = component;
-    }
-    else
-    {
-        dumb_ptr<component_t> component = *component_holder;
-        if (component->item_id == id)
-        {
-            component->count += count;
-            return;
-        }
-        else
-            magic_add_component(&component->next, id, count);
-        /* Tail-recurse; gcc can optimise this.  Not that it matters. */
-    }
-}
-
-static
-void copy_components(dumb_ptr<component_t> *component_holder, dumb_ptr<component_t> component)
-{
-    if (component == nullptr)
-        return;
-
-    magic_add_component(component_holder, component->item_id, component->count);
-    copy_components(component_holder, component->next);
-}
-
-typedef struct spellguard_check
-{
-    dumb_ptr<component_t> catalysts, components;
-    int mana;
-    interval_t casttime;
-} spellguard_check_t;
-
-static
-int check_prerequisites(dumb_ptr<map_session_data> caster, dumb_ptr<component_t> component)
-{
-    while (component)
-    {
-        if (pc_count_all_items(caster, component->item_id) < component->count)
-            return 0;           /* insufficient */
-
-        component = component->next;
-    }
-
-    return 1;
-}
-
-static
-void consume_components(dumb_ptr<map_session_data> caster, dumb_ptr<component_t> component)
-{
-    while (component)
-    {
-        pc_remove_items(caster, component->item_id, component->count);
-        component = component->next;
-    }
-}
-
-static
-int spellguard_can_satisfy(spellguard_check_t *check, dumb_ptr<map_session_data> caster,
-        dumb_ptr<env_t> env, int *near_miss)
-{
-    tick_t tick = gettick();
-
-    int retval = check_prerequisites(caster, check->catalysts);
-
-    if (retval && near_miss)
-        *near_miss = 1;         // close enough!
-
-    retval = retval && caster->cast_tick <= tick    /* Hasn't cast a spell too recently */
-        && check->mana <= caster->status.sp
-        && check_prerequisites(caster, check->components);
-
-    if (retval)
-    {
-        interval_t casttime = check->casttime;
-
-        if (ValInt *v = env->VAR(VAR_MIN_CASTTIME).get_if<ValInt>())
-        {
-            casttime = std::max(casttime, static_cast<interval_t>(v->v_int));
-        }
-
-        caster->cast_tick = tick + casttime;    /* Make sure not to cast too frequently */
-
-        consume_components(caster, check->components);
-        pc_heal(caster, 0, -check->mana);
-    }
-
-    return retval;
-}
-
-static
-const effect_set_t *spellguard_check_sub(spellguard_check_t *check,
-        dumb_ptr<spellguard_t> guard,
-        dumb_ptr<map_session_data> caster,
-        dumb_ptr<env_t> env,
-        int *near_miss)
-{
-    if (guard == nullptr)
-        return nullptr;
-
-    MATCH_BEGIN (*guard)
-    {
-        MATCH_CASE (const GuardCondition&, s)
-        {
-            if (!magic_eval_int(env, s.s_condition))
-                return nullptr;
-        }
-        MATCH_CASE (const GuardComponents&, s)
-        {
-            copy_components(&check->components, s.s_components);
-        }
-        MATCH_CASE (const GuardCatalysts&, s)
-        {
-            copy_components(&check->catalysts, s.s_catalysts);
-        }
-        MATCH_CASE (const GuardChoice&, s)
-        {
-            spellguard_check_t altcheck = *check;
-            const effect_set_t *retval;
-
-            altcheck.components = nullptr;
-            altcheck.catalysts = nullptr;
-
-            copy_components(&altcheck.catalysts, check->catalysts);
-            copy_components(&altcheck.components, check->components);
-
-            retval =
-                spellguard_check_sub(&altcheck, guard->next, caster, env,
-                                      near_miss);
-            free_components(&altcheck.catalysts);
-            free_components(&altcheck.components);
-            if (retval)
-                return retval;
-            else
-                return spellguard_check_sub(check, s.s_alt, caster,
-                                             env, near_miss);
-        }
-        MATCH_CASE (const GuardMana&, s)
-        {
-            check->mana += magic_eval_int(env, s.s_mana);
-        }
-        MATCH_CASE (const GuardCastTime&, s)
-        {
-            check->casttime += static_cast<interval_t>(magic_eval_int(env, s.s_casttime));
-        }
-        MATCH_CASE (const effect_set_t&, s_effect)
-        {
-            if (spellguard_can_satisfy(check, caster, env, near_miss))
-                return &s_effect;
-            else
-                return nullptr;
-        }
-    }
-    MATCH_END ();
-
-    return spellguard_check_sub(check, guard->next, caster, env, near_miss);
-}
-
-static
-const effect_set_t *check_spellguard(dumb_ptr<spellguard_t> guard,
-        dumb_ptr<map_session_data> caster, dumb_ptr<env_t> env,
-        int *near_miss)
-{
-    spellguard_check_t check;
-    const effect_set_t *retval;
-    check.catalysts = nullptr;
-    check.components = nullptr;
-    check.mana = 0;
-    check.casttime = interval_t::zero();
-
-    retval = spellguard_check_sub(&check, guard, caster, env, near_miss);
-
-    free_components(&check.catalysts);
-    free_components(&check.components);
-
-    return retval;
-}
-
-/* -------------------------------------------------------------------------------- */
-/* Public API */
-/* -------------------------------------------------------------------------------- */
-
-const effect_set_t *spell_trigger(dumb_ptr<spell_t> spell, dumb_ptr<map_session_data> caster,
-        dumb_ptr<env_t> env, int *near_miss)
-{
-    dumb_ptr<spellguard_t> guard = spell->spellguard;
-
-    if (near_miss)
-        *near_miss = 0;
-
-    for (letdef_t& ld : spell->letdefv)
-        magic_eval(env, &env->varu[ld.id], ld.expr);
-
-    return check_spellguard(guard, caster, env, near_miss);
-}
-
-static
-void spell_set_location(dumb_ptr<invocation> invocation, dumb_ptr<block_list> entity)
-{
-    magic_clear_var(&invocation->env->varu[VAR_LOCATION]);
-    ValLocation v;
-    v.v_location.m = entity->bl_m;
-    v.v_location.x = entity->bl_x;
-    v.v_location.y = entity->bl_y;
-    invocation->env->varu[VAR_LOCATION] = v;
-}
-
-void spell_update_location(dumb_ptr<invocation> invocation)
-{
-    if (bool(invocation->spell->flags & SPELL_FLAG::LOCAL))
-        return;
-    else
-    {
-        dumb_ptr<block_list> owner_bl = map_id2bl(invocation->subject);
-        if (!owner_bl)
-            return;
-        dumb_ptr<map_session_data> owner = owner_bl->is_player();
-
-        spell_set_location(invocation, owner);
-    }
-}
-
-dumb_ptr<invocation> spell_instantiate(const effect_set_t *effect_set, dumb_ptr<env_t> env)
-{
-    dumb_ptr<invocation> retval;
-    retval.new_();
-    dumb_ptr<block_list> caster;
-
-    retval->env = env;
-
-    retval->caster = env->VAR(VAR_CASTER).get_if<ValEntityInt>()->v_eid;
-    retval->spell = env->VAR(VAR_SPELL).get_if<ValSpell>()->v_spell;
-    retval->current_effect = effect_set->effect;
-    retval->trigger_effect = effect_set->at_trigger;
-    retval->end_effect = effect_set->at_end;
-
-    caster = map_id2bl(retval->caster);    // must still exist
-    retval->bl_id = map_addobject(retval);
-    retval->bl_type = BL::SPELL;
-    retval->bl_m = caster->bl_m;
-    retval->bl_x = caster->bl_x;
-    retval->bl_y = caster->bl_y;
-
-    map_addblock(retval);
-    set_invocation(&env->varu[VAR_INVOCATION], retval);
-
-    return retval;
-}
-
-dumb_ptr<invocation> spell_clone_effect(dumb_ptr<invocation> base)
-{
-    dumb_ptr<invocation> retval;
-    retval.new_();
-
-    // block_list in general is not copyable
-    // since this is the only call site, it is expanded here
-    //*retval = *base;
-
-    retval->next_invocation = nullptr;
-    retval->flags = INVOCATION_FLAG::ZERO;
-    dumb_ptr<env_t> env = retval->env = clone_env(base->env);
-    retval->spell = base->spell;
-    retval->caster = base->caster;
-    retval->subject = BlockId();
-    // retval->timer = 0;
-    // retval->stack = undef;
-    retval->script_pos = 0;
-    // huh?
-    retval->current_effect = base->trigger_effect;
-    retval->trigger_effect = base->trigger_effect;
-    retval->end_effect = nullptr;
-    // retval->status_change_refs = nullptr;
-
-    retval->bl_id = BlockId();
-    retval->bl_prev = nullptr;
-    retval->bl_next = nullptr;
-    retval->bl_m = base->bl_m;
-    retval->bl_x = base->bl_x;
-    retval->bl_y = base->bl_y;
-    retval->bl_type = base->bl_type;
-
-    retval->bl_id = map_addobject(retval);
-    set_invocation(&env->varu[VAR_INVOCATION], retval);
-
-    return retval;
-}
-
-void spell_bind(dumb_ptr<map_session_data> subject, dumb_ptr<invocation> invocation)
-{
-    /* Only bind nonlocal spells */
-
-    if (!bool(invocation->spell->flags & SPELL_FLAG::LOCAL))
-    {
-        if (bool(invocation->flags & INVOCATION_FLAG::BOUND)
-            || invocation->subject || invocation->next_invocation)
-        {
-            int *i = nullptr;
-            FPRINTF(stderr,
-                    "[magic] INTERNAL ERROR: Attempt to re-bind spell invocation `%s'\n"_fmt,
-                    invocation->spell->name);
-            *i = 1;
-            return;
-        }
-
-        invocation->next_invocation = subject->active_spells;
-        subject->active_spells = invocation;
-        invocation->flags |= INVOCATION_FLAG::BOUND;
-        invocation->subject = subject->bl_id;
-    }
-
-    spell_set_location(invocation, subject);
-}
-
-int spell_unbind(dumb_ptr<map_session_data> subject, dumb_ptr<invocation> invocation_)
-{
-    dumb_ptr<invocation> *seeker = &subject->active_spells;
-
-    while (*seeker)
-    {
-        if (*seeker == invocation_)
-        {
-            *seeker = invocation_->next_invocation;
-
-            invocation_->flags &= ~INVOCATION_FLAG::BOUND;
-            invocation_->next_invocation = nullptr;
-            invocation_->subject = BlockId();
-
-            return 0;
-        }
-        seeker = &((*seeker)->next_invocation);
-    }
-
-    return 1;
-}
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic-interpreter-base.hpp b/src/map/magic-interpreter-base.hpp
deleted file mode 100644
index 7c00db0..0000000
--- a/src/map/magic-interpreter-base.hpp
+++ /dev/null
@@ -1,84 +0,0 @@
-#pragma once
-//    magic-interpreter-base.hpp - Core of the old magic system.
-//
-//    Copyright © 2004-2011 The Mana World Development Team
-//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include "fwd.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-/**
- * Adds a component selection to a component holder (which may initially be nullptr)
- */
-void magic_add_component(dumb_ptr<component_t> *component_holder, ItemNameId id, int count);
-
-/**
- * Identifies the invocation used to trigger a spell
- *
- * Returns empty string if not found
- */
-AString magic_find_invocation(XString spellname);
-
-/**
- * Identifies the invocation used to denote a teleport location
- *
- * Returns empty string if not found
- */
-AString magic_find_anchor_invocation(XString teleport_location);
-
-dumb_ptr<teleport_anchor_t> magic_find_anchor(XString name);
-
-dumb_ptr<env_t> spell_create_env(magic_conf_t *conf, dumb_ptr<spell_t> spell,
-        dumb_ptr<map_session_data> caster, int spellpower, XString param);
-
-void magic_free_env(dumb_ptr<env_t> env);
-
-/**
- * near_miss is set to nonzero iff the spell only failed due to ephemereal issues (spell delay in effect, out of mana, out of components)
- */
-const effect_set_t *spell_trigger(dumb_ptr<spell_t> spell,
-        dumb_ptr<map_session_data> caster,
-        dumb_ptr<env_t> env, int *near_miss);
-
-dumb_ptr<invocation> spell_instantiate(const effect_set_t *effect, dumb_ptr<env_t> env);
-
-/**
- * Bind a spell to a subject (this is a no-op for `local' spells).
- */
-void spell_bind(dumb_ptr<map_session_data> subject, dumb_ptr<invocation> invocation);
-
-// 1 on failure
-int spell_unbind(dumb_ptr<map_session_data> subject, dumb_ptr<invocation> invocation);
-
-/**
- * Clones a spell to run the at_effect field
- */
-dumb_ptr<invocation> spell_clone_effect(dumb_ptr<invocation> source);
-
-dumb_ptr<spell_t> magic_find_spell(XString invocation);
-
-void spell_update_location(dumb_ptr<invocation> invocation);
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic-interpreter.hpp b/src/map/magic-interpreter.hpp
deleted file mode 100644
index cbd92a9..0000000
--- a/src/map/magic-interpreter.hpp
+++ /dev/null
@@ -1,630 +0,0 @@
-#pragma once
-//    magic-interpreter.hpp - Old magic.
-//
-//    Copyright © 2004-2011 The Mana World Development Team
-//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include "magic-interpreter.t.hpp"
-
-#include "fwd.hpp"
-
-#include <cassert>
-
-#include <memory>
-
-#include "../strings/rstring.hpp"
-
-#include "../sexpr/variant.hpp"
-
-#include "../net/timer.t.hpp"
-
-#include "../mmo/ids.hpp"
-
-#include "map.hpp"
-#include "script-buffer.hpp"
-#include "../mmo/skill.t.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-struct location_t
-{
-    Borrowed<map_local> m;
-    int x, y;
-
-    // This constructor exists solely to work around the design constraints
-    // of sexpr::Variant<>. See comments in variant.tcc for future plans.
-    __attribute__((deprecated))
-    location_t() noexcept : m(borrow(undefined_gat)), x(), y() {}
-    location_t(Borrowed<map_local> m_, int x_, int y_) : m(m_), x(x_), y(y_) {}
-};
-
-struct AreaUnion
-{
-    dumb_ptr<area_t> a_union[2];
-};
-struct AreaRect
-{
-    location_t loc;
-    int width, height;
-};
-struct AreaBar
-{
-    location_t loc;
-    int width, depth;
-    DIR dir;
-};
-
-using AreaVariantBase = Variant<
-    location_t,
-    AreaUnion,
-    AreaRect,
-    AreaBar
->;
-
-struct area_t : AreaVariantBase
-{
-    int size;
-
-    area_t() = delete;
-    area_t(area_t&&) = default;
-    area_t(const area_t&) = delete;
-    area_t& operator = (area_t&&) = default;
-    area_t& operator = (const area_t&) = delete;
-
-    area_t(location_t v) : AreaVariantBase(std::move(v)), size(1) {}
-    area_t(AreaUnion v) : AreaVariantBase(std::move(v)), size(v.a_union[0]->size + v.a_union[1]->size) {}
-    area_t(AreaRect v) : AreaVariantBase(std::move(v)), size(v.width * v.height) {}
-    area_t(AreaBar v) : AreaVariantBase(std::move(v)), size((v.width * 2 + 1) * v.depth) {}
-};
-
-struct ValUndef
-{
-};
-struct ValInt
-{
-    int v_int;
-};
-struct ValDir
-{
-    DIR v_dir;
-};
-struct ValString
-{
-    RString v_string;
-};
-struct ValEntityInt
-{
-    BlockId v_eid;
-};
-struct ValEntityPtr
-{
-    dumb_ptr<block_list> v_entity;
-};
-struct ValLocation
-{
-    location_t v_location;
-};
-struct ValArea
-{
-    dumb_ptr<area_t> v_area;
-};
-struct ValSpell
-{
-    dumb_ptr<spell_t> v_spell;
-};
-struct ValInvocationInt
-{
-    BlockId v_iid;
-};
-struct ValInvocationPtr
-{
-    dumb_ptr<invocation> v_invocation;
-};
-struct ValFail
-{
-};
-struct ValNegative1
-{
-};
-
-using ValVariantBase = Variant<
-    ValUndef,
-    ValInt,
-    ValDir,
-    ValString,
-    ValEntityInt,
-    ValEntityPtr,
-    ValLocation,
-    ValArea,
-    ValSpell,
-    ValInvocationInt,
-    ValInvocationPtr,
-    ValFail,
-    ValNegative1
->;
-struct val_t : ValVariantBase
-{
-    val_t() noexcept : ValVariantBase(ValUndef{}) {}
-    val_t(val_t&&) = default;
-    val_t(const val_t&) = delete;
-    val_t& operator = (val_t&&) = default;
-    val_t& operator = (const val_t&) = delete;
-
-    val_t(ValUndef v) : ValVariantBase(std::move(v)) {}
-    val_t(ValInt v) : ValVariantBase(std::move(v)) {}
-    val_t(ValDir v) : ValVariantBase(std::move(v)) {}
-    val_t(ValString v) : ValVariantBase(std::move(v)) {}
-    val_t(ValEntityInt v) : ValVariantBase(std::move(v)) {}
-    val_t(ValEntityPtr v) : ValVariantBase(std::move(v)) {}
-    val_t(ValLocation v) : ValVariantBase(std::move(v)) {}
-    val_t(ValArea v) : ValVariantBase(std::move(v)) {}
-    val_t(ValSpell v) : ValVariantBase(std::move(v)) {}
-    val_t(ValInvocationInt v) : ValVariantBase(std::move(v)) {}
-    val_t(ValInvocationPtr v) : ValVariantBase(std::move(v)) {}
-    val_t(ValFail v) : ValVariantBase(std::move(v)) {}
-    val_t(ValNegative1 v) : ValVariantBase(std::move(v)) {}
-};
-
-
-/* ----------- */
-/* Expressions */
-/* ----------- */
-
-#define MAX_ARGS 7              /* Max. # of args used in builtin primitive functions */
-
-struct e_area_t;
-
-struct e_location_t
-{
-    dumb_ptr<expr_t> m, x, y;
-
-    e_location_t() noexcept : m(), x(), y() {}
-};
-struct ExprAreaUnion
-{
-    dumb_ptr<e_area_t> a_union[2];
-};
-struct ExprAreaRect
-{
-    e_location_t loc;
-    dumb_ptr<expr_t> width, height;
-};
-struct ExprAreaBar
-{
-    e_location_t loc;
-    dumb_ptr<expr_t> width, depth, dir;
-};
-
-using ExprAreaVariantBase = Variant<
-    e_location_t,
-    ExprAreaUnion,
-    ExprAreaRect,
-    ExprAreaBar
->;
-
-struct e_area_t : ExprAreaVariantBase
-{
-    e_area_t() = delete;
-    e_area_t(e_area_t&&) = default;
-    e_area_t(const e_area_t&) = delete;
-    e_area_t& operator = (e_area_t&&) = default;
-    e_area_t& operator = (const e_area_t&) = delete;
-
-    e_area_t(e_location_t v) : ExprAreaVariantBase(std::move(v)) {}
-    e_area_t(ExprAreaUnion v) : ExprAreaVariantBase(std::move(v)) {}
-    e_area_t(ExprAreaRect v) : ExprAreaVariantBase(std::move(v)) {}
-    e_area_t(ExprAreaBar v) : ExprAreaVariantBase(std::move(v)) {}
-};
-
-struct ExprFunApp
-{
-    fun_t *funp;
-    int line_nr, column;
-    int args_nr;
-    dumb_ptr<expr_t> args[MAX_ARGS];
-};
-struct ExprId
-{
-    int e_id;
-};
-struct ExprField
-{
-    dumb_ptr<expr_t> expr;
-    int id;
-};
-
-using ExprVariantBase = Variant<
-    val_t,
-    e_location_t,
-    e_area_t,
-    ExprFunApp,
-    ExprId,
-    ExprField
->;
-struct expr_t : ExprVariantBase
-{
-    expr_t() = delete;
-    expr_t(expr_t&&) = default;
-    expr_t(const expr_t&) = delete;
-    expr_t& operator = (expr_t&&) = default;
-    expr_t& operator = (const expr_t&) = delete;
-
-    expr_t(val_t v) : ExprVariantBase(std::move(v)) {}
-    expr_t(e_location_t v) : ExprVariantBase(std::move(v)) {}
-    expr_t(e_area_t v) : ExprVariantBase(std::move(v)) {}
-    expr_t(ExprFunApp v) : ExprVariantBase(std::move(v)) {}
-    expr_t(ExprId v) : ExprVariantBase(std::move(v)) {}
-    expr_t(ExprField v) : ExprVariantBase(std::move(v)) {}
-};
-
-
-struct effect_t;
-
-struct EffectSkip
-{
-};
-struct EffectAbort
-{
-};
-struct EffectAssign
-{
-    int id;
-    dumb_ptr<expr_t> expr;
-};
-struct EffectForEach
-{
-    int id;
-    dumb_ptr<expr_t> area;
-    dumb_ptr<effect_t> body;
-    FOREACH_FILTER filter;
-};
-struct EffectFor
-{
-    int id;
-    dumb_ptr<expr_t> start, stop;
-    dumb_ptr<effect_t> body;
-};
-struct EffectIf
-{
-    dumb_ptr<expr_t> cond;
-    dumb_ptr<effect_t> true_branch, false_branch;
-};
-struct EffectSleep
-{
-    dumb_ptr<expr_t> e_sleep;        /* sleep time */
-};
-struct EffectScript
-{
-    dumb_ptr<const ScriptBuffer> e_script;
-};
-struct EffectBreak
-{
-};
-struct EffectOp
-{
-    op_t *opp;
-    int args_nr;
-    int line_nr, column;
-    dumb_ptr<expr_t> args[MAX_ARGS];
-};
-struct EffectEnd
-{
-};
-struct EffectCall
-{
-    std::vector<int> *formalv;
-    dumb_ptr<std::vector<dumb_ptr<expr_t>>> actualvp;
-    dumb_ptr<effect_t> body;
-};
-
-using EffectVariantBase = Variant<
-    EffectSkip,
-    EffectAbort,
-    EffectAssign,
-    EffectForEach,
-    EffectFor,
-    EffectIf,
-    EffectSleep,
-    EffectScript,
-    EffectBreak,
-    EffectOp,
-    EffectEnd,
-    EffectCall
->;
-struct effect_t : EffectVariantBase
-{
-    dumb_ptr<effect_t> next;
-
-    effect_t() = delete;
-    effect_t(effect_t&&) = default;
-    effect_t(const effect_t&) = delete;
-    effect_t& operator = (effect_t&&) = default;
-    effect_t& operator = (const effect_t&) = delete;
-
-    effect_t(EffectSkip v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-    effect_t(EffectAbort v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-    effect_t(EffectAssign v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-    effect_t(EffectForEach v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-    effect_t(EffectFor v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-    effect_t(EffectIf v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-    effect_t(EffectSleep v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-    effect_t(EffectScript v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-    effect_t(EffectBreak v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-    effect_t(EffectOp v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-    effect_t(EffectEnd v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-    effect_t(EffectCall v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {}
-};
-
-/* ---------- */
-/* Components */
-/* ---------- */
-
-struct component_t
-{
-    dumb_ptr<component_t> next;
-    ItemNameId item_id;
-    int count;
-};
-
-
-struct spellguard_t;
-struct GuardCondition
-{
-    dumb_ptr<expr_t> s_condition;
-};
-struct GuardMana
-{
-    dumb_ptr<expr_t> s_mana;
-};
-struct GuardCastTime
-{
-    dumb_ptr<expr_t> s_casttime;
-};
-struct GuardComponents
-{
-    dumb_ptr<component_t> s_components;
-};
-struct GuardCatalysts
-{
-    dumb_ptr<component_t> s_catalysts;
-};
-struct GuardChoice
-{
-    dumb_ptr<spellguard_t> s_alt;   /* either `next' or `s.s_alt' */
-};
-struct effect_set_t
-{
-    dumb_ptr<effect_t> effect, at_trigger, at_end;
-};
-
-using SpellGuardVariantBase = Variant<
-    GuardCondition,
-    GuardMana,
-    GuardCastTime,
-    GuardComponents,
-    GuardCatalysts,
-    GuardChoice,
-    effect_set_t
->;
-struct spellguard_t : SpellGuardVariantBase
-{
-    dumb_ptr<spellguard_t> next;
-
-    spellguard_t() = delete;
-    spellguard_t(spellguard_t&&) = default;
-    spellguard_t(const spellguard_t&) = delete;
-    spellguard_t& operator = (spellguard_t&&) = default;
-    spellguard_t& operator = (const spellguard_t&) = delete;
-
-    spellguard_t(GuardCondition v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
-    spellguard_t(GuardMana v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
-    spellguard_t(GuardCastTime v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
-    spellguard_t(GuardComponents v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
-    spellguard_t(GuardCatalysts v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
-    spellguard_t(GuardChoice v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
-    spellguard_t(effect_set_t v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {}
-};
-
-/* ------ */
-/* Spells */
-/* ------ */
-
-struct letdef_t
-{
-    int id;
-    dumb_ptr<expr_t> expr;
-};
-
-struct spell_t
-{
-    RString name;
-    RString invocation;
-    SPELL_FLAG flags;
-    int arg;
-    SPELLARG spellarg_ty;
-
-    std::vector<letdef_t> letdefv;
-
-    dumb_ptr<spellguard_t> spellguard;
-};
-
-/* ------- */
-/* Anchors */
-/* ------- */
-
-struct teleport_anchor_t
-{
-    RString name;
-    RString invocation;
-    dumb_ptr<expr_t> location;
-};
-
-/* ------------------- */
-/* The big config blob */
-/* ------------------- */
-
-struct magic_conf_t
-{
-    struct mcvar
-    {
-        RString name;
-        val_t val;
-    };
-    // This should probably be done by a dedicated "intern pool" class
-    std::vector<mcvar> varv;
-
-    std::map<RString, dumb_ptr<spell_t>> spells_by_name, spells_by_invocation;
-
-    std::map<RString, dumb_ptr<teleport_anchor_t>> anchors_by_name, anchors_by_invocation;
-};
-
-/* Execution environment */
-
-// these are not an enum they're a nasty intern hack
-#define VAR_MIN_CASTTIME        0
-#define VAR_OBSCURE_CHANCE      1
-#define VAR_CASTER              2
-#define VAR_SPELLPOWER          3
-#define VAR_SPELL               4
-#define VAR_INVOCATION          5
-#define VAR_TARGET              6
-#define VAR_SCRIPTTARGET        7
-#define VAR_LOCATION            8
-
-struct env_t
-{
-    magic_conf_t *base_env;
-    std::unique_ptr<val_t[]> varu;
-
-    val_t& VAR(size_t i)
-    {
-        assert (varu);
-        if (varu[i].is<ValUndef>())
-            return base_env->varv[i].val;
-        else
-            return varu[i];
-    }
-
-};
-
-struct CarForEach
-{
-    int id;
-    bool ty_is_spell_not_entity;
-    dumb_ptr<effect_t> body;
-    dumb_ptr<std::vector<BlockId>> entities_vp;
-    int index;
-};
-struct CarFor
-{
-    int id;
-    dumb_ptr<effect_t> body;
-    int current;
-    int stop;
-};
-struct CarProc
-{
-    int args_nr;
-    int *formalap;
-    dumb_ptr<val_t[]> old_actualpa;
-};
-
-using CarVariantBase = Variant<
-    CarForEach,
-    CarFor,
-    CarProc
->;
-
-struct cont_activation_record_t : CarVariantBase
-{
-    dumb_ptr<effect_t> return_location;
-
-    cont_activation_record_t() = delete;
-    cont_activation_record_t(cont_activation_record_t&&) = default;
-    cont_activation_record_t(const cont_activation_record_t&) = delete;
-    cont_activation_record_t& operator = (cont_activation_record_t&&) = default;
-    cont_activation_record_t& operator = (const cont_activation_record_t&) = delete;
-
-    cont_activation_record_t(CarForEach v, dumb_ptr<effect_t> rl) : CarVariantBase(std::move(v)), return_location(rl) {}
-    cont_activation_record_t(CarFor v, dumb_ptr<effect_t> rl) : CarVariantBase(std::move(v)), return_location(rl) {}
-    cont_activation_record_t(CarProc v, dumb_ptr<effect_t> rl) : CarVariantBase(std::move(v)), return_location(rl) {}
-};
-
-struct status_change_ref_t
-{
-    StatusChange sc_type;
-    BlockId bl_id;
-};
-
-struct invocation : block_list
-{
-    dumb_ptr<invocation> next_invocation; /* used for spells directly associated with a caster: they form a singly-linked list */
-    INVOCATION_FLAG flags;
-
-    dumb_ptr<env_t> env;
-    dumb_ptr<spell_t> spell;
-    BlockId caster;                /* this is the person who originally invoked the spell */
-    BlockId subject;               /* when this person dies, the spell dies with it */
-
-    Timer timer;                 /* spell timer, if any */
-
-    std::vector<cont_activation_record_t> stack;
-
-    int script_pos;            /* Script position; if nonzero, resume the script we were running. */
-    dumb_ptr<effect_t> current_effect;
-    dumb_ptr<effect_t> trigger_effect;   /* If non-nullptr, this is used to spawn a cloned effect based on the same environment */
-    dumb_ptr<effect_t> end_effect;       /* If non-nullptr, this is executed when the spell terminates naturally, e.g. when all status changes have run out or all delays are over. */
-
-    /* Status change references:  for status change updates, keep track of whom we updated where */
-    std::vector<status_change_ref_t> status_change_refv;
-
-};
-} // namespace magic
-
-// inlines for map.hpp
-inline dumb_ptr<magic::invocation> block_list::as_spell() { return dumb_ptr<magic::invocation>(static_cast<magic::invocation *>(this)); }
-inline dumb_ptr<magic::invocation> block_list::is_spell() { return bl_type == BL::SPELL ? as_spell() : nullptr; }
-
-namespace magic
-{
-/* The following is used only by the parser: */
-struct args_rec_t
-{
-    dumb_ptr<std::vector<dumb_ptr<expr_t>>> argvp;
-};
-
-struct proc_t
-{
-    RString name;
-    std::vector<int> argv;
-    dumb_ptr<effect_t> body;
-
-    proc_t()
-    : name()
-    , argv()
-    , body()
-    {}
-};
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic-interpreter.py b/src/map/magic-interpreter.py
deleted file mode 100644
index 520ab37..0000000
--- a/src/map/magic-interpreter.py
+++ /dev/null
@@ -1,215 +0,0 @@
-class AreaUnion(object):
-    __slots__ = ('_value')
-    name = 'tmwa::map::magic::AreaUnion'
-    enabled = True
-
-    def __init__(self, value):
-        self._value = value
-
-    def display_hint(self):
-        return 'array'
-
-    def to_string(self):
-        return None
-
-    def children(self):
-        v = self._value
-        for i in [0, 1]:
-            yield '[%d]', v['a_union'][i]['impl'].dereference()
-
-    tests = []
-
-class area_t(object):
-    enabled = True
-
-    test_extra = '''
-    #include "../strings/fwd.hpp"
-    using tmwa::operator "" _s;
-
-    inline
-    tmwa::Borrowed<tmwa::map::map_local> fake_map_local_x_dup_for_area_t(tmwa::ZString name)
-    {
-        auto *p = new tmwa::map::map_local{};
-        p->name_ = tmwa::stringish<tmwa::MapName>(name);
-        return tmwa::borrow(*p);
-    }
-    '''
-
-    tests = [
-            ('tmwa::map::magic::area_t(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}'),
-            ('tmwa::map::magic::area_t(tmwa::map::magic::AreaUnion{{tmwa::dumb_ptr<tmwa::map::magic::area_t>::make(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}), tmwa::dumb_ptr<tmwa::map::magic::area_t>::make(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 321, 654})}})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaUnion) = {{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}, {<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 321, y = 654}}, size = 1}}}, size = 2}'),
-            ('tmwa::map::magic::area_t(tmwa::map::magic::AreaRect{tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}, 789, 102})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaRect) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}, width = 789, height = 102}}, size = 80478}'),
-            ('tmwa::map::magic::area_t(tmwa::map::magic::AreaBar{tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 42, 43}, 123, 456, tmwa::DIR::NW})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaBar) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 43}, width = 123, depth = 456, dir = tmwa::DIR::NW}}, size = 112632}'),
-    ]
-
-
-class val_t(object):
-    enabled = True
-
-    test_extra = '''
-    #include "../strings/fwd.hpp"
-    using tmwa::operator "" _s;
-
-    inline
-    tmwa::Borrowed<tmwa::map::map_local> fake_map_local_x_dup_for_val_t(tmwa::ZString name)
-    {
-        auto *p = new tmwa::map::map_local{};
-        p->name_ = tmwa::stringish<tmwa::MapName>(name);
-        return tmwa::borrow(*p);
-    }
-    '''
-
-    tests = [
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValUndef{})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValUndef) = {<No data fields>}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValInt{42})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInt) = {v_int = 42}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValDir{tmwa::DIR::NW})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValDir) = {v_dir = tmwa::DIR::NW}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValString{"Hello"_s})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValString) = {v_string = "Hello"}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValEntityInt{tmwa::wrap<tmwa::BlockId>(123)})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValEntityInt) = {v_eid = 123}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValEntityPtr{tmwa::dumb_ptr<tmwa::map::block_list>()})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValEntityPtr) = {v_entity = 0x0}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValLocation{tmwa::map::magic::location_t{fake_map_local_x_dup_for_val_t("map"_s), 42, 123}})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValLocation) = {v_location = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 123}}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValArea{tmwa::dumb_ptr<tmwa::map::magic::area_t>()})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValArea) = {v_area = 0x0}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValSpell{tmwa::dumb_ptr<tmwa::map::magic::spell_t>()})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValSpell) = {v_spell = 0x0}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValInvocationInt{tmwa::wrap<tmwa::BlockId>(123)})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInvocationInt) = {v_iid = 123}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValInvocationPtr{})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInvocationPtr) = {v_invocation = 0x0}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValFail{})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValFail) = {<No data fields>}}, <No data fields>}'),
-            ('tmwa::map::magic::val_t(tmwa::map::magic::ValNegative1{})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValNegative1) = {<No data fields>}}, <No data fields>}'),
-    ]
-
-
-class ExprAreaUnion(object):
-    __slots__ = ('_value')
-    name = 'tmwa::map::magic::ExprAreaUnion'
-    enabled = True
-
-    def __init__(self, value):
-        self._value = value
-
-    def display_hint(self):
-        return 'array'
-
-    def to_string(self):
-        return None
-
-    def children(self):
-        v = self._value
-        for i in [0, 1]:
-            yield '[%d]', v['a_union'][i]['impl'].dereference()
-
-    tests = []
-
-
-class e_area_t(object):
-    enabled = True
-
-    tests = [
-            ('tmwa::map::magic::e_area_t(tmwa::map::magic::e_location_t())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'),
-            ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaUnion{{tmwa::dumb_ptr<tmwa::map::magic::e_area_t>::make(tmwa::map::magic::e_location_t()), tmwa::dumb_ptr<tmwa::map::magic::e_area_t>::make(tmwa::map::magic::e_location_t())}})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaUnion) = {{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}, {<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}}, <No data fields>}'),
-            ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaRect{tmwa::map::magic::e_location_t(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>()})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaRect) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, height = 0x0}}, <No data fields>}'),
-            ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaBar{tmwa::map::magic::e_location_t(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>()})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaBar) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, depth = 0x0, dir = 0x0}}, <No data fields>}'),
-    ]
-
-
-
-class expr_t(object):
-    enabled = True
-
-    tests = [
-            ('tmwa::map::magic::expr_t(tmwa::map::magic::val_t(tmwa::map::magic::ValUndef()))',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::val_t) = {<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValUndef) = {<No data fields>}}, <No data fields>}}, <No data fields>}'),
-            ('tmwa::map::magic::expr_t(tmwa::map::magic::e_location_t())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'),
-            ('tmwa::map::magic::expr_t(tmwa::map::magic::e_area_t(tmwa::map::magic::e_location_t()))',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::e_area_t) = {<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}, <No data fields>}'),
-            ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprFunApp())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprFunApp) = {funp = (fun_t *) nullptr, line_nr = 0, column = 0, args_nr = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, <No data fields>}'),
-            ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprId{123})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprId) = {e_id = 123}}, <No data fields>}'),
-            ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprField{tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), 42})',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprField) = {expr = 0x0, id = 42}}, <No data fields>}'),
-    ]
-
-
-class effect_t(object):
-    enabled = True
-
-    tests = [
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectSkip{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectSkip) = {<No data fields>}}, next = 0x0}'),
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectAbort{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectAbort) = {<No data fields>}}, next = 0x0}'),
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectAssign{42, tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectAssign) = {id = 42, expr = 0x0}}, next = 0x0}'),
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectForEach{123, tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::map::magic::FOREACH_FILTER::PC}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectForEach) = {id = 123, area = 0x0, body = 0x0, filter = tmwa::map::magic::FOREACH_FILTER::PC}}, next = 0x0}'),
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectFor{42, tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectFor) = {id = 42, start = 0x0, stop = 0x0, body = 0x0}}, next = 0x0}'),
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectIf{tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectIf) = {cond = 0x0, true_branch = 0x0, false_branch = 0x0}}, next = 0x0}'),
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectSleep{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectSleep) = {e_sleep = 0x0}}, next = 0x0}'),
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectScript{tmwa::dumb_ptr<const tmwa::map::ScriptBuffer>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectScript) = {e_script = 0x0}}, next = 0x0}'),
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectBreak{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectBreak) = {<No data fields>}}, next = 0x0}'),
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectOp(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectOp) = {opp = (op_t *) nullptr, args_nr = 0, line_nr = 0, column = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, next = 0x0}'),
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectEnd{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectEnd) = {<No data fields>}}, next = 0x0}'),
-            ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectCall{nullptr, tmwa::dumb_ptr<std::vector<tmwa::dumb_ptr<tmwa::map::magic::expr_t>>>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectCall) = {formalv = nullptr, actualvp = 0x0, body = 0x0}}, next = 0x0}'),
-    ]
-
-
-class spellguard_t(object):
-    enabled = True
-
-    tests = [
-            ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCondition{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCondition) = {s_condition = 0x0}}, next = 0x0}'),
-            ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardMana{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardMana) = {s_mana = 0x0}}, next = 0x0}'),
-            ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCastTime{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCastTime) = {s_casttime = 0x0}}, next = 0x0}'),
-            ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardComponents{tmwa::dumb_ptr<tmwa::map::magic::component_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardComponents) = {s_components = 0x0}}, next = 0x0}'),
-            ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCatalysts{tmwa::dumb_ptr<tmwa::map::magic::component_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCatalysts) = {s_catalysts = 0x0}}, next = 0x0}'),
-            ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardChoice{tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardChoice) = {s_alt = 0x0}}, next = 0x0}'),
-            ('tmwa::map::magic::spellguard_t(tmwa::map::magic::effect_set_t{tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::effect_set_t) = {effect = 0x0, at_trigger = 0x0, at_end = 0x0}}, next = 0x0}'),
-    ]
-
-
-class cont_activation_record_t(object):
-    enabled = True
-
-    tests = [
-            ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarForEach{42, true, tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<std::vector<tmwa::BlockId>>(), 123}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarForEach) = {id = 42, ty_is_spell_not_entity = true, body = 0x0, entities_vp = 0x0, index = 123}}, return_location = 0x0}'),
-            ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarFor{42, tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), 123, 456}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarFor) = {id = 42, body = 0x0, current = 123, stop = 456}}, return_location = 0x0}'),
-            ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarProc{123, nullptr, tmwa::dumb_ptr<tmwa::map::magic::val_t[]>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())',
-                '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarProc) = {args_nr = 123, formalap = nullptr, old_actualpa = 0x0 = {sz = 0}}}, return_location = 0x0}'),
-    ]
diff --git a/src/map/magic-interpreter.t.hpp b/src/map/magic-interpreter.t.hpp
deleted file mode 100644
index e302354..0000000
--- a/src/map/magic-interpreter.t.hpp
+++ /dev/null
@@ -1,85 +0,0 @@
-#pragma once
-//    magic-interpreter.t.hpp - Old magic.
-//
-//    Copyright © 2004-2011 The Mana World Development Team
-//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include "fwd.hpp"
-
-#include "../generic/enum.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-enum class SPELLARG : uint8_t
-{
-    NONE,
-    PC,
-    STRING,
-};
-
-enum class FOREACH_FILTER : uint8_t
-{
-    MOB,
-    PC,
-    ENTITY,
-    TARGET,
-    SPELL,
-    NPC,
-};
-
-namespace e
-{
-enum class SPELL_FLAG : uint8_t
-{
-    ZERO        = 0,
-
-    // spell associated not with caster but with place
-    LOCAL       = 1 << 0,
-    // spell invocation never uttered
-    SILENT      = 1 << 1,
-    // `magic word' only:  don't require spellcasting ability
-    NONMAGIC    = 1 << 2,
-};
-ENUM_BITWISE_OPERATORS(SPELL_FLAG)
-}
-using e::SPELL_FLAG;
-
-namespace e
-{
-enum class INVOCATION_FLAG : uint8_t
-{
-    ZERO        = 0,
-
-    // Bound directly to the caster (i.e., ignore its location)
-    BOUND       = 1 << 0,
-    // Used `abort' to terminate
-    ABORTED     = 1 << 1,
-    // On magical attacks: if we run out of steam, stop attacking altogether
-    STOPATTACK  = 1 << 2,
-};
-ENUM_BITWISE_OPERATORS(INVOCATION_FLAG)
-}
-using e::INVOCATION_FLAG;
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic-stmt.cpp b/src/map/magic-stmt.cpp
deleted file mode 100644
index 1a8085b..0000000
--- a/src/map/magic-stmt.cpp
+++ /dev/null
@@ -1,1547 +0,0 @@
-#include "magic-stmt.hpp"
-//    magic-stmt.cpp - Imperative commands for the magic backend.
-//
-//    Copyright © 2004-2011 The Mana World Development Team
-//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include <cassert>
-
-#include "../compat/attr.hpp"
-#include "../compat/fun.hpp"
-
-#include "../strings/zstring.hpp"
-
-#include "../generic/random2.hpp"
-
-#include "../io/cxxstdio.hpp"
-
-#include "../mmo/cxxstdio_enums.hpp"
-
-#include "../net/timer.hpp"
-
-#include "battle.hpp"
-#include "clif.hpp"
-#include "magic.hpp"
-#include "magic-expr.hpp"
-#include "magic-expr-eval.hpp"
-#include "magic-interpreter.hpp"
-#include "magic-interpreter-base.hpp"
-#include "mob.hpp"
-#include "npc.hpp"
-#include "npc-parse.hpp"
-#include "pc.hpp"
-#include "script-call.hpp"
-#include "skill.hpp"
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-/* used for local spell effects */
-constexpr Species INVISIBLE_NPC = wrap<Species>(127);
-
-static
-void clear_activation_record(cont_activation_record_t *ar)
-{
-    MATCH_BEGIN (*ar)
-    {
-        MATCH_CASE (CarForEach&, c_foreach)
-        {
-            c_foreach.entities_vp.delete_();
-        }
-        MATCH_CASE (CarProc&, c_proc)
-        {
-            c_proc.old_actualpa.delete_();
-        }
-    }
-    MATCH_END ();
-}
-
-static
-void invocation_timer_callback(TimerData *, tick_t, BlockId id)
-{
-    dumb_ptr<invocation> invocation = map_id_is_spell(id);
-
-    assert (invocation);
-    {
-        spell_execute(invocation);
-    }
-}
-
-static
-void clear_stack(dumb_ptr<invocation> invocation_)
-{
-    int i;
-
-    for (i = 0; i < invocation_->stack.size(); i++)
-        clear_activation_record(&invocation_->stack[i]);
-
-    invocation_->stack.clear();
-}
-
-void spell_free_invocation(dumb_ptr<invocation> invocation_)
-{
-    invocation_->status_change_refv.clear();
-
-    if (bool(invocation_->flags & INVOCATION_FLAG::BOUND))
-    {
-        dumb_ptr<map_session_data> e = map_id_is_player(invocation_->subject);
-        if (e)
-            spell_unbind(e, invocation_);
-    }
-
-    clear_stack(invocation_);
-
-    invocation_->timer.cancel();
-
-    magic_free_env(invocation_->env);
-
-    map_delblock(invocation_);
-    map_delobject(invocation_->bl_id, BL::SPELL);    // also frees the object
-//        free(invocation_);
-}
-
-static
-void char_set_weapon_icon(dumb_ptr<map_session_data> subject, int count,
-        StatusChange icon, ItemNameId look)
-{
-    const StatusChange old_icon = subject->attack_spell_icon_override;
-
-    subject->attack_spell_icon_override = icon;
-    subject->attack_spell_look_override = look;
-
-    if (old_icon != StatusChange::ZERO && old_icon != icon)
-        clif_status_change(subject, old_icon, 0);
-
-    clif_fixpcpos(subject);
-    if (count)
-    {
-        clif_changelook(subject, LOOK::WEAPON, unwrap<ItemNameId>(look));
-        if (icon != StatusChange::ZERO)
-            clif_status_change(subject, icon, 1);
-    }
-    else
-    {
-        /* Set it to `normal' */
-        clif_changelook(subject, LOOK::WEAPON,
-                static_cast<uint16_t>(subject->status.weapon));
-    }
-}
-
-static
-void char_set_attack_info(dumb_ptr<map_session_data> subject, interval_t speed, int range)
-{
-    subject->attack_spell_delay = speed;
-    subject->attack_spell_range = range;
-
-    if (speed == interval_t::zero())
-    {
-        pc_calcstatus(subject, 1);
-        clif_updatestatus(subject, SP::ASPD);
-        clif_updatestatus(subject, SP::ATTACKRANGE);
-    }
-    else
-    {
-        subject->aspd = speed;
-        clif_updatestatus(subject, SP::ASPD);
-        clif_updatestatus(subject, SP::ATTACKRANGE);
-    }
-}
-
-void magic_stop_completely(dumb_ptr<map_session_data> c)
-{
-    // Zap all status change references to spells
-    for (StatusChange i : erange(StatusChange(), StatusChange::MAX_STATUSCHANGE))
-        c->sc_data[i].spell_invocation = BlockId();
-
-    while (c->active_spells)
-        spell_free_invocation(c->active_spells);
-
-    if (c->attack_spell_override)
-    {
-        dumb_ptr<invocation> attack_spell = map_id_is_spell(c->attack_spell_override);
-        if (attack_spell)
-            spell_free_invocation(attack_spell);
-        c->attack_spell_override = BlockId();
-        char_set_weapon_icon(c, 0, StatusChange::ZERO, ItemNameId());
-        char_set_attack_info(c, interval_t::zero(), 0);
-    }
-}
-
-/* Spell execution has finished normally or we have been notified by a finished skill timer */
-static
-void try_to_finish_invocation(dumb_ptr<invocation> invocation)
-{
-    if (invocation->status_change_refv.empty() && !invocation->current_effect)
-    {
-        if (invocation->end_effect)
-        {
-            clear_stack(invocation);
-            invocation->current_effect = invocation->end_effect;
-            invocation->end_effect = nullptr;
-            spell_execute(invocation);
-        }
-        else
-            spell_free_invocation(invocation);
-    }
-}
-
-static
-BlockId trigger_spell(BlockId subject, BlockId spell)
-{
-    dumb_ptr<invocation> invocation_ = map_id_is_spell(spell);
-
-    if (!invocation_)
-        return BlockId();
-
-    invocation_ = spell_clone_effect(invocation_);
-
-    spell_bind(map_id_is_player(subject), invocation_);
-    magic_clear_var(&invocation_->env->varu[VAR_CASTER]);
-    invocation_->env->varu[VAR_CASTER] = ValEntityInt{subject};
-
-    return invocation_->bl_id;
-}
-
-static
-void entity_warp(dumb_ptr<block_list> target, Borrowed<map_local> destm, int destx, int desty);
-
-static
-void char_update(dumb_ptr<map_session_data> character)
-{
-    entity_warp(character, character->bl_m, character->bl_x,
-                 character->bl_y);
-}
-
-static
-void timer_callback_effect(TimerData *, tick_t, BlockId id, int data)
-{
-    dumb_ptr<block_list> target = map_id2bl(id);
-    if (target)
-        clif_misceffect(target, data);
-}
-
-static
-void entity_effect(dumb_ptr<block_list> entity, int effect_nr, interval_t delay)
-{
-    Timer(gettick() + delay,
-            std::bind(&timer_callback_effect, ph::_1, ph::_2,
-                entity->bl_id, effect_nr)
-    ).detach();
-}
-
-void magic_unshroud(dumb_ptr<map_session_data> other_char)
-{
-    other_char->state.shroud_active = 0;
-    // Now warp the caster out of and back into here to refresh everyone's display
-    char_update(other_char);
-    clif_displaymessage(other_char->sess, "Your shroud has been dispelled!"_s);
-//        entity_effect(other_char, MAGIC_EFFECT_REVEAL);
-}
-
-static
-void timer_callback_effect_npc_delete(TimerData *, tick_t, BlockId npc_id)
-{
-    dumb_ptr<npc_data> effect_npc = map_id_is_npc(npc_id);
-    npc_free(effect_npc);
-}
-
-static
-dumb_ptr<npc_data> local_spell_effect(Borrowed<map_local> m, int x, int y, int effect,
-        interval_t tdelay)
-{
-    /* 1 minute should be enough for all interesting spell effects, I hope */
-    std::chrono::seconds delay = 30_s;
-    dumb_ptr<npc_data> effect_npc = npc_spawn_text(m, x, y,
-            INVISIBLE_NPC, NpcName(), "?"_s);
-    BlockId effect_npc_id = effect_npc->bl_id;
-
-    entity_effect(effect_npc, effect, tdelay);
-    Timer(gettick() + delay,
-            std::bind(timer_callback_effect_npc_delete, ph::_1, ph::_2,
-                effect_npc_id)
-    ).detach();
-
-    return effect_npc;
-}
-
-static
-int op_sfx(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    interval_t delay = static_cast<interval_t>(ARGINT(2));
-
-    if (args[0].is<ValEntityPtr>())
-    {
-        entity_effect(ARGENTITY(0), ARGINT(1), delay);
-    }
-    else if (args[0].is<ValLocation>())
-    {
-        local_spell_effect(ARGLOCATION(0).m,
-                            ARGLOCATION(0).x,
-                            ARGLOCATION(0).y, ARGINT(1), delay);
-    }
-    else
-        return 1;
-
-    return 0;
-}
-
-static
-int op_instaheal(dumb_ptr<env_t> env, Slice<val_t> args)
-{
-    assert (!env->VAR(VAR_CASTER).is<ValEntityPtr>());
-    ValEntityInt *caster_id = env->VAR(VAR_CASTER).get_if<ValEntityInt>();
-    dumb_ptr<block_list> caster = caster_id
-        ? map_id2bl(caster_id->v_eid) : nullptr;
-    dumb_ptr<block_list> subject = ARGENTITY(0);
-    if (!caster)
-        caster = subject;
-
-    if (caster->bl_type == BL::PC && subject->bl_type == BL::PC)
-    {
-        dumb_ptr<map_session_data> caster_pc = caster->is_player();
-        dumb_ptr<map_session_data> subject_pc = subject->is_player();
-        MAP_LOG_PC(caster_pc, "SPELLHEAL-INSTA PC%d FOR %d"_fmt,
-                subject_pc->status_key.char_id, ARGINT(1));
-    }
-
-    battle_heal(caster, subject, ARGINT(1), ARGINT(2), 0);
-    return 0;
-}
-
-static
-int op_itemheal(dumb_ptr<env_t> env, Slice<val_t> args)
-{
-    dumb_ptr<block_list> subject = ARGENTITY(0);
-    if (subject->bl_type == BL::PC)
-    {
-        pc_itemheal(subject->is_player(),
-                     ARGINT(1), ARGINT(2));
-    }
-    else
-        return op_instaheal(env, args);
-
-    return 0;
-}
-
-namespace e
-{
-enum class Shroud
-{
-    HIDE_NAME_TALKING_FLAG      = 1 << 0,
-    DISAPPEAR_ON_PICKUP_FLAG    = 1 << 1,
-    DISAPPEAR_ON_TALK_FLAG      = 1 << 2,
-};
-ENUM_BITWISE_OPERATORS(Shroud)
-}
-using e::Shroud;
-
-// differs from ARGPC by checking
-#define ARGCHAR(n) (ARGENTITY(n)->is_player())
-
-static
-int op_shroud(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<map_session_data> subject = ARGCHAR(0);
-    Shroud arg = static_cast<Shroud>(ARGINT(1));
-
-    if (!subject)
-        return 0;
-
-    subject->state.shroud_active = 1;
-    subject->state.shroud_hides_name_talking =
-        bool(arg & Shroud::HIDE_NAME_TALKING_FLAG);
-    subject->state.shroud_disappears_on_pickup =
-        bool(arg & Shroud::DISAPPEAR_ON_PICKUP_FLAG);
-    subject->state.shroud_disappears_on_talk =
-        bool(arg & Shroud::DISAPPEAR_ON_TALK_FLAG);
-    return 0;
-}
-
-static
-int op_reveal(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<map_session_data> subject = ARGCHAR(0);
-
-    if (subject && subject->state.shroud_active)
-        magic_unshroud(subject);
-
-    return 0;
-}
-
-static
-int op_message(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<map_session_data> subject = ARGCHAR(0);
-
-    if (subject)
-        clif_displaymessage(subject->sess, ARGSTR(1));
-
-    return 0;
-}
-
-static
-void timer_callback_kill_npc(TimerData *, tick_t, BlockId npc_id)
-{
-    dumb_ptr<npc_data> npc = map_id_is_npc(npc_id);
-    if (npc)
-        npc_free(npc);
-}
-
-static
-int op_messenger_npc(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<npc_data> npc;
-    location_t *loc = &ARGLOCATION(0);
-
-    NpcName npcname = stringish<NpcName>(ARGSTR(2));
-    npc = npc_spawn_text(loc->m, loc->x, loc->y,
-            wrap<Species>(static_cast<uint16_t>(ARGINT(1))), npcname, ARGSTR(3));
-
-    Timer(gettick() + static_cast<interval_t>(ARGINT(4)),
-            std::bind(timer_callback_kill_npc, ph::_1, ph::_2,
-                npc->bl_id)
-    ).detach();
-
-    return 0;
-}
-
-static
-void entity_warp(dumb_ptr<block_list> target, Borrowed<map_local> destm, int destx, int desty)
-{
-    if (target->bl_type == BL::PC || target->bl_type == BL::MOB)
-    {
-
-        switch (target->bl_type)
-        {
-            case BL::PC:
-            {
-                dumb_ptr<map_session_data> character = target->is_player();
-                clif_clearchar(character, BeingRemoveWhy::WARPED);
-                map_delblock(character);
-                character->bl_x = destx;
-                character->bl_y = desty;
-                character->bl_m = destm;
-
-                pc_touch_all_relevant_npcs(character);
-
-                // Note that touching NPCs may have triggered warping and thereby updated x and y:
-                MapName map_name = character->bl_m->name_;
-
-                // Warp part #1: update relevant data, interrupt trading etc.:
-                pc_setpos(character, map_name, character->bl_x, character->bl_y, BeingRemoveWhy::GONE);
-                // Warp part #2: now notify the client
-                clif_changemap(character, map_name,
-                        character->bl_x, character->bl_y);
-                break;
-            }
-            case BL::MOB:
-                target->bl_x = destx;
-                target->bl_y = desty;
-                target->bl_m = destm;
-                clif_fixmobpos(target->is_mob());
-                break;
-        }
-    }
-}
-
-static
-int op_move(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<block_list> subject = ARGENTITY(0);
-    DIR dir = ARGDIR(1);
-
-    int newx = subject->bl_x + dirx[dir];
-    int newy = subject->bl_y + diry[dir];
-
-    if (!bool(map_getcell(subject->bl_m, newx, newy) & MapCell::UNWALKABLE))
-        entity_warp(subject, subject->bl_m, newx, newy);
-
-    return 0;
-}
-
-static
-int op_warp(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<block_list> subject = ARGENTITY(0);
-    location_t *loc = &ARGLOCATION(1);
-
-    entity_warp(subject, loc->m, loc->x, loc->y);
-
-    return 0;
-}
-
-static
-int op_banish(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<block_list> subject = ARGENTITY(0);
-
-    if (subject->bl_type == BL::MOB)
-    {
-        dumb_ptr<mob_data> mob = subject->is_mob();
-
-        if (bool(mob->mode & MobMode::SUMMONED))
-            mob_catch_delete(mob, BeingRemoveWhy::WARPED);
-    }
-
-    return 0;
-}
-
-static
-void record_status_change(dumb_ptr<invocation> invocation_, BlockId bl_id,
-        StatusChange sc_id)
-{
-    status_change_ref_t cr {};
-    cr.sc_type = sc_id;
-    cr.bl_id = bl_id;
-
-    invocation_->status_change_refv.push_back(cr);
-}
-
-static
-int op_status_change(dumb_ptr<env_t> env, Slice<val_t> args)
-{
-    dumb_ptr<block_list> subject = ARGENTITY(0);
-    assert (!env->VAR(VAR_INVOCATION).is<ValInvocationPtr>());
-    ValInvocationInt *ii = env->VAR(VAR_INVOCATION).get_if<ValInvocationInt>();
-    BlockId invocation_id = ii
-        ? ii->v_iid : BlockId();
-    dumb_ptr<invocation> invocation_ = map_id_is_spell(invocation_id);
-
-    assert (!ARGINT(3));
-    assert (!ARGINT(4));
-    assert (!ARGINT(5));
-    skill_status_effect(subject, static_cast<StatusChange>(ARGINT(1)),
-            ARGINT(2),
-            static_cast<interval_t>(ARGINT(6)), invocation_id);
-
-    if (invocation_ && subject->bl_type == BL::PC)
-        record_status_change(invocation_, subject->bl_id, static_cast<StatusChange>(ARGINT(1)));
-
-    return 0;
-}
-
-static
-int op_stop_status_change(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<block_list> subject = ARGENTITY(0);
-
-    StatusChange sc = static_cast<StatusChange>(ARGINT(1));
-    skill_status_change_end(subject, sc, nullptr);
-
-    return 0;
-}
-
-static
-int op_override_attack(dumb_ptr<env_t> env, Slice<val_t> args)
-{
-    dumb_ptr<block_list> psubject = ARGENTITY(0);
-    int charges = ARGINT(1);
-    interval_t attack_delay = static_cast<interval_t>(ARGINT(2));
-    int attack_range = ARGINT(3);
-    StatusChange icon = StatusChange(ARGINT(4));
-    ItemNameId look = wrap<ItemNameId>(static_cast<uint16_t>(ARGINT(5)));
-    int stopattack = ARGINT(6);
-    dumb_ptr<map_session_data> subject;
-
-    if (psubject->bl_type != BL::PC)
-        return 0;
-
-    subject = psubject->is_player();
-
-    if (subject->attack_spell_override)
-    {
-        dumb_ptr<invocation> old_invocation = map_id_is_spell(subject->attack_spell_override);
-        if (old_invocation)
-            spell_free_invocation(old_invocation);
-    }
-
-    ValInvocationInt *ii = env->VAR(VAR_INVOCATION).get_if<ValInvocationInt>();
-    subject->attack_spell_override =
-        trigger_spell(subject->bl_id, ii->v_iid);
-    subject->attack_spell_charges = charges;
-
-    if (subject->attack_spell_override)
-    {
-        dumb_ptr<invocation> attack_spell = map_id_is_spell(subject->attack_spell_override);
-        if (attack_spell && stopattack)
-            attack_spell->flags |= INVOCATION_FLAG::STOPATTACK;
-
-        char_set_weapon_icon(subject, charges, icon, look);
-        char_set_attack_info(subject, attack_delay, attack_range);
-    }
-
-    return 0;
-}
-
-static
-int op_create_item(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    Item item;
-    dumb_ptr<block_list> entity = ARGENTITY(0);
-    dumb_ptr<map_session_data> subject;
-    int stackable;
-    int count = ARGINT(2);
-    if (count <= 0)
-        return 0;
-
-    if (entity->bl_type == BL::PC)
-        subject = entity->is_player();
-    else
-        return 0;
-
-    GET_ARG_ITEM(1, item, stackable);
-
-    if (!stackable)
-        while (count--)
-            pc_additem(subject, &item, 1);
-    else
-        pc_additem(subject, &item, count);
-
-    return 0;
-}
-
-inline
-bool AGGRAVATION_MODE_ATTACKS_CASTER(int n)
-{
-    return n == 0 || n == 2;
-}
-inline
-bool AGGRAVATION_MODE_MAKES_AGGRESSIVE(int n)
-{
-    return n > 0;
-}
-
-static
-int op_aggravate(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<block_list> victim = ARGENTITY(2);
-    int mode = ARGINT(1);
-    dumb_ptr<block_list> target = ARGENTITY(0);
-    dumb_ptr<mob_data> other;
-
-    if (target->bl_type == BL::MOB)
-        other = target->is_mob();
-    else
-        return 0;
-
-    mob_target(other, victim, battle_get_range(victim));
-
-    if (AGGRAVATION_MODE_MAKES_AGGRESSIVE(mode))
-        other->mode = MobMode::war | (other->mode & MobMode::SENSIBLE_MASK);
-
-    if (AGGRAVATION_MODE_ATTACKS_CASTER(mode))
-    {
-        other->target_id = victim->bl_id;
-        other->attacked_id = victim->bl_id;
-    }
-
-    return 0;
-}
-
-enum class MonsterAttitude
-{
-    HOSTILE     = 0,
-    FRIENDLY    = 1,
-    SERVANT     = 2,
-    FROZEN      = 3,
-};
-
-static
-int op_spawn(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<area_t> area = ARGAREA(0);
-    dumb_ptr<block_list> owner_e = ARGENTITY(1);
-    Species monster_id = wrap<Species>(ARGINT(2));
-    MonsterAttitude monster_attitude = static_cast<MonsterAttitude>(ARGINT(3));
-    int monster_count = ARGINT(4);
-    interval_t monster_lifetime = static_cast<interval_t>(ARGINT(5));
-    int i;
-
-    dumb_ptr<map_session_data> owner = nullptr;
-    if (monster_attitude == MonsterAttitude::SERVANT
-        && owner_e->bl_type == BL::PC)
-        owner = owner_e->is_player();
-
-    for (i = 0; i < monster_count; i++)
-    {
-        location_t loc;
-        magic_random_location(&loc, area);
-
-        BlockId mob_id;
-        dumb_ptr<mob_data> mob;
-
-        mob_id = mob_once_spawn(owner, loc.m->name_, loc.x, loc.y, JAPANESE_NAME,    // Is that needed?
-                monster_id, 1, NpcEvent());
-
-        mob = map_id_is_mob(mob_id);
-
-        if (mob)
-        {
-            mob->mode = get_mob_db(monster_id).mode;
-
-            switch (monster_attitude)
-            {
-                case MonsterAttitude::SERVANT:
-                    mob->state.special_mob_ai = 1;
-                    mob->mode |= MobMode::AGGRESSIVE;
-                    break;
-
-                case MonsterAttitude::FRIENDLY:
-                    mob->mode = MobMode::CAN_ATTACK | (mob->mode & MobMode::CAN_MOVE);
-                    break;
-
-                case MonsterAttitude::HOSTILE:
-                    mob->mode = MobMode::CAN_ATTACK | MobMode::AGGRESSIVE | (mob->mode & MobMode::CAN_MOVE);
-                    if (owner)
-                    {
-                        mob->target_id = owner->bl_id;
-                        mob->attacked_id = owner->bl_id;
-                    }
-                    break;
-
-                case MonsterAttitude::FROZEN:
-                    mob->mode = MobMode::ZERO;
-                    break;
-            }
-
-            mob->mode |=
-                MobMode::SUMMONED | MobMode::TURNS_AGAINST_BAD_MASTER;
-
-            mob->deletetimer = Timer(gettick() + monster_lifetime,
-                    std::bind(mob_timer_delete, ph::_1, ph::_2,
-                        mob_id));
-
-            if (owner)
-            {
-                mob->master_id = owner->bl_id;
-                mob->master_dist = 6;
-            }
-        }
-    }
-
-    return 0;
-}
-
-static
-ZString get_invocation_name(dumb_ptr<env_t> env)
-{
-    assert (!env->VAR(VAR_INVOCATION).is<ValInvocationPtr>());
-
-    ValInvocationInt *ii = env->VAR(VAR_INVOCATION).get_if<ValInvocationInt>();
-    if (!ii)
-        return "?"_s;
-
-    dumb_ptr<invocation> invocation_;
-    invocation_ = map_id_is_spell(ii->v_iid);
-
-    if (invocation_)
-        return invocation_->spell->name;
-    else
-        return "??"_s;
-}
-
-static
-int op_injure(dumb_ptr<env_t> env, Slice<val_t> args)
-{
-    dumb_ptr<block_list> caster = ARGENTITY(0);
-    dumb_ptr<block_list> target = ARGENTITY(1);
-    int damage_caused = ARGINT(2);
-    int mp_damage = ARGINT(3);
-    int target_hp = battle_get_hp(target);
-    int mdef = battle_get_mdef(target);
-
-    if (target->bl_type == BL::PC                       // target is player
-        && !target->bl_m->flag.get(MapFlag::PVP)        // there is no pvpon flag
-        && (caster->bl_type == BL::PC)                  // caster is player
-        && ((target->is_player()->state.pvpchannel == 0)
-            || ((caster->is_player()->state.pvpchannel > 0)
-                && (target->is_player()->state.pvpchannel != caster->is_player()->state.pvpchannel))))
-        return 0;               /* Cannot damage other players outside of pvp */
-
-    if (target != caster)
-    {
-        /* Not protected against own spells */
-        damage_caused = (damage_caused * (100 - mdef)) / 100;
-        mp_damage = (mp_damage * (100 - mdef)) / 100;
-    }
-
-    damage_caused = (damage_caused > target_hp) ? target_hp : damage_caused;
-
-    if (damage_caused < 0)
-        damage_caused = 0;
-
-    // display damage first, because dealing damage may deallocate the target.
-    clif_damage(caster, target,
-            gettick(), interval_t::zero(), interval_t::zero(),
-            damage_caused, 0, DamageType::NORMAL);
-
-    if (caster->bl_type == BL::PC)
-    {
-        dumb_ptr<map_session_data> caster_pc = caster->is_player();
-        if (target->bl_type == BL::MOB)
-        {
-            dumb_ptr<mob_data> mob = target->is_mob();
-
-            MAP_LOG_PC(caster_pc, "SPELLDMG MOB%d %d FOR %d BY %s"_fmt,
-                    mob->bl_id, mob->mob_class, damage_caused,
-                    get_invocation_name(env));
-        }
-    }
-    battle_damage(caster, target, damage_caused, mp_damage);
-
-    return 0;
-}
-
-static
-int op_emote(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<block_list> victim = ARGENTITY(0);
-    int emotion = ARGINT(1);
-    clif_emotion(victim, emotion);
-
-    return 0;
-}
-
-static
-int op_set_script_variable(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<map_session_data> c = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr;
-    VarName varname = stringish<VarName>(ARGSTR(1));
-    int array_index = 0;
-
-    if (!c)
-        return 1;
-
-    set_script_var_i(c, varname, array_index, ARGINT(2));
-
-    return 0;
-}
-
-static
-int op_set_script_str(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<map_session_data> c = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr;
-    VarName varname = stringish<VarName>(ARGSTR(1));
-    int array_index = 0;
-
-    if (!c)
-        return 1;
-
-    set_script_var_s(c, varname, array_index, ARGSTR(2));
-
-    return 0;
-}
-
-static
-int op_set_hair_colour(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<map_session_data> c = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr;
-
-    if (!c)
-        return 1;
-
-    pc_changelook(c, LOOK::HAIR_COLOR, ARGINT(1));
-
-    return 0;
-}
-
-static
-int op_set_hair_style(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<map_session_data> c = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr;
-
-    if (!c)
-        return 1;
-
-    pc_changelook(c, LOOK::HAIR, ARGINT(1));
-
-    return 0;
-}
-
-static
-int op_drop_item_for (dumb_ptr<env_t>, Slice<val_t> args)
-{
-    Item item;
-    int stackable;
-    location_t *loc = &ARGLOCATION(0);
-    int count = ARGINT(2);
-    interval_t interval = static_cast<interval_t>(ARGINT(3));
-    dumb_ptr<map_session_data> c = ((args.size() > 4) && (ENTITY_TYPE(4) == BL::PC)) ? ARGPC(4) : nullptr;
-    interval_t delay = (args.size() > 5) ? static_cast<interval_t>(ARGINT(5)) : interval_t::zero();
-    interval_t delaytime[3] = { delay, delay, delay };
-    dumb_ptr<map_session_data> owners[3] = { c, nullptr, nullptr };
-
-    GET_ARG_ITEM(1, item, stackable);
-
-    if (stackable)
-    {
-        map_addflooritem_any(&item, count, loc->m, loc->x, loc->y,
-                owners, delaytime, interval, 0);
-    }
-    else
-    {
-        while (count-- > 0)
-            map_addflooritem_any(&item, 1, loc->m, loc->x, loc->y,
-                    owners, delaytime, interval, 0);
-    }
-
-    return 0;
-}
-
-static
-int op_gain_exp(dumb_ptr<env_t>, Slice<val_t> args)
-{
-    dumb_ptr<map_session_data> c = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr;
-
-    if (!c)
-        return 1;
-
-    pc_gainexp_reason(c, ARGINT(1), ARGINT(2),
-            static_cast<PC_GAINEXP_REASON>(ARGINT(3)));
-    return 0;
-}
-
-#define MAGIC_OPERATION(name, args, impl) {{name}, {{name}, {args}, impl}}
-#define MAGIC_OPERATION1(name, args) MAGIC_OPERATION(#name##_s, args, op_##name)
-static
-std::map<ZString, op_t> operations =
-{
-    MAGIC_OPERATION1(sfx, ".ii"_s),
-    MAGIC_OPERATION1(instaheal, "eii"_s),
-    MAGIC_OPERATION1(itemheal, "eii"_s),
-    MAGIC_OPERATION1(shroud, "ei"_s),
-    MAGIC_OPERATION("unshroud"_s, "e"_s, op_reveal),
-    MAGIC_OPERATION1(message, "es"_s),
-    MAGIC_OPERATION1(messenger_npc, "lissi"_s),
-    MAGIC_OPERATION1(move, "ed"_s),
-    MAGIC_OPERATION1(warp, "el"_s),
-    MAGIC_OPERATION1(banish, "e"_s),
-    MAGIC_OPERATION1(status_change, "eiiiiii"_s),
-    MAGIC_OPERATION1(stop_status_change, "ei"_s),
-    MAGIC_OPERATION1(override_attack, "eiiiiii"_s),
-    MAGIC_OPERATION1(create_item, "e.i"_s),
-    MAGIC_OPERATION1(aggravate, "eie"_s),
-    MAGIC_OPERATION1(spawn, "aeiiii"_s),
-    MAGIC_OPERATION1(injure, "eeii"_s),
-    MAGIC_OPERATION1(emote, "ei"_s),
-    MAGIC_OPERATION1(set_script_variable, "esi"_s),
-    MAGIC_OPERATION1(set_script_str, "ess"_s),
-    MAGIC_OPERATION1(set_hair_colour, "ei"_s),
-    MAGIC_OPERATION1(set_hair_style, "ei"_s),
-    MAGIC_OPERATION("drop_item"_s, "l.ii"_s, op_drop_item_for),
-    MAGIC_OPERATION1(drop_item_for, "l.iiei"_s),
-    MAGIC_OPERATION("gain_experience"_s, "eiii"_s, op_gain_exp),
-};
-
-op_t *magic_get_op(ZString name)
-{
-    auto it = operations.find(name);
-    if (it == operations.end())
-        return nullptr;
-    return &it->second;
-}
-
-void spell_effect_report_termination(BlockId invocation_id, BlockId bl_id,
-        StatusChange sc_id, int)
-{
-    dumb_ptr<invocation> invocation_ = map_id_is_spell(invocation_id);
-
-    if (!invocation_ || invocation_->bl_type != BL::SPELL)
-        return;
-
-    for (status_change_ref_t& cr : invocation_->status_change_refv)
-    {
-        if (cr.sc_type == sc_id && cr.bl_id == bl_id)
-        {
-            if (&cr != &invocation_->status_change_refv.back())
-                std::swap(cr, invocation_->status_change_refv.back());
-            invocation_->status_change_refv.pop_back();
-
-            try_to_finish_invocation(invocation_);
-            return;
-        }
-    }
-
-    {
-        dumb_ptr<block_list> entity = map_id2bl(bl_id);
-        if (entity->bl_type == BL::PC)
-            FPRINTF(stderr,
-                    "[magic] INTERNAL ERROR: spell-effect-report-termination:  tried to terminate on unexpected bl %d, sc %d\n"_fmt,
-                    bl_id, sc_id);
-        return;
-    }
-
-}
-
-static
-dumb_ptr<effect_t> return_to_stack(dumb_ptr<invocation> invocation_)
-{
-    if (invocation_->stack.empty())
-        return nullptr;
-    else
-    {
-        cont_activation_record_t *ar =
-            &invocation_->stack.back();
-        MATCH_BEGIN (*ar)
-        {
-            MATCH_CASE (const CarProc&, c_proc)
-            {
-                dumb_ptr<effect_t> ret = ar->return_location;
-                for (int i = 0; i < c_proc.args_nr; i++)
-                {
-                    val_t *var =
-                        &invocation_->env->varu[c_proc.formalap[i]];
-                    magic_clear_var(var);
-                    *var = std::move(c_proc.old_actualpa[i]);
-                }
-
-                // pop the stack
-                clear_activation_record(ar);
-                invocation_->stack.pop_back();
-
-                return ret;
-            }
-            MATCH_CASE (CarForEach&, c_foreach)
-            {
-                BlockId entity_id;
-                val_t *var = &invocation_->env->varu[c_foreach.id];
-
-                do
-                {
-                    // This >= is really supposed to be a ==, but
-                    // I have no clue if it's actually safe to change it.
-                    if (c_foreach.index >= c_foreach.entities_vp->size())
-                    {
-                        // pop the stack
-                        dumb_ptr<effect_t> ret = ar->return_location;
-                        clear_activation_record(ar);
-                        invocation_->stack.pop_back();
-                        return ret;
-                    }
-
-                    entity_id =
-                        (*c_foreach.entities_vp)[c_foreach.index++];
-                }
-                while (!entity_id || !map_id2bl(entity_id));
-
-                magic_clear_var(var);
-                if (c_foreach.ty_is_spell_not_entity)
-                    *var = ValInvocationInt{entity_id};
-                else
-                    *var = ValEntityInt{entity_id};
-
-                return c_foreach.body;
-            }
-            MATCH_CASE (CarFor&, c_for)
-            {
-                if (c_for.current > c_for.stop)
-                {
-                    dumb_ptr<effect_t> ret = ar->return_location;
-                    // pop the stack
-                    clear_activation_record(ar);
-                    invocation_->stack.pop_back();
-                    return ret;
-                }
-
-                magic_clear_var(&invocation_->env->varu[c_for.id]);
-                invocation_->env->varu[c_for.id] = ValInt{c_for.current++};
-
-                return c_for.body;
-            }
-        }
-        MATCH_END ();
-        abort();
-    }
-}
-
-static
-void find_entities_in_area_c(dumb_ptr<block_list> target,
-        std::vector<BlockId> *entities_vp,
-        FOREACH_FILTER filter)
-{
-    switch (target->bl_type)
-    {
-
-        case BL::PC:
-            if (filter == FOREACH_FILTER::PC
-                || filter == FOREACH_FILTER::ENTITY
-                || (filter == FOREACH_FILTER::TARGET
-                    && target->bl_m->flag.get(MapFlag::PVP)))
-                break;
-            else if (filter == FOREACH_FILTER::SPELL)
-            {                   /* Check all spells bound to the caster */
-                dumb_ptr<invocation> invoc = target->is_player()->active_spells;
-                /* Add all spells locked onto thie PC */
-
-                while (invoc)
-                {
-                    entities_vp->push_back(invoc->bl_id);
-                    invoc = invoc->next_invocation;
-                }
-            }
-            return;
-
-        case BL::MOB:
-            if (filter == FOREACH_FILTER::MOB
-                || filter == FOREACH_FILTER::ENTITY
-                || filter == FOREACH_FILTER::TARGET)
-                break;
-            else
-                return;
-
-        case BL::SPELL:
-            if (filter == FOREACH_FILTER::SPELL)
-            {
-                dumb_ptr<invocation> invocation = target->is_spell();
-
-                /* Check whether the spell is `bound'-- if so, we'll consider it iff we see the caster(case BL::PC). */
-                if (bool(invocation->flags & INVOCATION_FLAG::BOUND))
-                    return;
-                else
-                    break;      /* Add the spell */
-            }
-            else
-                return;
-
-        case BL::NPC:
-            if (filter == FOREACH_FILTER::NPC)
-                break;
-            else
-                return;
-
-        default:
-            return;
-    }
-
-    entities_vp->push_back(target->bl_id);
-}
-
-static
-void find_entities_in_area(area_t& area_,
-        std::vector<BlockId> *entities_vp,
-        FOREACH_FILTER filter)
-{
-    MATCH_BEGIN (area_)
-    {
-        MATCH_CASE (const AreaUnion&, a)
-        {
-            find_entities_in_area(*a.a_union[0], entities_vp, filter);
-            find_entities_in_area(*a.a_union[1], entities_vp, filter);
-        }
-        MATCH_CASE (const location_t&, a_loc)
-        {
-            (void)a_loc;
-            // TODO this can be simplified
-            int x, y, width, height;
-            Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_);
-            map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter),
-                    m,
-                    x, y,
-                    x + width, y + height,
-                    BL::NUL /* filter elsewhere */);
-        }
-        MATCH_CASE (const AreaRect&, a_rect)
-        {
-            (void)a_rect;
-            // TODO this can be simplified
-            int x, y, width, height;
-            Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_);
-            map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter),
-                    m,
-                    x, y,
-                    x + width, y + height,
-                    BL::NUL /* filter elsewhere */);
-        }
-        MATCH_CASE (const AreaBar&, a_bar)
-        {
-            (void)a_bar;
-            // TODO this is wrong
-            int x, y, width, height;
-            Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_);
-            map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter),
-                    m,
-                    x, y,
-                    x + width, y + height,
-                    BL::NUL /* filter elsewhere */);
-        }
-    }
-    MATCH_END ();
-}
-
-static
-dumb_ptr<effect_t> run_foreach(dumb_ptr<invocation> invocation,
-        const EffectForEach *foreach,
-        dumb_ptr<effect_t> return_location)
-{
-    const EffectForEach& e_foreach = *foreach;
-
-    val_t area;
-    FOREACH_FILTER filter = e_foreach.filter;
-    int id = e_foreach.id;
-    dumb_ptr<effect_t> body = e_foreach.body;
-
-    magic_eval(invocation->env, &area, e_foreach.area);
-
-    auto va = area.get_if<ValArea>();
-    if (!va)
-    {
-        magic_clear_var(&area);
-        FPRINTF(stderr,
-                "[magic] Error in spell `%s':  FOREACH loop over non-area\n"_fmt,
-                invocation->spell->name);
-        return return_location;
-    }
-
-    {
-        std::vector<BlockId> entities_v;
-        find_entities_in_area(*va->v_area,
-                &entities_v, filter);
-        entities_v.shrink_to_fit();
-        // iterator_pair will go away when this gets properly containerized.
-        random_::shuffle(entities_v);
-
-        CarForEach c_foreach;
-        c_foreach.id = id;
-        c_foreach.body = body;
-        c_foreach.index = 0;
-        c_foreach.entities_vp.new_(std::move(entities_v));
-        c_foreach.ty_is_spell_not_entity =
-            (filter == FOREACH_FILTER::SPELL);
-        invocation->stack.emplace_back(c_foreach, return_location);
-
-        magic_clear_var(&area);
-
-        return return_to_stack(invocation);
-    }
-}
-
-static
-dumb_ptr<effect_t> run_for (dumb_ptr<invocation> invocation,
-        const EffectFor *for_,
-        dumb_ptr<effect_t> return_location)
-{
-    const EffectFor& e_for = *for_;
-
-    int id = e_for.id;
-    val_t start;
-    val_t stop;
-
-    magic_eval(invocation->env, &start, e_for.start);
-    magic_eval(invocation->env, &stop, e_for.stop);
-
-    if (!start.is<ValInt>() || !stop.is<ValInt>())
-    {
-        magic_clear_var(&start);
-        magic_clear_var(&stop);
-        FPRINTF(stderr,
-                "[magic] Error in spell `%s':  FOR loop start or stop point is not an integer\n"_fmt,
-                invocation->spell->name);
-        return return_location;
-    }
-
-    CarFor c_for;
-
-    c_for.id = id;
-    c_for.current = start.get_if<ValInt>()->v_int;
-    c_for.stop = stop.get_if<ValInt>()->v_int;
-    c_for.body = e_for.body;
-    invocation->stack.emplace_back(c_for, return_location);
-
-    return return_to_stack(invocation);
-}
-
-static
-dumb_ptr<effect_t> run_call(dumb_ptr<invocation> invocation,
-        const EffectCall *call,
-        dumb_ptr<effect_t> return_location)
-{
-    const EffectCall& e_call = *call;
-
-    int args_nr = e_call.formalv->size();
-    int *formals = e_call.formalv->data();
-    auto old_actuals = dumb_ptr<val_t[]>::make(args_nr);
-
-    CarProc c_proc;
-    c_proc.args_nr = args_nr;
-    c_proc.formalap = formals;
-    c_proc.old_actualpa = old_actuals;
-    invocation->stack.emplace_back(c_proc, return_location);
-
-    for (int i = 0; i < args_nr; i++)
-    {
-        val_t *env_val = &invocation->env->varu[formals[i]];
-        magic_copy_var(&old_actuals[i], env_val);
-        magic_eval(invocation->env, env_val, (*e_call.actualvp)[i]);
-    }
-
-    return e_call.body;
-}
-
-/**
- * Execute a spell invocation until we abort, finish, or hit the next `sleep'.
- *
- * Use spell_execute() to automate handling of timers
- *
- * Returns: 0 if finished (all memory is freed implicitly)
- *          >1 if we hit `sleep'; the result is the number of ticks we should sleep for.
- *          -1 if we paused to wait for a user action (via script interaction)
- */
-static
-interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete)
-{
-    const BlockId invocation_id = invocation_->bl_id;
-#define REFRESH_INVOCATION invocation_ = map_id_is_spell(invocation_id); if (!invocation_) return interval_t::zero();
-
-    while (invocation_->current_effect)
-    {
-        dumb_ptr<effect_t> e = invocation_->current_effect;
-        dumb_ptr<effect_t> next = e->next;
-        int i;
-
-        MATCH_BEGIN (*e)
-        {
-            MATCH_CASE (const EffectSkip&, e_)
-            {
-                (void)e_;
-            }
-            MATCH_CASE (const EffectAbort&, e_)
-            {
-                (void)e_;
-                invocation_->flags |= INVOCATION_FLAG::ABORTED;
-                invocation_->end_effect = nullptr;
-                clear_stack(invocation_);
-                next = nullptr;
-            }
-            MATCH_CASE (const EffectEnd&, e_)
-            {
-                (void)e_;
-                clear_stack(invocation_);
-                next = nullptr;
-            }
-            MATCH_CASE (const EffectAssign&, e_assign)
-            {
-                magic_eval(invocation_->env,
-                            &invocation_->env->varu[e_assign.id],
-                            e_assign.expr);
-            }
-            MATCH_CASE (const EffectForEach&, e_foreach)
-            {
-                next = run_foreach(invocation_, &e_foreach, next);
-            }
-            MATCH_CASE (const EffectFor&, e_for)
-            {
-                next = run_for (invocation_, &e_for, next);
-            }
-            MATCH_CASE (const EffectIf&, e_if)
-            {
-                if (magic_eval_int(invocation_->env, e_if.cond))
-                    next = e_if.true_branch;
-                else
-                    next = e_if.false_branch;
-            }
-            MATCH_CASE (const EffectSleep&, e_)
-            {
-                interval_t sleeptime = static_cast<interval_t>(
-                        magic_eval_int(invocation_->env, e_.e_sleep));
-                invocation_->current_effect = next;
-                if (sleeptime > interval_t::zero())
-                    return sleeptime;
-            }
-            MATCH_CASE (const EffectScript&, e_)
-            {
-                dumb_ptr<map_session_data> caster = map_id_is_player(invocation_->caster);
-                if (caster)
-                {
-                    dumb_ptr<env_t> env = invocation_->env;
-                    ZString caster_name = (caster ? caster->status_key.name : CharName()).to__actual();
-                    argrec_t arg[1] =
-                    {
-                        {"@caster_name$"_s, caster_name},
-                    };
-                    assert (!env->VAR(VAR_SCRIPTTARGET).is<ValEntityPtr>());
-                    ValEntityInt *ei = env->VAR(VAR_SCRIPTTARGET).get_if<ValEntityInt>();
-                    BlockId message_recipient =
-                        ei
-                        ? ei->v_eid
-                        : invocation_->caster;
-                    dumb_ptr<map_session_data> recipient = map_id_is_player(message_recipient);
-
-                    if (recipient->npc_id
-                        && recipient->npc_id != invocation_->bl_id)
-                        goto break_match;  /* Don't send multiple message boxes at once */
-
-                    if (!invocation_->script_pos)    // first time running this script?
-                        clif_spawn_fake_npc_for_player(recipient,
-                                invocation_->bl_id);
-                    // We have to do this or otherwise the client won't think that it's
-                    // dealing with an NPC
-
-                    int newpos = run_script_l(
-                            ScriptPointer(borrow(*e_.e_script), invocation_->script_pos),
-                            message_recipient, invocation_->bl_id,
-                            arg);
-                    /* Returns the new script position, or -1 once the script is finished */
-                    if (newpos != -1)
-                    {
-                        /* Must set up for continuation */
-                        recipient->npc_id = invocation_->bl_id;
-                        recipient->npc_pos = invocation_->script_pos = newpos;
-                        return static_cast<interval_t>(-1);  /* Signal `wait for script' */
-                    }
-                    else
-                        invocation_->script_pos = 0;
-                    clif_clearchar_id(invocation_->bl_id, BeingRemoveWhy::DEAD, caster->sess);
-                }
-                REFRESH_INVOCATION; // Script may have killed the caster
-            }
-            MATCH_CASE (const EffectBreak&, e_)
-            {
-                (void)e_;
-                next = return_to_stack(invocation_);
-            }
-            MATCH_CASE (const EffectOp&, e_op)
-            {
-                op_t *op = e_op.opp;
-                val_t args[MAX_ARGS];
-
-                for (i = 0; i < e_op.args_nr; i++)
-                    magic_eval(invocation_->env, &args[i], e_op.args[i]);
-
-                if (!magic_signature_check("effect"_s, op->name, op->signature,
-                                            Slice<val_t>(args, e_op.args_nr),
-                                            e_op.line_nr,
-                                            e_op.column))
-                    op->op(invocation_->env, Slice<val_t>(args, e_op.args_nr));
-
-                for (i = 0; i < e_op.args_nr; i++)
-                    magic_clear_var(&args[i]);
-
-                REFRESH_INVOCATION; // Effect may have killed the caster
-            }
-            MATCH_CASE (const EffectCall&, e_call)
-            {
-                next = run_call(invocation_, &e_call, next);
-            }
-        }
-        MATCH_END ();
-
-    break_match:
-        if (!next)
-            next = return_to_stack(invocation_);
-
-        invocation_->current_effect = next;
-    }
-
-    if (allow_delete)
-        try_to_finish_invocation(invocation_);
-    return interval_t::zero();
-#undef REFRESH_INVOCATION
-}
-
-static
-void spell_execute_d(dumb_ptr<invocation> invocation, int allow_deletion)
-{
-    spell_update_location(invocation);
-    interval_t delta = spell_run(invocation, allow_deletion);
-
-    if (delta > interval_t::zero())
-    {
-        assert (!invocation->timer);
-        invocation->timer = Timer(gettick() + delta,
-                std::bind(invocation_timer_callback, ph::_1, ph::_2,
-                    invocation->bl_id));
-    }
-
-    /* If 0, the script cleaned itself.  If -1(wait-for-script), we must wait for the user. */
-}
-
-void spell_execute(dumb_ptr<invocation> invocation)
-{
-    spell_execute_d(invocation, 1);
-}
-
-void spell_execute_script(dumb_ptr<invocation> invocation)
-{
-    if (invocation->script_pos)
-        spell_execute_d(invocation, 1);
-    /* Otherwise the script-within-the-spell has been terminated by some other means.
-     * In practice this happens when the script doesn't wait for user input: the client
-     * may still notify the server that it's done.  Without the above check, we'd be
-     * running the same spell twice! */
-}
-
-int spell_attack(BlockId caster_id, BlockId target_id)
-{
-    dumb_ptr<map_session_data> caster = map_id_is_player(caster_id);
-    dumb_ptr<invocation> invocation_;
-    int stop_attack = 0;
-
-    if (!caster)
-        return 0;
-
-    invocation_ = map_id_is_spell(caster->attack_spell_override);
-
-    if (invocation_ && bool(invocation_->flags & INVOCATION_FLAG::STOPATTACK))
-        stop_attack = 1;
-
-    if (invocation_ && caster->attack_spell_charges > 0)
-    {
-        magic_clear_var(&invocation_->env->varu[VAR_TARGET]);
-        invocation_->env->varu[VAR_TARGET] = ValEntityInt{target_id};
-
-        invocation_->current_effect = invocation_->trigger_effect;
-        invocation_->flags &= ~INVOCATION_FLAG::ABORTED;
-        spell_execute_d(invocation_,
-                         0 /* don't delete the invocation if done */ );
-
-        // If the caster died, we need to refresh here:
-        invocation_ = map_id_is_spell(caster->attack_spell_override);
-
-        if (invocation_ && !bool(invocation_->flags & INVOCATION_FLAG::ABORTED))   // If we didn't abort:
-            caster->attack_spell_charges--;
-    }
-
-    if (invocation_ && caster->attack_spell_override != invocation_->bl_id)
-    {
-        /* Attack spell changed / was refreshed */
-        // spell_free_invocation(invocation); // [Fate] This would be a double free.
-    }
-    else if (!invocation_ || caster->attack_spell_charges <= 0)
-    {
-        caster->attack_spell_override = BlockId();
-        char_set_weapon_icon(caster, 0, StatusChange::ZERO, ItemNameId());
-        char_set_attack_info(caster, interval_t::zero(), 0);
-
-        if (stop_attack)
-            pc_stopattack(caster);
-
-        if (invocation_)
-            spell_free_invocation(invocation_);
-    }
-
-    return 1;
-}
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic-stmt.hpp b/src/map/magic-stmt.hpp
deleted file mode 100644
index 3b04fe3..0000000
--- a/src/map/magic-stmt.hpp
+++ /dev/null
@@ -1,93 +0,0 @@
-#pragma once
-//    magic-stmt.hpp - Imperative commands for the magic backend.
-//
-//    Copyright © 2004-2011 The Mana World Development Team
-//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include "fwd.hpp"
-
-#include "../strings/zstring.hpp"
-
-#include "../mmo/skill.t.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-struct op_t
-{
-    LString name;
-    LString signature;
-    int (*op)(dumb_ptr<env_t> env, Slice<val_t> arga);
-};
-
-/**
- * Retrieves an operation by name
- * @param name The name to look up
- * @return An operation of that name, or nullptr, and a function index
- */
-op_t *magic_get_op(ZString name);
-
-/**
- * Removes the shroud from a character
- *
- * \param character The character to remove the shroud from
- */
-void magic_unshroud(dumb_ptr<map_session_data> character);
-
-/**
- * Notifies a running spell that a status_change timer triggered by the spell has expired
- *
- * \param invocation The invocation to notify
- * \param bl_id ID of the PC for whom this happened
- * \param sc_id ID of the status change entry that finished
- * \param supplanted Whether the status_change finished normally (0) or was supplanted by a new status_change (1)
- */
-void spell_effect_report_termination(BlockId invocation, BlockId bl_id,
-        StatusChange sc_id, int supplanted);
-
-/**
- * Execute a spell invocation and sets up timers to finish
- */
-void spell_execute(dumb_ptr<invocation> invocation);
-
-/**
- * Continue an NPC script embedded in a spell
- */
-void spell_execute_script(dumb_ptr<invocation> invocation);
-
-/**
- * Stops all magic bound to the specified character
- *
- */
-void magic_stop_completely(dumb_ptr<map_session_data> c);
-
-/**
- * Attacks with a magical spell charged to the character
- *
- * Returns 0 if there is no charged spell or the spell is depleted.
- */
-int spell_attack(BlockId caster, BlockId target);
-
-void spell_free_invocation(dumb_ptr<invocation> invocation);
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic-stmt.py b/src/map/magic-stmt.py
deleted file mode 100644
index 7cc43d0..0000000
--- a/src/map/magic-stmt.py
+++ /dev/null
@@ -1,37 +0,0 @@
-class op_t(object):
-    __slots__ = ('_value')
-
-    name = 'tmwa::map::magic::op_t'
-    depth = 1
-    enabled = True
-
-    def __init__(self, value):
-        if not value:
-            value = None
-        self._value = value
-
-    def to_string(self):
-        value = self._value
-        if value is None:
-            return '(op_t *) nullptr'
-        return '(op_t *)'
-
-    def children(self):
-        value = self._value
-        if value is None:
-            return
-        value = value.dereference()
-        yield '->name', value['name']
-        yield '->signature', value['signature']
-        yield '->op', value['op']
-
-    test_extra = '''
-    using tmwa::operator "" _s;
-    '''
-
-    tests = [
-            ('static_cast<tmwa::map::magic::op_t *>(nullptr)',
-                '(op_t *) nullptr'),
-            ('new tmwa::map::magic::op_t{"name"_s, "sig"_s, nullptr}',
-                '(op_t *) = {->name = "name", ->signature = "sig", ->op = nullptr}'),
-    ]
diff --git a/src/map/magic-v2.cpp b/src/map/magic-v2.cpp
deleted file mode 100644
index 52b1b8f..0000000
--- a/src/map/magic-v2.cpp
+++ /dev/null
@@ -1,1295 +0,0 @@
-#include "magic-v2.hpp"
-//    magic-v2.cpp - second generation magic parser
-//
-//    Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include <cstddef>
-
-#include <algorithm>
-#include <map>
-#include <set>
-
-#include "../range/slice.hpp"
-
-#include "../strings/rstring.hpp"
-#include "../strings/literal.hpp"
-
-#include "../generic/dumb_ptr.hpp"
-
-#include "../io/cxxstdio.hpp"
-#include "../io/line.hpp"
-
-#include "../sexpr/parser.hpp"
-
-#include "../ast/script.hpp"
-
-#include "globals.hpp"
-#include "itemdb.hpp"
-#include "magic-expr.hpp"
-#include "magic-interpreter.hpp"
-#include "magic-interpreter-base.hpp"
-#include "magic-stmt.hpp"
-#include "script-parse.hpp"
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-namespace magic_v2
-{
-    static
-    size_t intern_id(ZString id_name)
-    {
-        // TODO use InternPool
-        size_t i;
-        for (i = 0; i < magic_conf.varv.size(); i++)
-            if (id_name == magic_conf.varv[i].name)
-                return i;
-
-        // i = magic_conf.varv.size();
-        /* Must add new */
-        magic_conf_t::mcvar new_var {};
-        new_var.name = id_name;
-        new_var.val = ValUndef();
-        magic_conf.varv.push_back(std::move(new_var));
-
-        return i;
-    }
-    inline
-    bool INTERN_ASSERT(ZString name, int id)
-    {
-        int zid = intern_id(name);
-        if (zid != id)
-        {
-            FPRINTF(stderr,
-                    "[magic-conf] INTERNAL ERROR: Builtin special var %s interned to %d, not %d as it should be!\n"_fmt,
-                    name, zid, id);
-        }
-        return zid == id;
-    }
-
-    static
-    bool init0()
-    {
-        bool ok = true;
-
-        ok &= INTERN_ASSERT("min_casttime"_s, VAR_MIN_CASTTIME);
-        ok &= INTERN_ASSERT("obscure_chance"_s, VAR_OBSCURE_CHANCE);
-        ok &= INTERN_ASSERT("caster"_s, VAR_CASTER);
-        ok &= INTERN_ASSERT("spellpower"_s, VAR_SPELLPOWER);
-        ok &= INTERN_ASSERT("self_spell"_s, VAR_SPELL);
-        ok &= INTERN_ASSERT("self_invocation"_s, VAR_INVOCATION);
-        ok &= INTERN_ASSERT("target"_s, VAR_TARGET);
-        ok &= INTERN_ASSERT("script_target"_s, VAR_SCRIPTTARGET);
-        ok &= INTERN_ASSERT("location"_s, VAR_LOCATION);
-
-        return ok;
-    }
-
-
-    static
-    bool bind_constant(io::LineSpan span, RString name, val_t val)
-    {
-        if (!const_defm.insert(std::make_pair(name, std::move(val))).second)
-        {
-            span.error(STRPRINTF("Redefinition of constant '%s'"_fmt, name));
-            return false;
-        }
-        return true;
-    }
-    static
-    const val_t *find_constant(RString name)
-    {
-        auto it = const_defm.find(name);
-        if (it != const_defm.end())
-            return &it->second;
-
-        return nullptr;
-    }
-    static
-    dumb_ptr<effect_t> set_effect_continuation(dumb_ptr<effect_t> src, dumb_ptr<effect_t> continuation)
-    {
-        dumb_ptr<effect_t> retval = src;
-        /* This function is completely analogous to `spellguard_implication' above; read the control flow implications above first before pondering it. */
-
-        if (src == continuation)
-            return retval;
-
-        /* For FOR and FOREACH, we use special stack handlers and thus don't have to set
-         * the continuation.  It's only IF that we need to handle in this fashion. */
-        MATCH_BEGIN (*src)
-        {
-            MATCH_CASE (EffectIf&, e_if)
-            {
-                set_effect_continuation(e_if.true_branch, continuation);
-                set_effect_continuation(e_if.false_branch, continuation);
-            }
-        }
-        MATCH_END ();
-
-        if (src->next)
-            set_effect_continuation(src->next, continuation);
-        else
-            src->next = continuation;
-
-        return retval;
-    }
-    static
-    dumb_ptr<spellguard_t> spellguard_implication(dumb_ptr<spellguard_t> a, dumb_ptr<spellguard_t> b)
-    {
-        dumb_ptr<spellguard_t> retval = a;
-
-        if (a == b)
-        {
-            /* This can happen due to reference sharing:
-             * e.g.,
-             *  (R0 -> (R1 | R2)) => (R3)
-             * yields
-             *  (R0 -> (R1 -> R3 | R2 -> R3))
-             *
-             * So if we now add => R4 to that, we want
-             *  (R0 -> (R1 -> R3 -> R4 | R2 -> R3 -> R4))
-             *
-             * but we only need to add it once, because the R3 reference is shared.
-             */
-            return retval;
-        }
-
-        /* If the premise is a disjunction, b is the continuation of _all_ branches */
-        MATCH_BEGIN (*a)
-        {
-            MATCH_CASE (const GuardChoice&, s)
-            {
-                spellguard_implication(s.s_alt, b);
-            }
-        }
-        MATCH_END ();
-
-        if (a->next)
-            spellguard_implication(a->next, b);
-        else
-            // this is the important bit
-            a->next = b;
-
-        return retval;
-    }
-
-
-    static
-    bool add_spell(io::LineSpan span, dumb_ptr<spell_t> spell)
-    {
-        auto pair1 = magic_conf.spells_by_name.insert({spell->name, spell});
-        if (!pair1.second)
-        {
-            span.error(STRPRINTF("Attempt to redefine spell '%s'"_fmt, spell->name));
-            return false;
-        }
-
-        auto pair2 = magic_conf.spells_by_invocation.insert({spell->invocation, spell});
-        if (!pair2.second)
-        {
-            span.error(STRPRINTF("Attempt to redefine spell invocation '%s'"_fmt, spell->invocation));
-            magic_conf.spells_by_name.erase(pair1.first);
-            return false;
-        }
-        return true;
-    }
-    static
-    bool add_teleport_anchor(io::LineSpan span, dumb_ptr<teleport_anchor_t> anchor)
-    {
-        auto pair1 = magic_conf.anchors_by_name.insert({anchor->name, anchor});
-        if (!pair1.second)
-        {
-            span.error(STRPRINTF("Attempt to redefine teleport anchor '%s'"_fmt, anchor->name));
-            return false;
-        }
-
-        auto pair2 = magic_conf.anchors_by_invocation.insert({anchor->name, anchor});
-        if (!pair2.second)
-        {
-            span.error(STRPRINTF("Attempt to redefine anchor invocation '%s'"_fmt, anchor->invocation));
-            magic_conf.anchors_by_name.erase(pair1.first);
-            return false;
-        }
-        return true;
-    }
-
-    static
-    bool install_proc(io::LineSpan span, dumb_ptr<proc_t> proc)
-    {
-        RString name = proc->name;
-        if (!procs.insert({name, std::move(*proc)}).second)
-        {
-            span.error("procedure already exists"_s);
-            return false;
-        }
-        return true;
-    }
-    static
-    bool call_proc(io::LineSpan span, ZString name, dumb_ptr<std::vector<dumb_ptr<expr_t>>> argvp, dumb_ptr<effect_t>& retval)
-    {
-        auto pi = procs.find(name);
-        if (pi == procs.end())
-        {
-            span.error(STRPRINTF("Unknown procedure '%s'"_fmt, name));
-            return false;
-        }
-
-        proc_t *p = &pi->second;
-
-        if (p->argv.size() != argvp->size())
-        {
-            span.error(STRPRINTF("Procedure %s/%zu invoked with %zu parameters"_fmt,
-                        name, p->argv.size(), argvp->size()));
-            return false;
-        }
-
-        EffectCall e_call;
-        e_call.body = p->body;
-        e_call.formalv = &p->argv;
-        e_call.actualvp = argvp;
-        retval = dumb_ptr<effect_t>::make(e_call, nullptr);
-        return true;
-    }
-    static
-    bool op_effect(io::LineSpan span, ZString name, Slice<dumb_ptr<expr_t>> argv, dumb_ptr<effect_t>& effect)
-    {
-        op_t *op = magic_get_op(name);
-        if (!op)
-        {
-            span.error(STRPRINTF("Unknown operation '%s'"_fmt, name));
-            return false;
-        }
-        if (op->signature.size() != argv.size())
-        {
-            span.error(STRPRINTF("Incorrect number of arguments to operation '%s': Expected %zu, found %zu"_fmt,
-                        name, op->signature.size(), argv.size()));
-            return false;
-        }
-
-        EffectOp e_op;
-        e_op.line_nr = span.begin.line;
-        e_op.column = span.begin.column;
-        e_op.opp = op;
-        assert (argv.size() <= MAX_ARGS);
-        e_op.args_nr = argv.size();
-
-        std::copy(argv.begin(), argv.end(), e_op.args);
-        effect = dumb_ptr<effect_t>::make(e_op, nullptr);
-        return true;
-    }
-
-    static
-    dumb_ptr<expr_t> dot_expr(dumb_ptr<expr_t> expr, int id)
-    {
-        ExprField e_field;
-        e_field.id = id;
-        e_field.expr = expr;
-        dumb_ptr<expr_t> retval = dumb_ptr<expr_t>::make(e_field);
-
-        return retval;
-    }
-    static
-    bool fun_expr(io::LineSpan span, ZString name, Slice<dumb_ptr<expr_t>> argv, dumb_ptr<expr_t>& expr)
-    {
-        fun_t *fun = magic_get_fun(name);
-        if (!fun)
-        {
-            span.error(STRPRINTF("Unknown function '%s'"_fmt, name));
-            return false;
-        }
-        if (fun->signature.size() != argv.size())
-        {
-            span.error(STRPRINTF("Incorrect number of arguments to function '%s': Expected %zu, found %zu"_fmt,
-                        name, fun->signature.size(), argv.size()));
-            return false;
-        }
-        ExprFunApp e_funapp;
-        e_funapp.line_nr = span.begin.line;
-        e_funapp.column = span.begin.column;
-        e_funapp.funp = fun;
-
-        assert (argv.size() <= MAX_ARGS);
-        e_funapp.args_nr = argv.size();
-
-        std::copy(argv.begin(), argv.end(), e_funapp.args);
-        expr = dumb_ptr<expr_t>::make(e_funapp);
-        return true;
-    }
-    static
-    dumb_ptr<expr_t> BIN_EXPR(io::LineSpan span, ZString name, dumb_ptr<expr_t> left, dumb_ptr<expr_t> right)
-    {
-        dumb_ptr<expr_t> e[2];
-        e[0] = left;
-        e[1] = right;
-        dumb_ptr<expr_t> rv;
-        if (!fun_expr(span, name, e, rv))
-            abort();
-        return rv;
-    }
-
-    static
-    bool fail(const sexpr::SExpr& s, ZString msg)
-    {
-        s._span.error(msg);
-        return false;
-    }
-}
-
-namespace magic_v2
-{
-    using sexpr::SExpr;
-
-    static
-    bool parse_expression(const SExpr& x, dumb_ptr<expr_t>& out);
-    static
-    bool parse_effect(const SExpr& s, dumb_ptr<effect_t>& out);
-    static
-    bool parse_spellguard(const SExpr& s, dumb_ptr<spellguard_t>& out);
-    static
-    bool parse_spellbody(const SExpr& s, dumb_ptr<spellguard_t>& out);
-
-    // Note: anything with dumb_ptr leaks memory on failure
-    // once this is all done, we can convert it to unique_ptr
-    // (may require bimaps somewhere)
-    static
-    bool is_comment(const SExpr& s)
-    {
-        if (s._type == sexpr::STRING)
-            return true;
-        if (s._type != sexpr::LIST)
-            return false;
-        if (s._list.empty())
-            return false;
-        if (s._list[0]._type != sexpr::TOKEN)
-            return false;
-        return s._list[0]._str == "DISABLED"_s;
-    }
-
-    static
-    bool parse_loc(const SExpr& s, e_location_t& loc)
-    {
-        if (s._type != sexpr::LIST)
-            return fail(s, "loc not list"_s);
-        if (s._list.size() != 4)
-            return fail(s, "loc not 3 args"_s);
-        if (s._list[0]._type != sexpr::TOKEN)
-            return fail(s._list[0], "loc cmd not tok"_s);
-        if (s._list[0]._str != "@"_s)
-            return fail(s._list[0], "loc cmd not cmd"_s);
-        return parse_expression(s._list[1], loc.m)
-            && parse_expression(s._list[2], loc.x)
-            && parse_expression(s._list[3], loc.y);
-    }
-
-    static
-    bool parse_expression(const SExpr& x, dumb_ptr<expr_t>& out)
-    {
-        switch (x._type)
-        {
-        case sexpr::INT:
-            {
-                val_t val;
-                val = ValInt{static_cast<int32_t>(x._int)};
-                if (val.get_if<ValInt>()->v_int != x._int)
-                    return fail(x, "integer too large"_s);
-
-                out = dumb_ptr<expr_t>::make(std::move(val));
-                return true;
-            }
-        case sexpr::STRING:
-            {
-                val_t val;
-                val = ValString{x._str};
-
-                out = dumb_ptr<expr_t>::make(std::move(val));
-                return true;
-            }
-        case sexpr::TOKEN:
-            {
-                earray<LString, DIR, DIR::COUNT> dirs //=
-                {{
-                    "S"_s, "SW"_s, "W"_s, "NW"_s,
-                    "N"_s, "NE"_s, "E"_s, "SE"_s,
-                }};
-                auto begin = std::begin(dirs);
-                auto end = std::end(dirs);
-                auto it = std::find(begin, end, x._str);
-                if (it != end)
-                {
-                    val_t val;
-                    val = ValDir{static_cast<DIR>(it - begin)};
-
-                    out = dumb_ptr<expr_t>::make(std::move(val));
-                    return true;
-                }
-            }
-            {
-                if (const val_t *val = find_constant(x._str))
-                {
-                    val_t copy;
-                    magic_copy_var(&copy, val);
-                    out = dumb_ptr<expr_t>::make(std::move(copy));
-                    return true;
-                }
-                else
-                {
-                    ExprId e;
-                    e.e_id = intern_id(x._str);
-                    out = dumb_ptr<expr_t>::make(e);
-                    return true;
-                }
-            }
-            break;
-        case sexpr::LIST:
-            if (x._list.empty())
-                return fail(x, "empty list"_s);
-            {
-                if (x._list[0]._type != sexpr::TOKEN)
-                    return fail(x._list[0], "op not token"_s);
-                ZString op = x._list[0]._str;
-                // area
-                if (op == "@"_s)
-                {
-                    e_location_t loc;
-                    if (!parse_loc(x, loc))
-                        return false;
-                    out = dumb_ptr<expr_t>::make(loc);
-                    return true;
-                }
-                if (op == "@+"_s)
-                {
-                    e_location_t loc;
-                    dumb_ptr<expr_t> width;
-                    dumb_ptr<expr_t> height;
-                    if (!parse_loc(x._list[1], loc))
-                        return false;
-                    if (!parse_expression(x._list[2], width))
-                        return false;
-                    if (!parse_expression(x._list[3], height))
-                        return false;
-                    ExprAreaRect a_rect;
-                    a_rect.loc = loc;
-                    a_rect.width = width;
-                    a_rect.height = height;
-                    out = dumb_ptr<expr_t>::make(a_rect);
-                    return true;
-                }
-                if (op == "TOWARDS"_s)
-                {
-                    e_location_t loc;
-                    dumb_ptr<expr_t> dir;
-                    dumb_ptr<expr_t> width;
-                    dumb_ptr<expr_t> depth;
-                    if (!parse_loc(x._list[1], loc))
-                        return false;
-                    if (!parse_expression(x._list[2], dir))
-                        return false;
-                    if (!parse_expression(x._list[3], width))
-                        return false;
-                    if (!parse_expression(x._list[4], depth))
-                        return false;
-                    ExprAreaBar a_bar;
-                    a_bar.loc = loc;
-                    a_bar.dir = dir;
-                    a_bar.width = width;
-                    a_bar.depth = depth;
-                    out = dumb_ptr<expr_t>::make(a_bar);
-                    return true;
-                }
-                if (op == "."_s)
-                {
-                    if (x._list.size() != 3)
-                        return fail(x, ". not 2"_s);
-                    dumb_ptr<expr_t> expr;
-                    if (!parse_expression(x._list[1], expr))
-                        return false;
-                    if (x._list[2]._type != sexpr::TOKEN)
-                        return fail(x._list[2], ".elem not name"_s);
-                    ZString elem = x._list[2]._str;
-                    out = dot_expr(expr, intern_id(elem));
-                    return true;
-                }
-                static // TODO LString
-                std::set<ZString> ops =
-                {
-                    "<"_s, ">"_s, "<="_s, ">="_s, "=="_s, "!="_s,
-                    "+"_s, "-"_s, "*"_s, "%"_s, "/"_s,
-                    "&"_s, "^"_s, "|"_s, "<<"_s, ">>"_s,
-                    "&&"_s, "||"_s,
-                };
-                // TODO implement unary operators
-                if (ops.count(op))
-                {
-                    // operators are n-ary and left-associative
-                    if (x._list.size() < 3)
-                        return fail(x, "operator not at least 2 args"_s);
-                    auto begin = x._list.begin() + 1;
-                    auto end = x._list.end();
-                    if (!parse_expression(*begin, out))
-                        return false;
-                    ++begin;
-                    for (; begin != end; ++begin)
-                    {
-                        dumb_ptr<expr_t> tmp;
-                        if (!parse_expression(*begin, tmp))
-                            return false;
-                        out = BIN_EXPR(x._span, op, out, tmp);
-                    }
-                    return true;
-                }
-                std::vector<dumb_ptr<expr_t>> argv;
-                for (auto it = x._list.begin() + 1, end = x._list.end(); it != end; ++it)
-                {
-                    dumb_ptr<expr_t> expr;
-                    if (!parse_expression(*it, expr))
-                        return false;
-                    argv.push_back(expr);
-                }
-                return fun_expr(x._span, op, argv, out);
-            }
-            break;
-        }
-        abort();
-    }
-
-    static
-    bool parse_item(const SExpr& s, ItemNameId& id, int& count)
-    {
-        if (s._type == sexpr::STRING)
-        {
-            count = 1;
-
-            Borrowed<item_data> item = TRY_UNWRAP(itemdb_searchname(s._str),
-                return fail(s, "no such item"_s)
-            );
-            id = item->nameid;
-            return true;
-        }
-        if (s._type != sexpr::LIST)
-            return fail(s, "item not string or list"_s);
-        if (s._list.size() != 2)
-            return fail(s, "item list is not pair"_s);
-        if (s._list[0]._type != sexpr::INT)
-            return fail(s._list[0], "item pair first not int"_s);
-        count = s._list[0]._int;
-        if (s._list[1]._type != sexpr::STRING)
-            return fail(s._list[1], "item pair second not name"_s);
-
-        Borrowed<item_data> item = TRY_UNWRAP(itemdb_searchname(s._list[1]._str),
-            return fail(s, "no such item"_s)
-        );
-        id = item->nameid;
-        return true;
-    }
-
-    static
-    bool parse_spellguard(const SExpr& s, dumb_ptr<spellguard_t>& out)
-    {
-        if (s._type != sexpr::LIST)
-            return fail(s, "not list"_s);
-        if (s._list.empty())
-            return fail(s, "empty list"_s);
-        if (s._list[0]._type != sexpr::TOKEN)
-            return fail(s._list[0], "not token"_s);
-        ZString cmd = s._list[0]._str;
-        if (cmd == "OR"_s)
-        {
-            auto begin = s._list.begin() + 1;
-            auto end = s._list.end();
-            if (begin == end)
-                return fail(s, "missing arguments"_s);
-            if (!parse_spellguard(*begin, out))
-                return false;
-            ++begin;
-            for (; begin != end; ++begin)
-            {
-                dumb_ptr<spellguard_t> alt;
-                if (!parse_spellguard(*begin, alt))
-                    return false;
-                GuardChoice choice;
-                auto next = out;
-                choice.s_alt = alt;
-                out = dumb_ptr<spellguard_t>::make(choice, next);
-            }
-            return true;
-        }
-        if (cmd == "GUARD"_s)
-        {
-            auto begin = s._list.begin() + 1;
-            auto end = s._list.end();
-            while (is_comment(end[-1]))
-                --end;
-            if (begin == end)
-                return fail(s, "missing arguments"_s);
-            if (!parse_spellguard(end[-1], out))
-                return false;
-            --end;
-            for (; begin != end; --end)
-            {
-                if (is_comment(end[-1]))
-                    continue;
-                dumb_ptr<spellguard_t> implier;
-                if (!parse_spellguard(end[-1], implier))
-                    return false;
-                out = spellguard_implication(implier, out);
-            }
-            return true;
-        }
-        if (cmd == "REQUIRE"_s)
-        {
-            if (s._list.size() != 2)
-                return fail(s, "not one argument"_s);
-            dumb_ptr<expr_t> condition;
-            if (!parse_expression(s._list[1], condition))
-                return false;
-            GuardCondition cond;
-            cond.s_condition = condition;
-            out = dumb_ptr<spellguard_t>::make(cond, nullptr);
-            return true;
-        }
-        if (cmd == "MANA"_s)
-        {
-            if (s._list.size() != 2)
-                return fail(s, "not one argument"_s);
-            dumb_ptr<expr_t> mana;
-            if (!parse_expression(s._list[1], mana))
-                return false;
-            GuardMana sp;
-            sp.s_mana = mana;
-            out = dumb_ptr<spellguard_t>::make(sp, nullptr);
-            return true;
-        }
-        if (cmd == "CASTTIME"_s)
-        {
-            if (s._list.size() != 2)
-                return fail(s, "not one argument"_s);
-            dumb_ptr<expr_t> casttime;
-            if (!parse_expression(s._list[1], casttime))
-                return false;
-            GuardCastTime ct;
-            ct.s_casttime = casttime;
-            out = dumb_ptr<spellguard_t>::make(ct, nullptr);
-            return true;
-        }
-        if (cmd == "CATALYSTS"_s)
-        {
-            dumb_ptr<component_t> items = nullptr;
-            for (auto it = s._list.begin() + 1, end = s._list.end(); it != end; ++it)
-            {
-                ItemNameId id;
-                int count;
-                if (!parse_item(*it, id, count))
-                    return false;
-                magic_add_component(&items, id, count);
-            }
-            GuardCatalysts cat;
-            cat.s_catalysts = items;
-            out = dumb_ptr<spellguard_t>::make(cat, nullptr);
-            return true;
-        }
-        if (cmd == "COMPONENTS"_s)
-        {
-            dumb_ptr<component_t> items = nullptr;
-            for (auto it = s._list.begin() + 1, end = s._list.end(); it != end; ++it)
-            {
-                ItemNameId id;
-                int count;
-                if (!parse_item(*it, id, count))
-                    return false;
-                magic_add_component(&items, id, count);
-            }
-            GuardComponents comp;
-            comp.s_components = items;
-            out = dumb_ptr<spellguard_t>::make(comp, nullptr);
-            return true;
-        }
-        return fail(s._list[0], "unknown guard"_s);
-    }
-
-    static
-    bool build_effect_list(std::vector<SExpr>::const_iterator begin,
-            std::vector<SExpr>::const_iterator end, dumb_ptr<effect_t>& out)
-    {
-        // these backward lists could be forward by keeping the reference
-        // I know this is true because Linus said so
-        out = dumb_ptr<effect_t>::make(EffectSkip{}, nullptr);
-        while (end != begin)
-        {
-            const SExpr& s = *--end;
-            if (is_comment(s))
-                continue;
-            dumb_ptr<effect_t> chain;
-            if (!parse_effect(s, chain))
-                return false;
-            out = set_effect_continuation(chain, out);
-        }
-        return true;
-    }
-
-    static
-    bool parse_effect(const SExpr& s, dumb_ptr<effect_t>& out)
-    {
-        if (s._type != sexpr::LIST)
-            return fail(s, "not list"_s);
-        if (s._list.empty())
-            return fail(s, "empty list"_s);
-        if (s._list[0]._type != sexpr::TOKEN)
-            return fail(s._list[0], "not token"_s);
-        ZString cmd = s._list[0]._str;
-        if (cmd == "BLOCK"_s)
-        {
-            return build_effect_list(s._list.begin() + 1, s._list.end(), out);
-        }
-        if (cmd == "SET"_s)
-        {
-            if (s._list.size() != 3)
-                return fail(s, "not 2 args"_s);
-            if (s._list[1]._type != sexpr::TOKEN)
-                return fail(s._list[1], "not token"_s);
-            ZString name = s._list[1]._str;
-            if (find_constant(name))
-                return fail(s._list[1], "assigning to constant"_s);
-            dumb_ptr<expr_t> expr;
-            if (!parse_expression(s._list[2], expr))
-                return false;
-
-            EffectAssign e_assign;
-            e_assign.id = intern_id(name);
-            e_assign.expr = expr;
-            out = dumb_ptr<effect_t>::make(e_assign, nullptr);
-            return true;
-        }
-        if (cmd == "SCRIPT"_s)
-        {
-            if (s._list.size() != 2)
-                return fail(s, "not 1 arg"_s);
-            if (s._list[1]._type != sexpr::STRING)
-                return fail(s._list[1], "not string"_s);
-            ZString body = s._list[1]._str;
-            auto begin = s._list[1]._span.begin;
-            io::LineCharReader lr(io::from_string, begin.filename, body, begin.line, begin.column);
-            ast::script::ScriptOptions opt;
-            opt.implicit_start = true;
-            opt.implicit_end = true;
-            opt.no_event = true;
-            auto code_res = ast::script::parse_script_body(lr, opt);
-            if (code_res.get_failure())
-            {
-                PRINTF("%s\n"_fmt, code_res.get_failure());
-            }
-            auto code = TRY_UNWRAP(code_res.get_success(),
-                    return fail(s._list[1], "script does not compile"_s));
-            std::unique_ptr<const ScriptBuffer> script = compile_script(STRPRINTF("script magic %s:%d"_fmt, begin.filename, begin.line), code, true);
-            if (!script)
-                return fail(s._list[1], "script does not compile"_s);
-            EffectScript e;
-            e.e_script = dumb_ptr<const ScriptBuffer>(script.release());
-            out = dumb_ptr<effect_t>::make(e, nullptr);
-            return true;
-        }
-        if (cmd == "SKIP"_s)
-        {
-            if (s._list.size() != 1)
-                return fail(s, "not 0 arg"_s);
-            out = dumb_ptr<effect_t>::make(EffectSkip{}, nullptr);
-            return true;
-        }
-        if (cmd == "ABORT"_s)
-        {
-            if (s._list.size() != 1)
-                return fail(s, "not 0 arg"_s);
-            out = dumb_ptr<effect_t>::make(EffectAbort{}, nullptr);
-            return true;
-        }
-        if (cmd == "END"_s)
-        {
-            if (s._list.size() != 1)
-                return fail(s, "not 0 arg"_s);
-            out = dumb_ptr<effect_t>::make(EffectEnd{}, nullptr);
-            return true;
-        }
-        if (cmd == "BREAK"_s)
-        {
-            if (s._list.size() != 1)
-                return fail(s, "not 0 arg"_s);
-            out = dumb_ptr<effect_t>::make(EffectBreak{}, nullptr);
-            return true;
-        }
-        if (cmd == "FOREACH"_s)
-        {
-            if (s._list.size() != 5)
-                return fail(s, "not 4 arg"_s);
-            if (s._list[1]._type != sexpr::TOKEN)
-                return fail(s._list[1], "foreach type not token"_s);
-            ZString type = s._list[1]._str;
-            FOREACH_FILTER filter;
-            if (type == "PC"_s)
-                filter = FOREACH_FILTER::PC;
-            else if (type == "MOB"_s)
-                filter = FOREACH_FILTER::MOB;
-            else if (type == "ENTITY"_s)
-                filter = FOREACH_FILTER::ENTITY;
-            else if (type == "SPELL"_s)
-                filter = FOREACH_FILTER::SPELL;
-            else if (type == "TARGET"_s)
-                filter = FOREACH_FILTER::TARGET;
-            else if (type == "NPC"_s)
-                filter = FOREACH_FILTER::NPC;
-            else
-                return fail(s._list[1], "unknown foreach filter"_s);
-            if (s._list[2]._type != sexpr::TOKEN)
-                return fail(s._list[2], "foreach var not token"_s);
-            ZString var = s._list[2]._str;
-            dumb_ptr<expr_t> area;
-            dumb_ptr<effect_t> effect;
-            if (!parse_expression(s._list[3], area))
-                return false;
-            if (!parse_effect(s._list[4], effect))
-                return false;
-
-            EffectForEach e_foreach;
-            e_foreach.id = intern_id(var);
-            e_foreach.area = area;
-            e_foreach.body = effect;
-            e_foreach.filter = filter;
-            out = dumb_ptr<effect_t>::make(e_foreach, nullptr);
-            return true;
-        }
-        if (cmd == "FOR"_s)
-        {
-            if (s._list.size() != 5)
-                return fail(s, "not 4 arg"_s);
-            if (s._list[1]._type != sexpr::TOKEN)
-                return fail(s._list[1], "for var not token"_s);
-            ZString var = s._list[1]._str;
-            dumb_ptr<expr_t> low;
-            dumb_ptr<expr_t> high;
-            dumb_ptr<effect_t> effect;
-            if (!parse_expression(s._list[2], low))
-                return false;
-            if (!parse_expression(s._list[3], high))
-                return false;
-            if (!parse_effect(s._list[4], effect))
-                return false;
-
-            EffectFor e_for;
-            e_for.id = intern_id(var);
-            e_for.start = low;
-            e_for.stop = high;
-            e_for.body = effect;
-            out = dumb_ptr<effect_t>::make(e_for, nullptr);
-            return true;
-        }
-        if (cmd == "IF"_s)
-        {
-            if (s._list.size() != 3 && s._list.size() != 4)
-                return fail(s, "not 2 or 3 args"_s);
-            dumb_ptr<expr_t> cond;
-            dumb_ptr<effect_t> if_true;
-            dumb_ptr<effect_t> if_false;
-            if (!parse_expression(s._list[1], cond))
-                return false;
-            if (!parse_effect(s._list[2], if_true))
-                return false;
-            if (s._list.size() == 4)
-            {
-                if (!parse_effect(s._list[3], if_false))
-                    return false;
-            }
-            else
-                if_false = dumb_ptr<effect_t>::make(EffectSkip{}, nullptr);
-
-            EffectIf e_if;
-            e_if.cond = cond;
-            e_if.true_branch = if_true;
-            e_if.false_branch = if_false;
-            out = dumb_ptr<effect_t>::make(e_if, nullptr);
-            return true;
-        }
-        if (cmd == "WAIT"_s)
-        {
-            if (s._list.size() != 2)
-                return fail(s, "not 1 arg"_s);
-            dumb_ptr<expr_t> expr;
-            if (!parse_expression(s._list[1], expr))
-                return false;
-            EffectSleep e;
-            e.e_sleep = expr;
-            out = dumb_ptr<effect_t>::make(e, nullptr);
-            return true;
-        }
-        if (cmd == "CALL"_s)
-        {
-            if (s._list.size() < 2)
-                return fail(s, "call what?"_s);
-            if (s._list[1]._type != sexpr::TOKEN)
-                return fail(s._list[1], "call token please"_s);
-            ZString func = s._list[1]._str;
-            auto argvp = dumb_ptr<std::vector<dumb_ptr<expr_t>>>::make();
-            for (auto it = s._list.begin() + 2, end = s._list.end(); it != end; ++it)
-            {
-                dumb_ptr<expr_t> expr;
-                if (!parse_expression(*it, expr))
-                    return false;
-                argvp->push_back(expr);
-            }
-            return call_proc(s._span, func, argvp, out);
-        }
-        auto argv = std::vector<dumb_ptr<expr_t>>();
-        for (auto it = s._list.begin() + 1, end = s._list.end(); it != end; ++it)
-        {
-            dumb_ptr<expr_t> expr;
-            if (!parse_expression(*it, expr))
-                return false;
-            argv.push_back(expr);
-        }
-        return op_effect(s._span, cmd, argv, out);
-    }
-
-    static
-    bool parse_spellbody(const SExpr& s, dumb_ptr<spellguard_t>& out)
-    {
-        if (s._type != sexpr::LIST)
-            return fail(s, "not list"_s);
-        if (s._list.empty())
-            return fail(s, "empty list"_s);
-        if (s._list[0]._type != sexpr::TOKEN)
-            return fail(s._list[0], "not token"_s);
-        ZString cmd = s._list[0]._str;
-        if (cmd == "=>"_s)
-        {
-            if (s._list.size() != 3)
-                return fail(s, "list does not have exactly 2 arguments"_s);
-            dumb_ptr<spellguard_t> guard;
-            if (!parse_spellguard(s._list[1], guard))
-                return false;
-            dumb_ptr<spellguard_t> body;
-            if (!parse_spellbody(s._list[2], body))
-                return false;
-            out = spellguard_implication(guard, body);
-            return true;
-        }
-        if (cmd == "|"_s)
-        {
-            if (s._list.size() == 1)
-                return fail(s, "spellbody choice empty"_s);
-            auto begin = s._list.begin() + 1;
-            auto end = s._list.end();
-            if (!parse_spellbody(*begin, out))
-                return false;
-            ++begin;
-            for (; begin != end; ++begin)
-            {
-                dumb_ptr<spellguard_t> alt;
-                if (!parse_spellbody(*begin, alt))
-                    return false;
-                auto tmp = out;
-                GuardChoice choice;
-                choice.s_alt = alt;
-                out = dumb_ptr<spellguard_t>::make(choice, tmp);
-            }
-            return true;
-        }
-        if (cmd == "EFFECT"_s)
-        {
-            auto begin = s._list.begin() + 1;
-            auto end = s._list.end();
-
-            dumb_ptr<effect_t> effect, attrig, atend;
-
-            // decreasing end can never pass begin, since we know that
-            // begin[-1] is token EFFECT
-            while (is_comment(end[-1]))
-                --end;
-            if (end[-1]._type == sexpr::LIST && !end[-1]._list.empty()
-                    && end[-1]._list[0]._type == sexpr::TOKEN
-                    && end[-1]._list[0]._str == "ATEND"_s)
-            {
-                auto atb = end[-1]._list.begin() + 1;
-                auto ate = end[-1]._list.end();
-                if (!build_effect_list(atb, ate, atend))
-                    return false;
-                --end;
-
-                while (is_comment(end[-1]))
-                    --end;
-            }
-            else
-            {
-                atend = nullptr;
-            }
-            if (end[-1]._type == sexpr::LIST && !end[-1]._list.empty()
-                    && end[-1]._list[0]._type == sexpr::TOKEN
-                    && end[-1]._list[0]._str == "ATTRIGGER"_s)
-            {
-                auto atb = end[-1]._list.begin() + 1;
-                auto ate = end[-1]._list.end();
-                if (!build_effect_list(atb, ate, attrig))
-                    return false;
-                --end;
-            }
-            else
-            {
-                attrig = nullptr;
-            }
-            if (!build_effect_list(begin, end, effect))
-                return false;
-            effect_set_t s_effect;
-            s_effect.effect = effect;
-            s_effect.at_trigger = attrig;
-            s_effect.at_end = atend;
-            out = dumb_ptr<spellguard_t>::make(s_effect, nullptr);
-            return true;
-        }
-        return fail(s._list[0], "unknown spellbody"_s);
-    }
-
-    static
-    bool parse_top_set(const std::vector<SExpr>& in)
-    {
-        if (in.size() != 3)
-            return fail(in[0], "not 2 arguments"_s);
-        ZString name = in[1]._str;
-        dumb_ptr<expr_t> expr;
-        if (!parse_expression(in[2], expr))
-            return false;
-        if (find_constant(name))
-            return fail(in[1], "assign constant"_s);
-        size_t var_id = intern_id(name);
-        magic_eval(dumb_ptr<env_t>(&magic_default_env), &magic_conf.varv[var_id].val, expr);
-        return true;
-    }
-    static
-    bool parse_const(io::LineSpan span, const std::vector<SExpr>& in)
-    {
-        if (in.size() != 3)
-            return fail(in[0], "not 2 arguments"_s);
-        if (in[1]._type != sexpr::TOKEN)
-            return fail(in[1], "not token"_s);
-        ZString name = in[1]._str;
-        dumb_ptr<expr_t> expr;
-        if (!parse_expression(in[2], expr))
-            return false;
-        val_t tmp;
-        magic_eval(dumb_ptr<env_t>(&magic_default_env), &tmp, expr);
-        return bind_constant(span, name, std::move(tmp));
-    }
-    static
-    bool parse_anchor(io::LineSpan span, const std::vector<SExpr>& in)
-    {
-        if (in.size() != 4)
-            return fail(in[0], "not 3 arguments"_s);
-        auto anchor = dumb_ptr<teleport_anchor_t>::make();
-        if (in[1]._type != sexpr::TOKEN)
-            return fail(in[1], "not token"_s);
-        anchor->name = in[1]._str;
-        if (in[2]._type != sexpr::STRING)
-            return fail(in[2], "not string"_s);
-        anchor->invocation = in[2]._str;
-        dumb_ptr<expr_t> expr;
-        if (!parse_expression(in[3], expr))
-            return false;
-        anchor->location = expr;
-        return add_teleport_anchor(span, anchor);
-    }
-    static
-    bool parse_proc(io::LineSpan span, const std::vector<SExpr>& in)
-    {
-        if (in.size() < 4)
-            return fail(in[0], "not at least 3 arguments"_s);
-        auto proc = dumb_ptr<proc_t>::make();
-        if (in[1]._type != sexpr::TOKEN)
-            return fail(in[1], "name not token"_s);
-        proc->name = in[1]._str;
-        if (in[2]._type != sexpr::LIST)
-            return fail(in[2], "args not list"_s);
-        for (const SExpr& arg : in[2]._list)
-        {
-            if (arg._type != sexpr::TOKEN)
-                return fail(arg, "arg not token"_s);
-            proc->argv.push_back(intern_id(arg._str));
-        }
-        if (!build_effect_list(in.begin() + 3, in.end(), proc->body))
-            return false;
-        return install_proc(span, proc);
-    }
-    static
-    bool parse_spell(io::LineSpan span, const std::vector<SExpr>& in)
-    {
-        if (in.size() < 6)
-            return fail(in[0], "not at least 5 arguments"_s);
-        if (in[1]._type != sexpr::LIST)
-            return fail(in[1], "flags not list"_s);
-
-        auto spell = dumb_ptr<spell_t>::make();
-
-        for (const SExpr& s : in[1]._list)
-        {
-            if (s._type != sexpr::TOKEN)
-                return fail(s, "flag not token"_s);
-            SPELL_FLAG flag = SPELL_FLAG::ZERO;
-            if (s._str == "LOCAL"_s)
-                flag = SPELL_FLAG::LOCAL;
-            else if (s._str == "NONMAGIC"_s)
-                flag = SPELL_FLAG::NONMAGIC;
-            else if (s._str == "SILENT"_s)
-                flag = SPELL_FLAG::SILENT;
-            else
-                return fail(s, "unknown flag"_s);
-            if (bool(spell->flags & flag))
-                return fail(s, "duplicate flag"_s);
-            spell->flags |= flag;
-        }
-        if (in[2]._type != sexpr::TOKEN)
-            return fail(in[2], "name not token"_s);
-        spell->name = in[2]._str;
-        if (in[3]._type != sexpr::STRING)
-            return fail(in[3], "invoc not string"_s);
-        spell->invocation = in[3]._str;
-        if (in[4]._type != sexpr::LIST)
-            return fail(in[4], "spellarg not list"_s);
-        if (in[4]._list.size() == 0)
-        {
-            spell->spellarg_ty = SPELLARG::NONE;
-        }
-        else
-        {
-            if (in[4]._list.size() != 2)
-                return fail(in[4], "spellarg not empty list or pair"_s);
-            if (in[4]._list[0]._type != sexpr::TOKEN)
-                return fail(in[4]._list[0], "spellarg type not token"_s);
-            if (in[4]._list[1]._type != sexpr::TOKEN)
-                return fail(in[4]._list[1], "spellarg name not token"_s);
-            ZString ty = in[4]._list[0]._str;
-            if (ty == "PC"_s)
-                spell->spellarg_ty = SPELLARG::PC;
-            else if (ty == "STRING"_s)
-                spell->spellarg_ty = SPELLARG::STRING;
-            else
-                return fail(in[4]._list[0], "unknown spellarg type"_s);
-            ZString an = in[4]._list[1]._str;
-            spell->arg = intern_id(an);
-        }
-        std::vector<SExpr>::const_iterator it = in.begin() + 5;
-        for (;; ++it)
-        {
-            if (it == in.end())
-                return fail(it[-1], "end of list scanning LET defs"_s);
-            if (is_comment(*it))
-                continue;
-            if (it->_type != sexpr::LIST || it->_list.empty())
-                break;
-            if (it->_list[0]._type != sexpr::TOKEN || it->_list[0]._str != "LET"_s)
-                break;
-
-            if (it->_list[1]._type != sexpr::TOKEN)
-                return fail(it->_list[1], "let name not token"_s);
-            ZString name = it->_list[1]._str;
-            if (find_constant(name))
-                return fail(it->_list[1], "constant exists"_s);
-            dumb_ptr<expr_t> expr;
-            if (!parse_expression(it->_list[2], expr))
-                return false;
-            letdef_t let;
-            let.id = intern_id(name);
-            let.expr = expr;
-            spell->letdefv.push_back(let);
-        }
-        if (it + 1 != in.end())
-            return fail(*it, "expected only one body entry besides LET"_s);
-
-        // formally, 'guard' only refers to the first argument of '=>'
-        // but internally, spellbodies use the same thing
-        dumb_ptr<spellguard_t> guard;
-        if (!parse_spellbody(*it, guard))
-            return false;
-        spell->spellguard = guard;
-        return add_spell(span, spell);
-    }
-
-    static
-    bool parse_top(io::LineSpan span, const std::vector<SExpr>& vs)
-    {
-        if (vs.empty())
-        {
-            span.error("Empty list at top"_s);
-            return false;
-        }
-        if (vs[0]._type != sexpr::TOKEN)
-            return fail(vs[0], "top not token"_s);
-        ZString cmd = vs[0]._str;
-        if (cmd == "CONST"_s)
-            return parse_const(span, vs);
-        if (cmd == "PROCEDURE"_s)
-            return parse_proc(span, vs);
-        if (cmd == "SET"_s)
-            return parse_top_set(vs);
-        if (cmd == "SPELL"_s)
-            return parse_spell(span, vs);
-        if (cmd == "TELEPORT-ANCHOR"_s)
-            return parse_anchor(span, vs);
-        return fail(vs[0], "Unknown top-level command"_s);
-    }
-
-    static
-    bool loop(sexpr::Lexer& in)
-    {
-        SExpr s;
-        while (sexpr::parse(in, s))
-        {
-            if (is_comment(s))
-                continue;
-            if (s._type != sexpr::LIST)
-                return fail(s, "top-level entity not a list or comment"_s);
-            if (!parse_top(s._span, s._list))
-                return false;
-        }
-        // handle low-level errors
-        if (in.peek() != sexpr::TOK_EOF)
-        {
-            in.span().error("parser gave up before end of file"_s);
-            return false;
-        }
-        return true;
-    }
-} // namespace magic_v2
-
-bool magic_init0()
-{
-    return magic_v2::init0();
-}
-
-bool load_magic_file_v2(ZString filename)
-{
-    sexpr::Lexer in(filename);
-    bool rv = magic_v2::loop(in);
-    if (!rv)
-    {
-        in.span().error(STRPRINTF("next token: %s '%s'"_fmt, sexpr::token_name(in.peek()), in.val_string()));
-    }
-    return rv;
-}
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic-v2.hpp b/src/map/magic-v2.hpp
deleted file mode 100644
index fac2773..0000000
--- a/src/map/magic-v2.hpp
+++ /dev/null
@@ -1,37 +0,0 @@
-#pragma once
-//    magic-v2.hpp - second generation magic parser
-//
-//    Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include "fwd.hpp"
-
-#include "../strings/zstring.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-bool magic_init0();
-// must be called after itemdb initialization
-bool load_magic_file_v2(ZString filename);
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic.cpp b/src/map/magic.cpp
deleted file mode 100644
index 418312a..0000000
--- a/src/map/magic.cpp
+++ /dev/null
@@ -1,130 +0,0 @@
-#include "magic.hpp"
-//    magic.cpp - Entry to the magic system.
-//
-//    Copyright © 2004-2011 The Mana World Development Team
-//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include <algorithm>
-#include <utility>
-
-#include "../strings/xstring.hpp"
-
-#include "../generic/dumb_ptr.hpp"
-
-#include "../io/cxxstdio.hpp"
-
-#include "globals.hpp"
-#include "magic-expr.hpp"
-#include "magic-interpreter.hpp"
-#include "magic-interpreter-base.hpp"
-#include "magic-stmt.hpp"
-#include "map.hpp"
-#include "pc.hpp"
-
-#include "../poison.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-/// Return a pair of strings, {spellname, parameter}
-/// Parameter may be empty.
-static
-std::pair<XString, XString> magic_tokenise(XString src)
-{
-    auto seeker = std::find(src.begin(), src.end(), ' ');
-
-    if (seeker == src.end())
-    {
-        return {src, XString()};
-    }
-    else
-    {
-        XString rv1 = src.xislice_h(seeker);
-        ++seeker;
-
-        while (seeker != src.end() && *seeker == ' ')
-            ++seeker;
-
-        // Note: this very well could be empty
-        XString rv2 = src.xislice_t(seeker);
-        return {rv1, rv2};
-    }
-}
-
-int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
-{
-    if (pc_isdead(caster))
-        return 0;
-    if (bool(caster->status.option & Opt0::HIDE))
-        return 0;           // No spellcasting while hidden
-
-    int power = caster->matk1;
-
-    // This was the only thing worth saving from magic_preprocess_message.
-    // May it rest only, and never rise again.
-    // For more information on how this code worked, travel through time
-    // and watch all the comments I wrote for myself while trying to figure
-    // out if it was safe to delete.
-    if (caster->state.shroud_active && caster->state.shroud_disappears_on_talk)
-        magic_unshroud(caster);
-
-    auto pair = magic_tokenise(source_invocation);
-    XString spell_invocation = pair.first;
-    XString parameter = pair.second;
-
-    dumb_ptr<spell_t> spell = magic_find_spell(spell_invocation);
-
-    if (spell)
-    {
-        int near_miss;
-        dumb_ptr<env_t> env =
-            spell_create_env(&magic_conf, spell, caster, power, parameter);
-        const effect_set_t *effects;
-
-        if (bool(spell->flags & SPELL_FLAG::NONMAGIC) || (power >= 1))
-            effects = spell_trigger(spell, caster, env, &near_miss);
-        else
-            effects = nullptr;
-
-        MAP_LOG_PC(caster, "CAST %s %s"_fmt,
-                spell->name, effects ? "SUCCESS"_s : "FAILURE"_s);
-
-        if (effects)
-        {
-            dumb_ptr<invocation> invocation = spell_instantiate(effects, env);
-
-            spell_bind(caster, invocation);
-            spell_execute(invocation);
-
-            return bool(spell->flags & SPELL_FLAG::SILENT) ? -1 : 1;
-        }
-        else
-            magic_free_env(env);
-
-        return 1;
-    }
-
-    return 0;                   /* Not a spell */
-}
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/magic.hpp b/src/map/magic.hpp
deleted file mode 100644
index 70d40dc..0000000
--- a/src/map/magic.hpp
+++ /dev/null
@@ -1,48 +0,0 @@
-#pragma once
-//    magic.hpp - Entry to the magic system.
-//
-//    Copyright © 2004-2011 The Mana World Development Team
-//    Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com>
-//
-//    This file is part of The Mana World (Athena server)
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-#include "fwd.hpp"
-
-#include "map.t.hpp"
-#include "../mmo/skill.t.hpp"
-
-
-namespace tmwa
-{
-namespace map
-{
-namespace magic
-{
-/**
- * Try to cast magic.
- *
- * As an intended side effect, the magic message may be distorted (text only).
- * No, it can't. Thank God.
- *
- * \param caster Player attempting to cast magic
- * \param source_invocation The prospective incantation
- * \return 1 or -1 if the input message was magic and was handled by this function, 0 otherwise.  -1 is returned when the
- *         message should not be repeated.
- */
-int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation);
-} // namespace magic
-} // namespace map
-} // namespace tmwa
diff --git a/src/map/map.cpp b/src/map/map.cpp
index d40977f..362d5d2 100644
--- a/src/map/map.cpp
+++ b/src/map/map.cpp
@@ -69,9 +69,6 @@
 #include "globals.hpp"
 #include "grfio.hpp"
 #include "itemdb.hpp"
-#include "magic-interpreter.hpp" // for is_spell inline body
-#include "magic-stmt.hpp"
-#include "magic-v2.hpp"
 #include "map_conf.hpp"
 #include "mob.hpp"
 #include "quest.hpp"
@@ -1454,9 +1451,6 @@ void cleanup_sub(dumb_ptr<block_list> bl)
         case BL::ITEM:
             map_clearflooritem(bl->bl_id);
             break;
-        case BL::SPELL:
-            magic::spell_free_invocation(bl->is_spell());
-            break;
     }
 }
 
@@ -1497,8 +1491,6 @@ bool map_confs(io::Spanned<XString> key, io::Spanned<ZString> value)
         return mob_readskilldb(value.data);
     if (key.data == "skill_db"_s)
         return skill_readdb(value.data);
-    if (key.data == "magic_conf"_s)
-        return magic::load_magic_file_v2(value.data);
 
     if (key.data == "resnametable"_s)
         return load_resnametable(value.data);
@@ -1515,16 +1507,7 @@ int map_scriptcont(dumb_ptr<map_session_data> sd, BlockId id)
     if (!bl)
         return 0;
 
-    switch (bl->bl_type)
-    {
-        case BL::NPC:
-            return npc_scriptcont(sd, id);
-        case BL::SPELL:
-            magic::spell_execute_script(bl->is_spell());
-            break;
-    }
-
-    return 0;
+    return npc_scriptcont(sd, id);
 }
 } // namespace map
 
@@ -1571,7 +1554,6 @@ int do_init(Slice<ZString> argv)
     using namespace tmwa::map;
 
     ZString argv0 = argv.pop_front();
-    runflag &= magic::magic_init0();
 
     bool loaded_config_yet = false;
     while (argv)
diff --git a/src/map/map.hpp b/src/map/map.hpp
index a2f2ff2..66253ca 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -89,13 +89,11 @@ private:
     dumb_ptr<npc_data> as_npc();
     dumb_ptr<mob_data> as_mob();
     dumb_ptr<flooritem_data> as_item();
-    dumb_ptr<magic::invocation> as_spell();
 public:
     dumb_ptr<map_session_data> is_player();
     dumb_ptr<npc_data> is_npc();
     dumb_ptr<mob_data> is_mob();
     dumb_ptr<flooritem_data> is_item();
-    dumb_ptr<magic::invocation> is_spell();
 };
 
 struct walkpath_data
@@ -107,7 +105,6 @@ struct status_change
 {
     Timer timer;
     int val1;
-    BlockId spell_invocation;      /* [Fate] If triggered by a spell, record here */
 };
 
 struct quick_regeneration
@@ -205,8 +202,7 @@ struct map_session_data : block_list, SessionData
     // used by @hugo and @linus
     BlockId followtarget;
 
-    tick_t cast_tick;     // [Fate] Next tick at which spellcasting is allowed
-    dumb_ptr<magic::invocation> active_spells;   // [Fate] Singly-linked list of active spells linked to this PC
+    //tick_t cast_tick;     // [Fate] Next tick at which spellcasting is allowed
     BlockId attack_spell_override; // [Fate] When an attack spell is active for this player, they trigger it
     NpcEvent magic_attack;
     // like a weapon.  Check pc_attack_timer() for details.
@@ -639,13 +635,6 @@ dumb_ptr<flooritem_data> map_id_is_item(BlockId id)
     dumb_ptr<block_list> bl = map_id2bl(id);
     return bl ? bl->is_item() : nullptr;
 }
-inline
-dumb_ptr<magic::invocation> map_id_is_spell(BlockId id)
-{
-    dumb_ptr<block_list> bl = map_id2bl(id);
-    return bl ? bl->is_spell() : nullptr;
-}
-
 
 Option<Borrowed<map_local>> map_mapname2mapid(MapName);
 int map_mapname2ipport(MapName, Borrowed<IP4Address>, Borrowed<int>);
@@ -679,13 +668,11 @@ inline dumb_ptr<map_session_data> block_list::as_player() { return dumb_ptr<map_
 inline dumb_ptr<npc_data> block_list::as_npc() { return dumb_ptr<npc_data>(static_cast<npc_data *>(this)) ; }
 inline dumb_ptr<mob_data> block_list::as_mob() { return dumb_ptr<mob_data>(static_cast<mob_data *>(this)) ; }
 inline dumb_ptr<flooritem_data> block_list::as_item() { return dumb_ptr<flooritem_data>(static_cast<flooritem_data *>(this)) ; }
-//inline dumb_ptr<invocation> block_list::as_spell() { return dumb_ptr<invocation>(static_cast<invocation *>(this)) ; }
 
 inline dumb_ptr<map_session_data> block_list::is_player() { return bl_type == BL::PC ? as_player() : nullptr; }
 inline dumb_ptr<npc_data> block_list::is_npc() { return bl_type == BL::NPC ? as_npc() : nullptr; }
 inline dumb_ptr<mob_data> block_list::is_mob() { return bl_type == BL::MOB ? as_mob() : nullptr; }
 inline dumb_ptr<flooritem_data> block_list::is_item() { return bl_type == BL::ITEM ? as_item() : nullptr; }
-//inline dumb_ptr<invocation> block_list::is_spell() { return bl_type == BL::SPELL ? as_spell() : nullptr; }
 
 // struct invocation is defined in another header
 
diff --git a/src/map/map.t.hpp b/src/map/map.t.hpp
index 267c049..89b9a87 100644
--- a/src/map/map.t.hpp
+++ b/src/map/map.t.hpp
@@ -43,7 +43,6 @@ enum class BL : uint8_t
     NPC,
     MOB,
     ITEM,
-    SPELL,
 };
 enum class NpcSubtype : uint8_t
 {
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index 0175916..eaf54ec 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -146,14 +146,23 @@ dumb_ptr<npc_data> npc_name2id(NpcName name)
 }
 
 /*==========================================
- * NPCを名前で探す
+ * NPC Spells
  *------------------------------------------
  */
-NpcEvent spell_name2id(RString name)
+NpcName spell_name2id(RString name)
 {
     return spells_by_name.get(name);
 }
 
+/*==========================================
+ * NPC Spells Events
+ *------------------------------------------
+ */
+NpcEvent spell_event2id(RString name)
+{
+    return spells_by_events.get(name);
+}
+
 /*==========================================
  * Spell Toknise
  * Return a pair of strings, {spellname, parameter}
@@ -189,26 +198,45 @@ std::pair<XString, XString> magic_tokenise(XString src)
  */
 int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
 {
-    if (pc_isdead(caster))
-        return 0;
-    if (bool(caster->status.option & Opt0::HIDE))
-        return 0;
-    if (caster->cast_tick > gettick())
-        return 0;
-
     auto pair = magic_tokenise(source_invocation);
     // Spell Cast
-    NpcName spell_name = stringish<NpcName>(pair.first);
+    NpcName spell_name = spell_name2id(pair.first);
+    NpcEvent spell_event = spell_event2id(pair.first);
+    PRINTF("Cast: %s\n"_fmt, RString(pair.first));
+
     RString spell_params = pair.second;
 
-    NpcEvent event = spell_name2id(spell_name);
+    dumb_ptr<npc_data> nd = npc_name2id(spell_name);
 
-    if (event)
+    if (nd)
     {
-        PRINTF("Cast:  %s\n"_fmt, spell_name);
-        PRINTF("event:  %s\n"_fmt, event);
+        PRINTF("NPC:  %s %d\n"_fmt, nd->name, nd->bl_id);
         PRINTF("Params:  %s\n"_fmt, spell_params);
-        npc_event(caster, event, 0);
+        caster->npc_id = nd->bl_id;
+        dumb_ptr<block_list> map_bl = map_id2bl(nd->bl_id);
+        if (!map_bl)
+            map_addnpc(caster->bl_m, nd);
+        argrec_t arg[1] =
+        {
+            {"@args$"_s, spell_params},
+        };
+        caster->npc_pos = run_script_l(ScriptPointer(borrow(*nd->is_script()->scr.script), 0), caster->bl_id, nd->bl_id, arg);
+        return 1;
+    }
+    if (spell_event.label)
+    {
+        dumb_ptr<npc_data> nd = npc_name2id(spell_event.npc);
+        PRINTF("NPC:  %s %d\n"_fmt, nd->name, nd->bl_id);
+        PRINTF("Params:  %s\n"_fmt, spell_params);
+        caster->npc_id = nd->bl_id;
+        dumb_ptr<block_list> map_bl = map_id2bl(nd->bl_id);
+        if (!map_bl)
+            map_addnpc(caster->bl_m, nd);
+        argrec_t arg[1] =
+        {
+            {"@args$"_s, spell_params},
+        };
+        caster->npc_pos = npc_event_do_l(spell_event, caster->bl_id, arg);
         return 1;
     }
     return 0;
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index c4c3ad9..7f7512c 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -54,7 +54,6 @@
 #include "globals.hpp"
 #include "intif.hpp"
 #include "itemdb.hpp"
-#include "magic-stmt.hpp"
 #include "map.hpp"
 #include "map_conf.hpp"
 #include "npc.hpp"
@@ -742,7 +741,7 @@ int pc_authok(AccountId id, int login_id2, ClientVersion client_version,
     // The above is no longer accurate now that we use <chrono>, but
     // I'm still not reverting this.
     // -o11c
-    sd->cast_tick = tick; // + pc_readglobalreg (sd, "MAGIC_CAST_TICK"_s);
+    //sd->cast_tick = tick; // + pc_readglobalreg (sd, "MAGIC_CAST_TICK"_s);
 
     // アカウント変数の送信要求
     intif_request_accountreg(sd);
@@ -3272,8 +3271,8 @@ int pc_damage(dumb_ptr<block_list> src, dumb_ptr<map_session_data> sd,
     clif_updatestatus(sd, SP::HP);
     pc_calcstatus(sd, 0);
     // [Fate] Reset magic
-    sd->cast_tick = gettick();
-    magic::magic_stop_completely(sd);
+    //sd->cast_tick = gettick();
+    //magic_stop_completely(sd);
 
     if (battle_config.death_penalty_type > 0 && sd->status.base_level >= 20)
     {
@@ -4992,7 +4991,7 @@ void do_init_pc(void)
 
 void pc_cleanup(dumb_ptr<map_session_data> sd)
 {
-    magic::magic_stop_completely(sd);
+    //magic_stop_completely(sd);
 }
 
 void pc_invisibility(dumb_ptr<map_session_data> sd, int enabled)
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 35e119b..2d9ac23 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -47,7 +47,6 @@
 #include "globals.hpp"
 #include "intif.hpp"
 #include "itemdb.hpp"
-#include "magic-interpreter-base.hpp"
 #include "map.hpp"
 #include "mob.hpp"
 #include "npc.hpp"
@@ -259,7 +258,6 @@ void builtin_menu(ScriptState *st)
             buf += choice_str;
             buf += ':';
         }
-
         clif_scriptmenu(script_rid2sd(st), st->oid, AString(buf));
     }
     else
@@ -2767,18 +2765,6 @@ void builtin_getitemlink(ScriptState *st)
     push_str<ScriptDataStr>(st->stack, buf);
 }
 
-static
-void builtin_getspellinvocation(ScriptState *st)
-{
-    RString name = conv_str(st, &AARG(0));
-
-    AString invocation = magic::magic_find_invocation(name);
-    if (!invocation)
-        invocation = "..."_s;
-
-    push_str<ScriptDataStr>(st->stack, invocation);
-}
-
 static
 void builtin_getpartnerid2(ScriptState *st)
 {
@@ -3328,18 +3314,6 @@ void builtin_npctalk(ScriptState *st)
         clif_message(nd, str);
 }
 
-/*==========================================
-  * casttime
-  *------------------------------------------
-  */
-static
-void builtin_casttime(ScriptState *st)
-{
-    dumb_ptr<map_session_data> sd = script_rid2sd(st);
-    interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(0)));
-    sd->cast_tick = gettick() + tick;
-}
-
 /*==========================================
   * register cmd
   *------------------------------------------
@@ -3347,13 +3321,15 @@ void builtin_casttime(ScriptState *st)
 static
 void builtin_registercmd(ScriptState *st)
 {
-    dumb_ptr<npc_data> nd = map_id_is_npc(st->oid);
     RString evoke = conv_str(st, &AARG(0));
+    NpcName npcname = stringish<NpcName>(conv_str(st, &AARG(1)));
     ZString event_ = conv_str(st, &AARG(1));
     NpcEvent event;
     extract(event_, &event);
-
-    spells_by_name.put(evoke, event);
+    if (event.label)
+        spells_by_events.put(evoke, event);
+    else
+        spells_by_name.put(evoke, npcname);
 }
 
 /*==========================================
@@ -3826,7 +3802,6 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(marriage, "P"_s, 'i'),
     BUILTIN(divorce, ""_s, 'i'),
     BUILTIN(getitemlink, "I"_s, 's'),
-    BUILTIN(getspellinvocation, "s"_s, 's'),
     BUILTIN(getpartnerid2, ""_s, 'i'),
     BUILTIN(explode, "Nss"_s, '\0'),
     BUILTIN(getinventorylist, ""_s, '\0'),
@@ -3843,8 +3818,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(npcareawarp, "xyxyis"_s, '\0'),
     BUILTIN(message, "Ps"_s, '\0'),
     BUILTIN(npctalk, "ss?"_s, '\0'),
-    BUILTIN(casttime, "i"_s, '\0'),
-    BUILTIN(registercmd, "sE"_s, '\0'),
+    BUILTIN(registercmd, "ss"_s, '\0'),
     BUILTIN(title, "s"_s, '\0'),
     BUILTIN(smsg, "e??"_s, '\0'),
     BUILTIN(remotecmd, "s?"_s, '\0'),
diff --git a/src/map/skill.cpp b/src/map/skill.cpp
index 8a397a3..6066a0d 100644
--- a/src/map/skill.cpp
+++ b/src/map/skill.cpp
@@ -51,7 +51,6 @@
 #include "battle_conf.hpp"
 #include "clif.hpp"
 #include "globals.hpp"
-#include "magic-stmt.hpp"
 #include "mob.hpp"
 #include "pc.hpp"
 
@@ -822,13 +821,6 @@ void skill_status_change_timer(TimerData *tid, tick_t tick, BlockId id, StatusCh
     if (bl->bl_type == BL::PC)
         sd = bl->is_player();
 
-    if (sc_data[type].spell_invocation)
-    {                           // Must report termination
-        magic::spell_effect_report_termination(sc_data[type].spell_invocation,
-                                         bl->bl_id, type, 0);
-        sc_data[type].spell_invocation = BlockId();
-    }
-
     switch (type)
     {
         case StatusChange::SC_POISON:
@@ -1050,11 +1042,6 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
         clif_changeoption(bl);
 
     sc_data[type].val1 = val1;
-    if (sc_data[type].spell_invocation) // Supplant by newer spell
-        magic::spell_effect_report_termination(sc_data[type].spell_invocation,
-                                         bl->bl_id, type, 1);
-
-    sc_data[type].spell_invocation = spell_invocation;
 
     /* タイマー設定 */
     sc_data[type].timer = Timer(gettick() + tick,
diff --git a/src/mmo/cxxstdio_enums.hpp b/src/mmo/cxxstdio_enums.hpp
index 28a8a14..01e8842 100644
--- a/src/mmo/cxxstdio_enums.hpp
+++ b/src/mmo/cxxstdio_enums.hpp
@@ -64,13 +64,6 @@ auto decay_for_printf(BF v) -> typename remove_enum<decltype(v)>::type { return
 inline
 auto decay_for_printf(MapCell v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
 } // namespace map::e
-namespace magic
-{
-enum class SPELLARG : uint8_t;
-
-inline
-auto decay_for_printf(SPELLARG v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); }
-} // namespace map::magic
 
 enum class BL : uint8_t;
 enum class ByteCode : uint8_t;
diff --git a/src/mmo/skill.t.hpp b/src/mmo/skill.t.hpp
index 21e4059..df9c40c 100644
--- a/src/mmo/skill.t.hpp
+++ b/src/mmo/skill.t.hpp
@@ -41,11 +41,7 @@ enum class StatusChange : uint16_t
     // these ones are used by clif_status_change,
     // e.g. by the magic system
     ZERO                = 0,
-    ATTACK_ICON_GENERIC = 2000,
-    ATTACK_ICON_SHEARING = 2001,
-    CART                = 0x0c,
     CLIF_OPTION_SC_INVISIBILITY = 0x1000,
-    CLIF_OPTION_SC_SCRIBE = 0x1001,
 
     // the rest are the normal effects
     SC_SLOWPOISON       = 14,   // item script
-- 
cgit v1.2.3-70-g09d2


From 2ca62094a347c9fe69383709728fe1a64650e401 Mon Sep 17 00:00:00 2001
From: wushin <pasekei@gmail.com>
Date: Thu, 11 Jun 2015 21:54:55 -0500
Subject: Allow getequipid to use charname

---
 src/map/script-fun.cpp | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 2d9ac23..8044b4d 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -1402,7 +1402,10 @@ void builtin_getequipid(ScriptState *st)
     int num;
     dumb_ptr<map_session_data> sd;
 
-    sd = script_rid2sd(st);
+    if (HARG(1))
+        sd = map_nick2sd(stringish<CharName>(ZString(conv_str(st, &AARG(1)))));
+    else
+        sd = script_rid2sd(st);
     if (sd == nullptr)
     {
         PRINTF("getequipid: sd == nullptr\n"_fmt);
@@ -3742,7 +3745,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(getcharid, "i?"_s, 'i'),
     BUILTIN(getversion, ""_s, 'i'),
     BUILTIN(strcharinfo, "i"_s, 's'),
-    BUILTIN(getequipid, "i"_s, 'i'),
+    BUILTIN(getequipid, "i?"_s, 'i'),
     BUILTIN(bonus, "ii"_s, '\0'),
     BUILTIN(bonus2, "iii"_s, '\0'),
     BUILTIN(skill, "ii?"_s, '\0'),
-- 
cgit v1.2.3-70-g09d2


From 87312fe719956671a77a3c78021e821c679764fe Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sat, 13 Jun 2015 14:43:17 -0400
Subject: shrink magic_message

---
 src/map/npc.cpp | 28 ++++++++--------------------
 1 file changed, 8 insertions(+), 20 deletions(-)

(limited to 'src')

diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index eaf54ec..5509b6b 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -206,12 +206,12 @@ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
 
     RString spell_params = pair.second;
 
-    dumb_ptr<npc_data> nd = npc_name2id(spell_name);
+    dumb_ptr<npc_data> nd = npc_name2id(spell_event.label? spell_event.npc: spell_name);
 
     if (nd)
     {
-        PRINTF("NPC:  %s %d\n"_fmt, nd->name, nd->bl_id);
-        PRINTF("Params:  %s\n"_fmt, spell_params);
+        PRINTF("NPC:  '%s' %d\n"_fmt, nd->name, nd->bl_id);
+        PRINTF("Params:  '%s'\n"_fmt, spell_params);
         caster->npc_id = nd->bl_id;
         dumb_ptr<block_list> map_bl = map_id2bl(nd->bl_id);
         if (!map_bl)
@@ -220,23 +220,11 @@ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
         {
             {"@args$"_s, spell_params},
         };
-        caster->npc_pos = run_script_l(ScriptPointer(borrow(*nd->is_script()->scr.script), 0), caster->bl_id, nd->bl_id, arg);
-        return 1;
-    }
-    if (spell_event.label)
-    {
-        dumb_ptr<npc_data> nd = npc_name2id(spell_event.npc);
-        PRINTF("NPC:  %s %d\n"_fmt, nd->name, nd->bl_id);
-        PRINTF("Params:  %s\n"_fmt, spell_params);
-        caster->npc_id = nd->bl_id;
-        dumb_ptr<block_list> map_bl = map_id2bl(nd->bl_id);
-        if (!map_bl)
-            map_addnpc(caster->bl_m, nd);
-        argrec_t arg[1] =
-        {
-            {"@args$"_s, spell_params},
-        };
-        caster->npc_pos = npc_event_do_l(spell_event, caster->bl_id, arg);
+
+        if (spell_event.label)
+            caster->npc_pos = npc_event_do_l(spell_event, caster->bl_id, arg);
+        else
+            caster->npc_pos = run_script_l(ScriptPointer(borrow(*nd->is_script()->scr.script), 0), caster->bl_id, nd->bl_id, arg);
         return 1;
     }
     return 0;
-- 
cgit v1.2.3-70-g09d2


From ef4ae4d281127a0b84a73ab034bd22d6281a09ed Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 18 Jun 2015 01:56:31 -0500
Subject: implement puppet npcs

---
 src/map/map.hpp          |   4 ++
 src/map/npc-internal.hpp |   1 +
 src/map/npc-parse.cpp    |   2 +-
 src/map/npc-parse.hpp    |   1 +
 src/map/npc.cpp          |  37 ++++++++-----
 src/map/script-call.cpp  |  22 +++++++-
 src/map/script-fun.cpp   | 131 +++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 183 insertions(+), 15 deletions(-)

(limited to 'src')

diff --git a/src/map/map.hpp b/src/map/map.hpp
index 66253ca..a2d0f5d 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -331,6 +331,7 @@ struct npc_data : block_list
     Opt3 opt3;
     Opt0 option;
     short flag;
+    bool disposable;
 
     std::list<RString> eventqueuel;
     Array<Timer, MAX_EVENTTIMER> eventtimer;
@@ -359,6 +360,9 @@ public:
         short xs, ys;
         bool event_needs_map;
 
+        // the npc containing the actual script
+        BlockId parent;
+
         // Whether the timer advances if not beyond end.
         bool timer_active;
         // Tick counter through the timers.
diff --git a/src/map/npc-internal.hpp b/src/map/npc-internal.hpp
index 993263f..8e9e030 100644
--- a/src/map/npc-internal.hpp
+++ b/src/map/npc-internal.hpp
@@ -31,6 +31,7 @@ namespace map
 struct event_data
 {
     dumb_ptr<npc_data_script> nd;
+    BlockId child;
     int pos;
 };
 } // namespace map
diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp
index 0bd23a9..edfe4c9 100644
--- a/src/map/npc-parse.cpp
+++ b/src/map/npc-parse.cpp
@@ -92,7 +92,6 @@ void npc_delsrcfile(XString name)
     }
 }
 
-static
 void register_npc_name(dumb_ptr<npc_data> nd)
 {
     earray<LString, NpcSubtype, NpcSubtype::COUNT> types //=
@@ -474,6 +473,7 @@ bool npc_load_script_none(ast::script::ScriptBody& body, ast::npc::ScriptNone& s
     nd->bl_type = BL::NPC;
     nd->npc_subtype = NpcSubtype::SCRIPT;
 
+    id_db.put(nd->bl_id, nd); // fix to get the oid in OnInit
     register_npc_name(nd);
 
     for (auto& pair : scriptlabel_db)
diff --git a/src/map/npc-parse.hpp b/src/map/npc-parse.hpp
index 9bc3448..d6719ee 100644
--- a/src/map/npc-parse.hpp
+++ b/src/map/npc-parse.hpp
@@ -39,6 +39,7 @@ dumb_ptr<npc_data> npc_spawn_text(Borrowed<map_local> m, int x, int y,
 
 void npc_addsrcfile(AString name);
 void npc_delsrcfile(XString name);
+void register_npc_name(dumb_ptr<npc_data> nd);
 bool do_init_npc(void);
 } // namespace map
 } // namespace tmwa
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index 5509b6b..ae126ea 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -61,6 +61,15 @@ namespace tmwa
 {
 namespace map
 {
+const std::vector<ByteCode> fake_buffer;
+const ScriptBuffer& fake_script = reinterpret_cast<const ScriptBuffer&>(fake_buffer);
+
+static
+Borrowed<const ScriptBuffer> script_or_parent(dumb_ptr<npc_data_script> nd)
+{
+    return borrow(nd->scr.parent ? fake_script : *nd->scr.script);
+}
+
 BlockId npc_get_new_npc_id(void)
 {
     BlockId rv = npc_id;
@@ -224,7 +233,7 @@ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
         if (spell_event.label)
             caster->npc_pos = npc_event_do_l(spell_event, caster->bl_id, arg);
         else
-            caster->npc_pos = run_script_l(ScriptPointer(borrow(*nd->is_script()->scr.script), 0), caster->bl_id, nd->bl_id, arg);
+            caster->npc_pos = run_script_l(ScriptPointer(script_or_parent(nd->is_script()), 0), caster->bl_id, nd->bl_id, arg);
         return 1;
     }
     return 0;
@@ -281,7 +290,9 @@ void npc_event_doall_sub(NpcEvent key, struct event_data *ev,
 
     if (name == p)
     {
-        run_script_l(ScriptPointer(borrow(*ev->nd->scr.script), ev->pos), rid, ev->nd->bl_id,
+        if (ev->nd->disposable)
+            return; // temporary npcs only respond to commands directly issued to them
+        run_script_l(ScriptPointer(script_or_parent(ev->nd), ev->pos), rid, ev->nd->bl_id,
                 argv);
         (*c)++;
     }
@@ -304,7 +315,7 @@ void npc_event_do_sub(NpcEvent key, struct event_data *ev,
 
     if (name == key)
     {
-        run_script_l(ScriptPointer(borrow(*ev->nd->scr.script), ev->pos), rid, ev->nd->bl_id,
+        run_script_l(ScriptPointer(script_or_parent(ev->nd), ev->pos), rid, ev->nd->bl_id,
                 argv);
         (*c)++;
     }
@@ -414,7 +425,7 @@ void npc_eventtimer(TimerData *, tick_t, BlockId id, NpcEvent data)
             return;
     }
 
-    run_script(ScriptPointer(borrow(*nd->scr.script), ev->pos), id, nd->bl_id);
+    run_script(ScriptPointer(script_or_parent(nd), ev->pos), id, nd->bl_id);
 }
 
 /*==========================================
@@ -486,7 +497,7 @@ void npc_timerevent(TimerData *, tick_t tick, BlockId id, interval_t data)
                     id, next));
     }
 
-    run_script(ScriptPointer(borrow(*nd->scr.script), te->pos), BlockId(), nd->bl_id);
+    run_script(ScriptPointer(script_or_parent(nd), te->pos), BlockId(), nd->bl_id);
 }
 
 /// Start (or resume) counting ticks to the next npc_timerevent.
@@ -651,7 +662,7 @@ int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname,
 
     sd->npc_id = nd->bl_id;
     sd->npc_pos =
-        run_script(ScriptPointer(borrow(*nd->scr.script), ev->pos), sd->bl_id, nd->bl_id);
+        run_script(ScriptPointer(script_or_parent(nd), ev->pos), sd->bl_id, nd->bl_id);
     return 0;
 }
 
@@ -805,7 +816,7 @@ int npc_click(dumb_ptr<map_session_data> sd, BlockId id)
             npc_event_dequeue(sd);
             break;
         case NpcSubtype::SCRIPT:
-            sd->npc_pos = run_script(ScriptPointer(borrow(*nd->is_script()->scr.script), 0), sd->bl_id, id);
+            sd->npc_pos = run_script(ScriptPointer(script_or_parent(nd->is_script()), 0), sd->bl_id, id);
             break;
         case NpcSubtype::MESSAGE:
             if (nd->is_message()->message)
@@ -846,7 +857,7 @@ int npc_scriptcont(dumb_ptr<map_session_data> sd, BlockId id)
         return 0;
     }
 
-    sd->npc_pos = run_script(ScriptPointer(borrow(*nd->is_script()->scr.script), sd->npc_pos), sd->bl_id, id);
+    sd->npc_pos = run_script(ScriptPointer(script_or_parent(nd->is_script()), sd->npc_pos), sd->bl_id, id);
 
     return 0;
 }
@@ -1032,18 +1043,18 @@ void npc_free_internal(dumb_ptr<npc_data> nd_)
     if (nd_->npc_subtype == NpcSubtype::SCRIPT)
     {
         dumb_ptr<npc_data_script> nd = nd_->is_script();
+        nd->scr.timerid.cancel();
         nd->scr.timer_eventv.clear();
-
-        {
-            nd->scr.script.reset();
-            nd->scr.label_listv.clear();
-        }
+        nd->scr.script.reset();
+        nd->scr.label_listv.clear();
     }
     else if (nd_->npc_subtype == NpcSubtype::MESSAGE)
     {
         dumb_ptr<npc_data_message> nd = nd_->is_message();
         nd->message = AString();
     }
+    if (nd_->name)
+        npcs_by_name.put(nd_->name, nullptr);
     nd_.delete_();
 }
 
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index 54f6c01..b8f531a 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -876,7 +876,26 @@ int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid,
 {
     struct script_stack stack;
     ScriptState st;
-    dumb_ptr<map_session_data> sd = map_id2sd(rid);
+    dumb_ptr<block_list> bl = map_id2bl(rid);
+    dumb_ptr<map_session_data> sd = bl? bl->is_player(): nullptr;
+    if (oid)
+    {
+        dumb_ptr<block_list> oid_bl = map_id2bl(oid);
+        if (oid_bl->bl_type == BL::NPC)
+        {
+            dumb_ptr<npc_data> nd = oid_bl->is_npc();
+            if(nd->npc_subtype == NpcSubtype::SCRIPT)
+            {
+                dumb_ptr<npc_data_script> nds = nd->is_script();
+                if (nds->scr.parent)
+                {
+                    dumb_ptr<npc_data_script> parent = map_id2bl(nds->scr.parent)->is_npc()->is_script();
+                    assert(parent->bl_type == BL::NPC && parent->npc_subtype == NpcSubtype::SCRIPT);
+                    sp = ScriptPointer(borrow(*parent->scr.script), sp.pos);
+                }
+            }
+        }
+    }
     P<const ScriptBuffer> rootscript = TRY_UNWRAP(sp.code, return -1);
     int i;
     if (sp.pos >> 24)
@@ -892,6 +911,7 @@ int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid,
     st.scriptp = sp;
     st.rid = rid;
     st.oid = oid;
+
     for (i = 0; i < args.size(); i++)
     {
         if (args[i].name.back() == '$')
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 8044b4d..4ef6499 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -50,6 +50,7 @@
 #include "map.hpp"
 #include "mob.hpp"
 #include "npc.hpp"
+#include "npc-parse.hpp"
 #include "party.hpp"
 #include "pc.hpp"
 #include "script-call-internal.hpp"
@@ -57,6 +58,7 @@
 #include "script-persist.hpp"
 #include "skill.hpp"
 #include "storage.hpp"
+#include "npc-internal.hpp"
 
 #include "../poison.hpp"
 
@@ -773,6 +775,132 @@ void builtin_foreach(ScriptState *st)
             x1, y1,
             block_type);
 }
+/*========================================
+ * Destructs a temp NPC
+ *----------------------------------------
+ */
+static
+void builtin_destroypuppet(ScriptState *st)
+{
+    BlockId id;
+    if (HARG(0))
+        id = wrap<BlockId>(conv_num(st, &AARG(0)));
+    else
+        id = st->oid;
+
+    dumb_ptr<npc_data_script> nd = map_id2bl(id)->is_npc()->is_script();
+    if(!nd)
+        return;
+    assert(nd->disposable == true);
+    npc_free(nd);
+    if (!HARG(0))
+        st->state = ScriptEndState::END;
+}
+/*========================================
+ * Creates a temp NPC
+ *----------------------------------------
+ */
+
+static
+void builtin_puppet(ScriptState *st)
+{
+    int x, y;
+
+    dumb_ptr<block_list> bl = map_id2bl(st->oid);
+    dumb_ptr<npc_data_script> parent_nd = bl->is_npc()->is_script();
+    dumb_ptr<npc_data_script> nd;
+    nd.new_();
+
+    MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0))));
+    x = conv_num(st, &AARG(1));
+    y = conv_num(st, &AARG(2));
+    Species sprite = wrap<Species>(static_cast<uint16_t>(conv_num(st, &AARG(4))));
+
+    P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return);
+
+    nd->bl_prev = nd->bl_next = nullptr;
+    nd->scr.event_needs_map = false;
+    nd->disposable = true; // allow to destroy
+
+    // PlayerName::SpellName
+    NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(3))));
+    PRINTF("Npc: %s\n"_fmt, npc);
+    nd->name = npc;
+
+    // Dynamically set location
+    nd->bl_m = m;
+    nd->bl_x = x;
+    nd->bl_y = y;
+    nd->bl_id = npc_get_new_npc_id();
+    nd->scr.parent = parent_nd->bl_id;
+    nd->dir = DIR::S;
+    nd->flag = 0;
+    nd->sit = DamageType::STAND;
+    nd->npc_class = sprite;
+    nd->speed = 200_ms;
+    nd->option = Opt0::ZERO;
+    nd->opt1 = Opt1::ZERO;
+    nd->opt2 = Opt2::ZERO;
+    nd->opt3 = Opt3::ZERO;
+    nd->scr.label_listv = parent_nd->scr.label_listv;
+    nd->bl_type = BL::NPC;
+    nd->npc_subtype = NpcSubtype::SCRIPT;
+    npc_script++;
+
+    nd->n = map_addnpc(nd->bl_m, nd);
+
+    map_addblock(nd);
+    clif_spawnnpc(nd);
+
+    register_npc_name(nd);
+
+    for (npc_label_list& el : parent_nd->scr.label_listv)
+    {
+        ScriptLabel lname = el.name;
+        int pos = el.pos;
+
+        if (lname.startswith("On"_s))
+        {
+            struct event_data ev {};
+            ev.nd = nd;
+            ev.pos = pos;
+            NpcEvent buf;
+            buf.npc = nd->name;
+            buf.label = lname;
+            ev_db.insert(buf, ev);
+        }
+    }
+
+    for (npc_label_list& el : parent_nd->scr.label_listv)
+    {
+        int t_ = 0;
+        ScriptLabel lname = el.name;
+        int pos = el.pos;
+        if (lname.startswith("OnTimer"_s) && extract(lname.xslice_t(7), &t_) && t_ > 0)
+        {
+            interval_t t = static_cast<interval_t>(t_);
+
+            npc_timerevent_list tel {};
+            tel.timer = t;
+            tel.pos = pos;
+
+            auto it = std::lower_bound(nd->scr.timer_eventv.begin(), nd->scr.timer_eventv.end(), tel,
+                    [](const npc_timerevent_list& l, const npc_timerevent_list& r)
+                    {
+                        return l.timer < r.timer;
+                    }
+            );
+            assert (it == nd->scr.timer_eventv.end() || it->timer != tel.timer);
+
+            nd->scr.timer_eventv.insert(it, std::move(tel));
+        }
+    }
+
+    nd->scr.timer = interval_t::zero();
+    nd->scr.next_event = nd->scr.timer_eventv.begin();
+
+    push_int<ScriptDataInt>(st->stack, unwrap<BlockId>(nd->bl_id));
+}
 
 /*==========================================
  * 変数設定
@@ -2472,6 +2600,7 @@ void builtin_attachrid(ScriptState *st)
     push_int<ScriptDataInt>(st->stack, (map_id2sd(st->rid) != nullptr));
 }
 
+
 /*==========================================
  * RIDのデタッチ
  *------------------------------------------
@@ -3838,6 +3967,8 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(isdead, ""_s, 'i'),
     BUILTIN(aggravate, "Mxyxyi"_s, '\0'),
     BUILTIN(fakenpcname, "ssi"_s, '\0'),
+    BUILTIN(puppet, "mxysi"_s, 'i'),
+    BUILTIN(destroypuppet, "?"_s, '\0'),
     BUILTIN(getx, ""_s, 'i'),
     BUILTIN(gety, ""_s, 'i'),
     BUILTIN(getnpcx, "?"_s, 'i'),
-- 
cgit v1.2.3-70-g09d2


From 98e5e62ef7a04977146fdbfbc3166dad5d082da0 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 18 Jun 2015 23:41:58 -0400
Subject: add scope, npc/mob variables; add new get/set

---
 src/map/map.hpp                  |  14 +-
 src/map/pc.cpp                   |   8 +-
 src/map/pc.hpp                   |   8 +-
 src/map/script-call-internal.hpp |  18 ++-
 src/map/script-call.cpp          |  87 ++++++++++---
 src/map/script-fun.cpp           | 272 +++++++++++++++++++++++++++++++++------
 src/map/script-parse.cpp         |   5 +
 7 files changed, 339 insertions(+), 73 deletions(-)

(limited to 'src')

diff --git a/src/map/map.hpp b/src/map/map.hpp
index a2d0f5d..72d5dd3 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -77,6 +77,13 @@ struct block_list
     short bl_x, bl_y;
     BL bl_type;
 
+    // register keys are ints (interned)
+    // Not anymore! Well, sort of.
+    DMap<SIR, int> regm;
+    // can't be DMap because we want predictable .c_str()s
+    // TODO this can change now
+    Map<SIR, RString> regstrm;
+
     // This deletes the copy-ctor also
     // TODO give proper ctors.
     block_list& operator = (block_list&&) = delete;
@@ -251,13 +258,6 @@ struct map_session_data : block_list, SessionData
 
     int die_counter;
 
-    // register keys are ints (interned)
-    // Not anymore! Well, sort of.
-    DMap<SIR, int> regm;
-    // can't be DMap because we want predictable .c_str()s
-    // TODO this can change now
-    Map<SIR, RString> regstrm;
-
     earray<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
 
     AccountId trade_partner;
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 7f7512c..f65e2b4 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -3814,7 +3814,7 @@ int pc_changelook(dumb_ptr<map_session_data> sd, LOOK type, int val)
  * script用変数の値を読む
  *------------------------------------------
  */
-int pc_readreg(dumb_ptr<map_session_data> sd, SIR reg)
+int pc_readreg(dumb_ptr<block_list> sd, SIR reg)
 {
     nullpo_retz(sd);
 
@@ -3825,7 +3825,7 @@ int pc_readreg(dumb_ptr<map_session_data> sd, SIR reg)
  * script用変数の値を設定
  *------------------------------------------
  */
-void pc_setreg(dumb_ptr<map_session_data> sd, SIR reg, int val)
+void pc_setreg(dumb_ptr<block_list> sd, SIR reg, int val)
 {
     nullpo_retv(sd);
 
@@ -3836,7 +3836,7 @@ void pc_setreg(dumb_ptr<map_session_data> sd, SIR reg, int val)
  * script用文字列変数の値を読む
  *------------------------------------------
  */
-ZString pc_readregstr(dumb_ptr<map_session_data> sd, SIR reg)
+ZString pc_readregstr(dumb_ptr<block_list> sd, SIR reg)
 {
     nullpo_retr(ZString(), sd);
 
@@ -3848,7 +3848,7 @@ ZString pc_readregstr(dumb_ptr<map_session_data> sd, SIR reg)
  * script用文字列変数の値を設定
  *------------------------------------------
  */
-void pc_setregstr(dumb_ptr<map_session_data> sd, SIR reg, RString str)
+void pc_setregstr(dumb_ptr<block_list> sd, SIR reg, RString str)
 {
     nullpo_retv(sd);
 
diff --git a/src/map/pc.hpp b/src/map/pc.hpp
index 3a35330..6bcfadb 100644
--- a/src/map/pc.hpp
+++ b/src/map/pc.hpp
@@ -144,10 +144,10 @@ int pc_changelook(dumb_ptr<map_session_data>, LOOK, int);
 
 int pc_readparam(dumb_ptr<map_session_data>, SP);
 int pc_setparam(dumb_ptr<map_session_data>, SP, int);
-int pc_readreg(dumb_ptr<map_session_data>, SIR);
-void pc_setreg(dumb_ptr<map_session_data>, SIR, int);
-ZString pc_readregstr(dumb_ptr<map_session_data> sd, SIR reg);
-void pc_setregstr(dumb_ptr<map_session_data> sd, SIR reg, RString str);
+int pc_readreg(dumb_ptr<block_list>, SIR);
+void pc_setreg(dumb_ptr<block_list>, SIR, int);
+ZString pc_readregstr(dumb_ptr<block_list> sd, SIR reg);
+void pc_setregstr(dumb_ptr<block_list> sd, SIR reg, RString str);
 void update_quest(dumb_ptr<map_session_data> sd, VarName quest_var, int value);
 void update_allquest(dumb_ptr<map_session_data> sd);
 int pc_readglobalreg(dumb_ptr<map_session_data>, VarName );
diff --git a/src/map/script-call-internal.hpp b/src/map/script-call-internal.hpp
index a887eb8..b3dfb5a 100644
--- a/src/map/script-call-internal.hpp
+++ b/src/map/script-call-internal.hpp
@@ -25,6 +25,8 @@
 
 #include "../mmo/ids.hpp"
 
+#include "../strings/rstring.hpp"
+#include "../generic/db.hpp"
 #include "script-persist.hpp"
 
 
@@ -55,6 +57,13 @@ public:
     ScriptPointer scriptp, new_scriptp;
     int defsp, new_defsp, freeloop;
     int is_true = 0;
+
+    // register keys are ints (interned)
+    // Not anymore! Well, sort of.
+    DMap<SIR, int> regm;
+    // can't be DMap because we want predictable .c_str()s
+    // TODO this can change now
+    Map<SIR, RString> regstrm;
 };
 
 void run_func(ScriptState *st);
@@ -70,13 +79,14 @@ enum class ScriptEndState
 };
 
 dumb_ptr<map_session_data> script_rid2sd(ScriptState *st);
-void get_val(dumb_ptr<map_session_data> sd, struct script_data *data);
+void get_val(dumb_ptr<block_list> sd, struct script_data *data);
 __attribute__((deprecated))
 void get_val(ScriptState *st, struct script_data *data);
 struct script_data get_val2(ScriptState *st, SIR reg);
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd);
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id);
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd);
+void set_scope_reg(ScriptState *, SIR, struct script_data);
+void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, struct script_data vd);
+void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, int id);
+void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, RString zd);
 __attribute__((warn_unused_result))
 RString conv_str(ScriptState *st, struct script_data *data);
 __attribute__((warn_unused_result))
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index b8f531a..2ebfca3 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -78,7 +78,7 @@ dumb_ptr<map_session_data> script_rid2sd(ScriptState *st)
  * 変数の読み取り
  *------------------------------------------
  */
-void get_val(dumb_ptr<map_session_data> sd, struct script_data *data)
+void get_val(dumb_ptr<block_list> sd, struct script_data *data)
 {
     MATCH_BEGIN (*data)
     {
@@ -88,7 +88,7 @@ void get_val(dumb_ptr<map_session_data> sd, struct script_data *data)
                 PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp());
             int numi = 0;
             if (sd)
-                numi = pc_readparam(sd, u.reg.sp());
+                numi = pc_readparam(sd->is_player(), u.reg.sp());
             *data = ScriptDataInt{numi};
         }
         MATCH_CASE (const ScriptDataVariable&, u)
@@ -106,7 +106,7 @@ void get_val(dumb_ptr<map_session_data> sd, struct script_data *data)
             if (postfix == '$')
             {
                 RString str;
-                if (prefix == '@')
+                if (prefix == '@' || prefix == '.')
                 {
                     if (sd)
                         str = pc_readregstr(sd, u.reg);
@@ -130,7 +130,7 @@ void get_val(dumb_ptr<map_session_data> sd, struct script_data *data)
             else
             {
                 int numi = 0;
-                if (prefix == '@')
+                if (prefix == '@' || prefix == '.')
                 {
                     if (sd)
                         numi = pc_readreg(sd, u.reg);
@@ -144,18 +144,18 @@ void get_val(dumb_ptr<map_session_data> sd, struct script_data *data)
                     if (name[1] == '#')
                     {
                         if (sd)
-                            numi = pc_readaccountreg2(sd, name);
+                            numi = pc_readaccountreg2(sd->is_player(), name);
                     }
                     else
                     {
                         if (sd)
-                            numi = pc_readaccountreg(sd, name);
+                            numi = pc_readaccountreg(sd->is_player(), name);
                     }
                 }
                 else
                 {
                     if (sd)
-                        numi = pc_readglobalreg(sd, name);
+                        numi = pc_readglobalreg(sd->is_player(), name);
                 }
                 *data = ScriptDataInt{numi};
             }
@@ -166,8 +166,38 @@ void get_val(dumb_ptr<map_session_data> sd, struct script_data *data)
 
 void get_val(ScriptState *st, struct script_data *data)
 {
-    dumb_ptr<map_session_data> sd = st->rid ? map_id2sd(st->rid) : nullptr;
-    get_val(sd, data);
+    dumb_ptr<block_list> bl = nullptr;
+    MATCH_BEGIN (*data)
+    {
+        MATCH_CASE (const ScriptDataParam&, u)
+        {
+            bl = map_id2bl(st->rid);
+        }
+        MATCH_CASE (const ScriptDataVariable&, u)
+        {
+            ZString name_ = variable_names.outtern(u.reg.base());
+            VarName name = stringish<VarName>(name_);
+            char prefix = name.front();
+            if (prefix == '.' && name[1] == '@')
+            {
+                if (name.back() == '$')
+                {
+                    Option<P<RString>> s = st->regstrm.search(u.reg);
+                    ZString val = s.map([](P<RString> s_) -> ZString { return *s_; }).copy_or(""_s);
+                    *data = ScriptDataStr{val};
+                }
+                else
+                    *data = ScriptDataInt{st->regm.get(u.reg)};
+                return;
+            }
+            if (prefix == '.' && st->oid)
+                bl = map_id2bl(st->oid);
+            else if (prefix != '$' && st->rid)
+                bl = map_id2bl(st->rid);
+        }
+    }
+    MATCH_END ();
+    get_val(bl, data);
 }
 
 /*==========================================
@@ -185,12 +215,12 @@ struct script_data get_val2(ScriptState *st, SIR reg)
  * 変数設定用
  *------------------------------------------
  */
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd)
+void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, struct script_data vd)
 {
     if (type == VariableCode::PARAM)
     {
         int val = vd.get_if<ScriptDataInt>()->numi;
-        pc_setparam(sd, reg.sp(), val);
+        pc_setparam(sd->is_player(), reg.sp(), val);
         return;
     }
     assert (type == VariableCode::VARIABLE);
@@ -203,7 +233,7 @@ void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct s
     if (postfix == '$')
     {
         RString str = vd.get_if<ScriptDataStr>()->str;
-        if (prefix == '@')
+        if (prefix == '@' || prefix == '.')
         {
             pc_setregstr(sd, reg, str);
         }
@@ -219,7 +249,7 @@ void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct s
     else
     {
         int val = vd.get_if<ScriptDataInt>()->numi;
-        if (prefix == '@')
+        if (prefix == '@' || prefix == '.')
         {
             pc_setreg(sd, reg, val);
         }
@@ -230,24 +260,45 @@ void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct s
         else if (prefix == '#')
         {
             if (name[1] == '#')
-                pc_setaccountreg2(sd, name, val);
+                pc_setaccountreg2(sd->is_player(), name, val);
             else
-                pc_setaccountreg(sd, name, val);
+                pc_setaccountreg(sd->is_player(), name, val);
         }
         else
         {
-            pc_setglobalreg(sd, name, val);
+            pc_setglobalreg(sd->is_player(), name, val);
+        }
+    }
+}
+
+void set_scope_reg(ScriptState *st, SIR reg, struct script_data vd)
+{
+    ZString name = variable_names.outtern(reg.base());
+    if (name.back() == '$')
+    {
+        if (auto *u = vd.get_if<ScriptDataStr>())
+        {
+            if (!u->str)
+            {
+                st->regstrm.erase(reg);
+                return;
+            }
+            st->regstrm.insert(reg, u->str);
         }
+        else
+            st->regstrm.erase(reg);
     }
+    else if (auto *u = vd.get_if<ScriptDataInt>())
+        st->regm.put(reg, u->numi);
 }
 
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id)
+void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, int id)
 {
     struct script_data vd = ScriptDataInt{id};
     set_reg(sd, type, reg, vd);
 }
 
-void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd)
+void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, RString zd)
 {
     struct script_data vd = ScriptDataStr{zd};
     set_reg(sd, type, reg, vd);
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 4ef6499..f215d73 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -780,7 +780,7 @@ void builtin_foreach(ScriptState *st)
  *----------------------------------------
  */
 static
-void builtin_destroypuppet(ScriptState *st)
+void builtin_destroy(ScriptState *st)
 {
     BlockId id;
     if (HARG(0))
@@ -909,37 +909,116 @@ void builtin_puppet(ScriptState *st)
 static
 void builtin_set(ScriptState *st)
 {
-    dumb_ptr<map_session_data> sd = nullptr;
+    BlockId id;
+    dumb_ptr<block_list> bl = nullptr;
     if (auto *u = AARG(0).get_if<ScriptDataParam>())
     {
         SIR reg = u->reg;
-        sd = script_rid2sd(st);
+        if(HARG(2))
+        {
+            struct script_data *sdata = &AARG(2);
+            get_val(st, sdata);
+            CharName name;
+            if (sdata->is<ScriptDataStr>())
+            {
+                name = stringish<CharName>(ZString(conv_str(st, sdata)));
+                if (name.to__actual())
+                    bl = map_nick2sd(name);
+            }
+            else
+            {
+                id = wrap<BlockId>(conv_num(st, sdata));
+                bl = map_id2bl(id);
+            }
+        }
+
+        else
+            bl = script_rid2sd(st)->is_player();
 
+        if (bl == nullptr)
+            return;
         int val = conv_num(st, &AARG(1));
-        set_reg(sd, VariableCode::PARAM, reg, val);
+        set_reg(bl, VariableCode::PARAM, reg, val);
         return;
     }
 
     SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg;
 
     ZString name = variable_names.outtern(reg.base());
-    char prefix = name.front();
-    char postfix = name.back();
+    VarName name_ = stringish<VarName>(name);
+    char prefix = name_.front();
+    char postfix = name_.back();
 
     if (prefix != '$')
-        sd = script_rid2sd(st);
+    {
+        if(HARG(2))
+        {
+            struct script_data *sdata = &AARG(2);
+            get_val(st, sdata);
+            if(prefix == '.')
+            {
+                if (name_[1] == '@')
+                {
+                    PRINTF("builtin_set: illegal scope!\n"_fmt);
+                    return;
+                }
+                NpcName n_name;
+                if (sdata->is<ScriptDataStr>())
+                {
+                    n_name = stringish<NpcName>(ZString(conv_str(st, sdata)));
+                    bl = npc_name2id(n_name);
+                }
+                else
+                {
+                    id = wrap<BlockId>(conv_num(st, sdata));
+                    bl = map_id2bl(id);
+                }
+            }
+            else
+            {
+                CharName c_name;
+                if (sdata->is<ScriptDataStr>())
+                {
+                    c_name = stringish<CharName>(ZString(conv_str(st, sdata)));
+                    if (c_name.to__actual())
+                        bl = map_nick2sd(c_name);
+                }
+                else
+                {
+                    id = wrap<BlockId>(conv_num(st, sdata));
+                    bl = map_id2bl(id);
+                }
+            }
+        }
+        else
+        {
+            if(prefix == '.')
+            {
+                if (name_[1] == '@')
+                {
+                        set_scope_reg(st, reg, AARG(1));
+                    return;
+                }
+                bl = map_id2bl(st->oid)->is_npc();
+            }
+            else
+                bl = map_id2bl(st->rid)->is_player();
+        }
+        if (bl == nullptr)
+            return;
+    }
 
     if (postfix == '$')
     {
         // 文字列
         RString str = conv_str(st, &AARG(1));
-        set_reg(sd, VariableCode::VARIABLE, reg, str);
+        set_reg(bl, VariableCode::VARIABLE, reg, str);
     }
     else
     {
         // 数値
         int val = conv_num(st, &AARG(1));
-        set_reg(sd, VariableCode::VARIABLE, reg, val);
+        set_reg(bl, VariableCode::VARIABLE, reg, val);
     }
 
 }
@@ -951,26 +1030,30 @@ void builtin_set(ScriptState *st)
 static
 void builtin_setarray(ScriptState *st)
 {
-    dumb_ptr<map_session_data> sd = nullptr;
+    dumb_ptr<block_list> bl = nullptr;
     SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg;
     ZString name = variable_names.outtern(reg.base());
     char prefix = name.front();
     char postfix = name.back();
 
-    if (prefix != '$' && prefix != '@')
+    if (prefix != '$' && prefix != '@' && prefix != '.')
     {
         PRINTF("builtin_setarray: illegal scope!\n"_fmt);
         return;
     }
-    if (prefix != '$')
-        sd = script_rid2sd(st);
+    if (prefix == '.' && name[1] != '@')
+        bl = map_id2bl(st->oid)->is_npc();
+    else if (prefix != '$')
+        bl = map_id2bl(st->rid)->is_player();
 
     for (int j = 0, i = 1; i < st->end - st->start - 2 && j < 256; i++, j++)
     {
-        if (postfix == '$')
-            set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_str(st, &AARG(i)));
+        if (prefix == '.' && name[1] == '@')
+            set_scope_reg(st, reg.iplus(j), AARG(i));
+        else if (postfix == '$')
+            set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), conv_str(st, &AARG(i)));
         else
-            set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_num(st, &AARG(i)));
+            set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), conv_num(st, &AARG(i)));
     }
 }
 
@@ -981,27 +1064,31 @@ void builtin_setarray(ScriptState *st)
 static
 void builtin_cleararray(ScriptState *st)
 {
-    dumb_ptr<map_session_data> sd = nullptr;
+    dumb_ptr<block_list> bl = nullptr;
     SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg;
     ZString name = variable_names.outtern(reg.base());
     char prefix = name.front();
     char postfix = name.back();
     int sz = conv_num(st, &AARG(2));
 
-    if (prefix != '$' && prefix != '@')
+    if (prefix != '$' && prefix != '@' && prefix != '.')
     {
         PRINTF("builtin_cleararray: illegal scope!\n"_fmt);
         return;
     }
-    if (prefix != '$')
-        sd = script_rid2sd(st);
+    if (prefix == '.' && name[1] != '@')
+        bl = map_id2bl(st->oid)->is_npc();
+    else if (prefix != '$')
+        bl = map_id2bl(st->rid)->is_player();
 
     for (int i = 0; i < sz; i++)
     {
-        if (postfix == '$')
-            set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARG(1)));
+        if (prefix == '.' && name[1] == '@')
+            set_scope_reg(st, reg.iplus(i), AARG(i));
+        else if (postfix == '$')
+            set_reg(bl, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARG(1)));
         else
-            set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_num(st, &AARG(1)));
+            set_reg(bl, VariableCode::VARIABLE, reg.iplus(i), conv_num(st, &AARG(1)));
     }
 
 }
@@ -1045,7 +1132,7 @@ void builtin_getarraysize(ScriptState *st)
     ZString name = variable_names.outtern(reg.base());
     char prefix = name.front();
 
-    if (prefix != '$' && prefix != '@')
+    if (prefix != '$' && prefix != '@' && prefix != '.')
     {
         PRINTF("builtin_copyarray: illegal scope!\n"_fmt);
         return;
@@ -2908,7 +2995,7 @@ void builtin_getpartnerid2(ScriptState *st)
 static
 void builtin_explode(ScriptState *st)
 {
-    dumb_ptr<map_session_data> sd = nullptr;
+    dumb_ptr<block_list> bl = nullptr;
     SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg;
     ZString name = variable_names.outtern(reg.base());
     const char separator = conv_str(st, &AARG(2))[0];
@@ -2917,33 +3004,49 @@ void builtin_explode(ScriptState *st)
     char prefix = name.front();
     char postfix = name.back();
 
-    if (prefix != '$' && prefix != '@')
+    if (prefix != '$' && prefix != '@' && prefix != '.')
     {
         PRINTF("builtin_explode: illegal scope!\n"_fmt);
         return;
     }
-    if (prefix != '$')
-        sd = script_rid2sd(st);
+    if (prefix == '.' && name[1] != '@')
+        bl = map_id2bl(st->oid)->is_npc();
+    else if (prefix != '$' && prefix != '.')
+        bl = map_id2bl(st->rid)->is_player();
 
     for (int j = 0; j < 256; j++)
     {
         auto find = std::find(str.begin(), str.end(), separator);
         if (find == str.end())
         {
-            if (postfix == '$')
-                set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), str);
+            if (prefix == '.' && name[1] == '@')
+            {
+                struct script_data vd = script_data(ScriptDataInt{atoi(str.c_str())});
+                if (postfix == '$')
+                    vd = script_data(ScriptDataStr{str});
+                set_scope_reg(st, reg.iplus(j), &vd);
+            }
+            else if (postfix == '$')
+                set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), str);
             else
-                set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), atoi(str.c_str()));
+                set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), atoi(str.c_str()));
             break;
         }
         {
             val = str.xislice_h(find);
             str = str.xislice_t(find + 1);
 
-            if (postfix == '$')
-                set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), val);
+            if (prefix == '.' && name[1] == '@')
+            {
+                struct script_data vd = script_data(ScriptDataInt{atoi(val.c_str())});
+                if (postfix == '$')
+                    vd = script_data(ScriptDataStr{val});
+                set_scope_reg(st, reg.iplus(j), &vd);
+            }
+            else if (postfix == '$')
+                set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), val);
             else
-                set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), atoi(val.c_str()));
+                set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), atoi(val.c_str()));
         }
     }
 }
@@ -3149,6 +3252,102 @@ void builtin_specialeffect2(ScriptState *st)
 
 }
 
+static
+void builtin_get(ScriptState *st)
+{
+    BlockId id;
+    dumb_ptr<block_list> bl = nullptr;
+    if (auto *u = AARG(0).get_if<ScriptDataParam>())
+    {
+        SIR reg = u->reg;
+        struct script_data *sdata = &AARG(1);
+        get_val(st, sdata);
+        CharName name;
+        if (sdata->is<ScriptDataStr>())
+        {
+            name = stringish<CharName>(ZString(conv_str(st, sdata)));
+            if (name.to__actual())
+                bl = map_nick2sd(name);
+        }
+        else
+        {
+            id = wrap<BlockId>(conv_num(st, sdata));
+            bl = map_id2bl(id);
+        }
+
+        if (bl == nullptr)
+            return;
+        int var = pc_readparam(bl->is_player(), reg.sp());
+        push_int<ScriptDataInt>(st->stack, var);
+        return;
+    }
+
+    struct script_data *sdata = &AARG(1);
+    get_val(st, sdata);
+
+    SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg;
+    ZString name_ = variable_names.outtern(reg.base());
+    char prefix = name_.front();
+    char postfix = name_.back();
+
+    if(prefix == '.')
+    {
+        if (name_[1] == '@')
+        {
+            PRINTF("builtin_get: illegal scope!\n"_fmt);
+            return;
+        }
+        NpcName name;
+        if (sdata->is<ScriptDataStr>())
+        {
+            name = stringish<NpcName>(ZString(conv_str(st, sdata)));
+            bl = npc_name2id(name);
+        }
+        else
+        {
+            id = wrap<BlockId>(conv_num(st, sdata));
+            bl = map_id2bl(id);
+        }
+    }
+    else if(prefix != '$')
+    {
+        CharName name;
+        if (sdata->is<ScriptDataStr>())
+        {
+            name = stringish<CharName>(ZString(conv_str(st, sdata)));
+            if (name.to__actual())
+                bl = map_nick2sd(name);
+        }
+        else
+        {
+            id = wrap<BlockId>(conv_num(st, sdata));
+            bl = map_id2bl(id);
+        }
+    }
+    else
+    {
+        PRINTF("builtin_get: illegal scope !\n"_fmt);
+        return;
+    }
+
+    if (!bl)
+    {
+        PRINTF("builtin_get: no block list attached !\n"_fmt);
+        return;
+    }
+
+    if (postfix == '$')
+    {
+        ZString var = pc_readregstr(bl, reg);
+        push_str<ScriptDataStr>(st->stack, var);
+    }
+    else
+    {
+        int var = pc_readreg(bl, reg);
+        push_int<ScriptDataInt>(st->stack, var);
+    }
+}
+
 /*==========================================
  * Nude [Valaris]
  *------------------------------------------
@@ -3859,7 +4058,8 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(if, "iF*"_s, '\0'),
     BUILTIN(elif, "iF*"_s, '\0'),
     BUILTIN(else, "F*"_s, '\0'),
-    BUILTIN(set, "Ne"_s, '\0'),
+    BUILTIN(set, "Ne?"_s, '\0'),
+    BUILTIN(get, "Ne"_s, '.'),
     BUILTIN(setarray, "Ne*"_s, '\0'),
     BUILTIN(cleararray, "Nei"_s, '\0'),
     BUILTIN(getarraysize, "N"_s, 'i'),
@@ -3968,7 +4168,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(aggravate, "Mxyxyi"_s, '\0'),
     BUILTIN(fakenpcname, "ssi"_s, '\0'),
     BUILTIN(puppet, "mxysi"_s, 'i'),
-    BUILTIN(destroypuppet, "?"_s, '\0'),
+    BUILTIN(destroy, "?"_s, '\0'),
     BUILTIN(getx, ""_s, 'i'),
     BUILTIN(gety, ""_s, 'i'),
     BUILTIN(getnpcx, "?"_s, 'i'),
diff --git a/src/map/script-parse.cpp b/src/map/script-parse.cpp
index a785748..a69df40 100644
--- a/src/map/script-parse.cpp
+++ b/src/map/script-parse.cpp
@@ -283,6 +283,10 @@ ZString::iterator skip_word(ZString::iterator p)
         p++;                    // MAP鯖内共有変数用
     if (*p == '@')
         p++;                    // 一時的変数用(like weiss)
+    if (*p == '.')
+        p++;                    // npc
+    if (*p == '@')
+        p++;                    // scope
     if (*p == '#')
         p++;                    // account変数用
     if (*p == '#')
@@ -623,6 +627,7 @@ ZString::iterator ScriptBuffer::parse_line(ZString::iterator p, bool *can_step)
             "end"_s,
             "mapexit"_s,
             "shop"_s,
+            "destroy"_s,
         };
         *can_step = terminators.count(cmd->strs) == 0;
     }
-- 
cgit v1.2.3-70-g09d2


From e8e5fb8831be2d549b0d2fa3a3353133b5dccf37 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sun, 21 Jun 2015 19:48:14 -0400
Subject: allow to specify npc in strnpcinfo

---
 src/map/script-fun.cpp | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index f215d73..bef3619 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -3947,11 +3947,23 @@ void builtin_strnpcinfo(ScriptState *st)
     dumb_ptr<npc_data> nd;
 
     if(HARG(1)){
-        NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(1))));
-        nd = npc_name2id(npc);
+        struct script_data *sdata = &AARG(1);
+        get_val(st, sdata);
+
+        if (sdata->is<ScriptDataStr>())
+        {
+            NpcName name = stringish<NpcName>(ZString(conv_str(st, sdata)));
+            nd = npc_name2id(name);
+        }
+        else
+        {
+            BlockId id = wrap<BlockId>(conv_num(st, sdata));
+            nd = map_id2bl(id)->is_npc();
+        }
+
         if (!nd)
         {
-            PRINTF("builtin_strnpcinfo: no such npc: '%s'\n"_fmt, npc);
+            PRINTF("builtin_strnpcinfo: npc not found\n"_fmt);
             return;
         }
     } else {
-- 
cgit v1.2.3-70-g09d2


From f0c8b228746f62123e2c4d53bcf0808f6c4e3c3a Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sun, 21 Jun 2015 21:41:37 -0400
Subject: add getdir builtin, add addnpctimer builtin

allow npcs to have multiple timers
---
 src/map/script-fun.cpp | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index bef3619..6fe77bf 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -2155,6 +2155,20 @@ void builtin_addtimer(ScriptState *st)
     pc_addeventtimer(script_rid2sd(st), tick, event);
 }
 
+/*==========================================
+ * NPCイベントタイマー追加
+ *------------------------------------------
+ */
+static
+void builtin_addnpctimer(ScriptState *st)
+{
+    interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(0)));
+    ZString event_ = ZString(conv_str(st, &AARG(1)));
+    NpcEvent event;
+    extract(event_, &event);
+    npc_addeventtimer(map_id2bl(st->oid), tick, event);
+}
+
 /*==========================================
  * NPCタイマー初期化
  *------------------------------------------
@@ -3925,6 +3939,18 @@ void builtin_gety(ScriptState *st)
     push_int<ScriptDataInt>(st->stack, sd->bl_y);
 }
 
+/*============================
+ * Gets the PC's direction
+ *----------------------------
+ */
+static
+void builtin_getdir(ScriptState *st)
+{
+    dumb_ptr<map_session_data> sd = script_rid2sd(st);
+
+    push_int<ScriptDataInt>(st->stack, static_cast<uint8_t>(sd->dir));
+}
+
 /*
  * Get the PC's current map's name
  */
@@ -4108,6 +4134,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(killmonster, "ME"_s, '\0'),
     BUILTIN(donpcevent, "E"_s, '\0'),
     BUILTIN(addtimer, "tE"_s, '\0'),
+    BUILTIN(addnpctimer, "tE"_s, '\0'),
     BUILTIN(initnpctimer, "?"_s, '\0'),
     BUILTIN(startnpctimer, "?"_s, '\0'),
     BUILTIN(stopnpctimer, "?"_s, '\0'),
@@ -4183,6 +4210,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(destroy, "?"_s, '\0'),
     BUILTIN(getx, ""_s, 'i'),
     BUILTIN(gety, ""_s, 'i'),
+    BUILTIN(getdir, ""_s, 'i'),
     BUILTIN(getnpcx, "?"_s, 'i'),
     BUILTIN(getnpcy, "?"_s, 'i'),
     BUILTIN(strnpcinfo, "i?"_s, 's'),
-- 
cgit v1.2.3-70-g09d2


From 9cc8039347c5136adf4c1936db0d221a8217b26a Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Wed, 24 Jun 2015 18:27:45 -0400
Subject: remove ugly workaround

---
 src/map/globals.cpp    |  1 -
 src/map/globals.hpp    |  1 -
 src/map/npc.cpp        | 41 ++++++++++++++++++++++-------------------
 src/map/script-fun.cpp |  6 +-----
 4 files changed, 23 insertions(+), 26 deletions(-)

(limited to 'src')

diff --git a/src/map/globals.cpp b/src/map/globals.cpp
index c96037e..49d6074 100644
--- a/src/map/globals.cpp
+++ b/src/map/globals.cpp
@@ -73,7 +73,6 @@ namespace tmwa
         BlockId npc_id = START_NPC_NUM;
         Map<NpcEvent, struct event_data> ev_db;
         DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name;
-        DMap<RString, NpcName> spells_by_name;
         DMap<RString, NpcEvent> spells_by_events;
         // used for clock-based event triggers
         // only tm_min, tm_hour, and tm_mday are used
diff --git a/src/map/globals.hpp b/src/map/globals.hpp
index 84e4765..1c8e70d 100644
--- a/src/map/globals.hpp
+++ b/src/map/globals.hpp
@@ -68,7 +68,6 @@ namespace tmwa
         extern BlockId npc_id;
         extern Map<NpcEvent, event_data> ev_db;
         extern DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name;
-        extern DMap<RString, NpcName> spells_by_name;
         extern DMap<RString, NpcEvent> spells_by_events;
         extern tm ev_tm_b;
         extern Map<PartyId, PartyMost> party_db;
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index ae126ea..137cd71 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -154,19 +154,11 @@ dumb_ptr<npc_data> npc_name2id(NpcName name)
     return npcs_by_name.get(name);
 }
 
-/*==========================================
- * NPC Spells
- *------------------------------------------
- */
-NpcName spell_name2id(RString name)
-{
-    return spells_by_name.get(name);
-}
-
 /*==========================================
  * NPC Spells Events
  *------------------------------------------
  */
+static
 NpcEvent spell_event2id(RString name)
 {
     return spells_by_events.get(name);
@@ -209,31 +201,23 @@ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
 {
     auto pair = magic_tokenise(source_invocation);
     // Spell Cast
-    NpcName spell_name = spell_name2id(pair.first);
     NpcEvent spell_event = spell_event2id(pair.first);
     PRINTF("Cast: %s\n"_fmt, RString(pair.first));
 
     RString spell_params = pair.second;
 
-    dumb_ptr<npc_data> nd = npc_name2id(spell_event.label? spell_event.npc: spell_name);
+    dumb_ptr<npc_data> nd = npc_name2id(spell_event.npc);
 
     if (nd)
     {
         PRINTF("NPC:  '%s' %d\n"_fmt, nd->name, nd->bl_id);
         PRINTF("Params:  '%s'\n"_fmt, spell_params);
-        caster->npc_id = nd->bl_id;
-        dumb_ptr<block_list> map_bl = map_id2bl(nd->bl_id);
-        if (!map_bl)
-            map_addnpc(caster->bl_m, nd);
         argrec_t arg[1] =
         {
             {"@args$"_s, spell_params},
         };
 
-        if (spell_event.label)
-            caster->npc_pos = npc_event_do_l(spell_event, caster->bl_id, arg);
-        else
-            caster->npc_pos = run_script_l(ScriptPointer(script_or_parent(nd->is_script()), 0), caster->bl_id, nd->bl_id, arg);
+        npc_event_do_l(spell_event, caster->bl_id, arg);
         return 1;
     }
     return 0;
@@ -321,6 +305,7 @@ void npc_event_do_sub(NpcEvent key, struct event_data *ev,
     }
 }
 
+// XXX maybe merge npc_event_do_l into npc_event ?
 int npc_event_do_l(NpcEvent name, BlockId rid, Slice<argrec_t> args)
 {
     int c = 0;
@@ -330,6 +315,17 @@ int npc_event_do_l(NpcEvent name, BlockId rid, Slice<argrec_t> args)
         return npc_event_doall_l(name.label, rid, args);
     }
 
+    if (!name.label && rid)
+    {
+        dumb_ptr<map_session_data> sd = map_id2bl(rid)->is_player();
+        dumb_ptr<npc_data_script> nd = npc_name2id(name.npc)->is_script();
+        if (!nd || !sd || sd->npc_id)
+            return 0;
+        sd->npc_id = nd->bl_id;
+        sd->npc_pos = run_script_l(ScriptPointer(script_or_parent(nd), 0), rid, nd->bl_id, args);
+        return sd->npc_pos;
+    }
+
     for (auto& pair : ev_db)
         npc_event_do_sub(pair.first, &pair.second, &c, name, rid, args);
     return c;
@@ -609,6 +605,12 @@ void npc_settimerevent_tick(dumb_ptr<npc_data_script> nd, interval_t newtimer)
 int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname,
         int mob_kill)
 {
+    if (!eventname.label && eventname.npc && sd)
+    {
+        npc_event_do_l(eventname, sd->bl_id, nullptr);
+        return 1;
+    }
+
     Option<P<struct event_data>> ev_ = ev_db.search(eventname);
     dumb_ptr<npc_data_script> nd;
 
@@ -816,6 +818,7 @@ int npc_click(dumb_ptr<map_session_data> sd, BlockId id)
             npc_event_dequeue(sd);
             break;
         case NpcSubtype::SCRIPT:
+            // XXX use npc_event_script_l instead?
             sd->npc_pos = run_script(ScriptPointer(script_or_parent(nd->is_script()), 0), sd->bl_id, id);
             break;
         case NpcSubtype::MESSAGE:
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 6fe77bf..8afe4ef 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -3667,14 +3667,10 @@ static
 void builtin_registercmd(ScriptState *st)
 {
     RString evoke = conv_str(st, &AARG(0));
-    NpcName npcname = stringish<NpcName>(conv_str(st, &AARG(1)));
     ZString event_ = conv_str(st, &AARG(1));
     NpcEvent event;
     extract(event_, &event);
-    if (event.label)
-        spells_by_events.put(evoke, event);
-    else
-        spells_by_name.put(evoke, npcname);
+    spells_by_events.put(evoke, event);
 }
 
 /*==========================================
-- 
cgit v1.2.3-70-g09d2


From e5c60513d5b9c41cdca5ff0c01023d9ef4eb0cfb Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Wed, 24 Jun 2015 21:36:28 -0400
Subject: merge npc_event_do_l into npc_event

---
 src/map/npc.cpp | 140 +++++++++++++++++++++++---------------------------------
 src/map/npc.hpp |  19 +++++++-
 2 files changed, 73 insertions(+), 86 deletions(-)

(limited to 'src')

diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index 137cd71..c4187c1 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -217,7 +217,7 @@ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
             {"@args$"_s, spell_params},
         };
 
-        npc_event_do_l(spell_event, caster->bl_id, arg);
+        npc_event(caster, spell_event, 0, arg);
         return 1;
     }
     return 0;
@@ -291,46 +291,6 @@ int npc_event_doall_l(ScriptLabel name, BlockId rid, Slice<argrec_t> args)
     return c;
 }
 
-static
-void npc_event_do_sub(NpcEvent key, struct event_data *ev,
-        int *c, NpcEvent name, BlockId rid, Slice<argrec_t> argv)
-{
-    nullpo_retv(ev);
-
-    if (name == key)
-    {
-        run_script_l(ScriptPointer(script_or_parent(ev->nd), ev->pos), rid, ev->nd->bl_id,
-                argv);
-        (*c)++;
-    }
-}
-
-// XXX maybe merge npc_event_do_l into npc_event ?
-int npc_event_do_l(NpcEvent name, BlockId rid, Slice<argrec_t> args)
-{
-    int c = 0;
-
-    if (!name.npc)
-    {
-        return npc_event_doall_l(name.label, rid, args);
-    }
-
-    if (!name.label && rid)
-    {
-        dumb_ptr<map_session_data> sd = map_id2bl(rid)->is_player();
-        dumb_ptr<npc_data_script> nd = npc_name2id(name.npc)->is_script();
-        if (!nd || !sd || sd->npc_id)
-            return 0;
-        sd->npc_id = nd->bl_id;
-        sd->npc_pos = run_script_l(ScriptPointer(script_or_parent(nd), 0), rid, nd->bl_id, args);
-        return sd->npc_pos;
-    }
-
-    for (auto& pair : ev_db)
-        npc_event_do_sub(pair.first, &pair.second, &c, name, rid, args);
-    return c;
-}
-
 /*==========================================
  * 時計イベント実行
  *------------------------------------------
@@ -603,68 +563,81 @@ void npc_settimerevent_tick(dumb_ptr<npc_data_script> nd, interval_t newtimer)
  *------------------------------------------
  */
 int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname,
-        int mob_kill)
+        int mob_kill, Slice<argrec_t> args)
 {
-    if (!eventname.label && eventname.npc && sd)
+    if (!eventname.npc)
     {
-        npc_event_do_l(eventname, sd->bl_id, nullptr);
-        return 1;
+        return npc_event_doall_l(eventname.label, sd->bl_id, args); // XXX maybe merge this into npc_event?
     }
 
     Option<P<struct event_data>> ev_ = ev_db.search(eventname);
     dumb_ptr<npc_data_script> nd;
 
-    if (sd == nullptr)
-    {
-        PRINTF("npc_event nullpo?\n"_fmt);
-    }
-
     if (ev_.is_none() && eventname.label == stringish<ScriptLabel>("OnTouch"_s))
         return 1;
 
-    P<struct event_data> ev = TRY_UNWRAP(ev_,
+    bool failed = false;
+    struct event_data ev {};
+    P<struct event_data> ev2 = TRY_UNWRAP(ev_,{ failed = true; });
+    if(failed)
     {
-        if (!mob_kill && battle_config.error_log)
-            PRINTF("npc_event: event not found [%s]\n"_fmt,
-                    eventname);
-        return 0;
-    });
-    if ((nd = ev->nd) == nullptr)
+        if (!eventname.label && eventname.npc && sd)
+        {
+            ev.nd = npc_name2id(eventname.npc)->is_script();
+            ev.pos = 0; // start from the beginning of a npc
+        }
+        else
+        {
+            if (!mob_kill && battle_config.error_log)
+                PRINTF("npc_event: event not found [%s]\n"_fmt,
+                        eventname);
+            return 0;
+        }
+    }
+    else
+    {
+        ev.nd = ev2->nd;
+        ev.pos = ev2->pos;
+    }
+
+    if ((nd = ev.nd) == nullptr)
     {
         if (!mob_kill && battle_config.error_log)
             PRINTF("npc_event: event not found [%s]\n"_fmt,
                     eventname);
         return 0;
     }
-
-    if (nd->scr.event_needs_map)
+    if (sd)
     {
-        int xs = nd->scr.xs;
-        int ys = nd->scr.ys;
-        if (nd->bl_m != sd->bl_m)
-            return 1;
-        if (xs > 0
-            && (sd->bl_x < nd->bl_x - xs / 2 || nd->bl_x + xs / 2 < sd->bl_x))
-            return 1;
-        if (ys > 0
-            && (sd->bl_y < nd->bl_y - ys / 2 || nd->bl_y + ys / 2 < sd->bl_y))
+        if (nd->scr.event_needs_map)
+        {
+            int xs = nd->scr.xs;
+            int ys = nd->scr.ys;
+            if (nd->bl_m != sd->bl_m)
+                return 1;
+            if (xs > 0
+                && (sd->bl_x < nd->bl_x - xs / 2 || nd->bl_x + xs / 2 < sd->bl_x))
+                return 1;
+            if (ys > 0
+                && (sd->bl_y < nd->bl_y - ys / 2 || nd->bl_y + ys / 2 < sd->bl_y))
+                return 1;
+        }
+        if (sd->npc_id)
+        {
+            sd->eventqueuel.push_back(eventname);
             return 1;
+        }
+        if (nd->flag & 1)
+        {                           // 無効化されている
+            npc_event_dequeue(sd);
+            return 0;
+        }
+        sd->npc_id = nd->bl_id;
     }
-
-    if (sd->npc_id)
-    {
-        sd->eventqueuel.push_back(eventname);
-        return 1;
-    }
-    if (nd->flag & 1)
-    {                           // 無効化されている
-        npc_event_dequeue(sd);
-        return 0;
-    }
-
-    sd->npc_id = nd->bl_id;
-    sd->npc_pos =
-        run_script(ScriptPointer(script_or_parent(nd), ev->pos), sd->bl_id, nd->bl_id);
+    int pos = run_script_l(ScriptPointer(script_or_parent(nd), ev.pos),
+                            (sd? sd->bl_id : BlockId()), nd->bl_id, args);
+    if (sd)
+        sd->npc_pos = pos;
     return 0;
 }
 
@@ -818,7 +791,6 @@ int npc_click(dumb_ptr<map_session_data> sd, BlockId id)
             npc_event_dequeue(sd);
             break;
         case NpcSubtype::SCRIPT:
-            // XXX use npc_event_script_l instead?
             sd->npc_pos = run_script(ScriptPointer(script_or_parent(nd->is_script()), 0), sd->bl_id, id);
             break;
         case NpcSubtype::MESSAGE:
diff --git a/src/map/npc.hpp b/src/map/npc.hpp
index be4686a..fa5b6a3 100644
--- a/src/map/npc.hpp
+++ b/src/map/npc.hpp
@@ -44,7 +44,18 @@ constexpr Species FAKE_NPC_CLASS = wrap<Species>(127);
 constexpr Species INVISIBLE_CLASS = wrap<Species>(32767);
 
 int npc_event_dequeue(dumb_ptr<map_session_data> sd);
-int npc_event(dumb_ptr<map_session_data> sd, NpcEvent npcname, int);
+int npc_event(dumb_ptr<map_session_data>, NpcEvent, int, Slice<argrec_t>);
+int npc_event(BlockId, NpcEvent, int, Slice<argrec_t>);
+inline
+int npc_event(dumb_ptr<map_session_data> sd, NpcEvent npcname, int i)
+{
+    return npc_event(sd, npcname, i, nullptr);
+}
+inline
+int npc_event(BlockId rid, NpcEvent eventname, int mob_kill, Slice<argrec_t> args)
+{
+    return npc_event(rid ? map_id2bl(rid)->is_player() : nullptr, eventname, mob_kill, args);
+}
 int npc_addeventtimer(dumb_ptr<block_list> bl, interval_t tick, NpcEvent name);
 int npc_touch_areanpc(dumb_ptr<map_session_data>, Borrowed<map_local>, int, int);
 int npc_click(dumb_ptr<map_session_data>, BlockId);
@@ -67,7 +78,11 @@ void npc_free(dumb_ptr<npc_data> npc);
 int npc_event_do_oninit(void);
 
 int npc_event_doall_l(ScriptLabel name, BlockId rid, Slice<argrec_t> argv);
-int npc_event_do_l(NpcEvent name, BlockId rid, Slice<argrec_t> argv);
+inline
+int npc_event_do_l(NpcEvent name, BlockId rid, Slice<argrec_t> argv)
+{
+    return npc_event(rid, name, 0, argv);
+}
 inline
 int npc_event_doall(ScriptLabel name)
 {
-- 
cgit v1.2.3-70-g09d2


From e0ab38974d08268fd0bbbae16293afb31686c9f8 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 25 Jun 2015 18:00:28 -0400
Subject: do non-timer events synchronously

---
 src/map/npc.cpp | 2 +-
 src/map/npc.hpp | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

(limited to 'src')

diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index c4187c1..a40a4ec 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -622,7 +622,7 @@ int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname,
                 && (sd->bl_y < nd->bl_y - ys / 2 || nd->bl_y + ys / 2 < sd->bl_y))
                 return 1;
         }
-        if (sd->npc_id)
+        if (sd->npc_id && sd->npc_pos > -1 && args.size() < 1) // if called from a timer we process async, otherwise sync
         {
             sd->eventqueuel.push_back(eventname);
             return 1;
diff --git a/src/map/npc.hpp b/src/map/npc.hpp
index fa5b6a3..e230ffe 100644
--- a/src/map/npc.hpp
+++ b/src/map/npc.hpp
@@ -45,7 +45,6 @@ constexpr Species INVISIBLE_CLASS = wrap<Species>(32767);
 
 int npc_event_dequeue(dumb_ptr<map_session_data> sd);
 int npc_event(dumb_ptr<map_session_data>, NpcEvent, int, Slice<argrec_t>);
-int npc_event(BlockId, NpcEvent, int, Slice<argrec_t>);
 inline
 int npc_event(dumb_ptr<map_session_data> sd, NpcEvent npcname, int i)
 {
-- 
cgit v1.2.3-70-g09d2


From c7592866c56e4c5bc2f603c462148ac5cf4fd374 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 25 Jun 2015 20:45:03 -0400
Subject: allow to get mob/npc params and add more params to readparam

---
 src/map/battle.cpp      |   9 -----
 src/map/battle.hpp      |   9 +++++
 src/map/pc.cpp          | 102 ++++++++++++++++++++++++++++++++++++++----------
 src/map/pc.hpp          |   2 +-
 src/map/script-call.cpp |   2 +-
 src/map/script-fun.cpp  |  14 +++++--
 6 files changed, 104 insertions(+), 34 deletions(-)

(limited to 'src')

diff --git a/src/map/battle.cpp b/src/map/battle.cpp
index 991a489..ef1567e 100644
--- a/src/map/battle.cpp
+++ b/src/map/battle.cpp
@@ -341,7 +341,6 @@ int battle_get_luk(dumb_ptr<block_list> bl)
  * 戻りは整数で1以上
  *------------------------------------------
  */
-static
 int battle_get_flee(dumb_ptr<block_list> bl)
 {
     int flee = 1;
@@ -370,7 +369,6 @@ int battle_get_flee(dumb_ptr<block_list> bl)
  * 戻りは整数で1以上
  *------------------------------------------
  */
-static
 int battle_get_hit(dumb_ptr<block_list> bl)
 {
     int hit = 1;
@@ -398,7 +396,6 @@ int battle_get_hit(dumb_ptr<block_list> bl)
  * 戻りは整数で1以上
  *------------------------------------------
  */
-static
 int battle_get_flee2(dumb_ptr<block_list> bl)
 {
     int flee2 = 1;
@@ -430,7 +427,6 @@ int battle_get_flee2(dumb_ptr<block_list> bl)
  * 戻りは整数で1以上
  *------------------------------------------
  */
-static
 int battle_get_critical(dumb_ptr<block_list> bl)
 {
     int critical = 1;
@@ -457,7 +453,6 @@ int battle_get_critical(dumb_ptr<block_list> bl)
  * 戻りは整数で1以上
  *------------------------------------------
  */
-static
 int battle_get_baseatk(dumb_ptr<block_list> bl)
 {
     eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
@@ -484,7 +479,6 @@ int battle_get_baseatk(dumb_ptr<block_list> bl)
  * 戻りは整数で0以上
  *------------------------------------------
  */
-static
 int battle_get_atk(dumb_ptr<block_list> bl)
 {
     eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
@@ -507,7 +501,6 @@ int battle_get_atk(dumb_ptr<block_list> bl)
  * 戻りは整数で0以上
  *------------------------------------------
  */
-static
 int battle_get_atk2(dumb_ptr<block_list> bl)
 {
     nullpo_retz(bl);
@@ -530,7 +523,6 @@ int battle_get_atk2(dumb_ptr<block_list> bl)
  * 戻りは整数で0以上
  *------------------------------------------
  */
-static
 int battle_get_matk1(dumb_ptr<block_list> bl)
 {
     eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
@@ -554,7 +546,6 @@ int battle_get_matk1(dumb_ptr<block_list> bl)
  * 戻りは整数で0以上
  *------------------------------------------
  */
-static
 int battle_get_matk2(dumb_ptr<block_list> bl)
 {
     nullpo_retz(bl);
diff --git a/src/map/battle.hpp b/src/map/battle.hpp
index 8f31fe0..2925b58 100644
--- a/src/map/battle.hpp
+++ b/src/map/battle.hpp
@@ -83,6 +83,15 @@ int battle_get_def(dumb_ptr<block_list> bl);
 int battle_get_mdef(dumb_ptr<block_list> bl);
 int battle_get_def2(dumb_ptr<block_list> bl);
 int battle_get_mdef2(dumb_ptr<block_list> bl);
+int battle_get_atk(dumb_ptr<block_list> bl);
+int battle_get_atk2(dumb_ptr<block_list> bl);
+int battle_get_matk1(dumb_ptr<block_list> bl);
+int battle_get_matk2(dumb_ptr<block_list> bl);
+int battle_get_hit(dumb_ptr<block_list> bl);
+int battle_get_flee(dumb_ptr<block_list> bl);
+int battle_get_flee2(dumb_ptr<block_list> bl);
+int battle_get_critical(dumb_ptr<block_list> bl);
+int battle_get_baseatk(dumb_ptr<block_list> bl);
 interval_t battle_get_speed(dumb_ptr<block_list> bl);
 interval_t battle_get_adelay(dumb_ptr<block_list> bl);
 interval_t battle_get_amotion(dumb_ptr<block_list> bl);
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index f65e2b4..84d02a4 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -3382,64 +3382,105 @@ int pc_damage(dumb_ptr<block_list> src, dumb_ptr<map_session_data> sd,
  * script用PCステータス読み出し
  *------------------------------------------
  */
-int pc_readparam(dumb_ptr<map_session_data> sd, SP type)
+int pc_readparam(dumb_ptr<block_list> bl, SP type)
 {
+    nullpo_retz(bl);
+    dumb_ptr<map_session_data> sd;
+    dumb_ptr<npc_data> nd;
+    dumb_ptr<mob_data> md;
     int val = 0;
 
-    nullpo_retz(sd);
+    if (bl->bl_type == BL::PC)
+        sd = bl->is_player();
+    else if (bl->bl_type == BL::MOB)
+        md = bl->is_mob();
+    else if (bl->bl_type == BL::NPC)
+        nd = bl->is_npc();
+    else
+        return val;
 
     switch (type)
     {
         case SP::SKILLPOINT:
-            val = sd->status.skill_point;
+            val = sd ? sd->status.skill_point : 0;
             break;
         case SP::STATUSPOINT:
-            val = sd->status.status_point;
+            val = sd ? sd->status.status_point : 0;
             break;
         case SP::ZENY:
-            val = sd->status.zeny;
+            val = sd ? sd->status.zeny : 0;
             break;
         case SP::BASELEVEL:
-            val = sd->status.base_level;
+            val = battle_get_lv(bl);
             break;
         case SP::JOBLEVEL:
-            val = sd->status.job_level;
+            val = sd ? sd->status.job_level : 0;
             break;
         case SP::CLASS:
-            val = unwrap<Species>(sd->status.species);
+            val = unwrap<Species>(battle_get_class(bl));
             break;
         case SP::SEX:
-            val = static_cast<uint8_t>(sd->status.sex);
+            if (sd)
+                val = static_cast<uint8_t>(sd->status.sex);
+            else if (nd)
+                val = static_cast<uint8_t>(nd->sex);
             break;
         case SP::WEIGHT:
-            val = sd->weight;
+            val = sd ? sd->weight : 0;
             break;
         case SP::MAXWEIGHT:
-            val = sd->max_weight;
+            val = sd ? sd->max_weight : 0;
             break;
         case SP::BASEEXP:
-            val = sd->status.base_exp;
+            val = sd ? sd->status.base_exp : 0;
             break;
         case SP::JOBEXP:
-            val = sd->status.job_exp;
+            val = sd ? sd->status.job_exp : 0;
             break;
         case SP::NEXTBASEEXP:
-            val = pc_nextbaseexp(sd);
+            val = sd ? pc_nextbaseexp(sd) : 0;
             break;
         case SP::NEXTJOBEXP:
-            val = pc_nextjobexp(sd);
+            val = sd ? pc_nextjobexp(sd) : 0;
             break;
         case SP::HP:
-            val = sd->status.hp;
+            val = battle_get_hp(bl);
             break;
         case SP::MAXHP:
-            val = sd->status.max_hp;
+            val = battle_get_max_hp(bl);
             break;
         case SP::SP:
-            val = sd->status.sp;
+            val = sd ? sd->status.sp : 0;
             break;
         case SP::MAXSP:
-            val = sd->status.max_sp;
+            val = sd ? sd->status.max_sp : 0;
+            break;
+        case SP::BASE_ATK:
+            val = battle_get_baseatk(bl);
+            break;
+        case SP::ATK1:
+            val = battle_get_atk(bl);
+            break;
+        case SP::ATK2:
+            val = battle_get_atk2(bl);
+            break;
+        case SP::DEF1:
+            val = battle_get_def(bl);
+            break;
+        case SP::DEF2:
+            val = battle_get_def2(bl);
+            break;
+        case SP::MATK1:
+            val = battle_get_matk1(bl);
+            break;
+        case SP::MATK2:
+            val = battle_get_matk2(bl);
+            break;
+        case SP::MDEF1:
+            val = battle_get_mdef(bl);
+            break;
+        case SP::MDEF2:
+            val = battle_get_mdef2(bl);
             break;
         case SP::STR:
         case SP::AGI:
@@ -3447,7 +3488,28 @@ int pc_readparam(dumb_ptr<map_session_data> sd, SP type)
         case SP::INT:
         case SP::DEX:
         case SP::LUK:
-            val = sd->status.attrs[sp_to_attr(type)];
+            val = battle_get_stat(type, bl);
+            break;
+        case SP::SPEED:
+            val = battle_get_speed(bl).count();
+            break;
+        case SP::HIT:
+            val = battle_get_hit(bl);
+            break;
+        case SP::FLEE1:
+            val = battle_get_flee(bl);
+            break;
+        case SP::FLEE2:
+            val = battle_get_flee2(bl);
+            break;
+        case SP::CRITICAL:
+            val = battle_get_critical(bl);
+            break;
+        case SP::GM:
+            val = sd ? pc_isGM(sd).get_all_bits() : 0;
+            break;
+        case SP::ATTACKRANGE:
+            val = battle_get_range(bl);
             break;
     }
 
diff --git a/src/map/pc.hpp b/src/map/pc.hpp
index 6bcfadb..35ca975 100644
--- a/src/map/pc.hpp
+++ b/src/map/pc.hpp
@@ -142,7 +142,7 @@ int pc_heal(dumb_ptr<map_session_data>, int, int);
 int pc_itemheal(dumb_ptr<map_session_data> sd, int hp, int sp);
 int pc_changelook(dumb_ptr<map_session_data>, LOOK, int);
 
-int pc_readparam(dumb_ptr<map_session_data>, SP);
+int pc_readparam(dumb_ptr<block_list>, SP);
 int pc_setparam(dumb_ptr<map_session_data>, SP, int);
 int pc_readreg(dumb_ptr<block_list>, SIR);
 void pc_setreg(dumb_ptr<block_list>, SIR, int);
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index 2ebfca3..8d2f58f 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -88,7 +88,7 @@ void get_val(dumb_ptr<block_list> sd, struct script_data *data)
                 PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp());
             int numi = 0;
             if (sd)
-                numi = pc_readparam(sd->is_player(), u.reg.sp());
+                numi = pc_readparam(sd, u.reg.sp());
             *data = ScriptDataInt{numi};
         }
         MATCH_CASE (const ScriptDataVariable&, u)
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 8afe4ef..5990e47 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -1676,7 +1676,11 @@ void builtin_freeloop(ScriptState *st)
 static
 void builtin_bonus(ScriptState *st)
 {
-    SP type = SP(conv_num(st, &AARG(0)));
+    SP type;
+    if (auto *u = AARG(0).get_if<ScriptDataParam>())
+        type = u->reg.sp();
+    else
+        type = SP(conv_num(st, &AARG(0)));
     int val = conv_num(st, &AARG(1));
     dumb_ptr<map_session_data> sd = script_rid2sd(st);
     pc_bonus(sd, type, val);
@@ -1690,7 +1694,11 @@ void builtin_bonus(ScriptState *st)
 static
 void builtin_bonus2(ScriptState *st)
 {
-    SP type = SP(conv_num(st, &AARG(0)));
+    SP type;
+    if (auto *u = AARG(0).get_if<ScriptDataParam>())
+        type = u->reg.sp();
+    else
+        type = SP(conv_num(st, &AARG(0)));
     int type2 = conv_num(st, &AARG(1));
     int val = conv_num(st, &AARG(2));
     dumb_ptr<map_session_data> sd = script_rid2sd(st);
@@ -3291,7 +3299,7 @@ void builtin_get(ScriptState *st)
 
         if (bl == nullptr)
             return;
-        int var = pc_readparam(bl->is_player(), reg.sp());
+        int var = pc_readparam(bl, reg.sp());
         push_int<ScriptDataInt>(st->stack, var);
         return;
     }
-- 
cgit v1.2.3-70-g09d2


From eeb22b821f36e260edcf0a4e01656608e0405ba4 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sun, 28 Jun 2015 16:41:09 -0400
Subject: remove hardcoded shit from builtin_injure and add builtin_target

---
 src/map/script-fun.cpp | 94 ++++++++++++++++++++++++++++++++------------------
 1 file changed, 60 insertions(+), 34 deletions(-)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 5990e47..6004072 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -59,6 +59,7 @@
 #include "skill.hpp"
 #include "storage.hpp"
 #include "npc-internal.hpp"
+#include "path.hpp"
 
 #include "../poison.hpp"
 
@@ -532,50 +533,74 @@ void builtin_eltlvl(ScriptState *st)
  *------------------------------------------
  */
 static
-void builtin_injure(ScriptState *st)
+void builtin_target(ScriptState *st)
 {
-    dumb_ptr<block_list> caster = map_id2bl(st->rid);
-    dumb_ptr<block_list> target = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0))));
-    int damage_caused = conv_num(st, &AARG(1));
-    int mp_damage = conv_num(st, &AARG(2));
-    int target_hp = battle_get_hp(target);
-    int mdef = battle_get_mdef(target);
-
-    if (target->bl_type == BL::PC
-        && !target->bl_m->flag.get(MapFlag::PVP)
-        && (caster->bl_type == BL::PC)
-        && ((caster->is_player()->state.pvpchannel > 1) && (target->is_player()->state.pvpchannel != caster->is_player()->state.pvpchannel)))
-        return;               /* Cannot damage other players outside of pvp */
-
-    if (target != caster)
+    // TODO maybe scrap all this and make it use battle_ functions? (add missing functions to battle)
+
+    dumb_ptr<block_list> source = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0))));
+    dumb_ptr<block_list> target = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1))));
+    int flag = conv_num(st, &AARG(2));
+    int val = 0;
+
+    if (flag & 0x01)
     {
-        /* Not protected against own spells */
-        damage_caused = (damage_caused * (100 - mdef)) / 100;
-        mp_damage = (mp_damage * (100 - mdef)) / 100;
+        int x0 = source->bl_x - AREA_SIZE;
+        int y0 = source->bl_y - AREA_SIZE;
+        int x1 = source->bl_x + AREA_SIZE;
+        int y1 = source->bl_y + AREA_SIZE;
+        if (target->bl_x >= x0 && target->bl_x <= x1 && target->bl_y >= y0 && target->bl_y <= y1)
+            val |= 0x01; // 0x01 target is in visible range
     }
 
-    damage_caused = (damage_caused > target_hp) ? target_hp : damage_caused;
+    if (flag & 0x02)
+    {
+        int range = battle_get_range(source);
+        int x2 = source->bl_x - range;
+        int y2 = source->bl_y - range;
+        int x3 = source->bl_x + range;
+        int y3 = source->bl_y + range;
+        if (target->bl_x >= x2 && target->bl_x <= x3 && target->bl_y >= y2 && target->bl_y <= y3)
+            val |= 0x02; // 0x02 target is in attack range
+    }
 
-    if (damage_caused < 0)
-        damage_caused = 0;
+    if (flag & 0x04)
+    {
+        struct walkpath_data wpd;
+        if (!path_search(&wpd, source->bl_m, source->bl_x, source->bl_y, target->bl_x, target->bl_y, 0))
+            val |= 0x04; // 0x04 target is walkable (has clear path to target)
+    }
+
+    // TODO 0x08 target is visible (not behind collision) XXX maybe this is line of sight?
+
+    if (flag & 0x10)
+    {
+        if (target->bl_type != BL::PC || (target->bl_type == BL::PC &&
+            (target->bl_m->flag.get(MapFlag::PVP) || pc_iskiller(source->is_player(), target->is_player()))))
+            val |= 0x10; // 0x10 target can be attacked by source (killer, killable and so on)
+    }
+
+    // TODO 0x20 target is in line of sight
+
+    push_int<ScriptDataInt>(st->stack, val);
+}
+
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_injure(ScriptState *st)
+{
+    dumb_ptr<block_list> source = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0))));
+    dumb_ptr<block_list> target = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1))));
+    int damage_caused = conv_num(st, &AARG(2));
 
     // display damage first, because dealing damage may deallocate the target.
-    clif_damage(caster, target,
+    clif_damage(source, target,
             gettick(), interval_t::zero(), interval_t::zero(),
             damage_caused, 0, DamageType::NORMAL);
 
-    if (caster->bl_type == BL::PC)
-    {
-        dumb_ptr<map_session_data> caster_pc = caster->is_player();
-        if (target->bl_type == BL::MOB)
-        {
-            dumb_ptr<mob_data> mob = target->is_mob();
-            dumb_ptr<npc_data> nd = map_id_is_npc(st->oid);
-            MAP_LOG_PC(caster_pc, "SPELLDMG MOB%d %d FOR %d BY %s"_fmt,
-                    mob->bl_id, mob->mob_class, damage_caused, caster->is_player()->magic_attack);
-        }
-    }
-    battle_damage(caster, target, damage_caused, mp_damage);
+    battle_damage(source, target, damage_caused, 0);
 
     return;
 }
@@ -4228,6 +4253,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(sqrt, "i"_s, 'i'),
     BUILTIN(cbrt, "i"_s, 'i'),
     BUILTIN(pow, "ii"_s, 'i'),
+    BUILTIN(target, "iii"_s, 'i'),
     {nullptr, ""_s, ""_s, '\0'},
 };
 } // namespace map
-- 
cgit v1.2.3-70-g09d2


From cc4e5c99f17846611db09e40b244e693b78ee94a Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Tue, 14 Jul 2015 14:09:54 -0400
Subject: add POS_X and POS_Y params

---
 src/map/pc.cpp     | 6 ++++++
 src/mmo/clif.t.hpp | 3 +++
 2 files changed, 9 insertions(+)

(limited to 'src')

diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 84d02a4..4f0c8d2 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -3511,6 +3511,12 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
         case SP::ATTACKRANGE:
             val = battle_get_range(bl);
             break;
+        case SP::POS_X:
+            val = bl->bl_x;
+            break;
+        case SP::POS_Y:
+            val = bl->bl_y;
+            break;
     }
 
     return val;
diff --git a/src/mmo/clif.t.hpp b/src/mmo/clif.t.hpp
index 0a51523..f2ff583 100644
--- a/src/mmo/clif.t.hpp
+++ b/src/mmo/clif.t.hpp
@@ -469,6 +469,9 @@ enum class SP : uint16_t
 #if 0
     RANDOM_ATTACK_INCREASE      = 1072,
 #endif
+
+    POS_X                       = 1074,
+    POS_Y                       = 1075,
 };
 
 constexpr
-- 
cgit v1.2.3-70-g09d2


From fee59601b3dd16c10e767cac44ee5197db7811e8 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Tue, 14 Jul 2015 15:04:47 -0400
Subject: add builtin_distance

---
 src/map/script-fun.cpp | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 6004072..53b4526 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -528,6 +528,37 @@ void builtin_eltlvl(ScriptState *st)
     push_int<ScriptDataInt>(st->stack, element_lvl);
 }
 
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_distance(ScriptState *st)
+{
+    dumb_ptr<block_list> source = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0))));
+    dumb_ptr<block_list> target = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1))));
+    int distance = 0;
+    int mode = HARG(2) ? conv_num(st, &AARG(2)) : 0;
+
+    switch (mode)
+    {
+        // TODO implement case 1 (walk distance)
+        case 0:
+        default:
+            if (source->bl_m->name_ != target->bl_m->name_)
+            {
+                // FIXME make it work even if source and target are not in the same map
+                distance = 0x7fffffff;
+                break;
+            }
+            int dx = abs(source->bl_x - target->bl_x);
+            int dy = abs(source->bl_y - target->bl_y);
+            distance = sqrt((dx * dx) + (dy * dy)); // Pythagoras' theorem
+    }
+
+    push_int<ScriptDataInt>(st->stack, distance);
+}
+
 /*==========================================
  *
  *------------------------------------------
@@ -4254,6 +4285,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(cbrt, "i"_s, 'i'),
     BUILTIN(pow, "ii"_s, 'i'),
     BUILTIN(target, "iii"_s, 'i'),
+    BUILTIN(distance, "ii?"_s, 'i'),
     {nullptr, ""_s, ""_s, '\0'},
 };
 } // namespace map
-- 
cgit v1.2.3-70-g09d2


From a185a08be08bf790141b3aa1b6fcecaea5915a7e Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Tue, 14 Jul 2015 16:23:09 -0400
Subject: misc modifications

---
 src/map/pc.cpp          |  6 ++++++
 src/map/script-call.cpp |  2 --
 src/map/script-fun.cpp  | 25 ++++++++++++++++++++++++-
 src/mmo/clif.t.hpp      |  2 ++
 4 files changed, 32 insertions(+), 3 deletions(-)

(limited to 'src')

diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 4f0c8d2..96e0cac 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -3517,6 +3517,12 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
         case SP::POS_Y:
             val = bl->bl_y;
             break;
+        case SP::PVP_CHANNEL:
+            val = sd ? sd->state.pvpchannel : 0;
+            break;
+        case SP::BL_ID:
+            val = unwrap<BlockId>(bl->bl_id);
+            break;
     }
 
     return val;
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index 8d2f58f..b9a098a 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -84,8 +84,6 @@ void get_val(dumb_ptr<block_list> sd, struct script_data *data)
     {
         MATCH_CASE (const ScriptDataParam&, u)
         {
-            if (sd == nullptr)
-                PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp());
             int numi = 0;
             if (sd)
                 numi = pc_readparam(sd, u.reg.sp());
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 53b4526..51039ad 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -545,7 +545,7 @@ void builtin_distance(ScriptState *st)
         // TODO implement case 1 (walk distance)
         case 0:
         default:
-            if (source->bl_m->name_ != target->bl_m->name_)
+            if (source->bl_m != target->bl_m)
             {
                 // FIXME make it work even if source and target are not in the same map
                 distance = 0x7fffffff;
@@ -1600,6 +1600,28 @@ void builtin_getcharid(ScriptState *st)
         push_int<ScriptDataInt>(st->stack, unwrap<AccountId>(sd->status_key.account_id));
 }
 
+/*==========================================
+ *
+ *------------------------------------------
+ */
+static
+void builtin_getnpcid(ScriptState *st)
+{
+    dumb_ptr<npc_data> nd;
+
+    if (HARG(0))
+        nd = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(0)))));
+    else
+        nd = map_id2bl(st->oid)->is_npc();
+    if (nd == nullptr)
+    {
+        push_int<ScriptDataInt>(st->stack, -1);
+        return;
+    }
+
+    push_int<ScriptDataInt>(st->stack, unwrap<BlockId>(nd->bl_id));
+}
+
 /*==========================================
  *指定IDのPT名取得
  *------------------------------------------
@@ -4170,6 +4192,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(makeitem, "IiMxy"_s, '\0'),
     BUILTIN(delitem, "Ii"_s, '\0'),
     BUILTIN(getcharid, "i?"_s, 'i'),
+    BUILTIN(getnpcid, "?"_s, 'i'),
     BUILTIN(getversion, ""_s, 'i'),
     BUILTIN(strcharinfo, "i"_s, 's'),
     BUILTIN(getequipid, "i?"_s, 'i'),
diff --git a/src/mmo/clif.t.hpp b/src/mmo/clif.t.hpp
index f2ff583..377e953 100644
--- a/src/mmo/clif.t.hpp
+++ b/src/mmo/clif.t.hpp
@@ -472,6 +472,8 @@ enum class SP : uint16_t
 
     POS_X                       = 1074,
     POS_Y                       = 1075,
+    PVP_CHANNEL                 = 1076,
+    BL_ID                       = 1077,
 };
 
 constexpr
-- 
cgit v1.2.3-70-g09d2


From 657526889f6a36d79e34ab77e25377fd46d8905c Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Wed, 12 Aug 2015 14:52:37 -0400
Subject: give priority to custom commands over built-in commands

---
 src/map/clif.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

(limited to 'src')

diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index 33de3e6..726d45c 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -3830,11 +3830,11 @@ RecvResult clif_parse_GlobalMessage(Session *s, dumb_ptr<map_session_data> sd)
         return rv;
     }
 
-    if (is_atcommand(s, sd, mbuf, GmLevel()))
-        return rv;
-
     if (!magic_message(sd, mbuf))
     {
+        if (is_atcommand(s, sd, mbuf, GmLevel()))
+            return rv;
+
         /* Don't send chat that results in an automatic ban. */
         if (tmw_CheckChatSpam(sd, mbuf))
         {
-- 
cgit v1.2.3-70-g09d2


From 879243c8661a346291b1ca57ffe6cdfdef90598f Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Wed, 12 Aug 2015 14:53:24 -0400
Subject: do not send spell invocation back to the caster

---
 src/map/clif.cpp | 46 +++++++++++++++++++++-------------------------
 1 file changed, 21 insertions(+), 25 deletions(-)

(limited to 'src')

diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index 726d45c..4cf1de2 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -229,6 +229,8 @@ void clif_send_sub(dumb_ptr<block_list> bl, const Buffer& buf,
             break;
 
         case SendWho::AREA_CHAT_WOC:
+            if (bl && bl == src_bl)
+                break;
             if (is_deaf(bl)
                 && !(bl->bl_type == BL::PC
                      && pc_isGM(src_bl->is_player())))
@@ -236,8 +238,6 @@ void clif_send_sub(dumb_ptr<block_list> bl, const Buffer& buf,
                 clif_emotion_towards(src_bl, bl, EMOTE_IGNORED);
                 return;
             }
-            if (bl && bl == src_bl)
-                return;
 
             break;
     }
@@ -3830,35 +3830,31 @@ RecvResult clif_parse_GlobalMessage(Session *s, dumb_ptr<map_session_data> sd)
         return rv;
     }
 
-    if (!magic_message(sd, mbuf))
-    {
-        if (is_atcommand(s, sd, mbuf, GmLevel()))
-            return rv;
+    if (magic_message(sd, mbuf))
+        return rv;
 
-        /* Don't send chat that results in an automatic ban. */
-        if (tmw_CheckChatSpam(sd, mbuf))
-        {
-            clif_displaymessage(s, "Your message could not be sent."_s);
-            return rv;
-        }
+    if (is_atcommand(s, sd, mbuf, GmLevel()))
+        return rv;
 
-        /* It's not a spell/magic message, so send the message to others. */
+    /* Don't send chat that results in an automatic ban. */
+    if (tmw_CheckChatSpam(sd, mbuf))
+    {
+        clif_displaymessage(s, "Your message could not be sent."_s);
+        return rv;
+    }
 
-        Buffer sendbuf;
-        clif_message_sub(sendbuf, sd, mbuf);
 
-        Buffer filteredBuf; // ManaPlus remote execution exploit prevention
-        XString filtered = mbuf;
-        if (mbuf.contains_seq("@@="_s) && mbuf.contains('|'))
-            filtered = "##B##3[##1Impossible to see this message. Please update your client.##3]"_s;
-        clif_message_sub(filteredBuf, sd, filtered);
+    Buffer sendbuf;
+    clif_message_sub(sendbuf, sd, mbuf);
 
-        clif_send(sendbuf, sd, SendWho::AREA_CHAT_WOC,
-            wrap<ClientVersion>(6), filteredBuf);
-    }
+    Buffer filteredBuf; // ManaPlus remote execution exploit prevention
+    XString filtered = mbuf;
+    if (mbuf.contains_seq("@@="_s) && mbuf.contains('|'))
+        filtered = "##B##3[##1Impossible to see this message. Please update your client.##3]"_s;
+    clif_message_sub(filteredBuf, sd, filtered);
 
-    /* Send the message back to the speaker. */
-    send_packet_repeatonly<0x008e, 4, 1>(s, STRPRINTF("%s : %s"_fmt, battle_get_name(sd), mbuf));
+    clif_send(sendbuf, sd, SendWho::AREA_CHAT_WOC,
+        wrap<ClientVersion>(6), filteredBuf);
 
     return rv;
 }
-- 
cgit v1.2.3-70-g09d2


From 150e3098dee0b8e72238436f25c37b14c83410aa Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Tue, 18 Aug 2015 16:28:57 -0400
Subject: replicate magic-v2's line_of_sight

---
 src/map/script-fun.cpp | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 51039ad..0c6ebb8 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -601,7 +601,7 @@ void builtin_target(ScriptState *st)
             val |= 0x04; // 0x04 target is walkable (has clear path to target)
     }
 
-    // TODO 0x08 target is visible (not behind collision) XXX maybe this is line of sight?
+    // TODO 0x08 target is visible (not behind collision)
 
     if (flag & 0x10)
     {
@@ -610,7 +610,11 @@ void builtin_target(ScriptState *st)
             val |= 0x10; // 0x10 target can be attacked by source (killer, killable and so on)
     }
 
-    // TODO 0x20 target is in line of sight
+    if (flag & 0x20)
+    {
+        if (battle_check_range(source, target, 0))
+            val |= 0x20; // 0x20 target is in line of sight
+    }
 
     push_int<ScriptDataInt>(st->stack, val);
 }
-- 
cgit v1.2.3-70-g09d2


From a5058369055b26bdba51806f3e2457a067670f7a Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 20 Aug 2015 12:19:42 -0400
Subject: make addnpctimer attach to the target npc instead of oid

---
 src/map/script-fun.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 0c6ebb8..3a5226e 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -2256,7 +2256,7 @@ void builtin_addnpctimer(ScriptState *st)
     ZString event_ = ZString(conv_str(st, &AARG(1)));
     NpcEvent event;
     extract(event_, &event);
-    npc_addeventtimer(map_id2bl(st->oid), tick, event);
+    npc_addeventtimer(npc_name2id(event.npc), tick, event);
 }
 
 /*==========================================
-- 
cgit v1.2.3-70-g09d2


From db232cbf9f4797433d1d9ba8f5dd58fc44a04180 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sat, 22 Aug 2015 13:36:30 -0400
Subject: fix compilation errors

---
 src/map/map.cpp         | 2 --
 src/map/npc.cpp         | 4 ++--
 src/map/pc.cpp          | 5 -----
 src/map/pc.hpp          | 1 -
 src/map/script-call.cpp | 1 +
 src/map/script-fun.cpp  | 4 ++--
 src/map/skill.cpp       | 4 ++--
 src/map/skill.hpp       | 2 +-
 8 files changed, 8 insertions(+), 15 deletions(-)

(limited to 'src')

diff --git a/src/map/map.cpp b/src/map/map.cpp
index 362d5d2..e7b0da8 100644
--- a/src/map/map.cpp
+++ b/src/map/map.cpp
@@ -573,8 +573,6 @@ void map_delobject(BlockId id, BL type)
         return;
 
     map_delobjectnofree(id, type);
-    if (obj->bl_type == BL::PC)     // [Fate] Not sure where else to put this... I'm not sure where delobject for PCs is called from
-        pc_cleanup(obj->is_player());
 
     MapBlockLock::freeblock(obj);
 }
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index a40a4ec..c55dbbf 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -61,8 +61,8 @@ namespace tmwa
 {
 namespace map
 {
-const std::vector<ByteCode> fake_buffer;
-const ScriptBuffer& fake_script = reinterpret_cast<const ScriptBuffer&>(fake_buffer);
+static const std::vector<ByteCode> fake_buffer;
+static const ScriptBuffer& fake_script = reinterpret_cast<const ScriptBuffer&>(fake_buffer);
 
 static
 Borrowed<const ScriptBuffer> script_or_parent(dumb_ptr<npc_data_script> nd)
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 96e0cac..64e0a6e 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -5063,11 +5063,6 @@ void do_init_pc(void)
     ).detach();
 }
 
-void pc_cleanup(dumb_ptr<map_session_data> sd)
-{
-    //magic_stop_completely(sd);
-}
-
 void pc_invisibility(dumb_ptr<map_session_data> sd, int enabled)
 {
     if (enabled && !bool(sd->status.option & Opt0::INVISIBILITY))
diff --git a/src/map/pc.hpp b/src/map/pc.hpp
index 35ca975..0f04698 100644
--- a/src/map/pc.hpp
+++ b/src/map/pc.hpp
@@ -170,7 +170,6 @@ int pc_divorce(dumb_ptr<map_session_data> sd);
 dumb_ptr<map_session_data> pc_get_partner(dumb_ptr<map_session_data> sd);
 void pc_set_gm_level(AccountId account_id, GmLevel level);
 void pc_setstand(dumb_ptr<map_session_data> sd);
-void pc_cleanup(dumb_ptr<map_session_data> sd);  // [Fate] Clean up after a logged-out PC
 
 int pc_read_gm_account(Session *, const std::vector<Packet_Repeat<0x2b15>>&);
 int pc_setpvptimer(dumb_ptr<map_session_data> sd, interval_t);
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index b9a098a..b397fb4 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -169,6 +169,7 @@ void get_val(ScriptState *st, struct script_data *data)
     {
         MATCH_CASE (const ScriptDataParam&, u)
         {
+            (void)u; // XXX travis complains if we don't use u
             bl = map_id2bl(st->rid);
         }
         MATCH_CASE (const ScriptDataVariable&, u)
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 3a5226e..61e63c8 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -4064,8 +4064,8 @@ void builtin_strnpcinfo(ScriptState *st)
 
         if (sdata->is<ScriptDataStr>())
         {
-            NpcName name = stringish<NpcName>(ZString(conv_str(st, sdata)));
-            nd = npc_name2id(name);
+            NpcName name_ = stringish<NpcName>(ZString(conv_str(st, sdata)));
+            nd = npc_name2id(name_);
         }
         else
         {
diff --git a/src/map/skill.cpp b/src/map/skill.cpp
index 6066a0d..5ea1901 100644
--- a/src/map/skill.cpp
+++ b/src/map/skill.cpp
@@ -892,12 +892,12 @@ int skill_status_change_start(dumb_ptr<block_list> bl, StatusChange type,
         int val1,
         interval_t tick)
 {
-    return skill_status_effect(bl, type, val1, tick, BlockId());
+    return skill_status_effect(bl, type, val1, tick);
 }
 
 int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
         int val1,
-        interval_t tick, BlockId spell_invocation)
+        interval_t tick)
 {
     dumb_ptr<map_session_data> sd = nullptr;
     eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data;
diff --git a/src/map/skill.hpp b/src/map/skill.hpp
index 23881d4..8b3fbc7 100644
--- a/src/map/skill.hpp
+++ b/src/map/skill.hpp
@@ -108,7 +108,7 @@ int skill_castcancel(dumb_ptr<block_list> bl, int type);
 // ステータス異常
 int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
         int val1,
-        interval_t tick, BlockId spell_invocation);
+        interval_t tick);
 int skill_status_change_start(dumb_ptr<block_list> bl, StatusChange type,
         int val1,
         interval_t tick);
-- 
cgit v1.2.3-70-g09d2


From aa7b231359fdf5c505efcfab02079359f7d5d275 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sun, 23 Aug 2015 12:54:13 -0400
Subject: add being id parameter to sc_check, sc_end, getpvpflag and
 strcharinfo

---
 src/map/script-fun.cpp | 32 ++++++++++++++++++++++++--------
 1 file changed, 24 insertions(+), 8 deletions(-)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 61e63c8..b48b852 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -1648,7 +1648,11 @@ void builtin_strcharinfo(ScriptState *st)
     dumb_ptr<map_session_data> sd;
     int num;
 
-    sd = script_rid2sd(st);
+    if (HARG(1))    //指定したキャラを状態異常にする
+        sd = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1))))->is_player();
+    else
+        sd = script_rid2sd(st);
+
     num = conv_num(st, &AARG(0));
     if (num == 0)
     {
@@ -2741,7 +2745,11 @@ void builtin_sc_end(ScriptState *st)
 {
     dumb_ptr<block_list> bl;
     StatusChange type = StatusChange(conv_num(st, &AARG(0)));
-    bl = map_id2bl(st->rid);
+    if (HARG(1))    //指定したキャラを状態異常にする
+        bl = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1))));
+    else
+        bl = map_id2bl(st->rid);
+
     skill_status_change_end(bl, type, nullptr);
 }
 
@@ -2750,7 +2758,10 @@ void builtin_sc_check(ScriptState *st)
 {
     dumb_ptr<block_list> bl;
     StatusChange type = StatusChange(conv_num(st, &AARG(0)));
-    bl = map_id2bl(st->rid);
+    if (HARG(1))    //指定したキャラを状態異常にする
+        bl = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1))));
+    else
+        bl = map_id2bl(st->rid);
 
     push_int<ScriptDataInt>(st->stack, skill_status_change_active(bl, type));
 
@@ -2938,7 +2949,12 @@ void builtin_setpvpchannel(ScriptState *st)
 static
 void builtin_getpvpflag(ScriptState *st)
 {
-    dumb_ptr<map_session_data> sd = script_rid2sd(st);
+    dumb_ptr<map_session_data> sd;
+    if (HARG(1))    //指定したキャラを状態異常にする
+        sd = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1))))->is_player();
+    else
+        sd = script_rid2sd(st);
+
     int num = conv_num(st, &AARG(0));
     int flag = 0;
 
@@ -4198,7 +4214,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(getcharid, "i?"_s, 'i'),
     BUILTIN(getnpcid, "?"_s, 'i'),
     BUILTIN(getversion, ""_s, 'i'),
-    BUILTIN(strcharinfo, "i"_s, 's'),
+    BUILTIN(strcharinfo, "i?"_s, 's'),
     BUILTIN(getequipid, "i?"_s, 'i'),
     BUILTIN(bonus, "ii"_s, '\0'),
     BUILTIN(bonus2, "iii"_s, '\0'),
@@ -4238,8 +4254,8 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(enablenpc, "s"_s, '\0'),
     BUILTIN(disablenpc, "s"_s, '\0'),
     BUILTIN(sc_start, "iTi?"_s, '\0'),
-    BUILTIN(sc_end, "i"_s, '\0'),
-    BUILTIN(sc_check, "i"_s, 'i'),
+    BUILTIN(sc_end, "i?"_s, '\0'),
+    BUILTIN(sc_check, "i?"_s, 'i'),
     BUILTIN(debugmes, "s"_s, '\0'),
     BUILTIN(wgm, "s"_s, '\0'),
     BUILTIN(gmlog, "s"_s, '\0'),
@@ -4253,7 +4269,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(pvpon, "M"_s, '\0'),
     BUILTIN(pvpoff, "M"_s, '\0'),
     BUILTIN(setpvpchannel, "i"_s, '\0'),
-    BUILTIN(getpvpflag, "i"_s, 'i'),
+    BUILTIN(getpvpflag, "i?"_s, 'i'),
     BUILTIN(emotion, "i?"_s, '\0'),
     BUILTIN(mapwarp, "MMxy"_s, '\0'),
     BUILTIN(mobcount, "ME"_s, 'i'),
-- 
cgit v1.2.3-70-g09d2


From aefc5982bd5fdc776a9ac5a5b68f6980e8f09387 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Tue, 25 Aug 2015 13:35:37 -0400
Subject: add type 3 to foreach

---
 src/map/script-fun.cpp | 3 +++
 1 file changed, 3 insertions(+)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index b48b852..75df30f 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -825,6 +825,9 @@ void builtin_foreach(ScriptState *st)
         case 2:
             block_type = BL::MOB;
             break;
+        case 3:
+            block_type = BL::NUL;
+            break;
         default:
             return;
     }
-- 
cgit v1.2.3-70-g09d2


From cf6f557700db4f88050674468a70a38f93441e98 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Tue, 25 Aug 2015 14:43:31 -0400
Subject: add BL_TYPE param

---
 src/map/pc.cpp     | 3 +++
 src/mmo/clif.t.hpp | 1 +
 2 files changed, 4 insertions(+)

(limited to 'src')

diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 64e0a6e..7940812 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -3523,6 +3523,9 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
         case SP::BL_ID:
             val = unwrap<BlockId>(bl->bl_id);
             break;
+        case SP::BL_TYPE:
+            val = static_cast<uint8_t>(bl->bl_type);
+            break;
     }
 
     return val;
diff --git a/src/mmo/clif.t.hpp b/src/mmo/clif.t.hpp
index 377e953..fec0201 100644
--- a/src/mmo/clif.t.hpp
+++ b/src/mmo/clif.t.hpp
@@ -474,6 +474,7 @@ enum class SP : uint16_t
     POS_Y                       = 1075,
     PVP_CHANNEL                 = 1076,
     BL_ID                       = 1077,
+    BL_TYPE                     = 1078,
 };
 
 constexpr
-- 
cgit v1.2.3-70-g09d2


From f3dd34b5172f2bcb4f79d472e4c8ba2dbfe9cce0 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Tue, 1 Sep 2015 15:01:01 -0400
Subject: convert unhandled types for scope vars

---
 src/map/script-call-internal.hpp |  2 +-
 src/map/script-call.cpp          | 10 ++++++----
 src/map/script-fun.cpp           | 10 +++++-----
 3 files changed, 12 insertions(+), 10 deletions(-)

(limited to 'src')

diff --git a/src/map/script-call-internal.hpp b/src/map/script-call-internal.hpp
index b3dfb5a..da6c082 100644
--- a/src/map/script-call-internal.hpp
+++ b/src/map/script-call-internal.hpp
@@ -83,7 +83,7 @@ void get_val(dumb_ptr<block_list> sd, struct script_data *data);
 __attribute__((deprecated))
 void get_val(ScriptState *st, struct script_data *data);
 struct script_data get_val2(ScriptState *st, SIR reg);
-void set_scope_reg(ScriptState *, SIR, struct script_data);
+void set_scope_reg(ScriptState *, SIR, struct script_data *);
 void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, struct script_data vd);
 void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, int id);
 void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, RString zd);
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index b397fb4..0164c2a 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -270,12 +270,12 @@ void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, struct script_
     }
 }
 
-void set_scope_reg(ScriptState *st, SIR reg, struct script_data vd)
+void set_scope_reg(ScriptState *st, SIR reg, struct script_data *vd)
 {
     ZString name = variable_names.outtern(reg.base());
     if (name.back() == '$')
     {
-        if (auto *u = vd.get_if<ScriptDataStr>())
+        if (auto *u = vd->get_if<ScriptDataStr>())
         {
             if (!u->str)
             {
@@ -285,10 +285,12 @@ void set_scope_reg(ScriptState *st, SIR reg, struct script_data vd)
             st->regstrm.insert(reg, u->str);
         }
         else
-            st->regstrm.erase(reg);
+            st->regstrm.insert(reg, conv_str(st, vd));
     }
-    else if (auto *u = vd.get_if<ScriptDataInt>())
+    else if (auto *u = vd->get_if<ScriptDataInt>())
         st->regm.put(reg, u->numi);
+    else
+        st->regm.put(reg, conv_num(st, vd));
 }
 
 void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, int id)
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 75df30f..7e55565 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -1059,7 +1059,7 @@ void builtin_set(ScriptState *st)
             {
                 if (name_[1] == '@')
                 {
-                        set_scope_reg(st, reg, AARG(1));
+                        set_scope_reg(st, reg, &AARG(1));
                     return;
                 }
                 bl = map_id2bl(st->oid)->is_npc();
@@ -1106,13 +1106,13 @@ void builtin_setarray(ScriptState *st)
     }
     if (prefix == '.' && name[1] != '@')
         bl = map_id2bl(st->oid)->is_npc();
-    else if (prefix != '$')
+    else if (prefix != '$' && !(prefix == '.' && name[1] == '@'))
         bl = map_id2bl(st->rid)->is_player();
 
     for (int j = 0, i = 1; i < st->end - st->start - 2 && j < 256; i++, j++)
     {
         if (prefix == '.' && name[1] == '@')
-            set_scope_reg(st, reg.iplus(j), AARG(i));
+            set_scope_reg(st, reg.iplus(j), &AARG(i));
         else if (postfix == '$')
             set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), conv_str(st, &AARG(i)));
         else
@@ -1141,13 +1141,13 @@ void builtin_cleararray(ScriptState *st)
     }
     if (prefix == '.' && name[1] != '@')
         bl = map_id2bl(st->oid)->is_npc();
-    else if (prefix != '$')
+    else if (prefix != '$' && !(prefix == '.' && name[1] == '@'))
         bl = map_id2bl(st->rid)->is_player();
 
     for (int i = 0; i < sz; i++)
     {
         if (prefix == '.' && name[1] == '@')
-            set_scope_reg(st, reg.iplus(i), AARG(i));
+            set_scope_reg(st, reg.iplus(i), &AARG(i));
         else if (postfix == '$')
             set_reg(bl, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARG(1)));
         else
-- 
cgit v1.2.3-70-g09d2


From 4cb881c88f5e1879e89ab2c830a622a2edfb0f82 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 10 Sep 2015 22:30:23 -0400
Subject: do not bind trigger detection to map cells

---
 src/map/map.t.hpp      |  2 --
 src/map/npc-parse.cpp  | 30 ------------------------------
 src/map/npc.cpp        |  4 ++--
 src/map/pc.cpp         |  8 ++------
 src/map/script-fun.cpp |  7 ++++++-
 5 files changed, 10 insertions(+), 41 deletions(-)

(limited to 'src')

diff --git a/src/map/map.t.hpp b/src/map/map.t.hpp
index 89b9a87..ea7ead5 100644
--- a/src/map/map.t.hpp
+++ b/src/map/map.t.hpp
@@ -185,8 +185,6 @@ enum class MapCell : uint8_t
     UNWALKABLE  = 0x01,
     // not in tmwa data
     _range      = 0x04,
-    // set in code, not imported
-    NPC_NEAR    = 0x80,
 };
 ENUM_BITWISE_OPERATORS(MapCell)
 }
diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp
index edfe4c9..e30225a 100644
--- a/src/map/npc-parse.cpp
+++ b/src/map/npc-parse.cpp
@@ -166,21 +166,6 @@ bool npc_load_warp(ast::npc::Warp& warp)
     nd->warp.xs = xs;
     nd->warp.ys = ys;
 
-    for (int i = 0; i < ys; i++)
-    {
-        for (int j = 0; j < xs; j++)
-        {
-            int x_lo = x - xs / 2;
-            int y_lo = y - ys / 2;
-            int xc = x_lo + j;
-            int yc = y_lo + i;
-            MapCell t = map_getcell(m, xc, yc);
-            if (bool(t & MapCell::UNWALKABLE))
-                continue;
-            map_setcell(m, xc, yc, t | MapCell::NPC_NEAR);
-        }
-    }
-
     npc_warp++;
     nd->bl_type = BL::NPC;
     nd->npc_subtype = NpcSubtype::WARP;
@@ -554,21 +539,6 @@ bool npc_load_script_map(ast::script::ScriptBody& body, ast::npc::ScriptMap& scr
     int xs = script_map.xs.data, ys = script_map.ys.data;
 
     {
-        for (int i = 0; i < ys; i++)
-        {
-            for (int j = 0; j < xs; j++)
-            {
-                int x_lo = x - xs / 2;
-                int y_lo = y - ys / 2;
-                int xc = x_lo + j;
-                int yc = y_lo + i;
-                MapCell t = map_getcell(m, xc, yc);
-                if (bool(t & MapCell::UNWALKABLE))
-                    continue;
-                map_setcell(m, xc, yc, t | MapCell::NPC_NEAR);
-            }
-        }
-
         nd->scr.xs = xs;
         nd->scr.ys = ys;
         nd->scr.event_needs_map = true;
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index c55dbbf..b7fb9ca 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -713,12 +713,12 @@ int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int
             aname.label = stringish<ScriptLabel>("OnTouch"_s);
 
             if (sd->areanpc_id == m->npc[i]->bl_id)
-                return 1;
+                return 2;
 
             sd->areanpc_id = m->npc[i]->bl_id;
             if (npc_event(sd, aname, 0) > 0)
                 npc_click(sd, m->npc[i]->bl_id);
-            break;
+            return 2;
         }
     }
     return 0;
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 7940812..1440454 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -2457,9 +2457,7 @@ void pc_walk(TimerData *, tick_t tick, BlockId id, unsigned char data)
             }
         }
 
-        if (bool(map_getcell(sd->bl_m, x, y) & MapCell::NPC_NEAR))
-            npc_touch_areanpc(sd, sd->bl_m, x, y);
-        else
+        if (npc_touch_areanpc(sd, sd->bl_m, x, y) != 2)
             sd->areanpc_id = BlockId();
     }
     interval_t i = calc_next_walk_step(sd);
@@ -2563,9 +2561,7 @@ int pc_stop_walking(dumb_ptr<map_session_data> sd, int type)
 
 void pc_touch_all_relevant_npcs(dumb_ptr<map_session_data> sd)
 {
-    if (bool(map_getcell(sd->bl_m, sd->bl_x, sd->bl_y) & MapCell::NPC_NEAR))
-        npc_touch_areanpc(sd, sd->bl_m, sd->bl_x, sd->bl_y);
-    else
+    if (npc_touch_areanpc(sd, sd->bl_m, sd->bl_x, sd->bl_y) != 2)
         sd->areanpc_id = BlockId();
 }
 
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 7e55565..f8c81c4 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -894,6 +894,11 @@ void builtin_puppet(ScriptState *st)
     nd->bl_m = m;
     nd->bl_x = x;
     nd->bl_y = y;
+    if (HARG(5) && HARG(6))
+    {
+        nd->scr.xs = ((conv_num(st, &AARG(5)) * 2) + 1); // do the same equation as in AST
+        nd->scr.ys = ((conv_num(st, &AARG(6)) * 2) + 1);
+    }
     nd->bl_id = npc_get_new_npc_id();
     nd->scr.parent = parent_nd->bl_id;
     nd->dir = DIR::S;
@@ -4312,7 +4317,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(isdead, ""_s, 'i'),
     BUILTIN(aggravate, "Mxyxyi"_s, '\0'),
     BUILTIN(fakenpcname, "ssi"_s, '\0'),
-    BUILTIN(puppet, "mxysi"_s, 'i'),
+    BUILTIN(puppet, "mxysi??"_s, 'i'),
     BUILTIN(destroy, "?"_s, '\0'),
     BUILTIN(getx, ""_s, 'i'),
     BUILTIN(gety, ""_s, 'i'),
-- 
cgit v1.2.3-70-g09d2


From da8a93191a589cd32f3f3517d4fa7e5c8c8570f5 Mon Sep 17 00:00:00 2001
From: wushin <pasekei@gmail.com>
Date: Tue, 15 Sep 2015 13:20:08 -0500
Subject: Fix range issue in magic spells

---
 src/map/battle.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/map/battle.cpp b/src/map/battle.cpp
index ef1567e..554ef14 100644
--- a/src/map/battle.cpp
+++ b/src/map/battle.cpp
@@ -134,7 +134,9 @@ int battle_get_range(dumb_ptr<block_list> bl)
     if (bl->bl_type == BL::MOB)
         return get_mob_db(bl->is_mob()->mob_class).range;
     else if (bl->bl_type == BL::PC)
-        return bl->is_player()->attackrange;
+        return (bl->is_player()->attack_spell_override
+                    ? bl->is_player()->attack_spell_range
+                    : bl->is_player()->attackrange);
     else
         return 0;
 }
-- 
cgit v1.2.3-70-g09d2


From 050f810ced9e9f600178873d378c50f057a8e7e9 Mon Sep 17 00:00:00 2001
From: wushin <pasekei@gmail.com>
Date: Tue, 15 Sep 2015 22:46:41 -0500
Subject: Fix betsanc and sc_start recalc

---
 src/map/skill.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src')

diff --git a/src/map/skill.cpp b/src/map/skill.cpp
index 5ea1901..d9a7717 100644
--- a/src/map/skill.cpp
+++ b/src/map/skill.cpp
@@ -1001,10 +1001,10 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type,
             break;
 
         case StatusChange::SC_HASTE:
-            calc_flag = 1;
-            break;
         case StatusChange::SC_PHYS_SHIELD:
         case StatusChange::SC_MBARRIER:
+            calc_flag = 1;
+            break;
         case StatusChange::SC_HALT_REGENERATE:
         case StatusChange::SC_HIDE:
             break;
-- 
cgit v1.2.3-70-g09d2


From 0fd54ac797cea996ba3430168b4a962df13c12fa Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 17 Sep 2015 22:07:44 -0400
Subject: allow to get another player's account vars

---
 src/map/script-fun.cpp | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index f8c81c4..3592095 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -3471,7 +3471,17 @@ void builtin_get(ScriptState *st)
     }
     else
     {
-        int var = pc_readreg(bl, reg);
+        int var;
+        if (prefix == '#' && bl)
+        {
+            if (name_[1] == '#')
+                var = pc_readaccountreg2(bl->is_player(), stringish<VarName>(name_));
+            else
+                var = pc_readaccountreg(bl->is_player(), stringish<VarName>(name_));
+        }
+        else
+            var = pc_readreg(bl, reg);
+
         push_int<ScriptDataInt>(st->stack, var);
     }
 }
-- 
cgit v1.2.3-70-g09d2


From 3731a8e9a30f1fd9b30f99f94e2fe62bbdb285a1 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Wed, 14 Oct 2015 15:58:59 -0400
Subject: add more params, allow get/set to use char id

---
 src/map/pc.cpp         | 21 +++++++++++++++++++++
 src/map/script-fun.cpp | 28 ++++++++++++++++++++++++++--
 src/mmo/clif.t.hpp     |  4 +++-
 3 files changed, 50 insertions(+), 3 deletions(-)

(limited to 'src')

diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 1440454..0f6c9a6 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -3522,6 +3522,12 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
         case SP::BL_TYPE:
             val = static_cast<uint8_t>(bl->bl_type);
             break;
+        case SP::PARTNER:
+            val = sd ? unwrap<CharId>(sd->status.partner_id) : 0;
+            break;
+        case SP::CHAR_ID:
+            val = sd ? unwrap<CharId>(sd->status_key.char_id) : 0;
+            break;
     }
 
     return val;
@@ -3663,6 +3669,21 @@ int pc_setparam(dumb_ptr<map_session_data> sd, SP type, int val)
         case SP::LUK:
             pc_statusup2(sd, type, (val - sd->status.attrs[sp_to_attr(type)]));
             break;
+        case SP::PARTNER:
+            dumb_ptr<block_list> p_bl;
+            if (val < 2000000 && val >= 150000)
+            {
+                dumb_ptr<map_session_data> p_sd = nullptr;
+                if ((p_sd = map_nick2sd(map_charid2nick(wrap<CharId>(val)))) != nullptr)
+                    p_bl = map_id2bl(p_sd->bl_id);
+            }
+            else
+                p_bl = map_id2bl(wrap<BlockId>(val));
+            if (val < 1)
+                pc_divorce(sd);
+            else
+                p_bl ? pc_marriage(sd, p_bl->is_player()) : 0;
+            break;
     }
     clif_updatestatus(sd, type);
 
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 3592095..f7108d0 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -995,7 +995,19 @@ void builtin_set(ScriptState *st)
             }
             else
             {
-                id = wrap<BlockId>(conv_num(st, sdata));
+                int num = conv_num(st, sdata);
+                if (num >= 2000000)
+                    id = wrap<BlockId>(num);
+                else if (num >= 150000)
+                {
+                    dumb_ptr<map_session_data> p_sd = nullptr;
+                    if ((p_sd = map_nick2sd(map_charid2nick(wrap<CharId>(num)))) != nullptr)
+                        id = p_sd->bl_id;
+                    else
+                        return;
+                }
+                else
+                    return;
                 bl = map_id2bl(id);
             }
         }
@@ -3399,7 +3411,19 @@ void builtin_get(ScriptState *st)
         }
         else
         {
-            id = wrap<BlockId>(conv_num(st, sdata));
+            int num = conv_num(st, sdata);
+            if (num >= 2000000)
+                id = wrap<BlockId>(num);
+            else if (num >= 150000)
+            {
+                dumb_ptr<map_session_data> p_sd = nullptr;
+                if ((p_sd = map_nick2sd(map_charid2nick(wrap<CharId>(num)))) != nullptr)
+                    id = p_sd->bl_id;
+                else
+                    return;
+            }
+            else
+                return;
             bl = map_id2bl(id);
         }
 
diff --git a/src/mmo/clif.t.hpp b/src/mmo/clif.t.hpp
index fec0201..888fd66 100644
--- a/src/mmo/clif.t.hpp
+++ b/src/mmo/clif.t.hpp
@@ -307,8 +307,9 @@ enum class SP : uint16_t
     // sent to client
     JOBLEVEL                    = 55,
 
-#if 0
     PARTNER                     = 57,
+
+#if 0
     CART                        = 58,
     FAME                        = 59,
     UNBREAKABLE                 = 60,
@@ -475,6 +476,7 @@ enum class SP : uint16_t
     PVP_CHANNEL                 = 1076,
     BL_ID                       = 1077,
     BL_TYPE                     = 1078,
+    CHAR_ID                     = 1079,
 };
 
 constexpr
-- 
cgit v1.2.3-70-g09d2


From 69e4ca9779763ac6fa657e21b13f78025394512e Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 22 Oct 2015 06:52:25 -0400
Subject: add chr and ord builtins

---
 src/map/script-fun.cpp | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index f7108d0..f59ba55 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -3132,6 +3132,20 @@ void builtin_getpartnerid2(ScriptState *st)
     push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status.partner_id));
 }
 
+static
+void builtin_chr(ScriptState *st)
+{
+    const char ascii = conv_num(st, &AARG(0));
+    push_str<ScriptDataStr>(st->stack, VString<1>(ascii));
+}
+
+static
+void builtin_ord(ScriptState *st)
+{
+    const char ascii = conv_str(st, &AARG(0))[0];
+    push_int<ScriptDataInt>(st->stack, static_cast<int>(ascii));
+}
+
 static
 void builtin_explode(ScriptState *st)
 {
@@ -4371,6 +4385,8 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(pow, "ii"_s, 'i'),
     BUILTIN(target, "iii"_s, 'i'),
     BUILTIN(distance, "ii?"_s, 'i'),
+    BUILTIN(chr, "i"_s, 'i'),
+    BUILTIN(ord, "s"_s, 'i'),
     {nullptr, ""_s, ""_s, '\0'},
 };
 } // namespace map
-- 
cgit v1.2.3-70-g09d2


From 7b3453a4e1c3bb7c0d7dcc0c8b075f8bbdb8e65b Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Wed, 28 Oct 2015 21:06:51 -0400
Subject: add builtin_call and builtin_getarg

---
 src/map/script-call.cpp |  2 +-
 src/map/script-fun.cpp  | 88 ++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 85 insertions(+), 5 deletions(-)

(limited to 'src')

diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index 0164c2a..a669b59 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -728,7 +728,7 @@ void run_func(ScriptState *st)
         st->defsp = conv_num(st, &st->stack->stack_datav[olddefsp - 3]); // 基準スタックポインタを復元
         // Number of arguments.
         int i = conv_num(st, &st->stack->stack_datav[olddefsp - 4]); // 引数の数所得
-        assert (i == 0);
+        //assert (i == 0);
 
         pop_stack(st->stack, olddefsp - 4 - i, olddefsp);  // 要らなくなったスタック(引数と復帰用データ)削除
 
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index f59ba55..ccf8f11 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -131,7 +131,6 @@ void builtin_callfunc(ScriptState *st)
             for (int i = st->start + 3; i < st->end; i++, j++)
                 push_copy(st->stack, i);
 #endif
-
             push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ
             push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ
             push_int<ScriptDataInt>(st->stack, st->scriptp.pos);   // 現在のスクリプト位置をプッシュ
@@ -151,6 +150,85 @@ void builtin_callfunc(ScriptState *st)
     OMATCH_END ();
 }
 
+static
+void builtin_call(ScriptState *st)
+{
+    struct script_data *sdata = &AARG(0);
+    get_val(st, sdata);
+    RString str;
+    if (sdata->is<ScriptDataStr>())
+    {
+        str = conv_str(st, sdata);
+        Option<P<const ScriptBuffer>> scr_ = userfunc_db.get(str);
+        OMATCH_BEGIN (scr_)
+        {
+            OMATCH_CASE_SOME (scr)
+            {
+                int j = 0;
+
+                for (int i = st->start + 3; i < st->end; i++, j++)
+                    push_copy(st->stack, i);
+
+                push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ
+                push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ
+                push_int<ScriptDataInt>(st->stack, st->scriptp.pos);   // 現在のスクリプト位置をプッシュ
+                push_script<ScriptDataRetInfo>(st->stack, TRY_UNWRAP(st->scriptp.code, abort()));  // 現在のスクリプトをプッシュ
+
+                st->scriptp = ScriptPointer(scr, 0);
+                st->defsp = st->start + 4 + j;
+                st->state = ScriptEndState::GOTO;
+                return;
+            }
+            OMATCH_CASE_NONE ()
+            {
+                PRINTF("fatal: script: callfunc: function not found! [%s]\n"_fmt, str);
+                st->state = ScriptEndState::END;
+                abort();
+            }
+        }
+        OMATCH_END ();
+    }
+    else
+    {
+        int pos_ = conv_num(st, &AARG(0));
+        int j = 0;
+
+        for (int i = st->start + 3; i < st->end; i++, j++)
+            push_copy(st->stack, i);
+
+        push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ
+        push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ
+        push_int<ScriptDataInt>(st->stack, st->scriptp.pos);   // 現在のスクリプト位置をプッシュ
+        push_script<ScriptDataRetInfo>(st->stack, TRY_UNWRAP(st->scriptp.code, abort()));  // 現在のスクリプトをプッシュ
+
+        st->scriptp.pos = pos_;
+        st->defsp = st->start + 4 + j;
+        st->state = ScriptEndState::GOTO;
+    }
+
+}
+
+static
+void builtin_getarg(ScriptState *st)
+{
+    int arg = conv_num(st, &AARG(0));
+    if(st->defsp < 1 || !(st->stack->stack_datav[st->defsp - 1].is<ScriptDataRetInfo>()))
+    {
+        dumb_ptr<npc_data> nd = map_id_is_npc(st->oid);
+        if(nd)
+            PRINTF("builtin_getarg: no callfunc or callsub! @ %s\n"_fmt, nd->name);
+        else
+            PRINTF("builtin_getarg: no callfunc or callsub! (no npc)\n"_fmt);
+        st->state = ScriptEndState::END;
+        return;
+    }
+
+    int i = conv_num(st, &st->stack->stack_datav[st->defsp - 4]); // Number of arguments.
+    if (arg > i || arg < 0)
+        return;
+    push_copy(st->stack, (st->defsp - 4 - i) + arg);
+}
+
 /*==========================================
  * サブルーティンの呼び出し
  *------------------------------------------
@@ -191,12 +269,12 @@ void builtin_return(ScriptState *st)
         else
             PRINTF("Deprecated: return outside of callfunc or callsub! (no npc)\n"_fmt);
     }
-#if 0
+
     if (HARG(0))
     {                           // 戻り値有り
         push_copy(st->stack, st->start + 2);
     }
-#endif
+
     st->state = ScriptEndState::RETFUNC;
 }
 
@@ -4236,8 +4314,10 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(mes, "s"_s, '\0'),
     BUILTIN(goto, "L"_s, '\0'),
     BUILTIN(callfunc, "F"_s, '\0'),
+    BUILTIN(call, "F?*"_s, '.'),
     BUILTIN(callsub, "L"_s, '\0'),
-    BUILTIN(return, ""_s, '\0'),
+    BUILTIN(getarg, "i"_s, '.'),
+    BUILTIN(return, "?*"_s, '\0'),
     BUILTIN(next, ""_s, '\0'),
     BUILTIN(close, ""_s, '\0'),
     BUILTIN(close2, ""_s, '\0'),
-- 
cgit v1.2.3-70-g09d2


From d40c8dfa03ed4735299963c2dc51b189cef91e4d Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 29 Oct 2015 12:04:39 -0400
Subject: builtin_void

---
 src/map/script-fun.cpp | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index ccf8f11..fb9d1e3 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -229,6 +229,12 @@ void builtin_getarg(ScriptState *st)
     push_copy(st->stack, (st->defsp - 4 - i) + arg);
 }
 
+static
+void builtin_void(ScriptState *)
+{
+    return;
+}
+
 /*==========================================
  * サブルーティンの呼び出し
  *------------------------------------------
@@ -4317,7 +4323,8 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(call, "F?*"_s, '.'),
     BUILTIN(callsub, "L"_s, '\0'),
     BUILTIN(getarg, "i"_s, '.'),
-    BUILTIN(return, "?*"_s, '\0'),
+    BUILTIN(return, "?"_s, '\0'),
+    BUILTIN(void, "?*"_s, '\0'),
     BUILTIN(next, ""_s, '\0'),
     BUILTIN(close, ""_s, '\0'),
     BUILTIN(close2, ""_s, '\0'),
-- 
cgit v1.2.3-70-g09d2


From bd9a15547dab82334bbeafc00c1208cbbcdf6f69 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 29 Oct 2015 13:22:10 -0400
Subject: default getarg value

---
 src/map/script-fun.cpp | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index fb9d1e3..9050189 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -224,8 +224,15 @@ void builtin_getarg(ScriptState *st)
     }
 
     int i = conv_num(st, &st->stack->stack_datav[st->defsp - 4]); // Number of arguments.
-    if (arg > i || arg < 0)
+    if (arg > i || arg < 0 || i == 0)
+    {
+        const char a = 3; // ETX
+        if (HARG(1))
+            push_copy(st->stack, st->start + 3);
+        else
+            push_str<ScriptDataStr>(st->stack, VString<1>(a));
         return;
+    }
     push_copy(st->stack, (st->defsp - 4 - i) + arg);
 }
 
@@ -4322,7 +4329,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(callfunc, "F"_s, '\0'),
     BUILTIN(call, "F?*"_s, '.'),
     BUILTIN(callsub, "L"_s, '\0'),
-    BUILTIN(getarg, "i"_s, '.'),
+    BUILTIN(getarg, "i?"_s, '.'),
     BUILTIN(return, "?"_s, '\0'),
     BUILTIN(void, "?*"_s, '\0'),
     BUILTIN(next, ""_s, '\0'),
-- 
cgit v1.2.3-70-g09d2


From f4183108c70a51217edf07a63e843f1a5d2c1747 Mon Sep 17 00:00:00 2001
From: wushin <pasekei@gmail.com>
Date: Wed, 9 Mar 2016 13:28:42 -0600
Subject: Expose sd->heal_xp

---
 src/map/pc.cpp     | 3 +++
 src/mmo/clif.t.hpp | 3 ++-
 2 files changed, 5 insertions(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 0f6c9a6..929143c 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -3445,6 +3445,9 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
         case SP::MAXHP:
             val = battle_get_max_hp(bl);
             break;
+        case SP::HEALXP:
+            val = sd ? sd->heal_xp : 0;
+            break;
         case SP::SP:
             val = sd ? sd->status.sp : 0;
             break;
diff --git a/src/mmo/clif.t.hpp b/src/mmo/clif.t.hpp
index 888fd66..3571a1e 100644
--- a/src/mmo/clif.t.hpp
+++ b/src/mmo/clif.t.hpp
@@ -234,7 +234,8 @@ enum class SP : uint16_t
     MAXSP                       = 8,
     // sent to client
     STATUSPOINT                 = 9,
-
+    // sent to client
+    HEALXP                      = 10,
     // sent to client
     BASELEVEL                   = 11,
     // sent to client
-- 
cgit v1.2.3-70-g09d2


From 836b7c95f1528183fd90c5493d638d708c1a2adc Mon Sep 17 00:00:00 2001
From: wushin <pasekei@gmail.com>
Date: Wed, 9 Mar 2016 21:11:47 -0600
Subject: Fake a response on no BL

---
 src/map/script-fun.cpp | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

(limited to 'src')

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 9050189..b3e4f96 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -3589,7 +3589,11 @@ void builtin_get(ScriptState *st)
 
     if (!bl)
     {
-        PRINTF("builtin_get: no block list attached !\n"_fmt);
+        PRINTF("builtin_get: no block list attached %s!\n"_fmt, conv_str(st, &AARG(1)));
+        if (postfix == '$')
+            push_str<ScriptDataStr>(st->stack, conv_str(st, &AARG(1)));
+        else
+            push_int<ScriptDataInt>(st->stack, 0);
         return;
     }
 
-- 
cgit v1.2.3-70-g09d2


From 502b3b714f0f636dac80bd600e174a173bd7028e Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Fri, 30 Oct 2015 17:17:54 -0400
Subject: add new builtins for npc commands

add requestitem builtin
add requestlang builtin
add camera builtin
send a force-close on close when no npc dialog
add clear builtin
---
 src/map/map.hpp         |   1 +
 src/map/script-call.cpp |  11 ++-
 src/map/script-fun.cpp  | 214 ++++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 218 insertions(+), 8 deletions(-)

(limited to 'src')

diff --git a/src/map/map.hpp b/src/map/map.hpp
index 72d5dd3..1fd86d1 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -148,6 +148,7 @@ struct map_session_data : block_list, SessionData
         unsigned seen_motd:1;
         unsigned pvpchannel;
         unsigned pvp_rank;
+        unsigned npc_dialog_mes:1;
     } state;
     struct
     {
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index a669b59..fbb9b97 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -882,20 +882,25 @@ void run_script_main(ScriptState *st, Borrowed<const ScriptBuffer> rootscript)
             abort();
         }
     }
+    dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
     switch (st->state)
     {
         case ScriptEndState::STOP:
+            if (sd && sd->npc_id == st->oid)
+                sd->state.npc_dialog_mes = 0;
             break;
         case ScriptEndState::END:
-        {
-            dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
             st->scriptp.code = None;
             st->scriptp.pos = -1;
             if (sd && sd->npc_id == st->oid)
+            {
+                sd->state.npc_dialog_mes = 0;
                 npc_event_dequeue(sd);
-        }
+            }
             break;
         case ScriptEndState::RERUNLINE:
+            if (sd && sd->npc_id == st->oid)
+                sd->state.npc_dialog_mes = 0;
             st->scriptp.pos = rerun_pos;
             break;
     }
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index b3e4f96..faa8c81 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -89,8 +89,16 @@ enum class MonsterAttitude
 static
 void builtin_mes(ScriptState *st)
 {
-    RString mes = conv_str(st, &AARG(0));
-    clif_scriptmes(script_rid2sd(st), st->oid, mes);
+    dumb_ptr<map_session_data> sd = script_rid2sd(st);
+    sd->state.npc_dialog_mes = 1;
+    RString mes = HARG(0) ? conv_str(st, &AARG(0)) : ""_s;
+    clif_scriptmes(sd, st->oid, mes);
+}
+
+static
+void builtin_clear(ScriptState *st)
+{
+    clif_npc_action(script_rid2sd(st), st->oid, 9, 0, 0, 0);
 }
 
 /*==========================================
@@ -318,14 +326,22 @@ void builtin_close(ScriptState *st)
             PRINTF("Deprecated: close in a callfunc or callsub! (no npc)\n"_fmt);
     }
     st->state = ScriptEndState::END;
-    clif_scriptclose(script_rid2sd(st), st->oid);
+    dumb_ptr<map_session_data> sd = script_rid2sd(st);
+    if (sd->state.npc_dialog_mes)
+        clif_scriptclose(sd, st->oid);
+    else
+        clif_npc_action(sd, st->oid, 5, 0, 0, 0);
 }
 
 static
 void builtin_close2(ScriptState *st)
 {
     st->state = ScriptEndState::STOP;
-    clif_scriptclose(script_rid2sd(st), st->oid);
+    dumb_ptr<map_session_data> sd = script_rid2sd(st);
+    if (sd->state.npc_dialog_mes)
+        clif_scriptclose(sd, st->oid);
+    else
+        clif_npc_action(sd, st->oid, 5, 0, 0, 0);
 }
 
 /*==========================================
@@ -782,6 +798,147 @@ void builtin_input(ScriptState *st)
     }
 }
 
+static
+void builtin_requestitem(ScriptState *st)
+{
+    dumb_ptr<map_session_data> sd = nullptr;
+    script_data& scrd = AARG(0);
+    assert (scrd.is<ScriptDataVariable>());
+    int amount = HARG(1) ? conv_num(st, &AARG(1)) : 1;
+    if (amount < 1 || amount > 16)
+        amount = 1;
+
+    SIR reg = scrd.get_if<ScriptDataVariable>()->reg;
+    ZString name = variable_names.outtern(reg.base());
+    char prefix = name.front();
+    char postfix = name.back();
+
+    if (prefix != '$' && prefix != '@' && prefix != '.')
+    {
+        PRINTF("builtin_requestitem: illegal scope!\n"_fmt);
+        runflag = 0;
+        return;
+    }
+
+    sd = script_rid2sd(st);
+    if (sd->state.menu_or_input)
+    {
+        // Second time (rerunline)
+        sd->state.menu_or_input = 0;
+        RString str = sd->npc_str;
+        RString val;
+        const char separator = ';';
+        for (int j = 0; j < amount; j++)
+        {
+            auto find = std::find(str.begin(), str.end(), separator);
+            if (find == str.end())
+                val = str.xislice_h(std::find(str.begin(), str.end(), ','));
+            else
+            {
+                val = str.xislice_h(find);
+                val = val.xislice_h(std::find(val.begin(), val.end(), ','));
+                str = str.xislice_t(find + 1);
+            }
+
+            // check that the item exists in the inventory
+            int num = atoi(val.c_str());
+            if (num < 1)
+            {
+                j--;
+                if (find == str.end())
+                    break;
+                continue;
+            }
+            ItemNameId nameid = wrap<ItemNameId>(num);
+            for (IOff0 i : IOff0::iter())
+                if (sd->status.inventory[i].nameid == nameid)
+                    goto pass;
+        fail:
+            j--;
+            if (find == str.end())
+                break;
+            continue;
+
+        pass:
+            // push to array
+            if (postfix == '$')
+            {
+                Option<P<struct item_data>> i_data = Some(itemdb_search(nameid));
+                RString item_name = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
+                if (item_name == ""_s)
+                    goto fail;
+                if (prefix == '.' && name[1] == '@')
+                {
+                    struct script_data vd = script_data(ScriptDataStr{item_name});
+                    set_scope_reg(st, reg.iplus(j), &vd);
+                }
+                else
+                    set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), item_name);
+            }
+            else
+            {
+                if (prefix == '.' && name[1] == '@')
+                {
+                    struct script_data vd = script_data(ScriptDataInt{num});
+                    set_scope_reg(st, reg.iplus(j), &vd);
+                }
+                else
+                    set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), num);
+            }
+            if (find == str.end())
+                break;
+        }
+    }
+    else
+    {
+        // First time - send prompt to client, then wait
+        st->state = ScriptEndState::RERUNLINE;
+        clif_scriptinputstr(sd, st->oid); // send string prompt
+        clif_npc_action(sd, st->oid, 10, amount, 0, 0); // send item request
+        sd->state.menu_or_input = 1;
+    }
+}
+
+static
+void builtin_requestlang(ScriptState *st)
+{
+    dumb_ptr<map_session_data> sd = script_rid2sd(st);
+    script_data& scrd = AARG(0);
+    assert (scrd.is<ScriptDataVariable>());
+    SIR reg = scrd.get_if<ScriptDataVariable>()->reg;
+    ZString name = variable_names.outtern(reg.base());
+    char prefix = name.front();
+    char postfix = name.back();
+
+    if (postfix != '$')
+    {
+        PRINTF("builtin_requestlang: illegal type (expects string)!\n"_fmt);
+        runflag = 0;
+        return;
+    }
+
+    if (sd->state.menu_or_input)
+    {
+        // Second time (rerunline)
+        sd->state.menu_or_input = 0;
+        if (prefix == '.' && name[1] == '@')
+        {
+            struct script_data vd = script_data(ScriptDataStr{sd->npc_str});
+            set_scope_reg(st, reg, &vd);
+        }
+        else
+            set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_str);
+    }
+    else
+    {
+        // First time - send prompt to client, then wait
+        st->state = ScriptEndState::RERUNLINE;
+        clif_npc_action(sd, st->oid, 0, 0, 0, 0); // send lang request
+        clif_scriptinputstr(sd, st->oid); // send string prompt
+        sd->state.menu_or_input = 1;
+    }
+}
+
 /*==========================================
  *
  *------------------------------------------
@@ -2504,6 +2661,49 @@ void builtin_npcaction(ScriptState *st)
     clif_npc_action(sd, st->oid, command, id, x, y);
 }
 
+static
+void builtin_camera(ScriptState *st)
+{
+    dumb_ptr<map_session_data> sd = script_rid2sd(st);
+    if (HARG(0))
+    {
+        if (HARG(1) && !HARG(2))
+            clif_npc_action(sd, st->oid, 2, 0, conv_num(st, &AARG(0)), conv_num(st, &AARG(1))); // camera to x, y
+        else
+        {
+            dumb_ptr<block_list> bl;
+            short x = 0, y = 0;
+            int id;
+            bool rel;
+            if (auto *u = AARG(0).get_if<ScriptDataInt>())
+                bl = map_id2bl(wrap<BlockId>(u->numi));
+            if (auto *g = AARG(0).get_if<ScriptDataStr>())
+            {
+                if (g->str == "rid"_s || g->str == "player"_s)
+                    bl = sd;
+                if (g->str == "relative"_s)
+                    rel = true;
+                else if (g->str == "oid"_s || g->str == "npc"_s)
+                    bl = map_id2bl(st->oid);
+                else
+                    bl = npc_name2id(stringish<NpcName>(g->str));
+            }
+            if (HARG(1) && HARG(2))
+            {
+                x = conv_num(st, &AARG(1));
+                y = conv_num(st, &AARG(2));
+            }
+            if (rel)
+                clif_npc_action(sd, st->oid, 4, 0, x, y); // camera relative from current camera
+            else
+                clif_npc_action(sd, st->oid, 2, unwrap<BlockId>(bl->bl_id), x, y); // camera to actor
+        }
+    }
+
+    else
+        clif_npc_action(sd, st->oid, 3, 0, 0, 0); // return camera
+}
+
 static
 void builtin_setnpcdirection(ScriptState *st)
 {
@@ -4328,7 +4528,8 @@ void builtin_mapexit(ScriptState *)
 
 BuiltinFunction builtin_functions[] =
 {
-    BUILTIN(mes, "s"_s, '\0'),
+    BUILTIN(mes, "?"_s, '\0'),
+    BUILTIN(clear, ""_s, '\0'),
     BUILTIN(goto, "L"_s, '\0'),
     BUILTIN(callfunc, "F"_s, '\0'),
     BUILTIN(call, "F?*"_s, '.'),
@@ -4349,6 +4550,8 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(eltlvl, "i"_s, 'i'),
     BUILTIN(injure, "iii"_s, '\0'),
     BUILTIN(input, "N"_s, '\0'),
+    BUILTIN(requestitem, "N?"_s, '\0'),
+    BUILTIN(requestlang, "N"_s, '\0'),
     BUILTIN(if, "iF*"_s, '\0'),
     BUILTIN(elif, "iF*"_s, '\0'),
     BUILTIN(else, "F*"_s, '\0'),
@@ -4399,6 +4602,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(setnpctimer, "i?"_s, '\0'),
     BUILTIN(setnpcdirection, "iii?"_s, '\0'),
     BUILTIN(npcaction, "i???"_s, '\0'),
+    BUILTIN(camera, "???"_s, '\0'),
     BUILTIN(announce, "si"_s, '\0'),
     BUILTIN(mapannounce, "Msi"_s, '\0'),
     BUILTIN(getusers, "i"_s, 'i'),
-- 
cgit v1.2.3-70-g09d2


From 6eb63903cd839b2c0dbf3acefceee4ba7b07b0f8 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 24 Mar 2016 14:32:09 -0400
Subject: remove MESSAGE npc subtype

---
 src/map/map.hpp       | 10 ----------
 src/map/npc-parse.cpp | 30 ------------------------------
 src/map/npc-parse.hpp |  8 --------
 src/map/npc.cpp       | 22 +---------------------
 4 files changed, 1 insertion(+), 69 deletions(-)

(limited to 'src')

diff --git a/src/map/map.hpp b/src/map/map.hpp
index 1fd86d1..2919d04 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -342,12 +342,10 @@ private:
     dumb_ptr<npc_data_script> as_script();
     dumb_ptr<npc_data_shop> as_shop();
     dumb_ptr<npc_data_warp> as_warp();
-    dumb_ptr<npc_data_message> as_message();
 public:
     dumb_ptr<npc_data_script> is_script();
     dumb_ptr<npc_data_shop> is_shop();
     dumb_ptr<npc_data_warp> is_warp();
-    dumb_ptr<npc_data_message> is_message();
 };
 
 class npc_data_script : public npc_data
@@ -402,12 +400,6 @@ public:
     } warp;
 };
 
-class npc_data_message : public npc_data
-{
-public:
-    RString message;
-};
-
 constexpr int MOB_XP_BONUS_BASE = 1024;
 constexpr int MOB_XP_BONUS_SHIFT = 10;
 
@@ -684,12 +676,10 @@ inline dumb_ptr<flooritem_data> block_list::is_item() { return bl_type == BL::IT
 inline dumb_ptr<npc_data_script> npc_data::as_script() { return dumb_ptr<npc_data_script>(static_cast<npc_data_script *>(this)) ; }
 inline dumb_ptr<npc_data_shop> npc_data::as_shop() { return dumb_ptr<npc_data_shop>(static_cast<npc_data_shop *>(this)) ; }
 inline dumb_ptr<npc_data_warp> npc_data::as_warp() { return dumb_ptr<npc_data_warp>(static_cast<npc_data_warp *>(this)) ; }
-inline dumb_ptr<npc_data_message> npc_data::as_message() { return dumb_ptr<npc_data_message>(static_cast<npc_data_message *>(this)) ; }
 
 inline dumb_ptr<npc_data_script> npc_data::is_script() { return npc_subtype == NpcSubtype::SCRIPT ? as_script() : nullptr ; }
 inline dumb_ptr<npc_data_shop> npc_data::is_shop() { return npc_subtype == NpcSubtype::SHOP ? as_shop() : nullptr ; }
 inline dumb_ptr<npc_data_warp> npc_data::is_warp() { return npc_subtype == NpcSubtype::WARP ? as_warp() : nullptr ; }
-inline dumb_ptr<npc_data_message> npc_data::is_message() { return npc_subtype == NpcSubtype::MESSAGE ? as_message() : nullptr ; }
 
 void map_addmap(MapName mapname);
 void map_delmap(MapName mapname);
diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp
index e30225a..164d793 100644
--- a/src/map/npc-parse.cpp
+++ b/src/map/npc-parse.cpp
@@ -99,12 +99,9 @@ void register_npc_name(dumb_ptr<npc_data> nd)
         "WARP"_s,
         "SHOP"_s,
         "SCRIPT"_s,
-        "MESSAGE"_s,
     }};
     if (!nd->name)
     {
-        if (nd->npc_subtype == NpcSubtype::MESSAGE)
-            return;
         PRINTF("WARNING: npc with no name:\n%s @ %s,%d,%d\n"_fmt,
                 types[nd->npc_subtype],
                 nd->bl_m->name_, nd->bl_x, nd->bl_y);
@@ -655,33 +652,6 @@ bool npc_load_script_any(ast::npc::Script *script)
     abort();
 }
 
-dumb_ptr<npc_data> npc_spawn_text(Borrowed<map_local> m, int x, int y,
-        Species npc_class, NpcName name, AString message)
-{
-    dumb_ptr<npc_data_message> retval;
-    retval.new_();
-    retval->bl_id = npc_get_new_npc_id();
-    retval->bl_x = x;
-    retval->bl_y = y;
-    retval->bl_m = m;
-    retval->bl_type = BL::NPC;
-    retval->npc_subtype = NpcSubtype::MESSAGE;
-
-    retval->name = name;
-    if (message)
-        retval->message = message;
-
-    retval->npc_class = npc_class;
-    retval->speed = 200_ms;
-
-    clif_spawnnpc(retval);
-    map_addblock(retval);
-    map_addiddb(retval);
-    register_npc_name(retval);
-
-    return retval;
-}
-
 static
 bool load_one_npc(io::LineCharReader& fp, bool& done)
 {
diff --git a/src/map/npc-parse.hpp b/src/map/npc-parse.hpp
index d6719ee..f436fb9 100644
--- a/src/map/npc-parse.hpp
+++ b/src/map/npc-parse.hpp
@@ -29,14 +29,6 @@ namespace map
 {
 bool npc_load_warp(ast::npc::Warp& warp);
 
-/**
- * Spawns and installs a talk-only NPC
- *
- * \param message The message to speak.  If message is nullptr, the NPC will not do anything at all.
- */
-dumb_ptr<npc_data> npc_spawn_text(Borrowed<map_local> m, int x, int y,
-        Species class_, NpcName name, AString message);
-
 void npc_addsrcfile(AString name);
 void npc_delsrcfile(XString name);
 void register_npc_name(dumb_ptr<npc_data> nd);
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index b7fb9ca..a8c90d8 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -669,11 +669,6 @@ int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int
                 xs = m->npc[i]->is_warp()->warp.xs;
                 ys = m->npc[i]->is_warp()->warp.ys;
                 break;
-            case NpcSubtype::MESSAGE:
-                assert (0 && "I'm pretty sure these are never put on a map"_s);
-                xs = 0;
-                ys = 0;
-                break;
             case NpcSubtype::SCRIPT:
                 xs = m->npc[i]->is_script()->scr.xs;
                 ys = m->npc[i]->is_script()->scr.ys;
@@ -703,9 +698,6 @@ int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int
             pc_setpos(sd, m->npc[i]->is_warp()->warp.name,
                        m->npc[i]->is_warp()->warp.x, m->npc[i]->is_warp()->warp.y, BeingRemoveWhy::GONE);
             break;
-        case NpcSubtype::MESSAGE:
-            assert (0 && "I'm pretty sure these NPCs are never put on a map."_s);
-            break;
         case NpcSubtype::SCRIPT:
         {
             NpcEvent aname;
@@ -793,13 +785,6 @@ int npc_click(dumb_ptr<map_session_data> sd, BlockId id)
         case NpcSubtype::SCRIPT:
             sd->npc_pos = run_script(ScriptPointer(script_or_parent(nd->is_script()), 0), sd->bl_id, id);
             break;
-        case NpcSubtype::MESSAGE:
-            if (nd->is_message()->message)
-            {
-                clif_scriptmes(sd, id, nd->is_message()->message);
-                clif_scriptclose(sd, id);
-            }
-            break;
     }
 
     return 0;
@@ -825,7 +810,7 @@ int npc_scriptcont(dumb_ptr<map_session_data> sd, BlockId id)
 
     nd = map_id_is_npc(id);
 
-    if (!nd /* NPC was disposed? */  || nd->npc_subtype == NpcSubtype::MESSAGE)
+    if (!nd /* NPC was disposed? */)
     {
         clif_scriptclose(sd, id);
         npc_event_dequeue(sd);
@@ -1023,11 +1008,6 @@ void npc_free_internal(dumb_ptr<npc_data> nd_)
         nd->scr.script.reset();
         nd->scr.label_listv.clear();
     }
-    else if (nd_->npc_subtype == NpcSubtype::MESSAGE)
-    {
-        dumb_ptr<npc_data_message> nd = nd_->is_message();
-        nd->message = AString();
-    }
     if (nd_->name)
         npcs_by_name.put(nd_->name, nullptr);
     nd_.delete_();
-- 
cgit v1.2.3-70-g09d2


From 2a581450a6ff87c5a183be3855e68b3f2e1b53f3 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 24 Mar 2016 15:10:40 -0400
Subject: improvements, cleanup, bug fixes

---
 src/map/battle.cpp       |   2 +
 src/map/clif.cpp         |   6 +-
 src/map/fwd.hpp          |   1 -
 src/map/map.hpp          |   1 -
 src/map/map.t.hpp        |   1 -
 src/map/mob.cpp          |  13 ++-
 src/map/npc.cpp          |  47 ++++++-----
 src/map/pc.cpp           |  19 +++--
 src/map/script-call.cpp  |  20 ++---
 src/map/script-fun.cpp   | 202 ++++++++++++++++++++++++++++++++---------------
 src/map/script-parse.cpp |   1 +
 src/mmo/clif.t.hpp       |   8 +-
 12 files changed, 211 insertions(+), 110 deletions(-)

(limited to 'src')

diff --git a/src/map/battle.cpp b/src/map/battle.cpp
index 554ef14..031b79d 100644
--- a/src/map/battle.cpp
+++ b/src/map/battle.cpp
@@ -85,6 +85,8 @@ Species battle_get_class(dumb_ptr<block_list> bl)
     nullpo_retr(Species(), bl);
     if (bl->bl_type == BL::MOB)
         return bl->is_mob()->mob_class;
+    else if (bl->bl_type == BL::NPC)
+        return bl->is_npc()->npc_class;
     else if (bl->bl_type == BL::PC)
         return bl->is_player()->status.species;
     else
diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index 4cf1de2..ea11bbf 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -2550,7 +2550,9 @@ int clif_damage(dumb_ptr<block_list> src, dumb_ptr<block_list> dst,
     nullpo_retz(src);
     nullpo_retz(dst);
 
-    sc_data = battle_get_sc_data(dst);
+    int target_hp = battle_get_hp(dst);
+    if (target_hp < damage)
+        damage = target_hp; // limit damage to hp
 
     Packet_Fixed<0x008a> fixed_8a;
     fixed_8a.src_id = src->bl_id;
@@ -4423,6 +4425,8 @@ RecvResult clif_parse_NpcClicked(Session *s, dumb_ptr<map_session_data> sd)
     }
     if (sd->npc_id)
         return rv;
+    if (battle_get_class(map_id2bl(fixed.block_id)) == INVISIBLE_CLASS)
+        return rv;
     npc_click(sd, fixed.block_id);
 
     return rv;
diff --git a/src/map/fwd.hpp b/src/map/fwd.hpp
index de65216..74e0ccf 100644
--- a/src/map/fwd.hpp
+++ b/src/map/fwd.hpp
@@ -61,7 +61,6 @@ struct map_local;
 class npc_data_script;
 class npc_data_shop;
 class npc_data_warp;
-class npc_data_message;
 
 struct item_data;
 struct quest_data;
diff --git a/src/map/map.hpp b/src/map/map.hpp
index 2919d04..25fdba5 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -332,7 +332,6 @@ struct npc_data : block_list
     Opt3 opt3;
     Opt0 option;
     short flag;
-    bool disposable;
 
     std::list<RString> eventqueuel;
     Array<Timer, MAX_EVENTTIMER> eventtimer;
diff --git a/src/map/map.t.hpp b/src/map/map.t.hpp
index ea7ead5..d685c01 100644
--- a/src/map/map.t.hpp
+++ b/src/map/map.t.hpp
@@ -49,7 +49,6 @@ enum class NpcSubtype : uint8_t
     WARP,
     SHOP,
     SCRIPT,
-    MESSAGE,
 
     COUNT,
 };
diff --git a/src/map/mob.cpp b/src/map/mob.cpp
index 1be40f0..0da946a 100644
--- a/src/map/mob.cpp
+++ b/src/map/mob.cpp
@@ -2701,7 +2701,6 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
     }                           // [MouseJstr]
 
     // SCRIPT実行
-    if (md->npc_event)
     {
         if (sd == nullptr)
         {
@@ -2711,7 +2710,17 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage,
             }
         }
         if (sd)
-            npc_event(sd, md->npc_event, 0);
+        {
+            if (md->npc_event)
+                npc_event(sd, md->npc_event, 0);
+
+            // TODO: in the future, OnPCKillEvent, OnMobKillEvent and OnPCDieEvent should be combined
+            argrec_t arg[1] =
+            {
+                {"@mobID"_s, static_cast<int32_t>(unwrap<Species>(md->mob_class))},
+            };
+            npc_event_doall_l(stringish<ScriptLabel>("OnMobKillEvent"_s), sd->bl_id, arg);
+        }
     }
 
     clif_clearchar(md, BeingRemoveWhy::DEAD);
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index a8c90d8..5b0ee7b 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -202,23 +202,23 @@ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation)
     auto pair = magic_tokenise(source_invocation);
     // Spell Cast
     NpcEvent spell_event = spell_event2id(pair.first);
-    PRINTF("Cast: %s\n"_fmt, RString(pair.first));
 
     RString spell_params = pair.second;
 
-    dumb_ptr<npc_data> nd = npc_name2id(spell_event.npc);
-
-    if (nd)
+    if (spell_event.npc)
     {
-        PRINTF("NPC:  '%s' %d\n"_fmt, nd->name, nd->bl_id);
-        PRINTF("Params:  '%s'\n"_fmt, spell_params);
-        argrec_t arg[1] =
+        dumb_ptr<npc_data> nd = npc_name2id(spell_event.npc);
+
+        if (nd)
         {
-            {"@args$"_s, spell_params},
-        };
+            argrec_t arg[1] =
+            {
+                {"@args$"_s, spell_params},
+            };
 
-        npc_event(caster, spell_event, 0, arg);
-        return 1;
+            npc_event(caster, spell_event, 0, arg);
+            return 1;
+        }
     }
     return 0;
 }
@@ -274,7 +274,7 @@ void npc_event_doall_sub(NpcEvent key, struct event_data *ev,
 
     if (name == p)
     {
-        if (ev->nd->disposable)
+        if (ev->nd->scr.parent != BlockId())
             return; // temporary npcs only respond to commands directly issued to them
         run_script_l(ScriptPointer(script_or_parent(ev->nd), ev->pos), rid, ev->nd->bl_id,
                 argv);
@@ -647,7 +647,7 @@ int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname,
  */
 int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int x, int y)
 {
-    int i, f = 1;
+    int i;
     int xs, ys;
 
     nullpo_retr(1, sd);
@@ -658,10 +658,7 @@ int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int
     for (i = 0; i < m->npc_num; i++)
     {
         if (m->npc[i]->flag & 1)
-        {                       // 無効化されている
-            f = 0;
             continue;
-        }
 
         switch (m->npc[i]->npc_subtype)
         {
@@ -684,11 +681,6 @@ int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int
     }
     if (i == m->npc_num)
     {
-        if (f)
-        {
-            if (battle_config.error_log)
-                PRINTF("npc_touch_areanpc : some bug \n"_fmt);
-        }
         return 1;
     }
     switch (m->npc[i]->npc_subtype)
@@ -1005,6 +997,19 @@ void npc_free_internal(dumb_ptr<npc_data> nd_)
         dumb_ptr<npc_data_script> nd = nd_->is_script();
         nd->scr.timerid.cancel();
         nd->scr.timer_eventv.clear();
+        nd->eventqueuel.clear();
+        for (int i = 0; i < MAX_EVENTTIMER; i++)
+            nd->eventtimer[i].cancel();
+
+        // destroy all children (puppets), if any
+        if (nd_->name && nd->scr.parent == BlockId())
+        {
+            for (auto& pair : npcs_by_name)
+                if (pair.second->npc_subtype == NpcSubtype::SCRIPT
+                    && pair.second->is_script()->scr.parent == nd_->bl_id)
+                        npc_free(pair.second);
+        }
+
         nd->scr.script.reset();
         nd->scr.label_listv.clear();
     }
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 929143c..db3b372 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -2731,7 +2731,8 @@ int pc_attack(dumb_ptr<map_session_data> sd, BlockId target_id, int type)
 
     if (bl->bl_type == BL::NPC)
     {                           // monster npcs [Valaris]
-        npc_click(sd, target_id);
+        if (battle_get_class(bl) != INVISIBLE_CLASS && !pc_isdead(sd))
+            npc_click(sd, target_id);
         return 0;
     }
 
@@ -3352,13 +3353,10 @@ int pc_damage(dumb_ptr<block_list> src, dumb_ptr<map_session_data> sd,
     if (src && src->bl_type == BL::PC)
     {
         // [Fate] PK death, trigger scripts
-        argrec_t arg[3] =
+        argrec_t arg[1] =
         {
-            {"@killerrid"_s, static_cast<int32_t>(unwrap<BlockId>(src->bl_id))},
             {"@victimrid"_s, static_cast<int32_t>(unwrap<BlockId>(sd->bl_id))},
-            {"@victimlvl"_s, sd->status.base_level},
         };
-        npc_event_doall_l(stringish<ScriptLabel>("OnPCKilledEvent"_s), sd->bl_id, arg);
         npc_event_doall_l(stringish<ScriptLabel>("OnPCKillEvent"_s), src->bl_id, arg);
 
         sd->state.pvp_rank = 0;
@@ -3487,7 +3485,10 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
         case SP::INT:
         case SP::DEX:
         case SP::LUK:
-            val = battle_get_stat(type, bl);
+            if (bl && bl->bl_type == BL::PC)
+                val = bl->is_player()->status.attrs[sp_to_attr(type)];
+            else
+                val = battle_get_stat(type, bl);
             break;
         case SP::SPEED:
             val = battle_get_speed(bl).count();
@@ -3531,6 +3532,12 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
         case SP::CHAR_ID:
             val = sd ? unwrap<CharId>(sd->status_key.char_id) : 0;
             break;
+        case SP::ELTLVL:
+            val = static_cast<int>(battle_get_element(sd).level);
+            break;
+        case SP::ELTTYPE:
+            val = static_cast<int>(battle_get_element(sd).element);
+            break;
     }
 
     return val;
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index fbb9b97..085d90a 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -908,7 +908,6 @@ void run_script_main(ScriptState *st, Borrowed<const ScriptBuffer> rootscript)
     if (st->state != ScriptEndState::END)
     {
         // 再開するためにスタック情報を保存
-        dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
         if (sd)
         {
             sd->npc_stackbuf = stack->stack_datav;
@@ -938,17 +937,20 @@ int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid,
     if (oid)
     {
         dumb_ptr<block_list> oid_bl = map_id2bl(oid);
-        if (oid_bl->bl_type == BL::NPC)
+        if (oid_bl)
         {
-            dumb_ptr<npc_data> nd = oid_bl->is_npc();
-            if(nd->npc_subtype == NpcSubtype::SCRIPT)
+            if (oid_bl->bl_type == BL::NPC)
             {
-                dumb_ptr<npc_data_script> nds = nd->is_script();
-                if (nds->scr.parent)
+                dumb_ptr<npc_data> nd = oid_bl->is_npc();
+                if(nd->npc_subtype == NpcSubtype::SCRIPT)
                 {
-                    dumb_ptr<npc_data_script> parent = map_id2bl(nds->scr.parent)->is_npc()->is_script();
-                    assert(parent->bl_type == BL::NPC && parent->npc_subtype == NpcSubtype::SCRIPT);
-                    sp = ScriptPointer(borrow(*parent->scr.script), sp.pos);
+                    dumb_ptr<npc_data_script> nds = nd->is_script();
+                    if (nds->scr.parent)
+                    {
+                        dumb_ptr<npc_data_script> parent = map_id2bl(nds->scr.parent)->is_npc()->is_script();
+                        assert(parent->bl_type == BL::NPC && parent->npc_subtype == NpcSubtype::SCRIPT);
+                        sp = ScriptPointer(borrow(*parent->scr.script), sp.pos);
+                    }
                 }
             }
         }
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index faa8c81..beabef3 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -472,14 +472,43 @@ void builtin_max(ScriptState *st)
 static
 void builtin_min(ScriptState *st)
 {
-    int min, num;
-    min = conv_num(st, &AARG(0));
+    int min = 0xFFFFFFF6, num;
 
-    for (int i = 1; HARG(i); i++)
+    if (HARG(1))
     {
-        num = conv_num(st, &AARG(i));
-        if (num < min)
-            min = num;
+        min = conv_num(st, &AARG(0));
+        for (int i = 1; HARG(i); i++)
+        {
+            num = conv_num(st, &AARG(i));
+            if (num < min)
+                min = num;
+        }
+    }
+    else
+    {
+        SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg;
+        ZString name = variable_names.outtern(reg.base());
+        char prefix = name.front();
+        if (prefix != '$' && prefix != '@' && prefix != '.')
+        {
+            PRINTF("builtin_max: illegal scope!\n"_fmt);
+            return;
+        }
+        for (int i = reg.index(); i < 256; i++)
+        {
+            struct script_data vd = get_val2(st, reg.iplus(i));
+            MATCH_BEGIN (vd)
+            {
+                MATCH_CASE (const ScriptDataInt&, u)
+                {
+                    if (u.numi < min)
+                        min = u.numi;
+                    continue;
+                }
+            }
+            MATCH_END ();
+            abort();
+        }
     }
 
     push_int<ScriptDataInt>(st->stack, min);
@@ -613,28 +642,6 @@ void builtin_heal(ScriptState *st)
         pc_heal(sd, hp, sp);
 }
 
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_elttype(ScriptState *st)
-{
-    int element_type = static_cast<int>(battle_get_element(map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0))))).element);
-    push_int<ScriptDataInt>(st->stack, element_type);
-}
-
-/*==========================================
- *
- *------------------------------------------
- */
-static
-void builtin_eltlvl(ScriptState *st)
-{
-    int element_lvl = static_cast<int>(battle_get_element(map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0))))).level);
-    push_int<ScriptDataInt>(st->stack, element_lvl);
-}
-
 /*==========================================
  *
  *------------------------------------------
@@ -816,8 +823,7 @@ void builtin_requestitem(ScriptState *st)
     if (prefix != '$' && prefix != '@' && prefix != '.')
     {
         PRINTF("builtin_requestitem: illegal scope!\n"_fmt);
-        runflag = 0;
-        return;
+        abort();
     }
 
     sd = script_rid2sd(st);
@@ -867,7 +873,7 @@ void builtin_requestitem(ScriptState *st)
                 RString item_name = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s));
                 if (item_name == ""_s)
                     goto fail;
-                if (prefix == '.' && name[1] == '@')
+                if (name.startswith(".@"_s))
                 {
                     struct script_data vd = script_data(ScriptDataStr{item_name});
                     set_scope_reg(st, reg.iplus(j), &vd);
@@ -877,7 +883,7 @@ void builtin_requestitem(ScriptState *st)
             }
             else
             {
-                if (prefix == '.' && name[1] == '@')
+                if (name.startswith(".@"_s))
                 {
                     struct script_data vd = script_data(ScriptDataInt{num});
                     set_scope_reg(st, reg.iplus(j), &vd);
@@ -907,21 +913,19 @@ void builtin_requestlang(ScriptState *st)
     assert (scrd.is<ScriptDataVariable>());
     SIR reg = scrd.get_if<ScriptDataVariable>()->reg;
     ZString name = variable_names.outtern(reg.base());
-    char prefix = name.front();
     char postfix = name.back();
 
     if (postfix != '$')
     {
         PRINTF("builtin_requestlang: illegal type (expects string)!\n"_fmt);
-        runflag = 0;
-        return;
+        abort();
     }
 
     if (sd->state.menu_or_input)
     {
         // Second time (rerunline)
         sd->state.menu_or_input = 0;
-        if (prefix == '.' && name[1] == '@')
+        if (name.startswith(".@"_s))
         {
             struct script_data vd = script_data(ScriptDataStr{sd->npc_str});
             set_scope_reg(st, reg, &vd);
@@ -1102,7 +1106,7 @@ void builtin_destroy(ScriptState *st)
     dumb_ptr<npc_data_script> nd = map_id2bl(id)->is_npc()->is_script();
     if(!nd)
         return;
-    assert(nd->disposable == true);
+    //assert(nd->disposable == true); we don't care about it anymore
     npc_free(nd);
     if (!HARG(0))
         st->state = ScriptEndState::END;
@@ -1131,11 +1135,9 @@ void builtin_puppet(ScriptState *st)
 
     nd->bl_prev = nd->bl_next = nullptr;
     nd->scr.event_needs_map = false;
-    nd->disposable = true; // allow to destroy
 
     // PlayerName::SpellName
     NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(3))));
-    PRINTF("Npc: %s\n"_fmt, npc);
     nd->name = npc;
 
     // Dynamically set location
@@ -1285,7 +1287,7 @@ void builtin_set(ScriptState *st)
             get_val(st, sdata);
             if(prefix == '.')
             {
-                if (name_[1] == '@')
+                if (name_.startswith(".@"_s))
                 {
                     PRINTF("builtin_set: illegal scope!\n"_fmt);
                     return;
@@ -1322,7 +1324,7 @@ void builtin_set(ScriptState *st)
         {
             if(prefix == '.')
             {
-                if (name_[1] == '@')
+                if (name_.startswith(".@"_s))
                 {
                         set_scope_reg(st, reg, &AARG(1));
                     return;
@@ -1351,6 +1353,45 @@ void builtin_set(ScriptState *st)
 
 }
 
+// this is a special function that returns array index for a variable stored in another being
+static
+int getarraysize2(SIR reg, dumb_ptr<block_list> bl)
+{
+    int i = reg.index(), c = i;
+    bool zero = true; // index zero is empty
+    for (; i < 256; i++)
+    {
+        struct script_data vd = ScriptDataVariable{reg.iplus(i)};
+        get_val(bl, &vd);
+        MATCH_BEGIN (vd)
+        {
+            MATCH_CASE (const ScriptDataStr&, u)
+            {
+                if (u.str[0])
+                {
+                    if (i == 0)
+                        zero = false; // index zero is not empty
+                    c = i;
+                }
+                continue;
+            }
+            MATCH_CASE (const ScriptDataInt&, u)
+            {
+                if (u.numi)
+                {
+                    if (i == 0)
+                        zero = false; // index zero is not empty
+                    c = i;
+                }
+                continue;
+            }
+        }
+        MATCH_END ();
+        abort();
+    }
+    return (c == 0 && zero) ? c : (c + 1);
+}
+
 /*==========================================
  * 配列変数設定
  *------------------------------------------
@@ -1363,20 +1404,51 @@ void builtin_setarray(ScriptState *st)
     ZString name = variable_names.outtern(reg.base());
     char prefix = name.front();
     char postfix = name.back();
+    int i = 1, j = 0;
 
     if (prefix != '$' && prefix != '@' && prefix != '.')
     {
         PRINTF("builtin_setarray: illegal scope!\n"_fmt);
         return;
     }
-    if (prefix == '.' && name[1] != '@')
-        bl = map_id2bl(st->oid)->is_npc();
-    else if (prefix != '$' && !(prefix == '.' && name[1] == '@'))
+    if (prefix == '.' && !name.startswith(".@"_s))
+    {
+        struct script_data *sdata = &AARG(1);
+        get_val(st, sdata);
+        i++; // 2nd argument is npc, not an array element
+        if (sdata->is<ScriptDataStr>())
+        {
+            ZString tn = conv_str(st, sdata);
+            if (tn == "this"_s || tn == "oid"_s)
+                bl = map_id2bl(st->oid)->is_npc();
+            else
+            {
+                NpcName name_ = stringish<NpcName>(tn);
+                bl = npc_name2id(name_);
+            }
+        }
+        else
+        {
+            int tid = conv_num(st, sdata);
+            if (tid == 0)
+                bl = map_id2bl(st->oid)->is_npc();
+            else
+               bl = map_id2bl(wrap<BlockId>(tid))->is_npc();
+        }
+        if (!bl)
+        {
+            PRINTF("builtin_setarray: npc not found\n"_fmt);
+            return;
+        }
+        if (st->oid && bl->bl_id != st->oid)
+            j = getarraysize2(reg, bl);
+    }
+    else if (prefix != '$' && !name.startswith(".@"_s))
         bl = map_id2bl(st->rid)->is_player();
 
-    for (int j = 0, i = 1; i < st->end - st->start - 2 && j < 256; i++, j++)
+    for (; i < st->end - st->start - 2 && j < 256; i++, j++)
     {
-        if (prefix == '.' && name[1] == '@')
+        if (name.startswith(".@"_s))
             set_scope_reg(st, reg.iplus(j), &AARG(i));
         else if (postfix == '$')
             set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), conv_str(st, &AARG(i)));
@@ -1404,14 +1476,14 @@ void builtin_cleararray(ScriptState *st)
         PRINTF("builtin_cleararray: illegal scope!\n"_fmt);
         return;
     }
-    if (prefix == '.' && name[1] != '@')
+    if (prefix == '.' && !name.startswith(".@"_s))
         bl = map_id2bl(st->oid)->is_npc();
-    else if (prefix != '$' && !(prefix == '.' && name[1] == '@'))
+    else if (prefix != '$' && !name.startswith(".@"_s))
         bl = map_id2bl(st->rid)->is_player();
 
     for (int i = 0; i < sz; i++)
     {
-        if (prefix == '.' && name[1] == '@')
+        if (name.startswith(".@"_s))
             set_scope_reg(st, reg.iplus(i), &AARG(i));
         else if (postfix == '$')
             set_reg(bl, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARG(1)));
@@ -2673,8 +2745,7 @@ void builtin_camera(ScriptState *st)
         {
             dumb_ptr<block_list> bl;
             short x = 0, y = 0;
-            int id;
-            bool rel;
+            bool rel = false;
             if (auto *u = AARG(0).get_if<ScriptDataInt>())
                 bl = map_id2bl(wrap<BlockId>(u->numi));
             if (auto *g = AARG(0).get_if<ScriptDataStr>())
@@ -3109,7 +3180,13 @@ void builtin_resetstatus(ScriptState *st)
 static
 void builtin_attachrid(ScriptState *st)
 {
-    st->rid = wrap<BlockId>(conv_num(st, &AARG(0)));
+    dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
+    BlockId newid = wrap<BlockId>(conv_num(st, &AARG(0)));
+
+    if (sd && newid != st->rid)
+        sd->npc_id = BlockId();
+
+    st->rid = newid;
     push_int<ScriptDataInt>(st->stack, (map_id2sd(st->rid) != nullptr));
 }
 
@@ -3121,6 +3198,9 @@ void builtin_attachrid(ScriptState *st)
 static
 void builtin_detachrid(ScriptState *st)
 {
+    dumb_ptr<map_session_data> sd = map_id2sd(st->rid);
+    if (sd)
+        sd->npc_id = BlockId();
     st->rid = BlockId();
 }
 
@@ -3433,7 +3513,7 @@ void builtin_chr(ScriptState *st)
 static
 void builtin_ord(ScriptState *st)
 {
-    const char ascii = conv_str(st, &AARG(0))[0];
+    const char ascii = conv_str(st, &AARG(0)).front();
     push_int<ScriptDataInt>(st->stack, static_cast<int>(ascii));
 }
 
@@ -3443,7 +3523,7 @@ void builtin_explode(ScriptState *st)
     dumb_ptr<block_list> bl = nullptr;
     SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg;
     ZString name = variable_names.outtern(reg.base());
-    const char separator = conv_str(st, &AARG(2))[0];
+    const char separator = conv_str(st, &AARG(2)).front();
     RString str = conv_str(st, &AARG(1));
     RString val;
     char prefix = name.front();
@@ -3454,7 +3534,7 @@ void builtin_explode(ScriptState *st)
         PRINTF("builtin_explode: illegal scope!\n"_fmt);
         return;
     }
-    if (prefix == '.' && name[1] != '@')
+    if (prefix == '.' && !name.startswith(".@"_s))
         bl = map_id2bl(st->oid)->is_npc();
     else if (prefix != '$' && prefix != '.')
         bl = map_id2bl(st->rid)->is_player();
@@ -3464,7 +3544,7 @@ void builtin_explode(ScriptState *st)
         auto find = std::find(str.begin(), str.end(), separator);
         if (find == str.end())
         {
-            if (prefix == '.' && name[1] == '@')
+            if (name.startswith(".@"_s))
             {
                 struct script_data vd = script_data(ScriptDataInt{atoi(str.c_str())});
                 if (postfix == '$')
@@ -3481,7 +3561,7 @@ void builtin_explode(ScriptState *st)
             val = str.xislice_h(find);
             str = str.xislice_t(find + 1);
 
-            if (prefix == '.' && name[1] == '@')
+            if (name.startswith(".@"_s))
             {
                 struct script_data vd = script_data(ScriptDataInt{atoi(val.c_str())});
                 if (postfix == '$')
@@ -3749,7 +3829,7 @@ void builtin_get(ScriptState *st)
 
     if(prefix == '.')
     {
-        if (name_[1] == '@')
+        if (name_.startswith(".@"_s))
         {
             PRINTF("builtin_get: illegal scope!\n"_fmt);
             return;
@@ -3807,7 +3887,7 @@ void builtin_get(ScriptState *st)
         int var;
         if (prefix == '#' && bl)
         {
-            if (name_[1] == '#')
+            if (name_.startswith("##"_s))
                 var = pc_readaccountreg2(bl->is_player(), stringish<VarName>(name_));
             else
                 var = pc_readaccountreg(bl->is_player(), stringish<VarName>(name_));
@@ -4546,8 +4626,6 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(warp, "Mxy"_s, '\0'),
     BUILTIN(areawarp, "MxyxyMxy"_s, '\0'),
     BUILTIN(heal, "ii?"_s, '\0'),
-    BUILTIN(elttype, "i"_s, 'i'),
-    BUILTIN(eltlvl, "i"_s, 'i'),
     BUILTIN(injure, "iii"_s, '\0'),
     BUILTIN(input, "N"_s, '\0'),
     BUILTIN(requestitem, "N?"_s, '\0'),
@@ -4680,7 +4758,7 @@ BuiltinFunction builtin_functions[] =
     BUILTIN(freeloop, "i"_s, '\0'),
     BUILTIN(if_then_else, "iii"_s, '.'),
     BUILTIN(max, "e?*"_s, 'i'),
-    BUILTIN(min, "ii*"_s, 'i'),
+    BUILTIN(min, "e?*"_s, 'i'),
     BUILTIN(average, "ii*"_s, 'i'),
     BUILTIN(sqrt, "i"_s, 'i'),
     BUILTIN(cbrt, "i"_s, 'i'),
diff --git a/src/map/script-parse.cpp b/src/map/script-parse.cpp
index a69df40..f8d7b6b 100644
--- a/src/map/script-parse.cpp
+++ b/src/map/script-parse.cpp
@@ -617,6 +617,7 @@ ZString::iterator ScriptBuffer::parse_line(ZString::iterator p, bool *can_step)
 
     {
         // TODO should be LString, but no heterogenous lookup yet
+        // FIXME / TODO: allow destroy to both be a statement and a terminator
         static
         std::set<ZString> terminators =
         {
diff --git a/src/mmo/clif.t.hpp b/src/mmo/clif.t.hpp
index 3571a1e..c1b222f 100644
--- a/src/mmo/clif.t.hpp
+++ b/src/mmo/clif.t.hpp
@@ -323,12 +323,8 @@ enum class SP : uint16_t
 
     // sent to client
     ATTACKRANGE                 = 1000,
-#if 0
-    ATKELE                      = 1001,
-#endif
-#if 0
-    DEFELE                      = 1002,
-#endif
+    ELTLVL                      = 1001,
+    ELTTYPE                     = 1002,
 #if 0
     CASTRATE                    = 1003,
 #endif
-- 
cgit v1.2.3-70-g09d2


From 636f0212bd2ea7f0f55d2859fcbea9f91d633903 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Wed, 13 Apr 2016 12:35:27 -0400
Subject: do not send superfluous info on LoadEndAck

---
 src/map/clif.cpp | 54 ++++++++++++++++++++++++++----------------------------
 src/map/map.hpp  |  1 -
 src/map/pc.cpp   |  5 +----
 3 files changed, 27 insertions(+), 33 deletions(-)

(limited to 'src')

diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index ea11bbf..31ecaf8 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -3504,21 +3504,10 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd)
     //clif_authok();
     if (sd->npc_id)
         npc_event_dequeue(sd);
-    clif_skillinfoblock(sd);
-    pc_checkitem(sd);
     //guild_info();
 
     // loadendack時
     // next exp
-    clif_updatestatus(sd, SP::NEXTBASEEXP);
-    clif_updatestatus(sd, SP::NEXTJOBEXP);
-    // skill point
-    clif_updatestatus(sd, SP::SKILLPOINT);
-    // item
-    clif_itemlist(sd);
-    clif_equiplist(sd);
-    // param all
-    clif_initialstatus(sd);
     // party
     party_send_movemap(sd);
     // 119
@@ -3532,14 +3521,11 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd)
     map_addblock(sd);     // ブロック登録
     clif_spawnpc(sd);          // spawn
 
-    clif_map_pvp(sd); // send map pvp status
-
-    // weight max , now
-    clif_updatestatus(sd, SP::MAXWEIGHT);
-    clif_updatestatus(sd, SP::WEIGHT);
+    if (sd->bl_m->flag.get(MapFlag::PVP))
+        clif_map_pvp(sd); // send map pvp status
 
     // pvp
-    if (!battle_config.pk_mode)
+    /*if (!battle_config.pk_mode)
         sd->pvp_timer.cancel();
 
     if (sd->bl_m->flag.get(MapFlag::PVP))
@@ -3557,18 +3543,13 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd)
     else
     {
         // sd->pvp_timer = nullptr;
-    }
-
-    sd->state.connect_new = 0;
+    }*/
 
     // view equipment item
-    clif_changelook(sd, LOOK::WEAPON, static_cast<uint16_t>(ItemLook::NONE));
-    if (battle_config.save_clothcolor == 1 && sd->status.clothes_color > 0)
-        clif_changelook(sd, LOOK::CLOTHES_COLOR,
-                         sd->status.clothes_color);
+    //if (battle_config.save_clothcolor == 1 && sd->status.clothes_color > 0)
+    //    clif_changelook(sd, LOOK::CLOTHES_COLOR,
+    //                     sd->status.clothes_color);
 
-    // option
-    clif_changeoption(sd);
     // broken equipment
 
 //        clif_changelook_accessories(sd, nullptr);
@@ -3579,8 +3560,25 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd)
             sd->bl_x + AREA_SIZE, sd->bl_y + AREA_SIZE,
             BL::NUL);
 
-    if (!sd->state.seen_motd)
-        pc_show_motd(sd);
+    if (!sd->state.connect_new)
+        return rv;
+
+    // all the code below is only executed once, on login
+    pc_show_motd(sd);
+    clif_skillinfoblock(sd);
+    pc_checkitem(sd);
+    clif_updatestatus(sd, SP::NEXTBASEEXP);
+    clif_updatestatus(sd, SP::NEXTJOBEXP);
+    clif_updatestatus(sd, SP::SKILLPOINT);
+    clif_itemlist(sd);
+    clif_equiplist(sd);
+    clif_initialstatus(sd);
+    clif_changeoption(sd);
+    clif_changelook(sd, LOOK::WEAPON, static_cast<uint16_t>(ItemLook::NONE));
+    clif_updatestatus(sd, SP::MAXWEIGHT);
+    clif_updatestatus(sd, SP::WEIGHT);
+    npc_event_doall_l(stringish<ScriptLabel>("OnPCLoginEvent"_s), sd->bl_id, nullptr);
+    sd->state.connect_new = 0;
 
     return rv;
 }
diff --git a/src/map/map.hpp b/src/map/map.hpp
index 25fdba5..7732af9 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -145,7 +145,6 @@ struct map_session_data : block_list, SessionData
         unsigned shroud_hides_name_talking:1;
         unsigned shroud_disappears_on_pickup:1;
         unsigned shroud_disappears_on_talk:1;
-        unsigned seen_motd:1;
         unsigned pvpchannel;
         unsigned pvp_rank;
         unsigned npc_dialog_mes:1;
diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index db3b372..066484d 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -850,10 +850,7 @@ void pc_show_motd(dumb_ptr<map_session_data> sd)
     // If you remove the sending of this message,
     // the license does not permit you to publicly use this software.
 
-    clif_displaymessage(sd->sess, "Server : ##7This server is Free Software, for details type @source in chat or use the tmwa-source tool"_s);
-    npc_event_doall_l(stringish<ScriptLabel>("OnPCLoginEvent"_s), sd->bl_id, nullptr);
-
-    sd->state.seen_motd = true;
+    clif_displaymessage(sd->sess, "Server : ##7This server is Free Software, for details type @source in chat."_s);
 }
 
 /*==========================================
-- 
cgit v1.2.3-70-g09d2


From 982dc85b60d5dff796ab87de052279350d5e1450 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Wed, 13 Apr 2016 14:17:33 -0400
Subject: do not send BEING_REMOVE when the npc is already removed

---
 src/map/npc.cpp | 31 +++++++++++++++----------------
 1 file changed, 15 insertions(+), 16 deletions(-)

(limited to 'src')

diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index 5b0ee7b..47bf820 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -121,27 +121,26 @@ int npc_enable(NpcName name, bool flag)
     {                           // 有効化
         nd->flag &= ~1;
         clif_spawnnpc(nd);
+        int xs = 0, ys = 0;
+        if (dumb_ptr<npc_data_script> nd_ = nd->is_script())
+        {
+            xs = nd_->scr.xs;
+            ys = nd_->scr.ys;
+        }
+
+        if (flag && (xs > 0 || ys > 0))
+            map_foreachinarea(std::bind(npc_enable_sub, ph::_1, nd),
+                    nd->bl_m,
+                    nd->bl_x - xs, nd->bl_y - ys,
+                    nd->bl_x + xs, nd->bl_y + ys,
+                    BL::PC);
     }
-    else
+    else if (!(nd->flag & 1))
     {                           // 無効化
-        nd->flag |= 1;
         clif_clearchar(nd, BeingRemoveWhy::GONE);
+        nd->flag |= 1;
     }
 
-    int xs = 0, ys = 0;
-    if (dumb_ptr<npc_data_script> nd_ = nd->is_script())
-    {
-        xs = nd_->scr.xs;
-        ys = nd_->scr.ys;
-    }
-
-    if (flag && (xs > 0 || ys > 0))
-        map_foreachinarea(std::bind(npc_enable_sub, ph::_1, nd),
-                nd->bl_m,
-                nd->bl_x - xs, nd->bl_y - ys,
-                nd->bl_x + xs, nd->bl_y + ys,
-                BL::PC);
-
     return 0;
 }
 
-- 
cgit v1.2.3-70-g09d2


From c786a93e91adaf68780a5fd7585f51d0528f92ed Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Mon, 18 Apr 2016 13:36:59 -0400
Subject: fix ELTLVL and ELTTYPE params

---
 src/map/pc.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

(limited to 'src')

diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 066484d..bf75852 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -3482,8 +3482,8 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
         case SP::INT:
         case SP::DEX:
         case SP::LUK:
-            if (bl && bl->bl_type == BL::PC)
-                val = bl->is_player()->status.attrs[sp_to_attr(type)];
+            if (sd)
+                val = sd->status.attrs[sp_to_attr(type)];
             else
                 val = battle_get_stat(type, bl);
             break;
@@ -3530,10 +3530,10 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
             val = sd ? unwrap<CharId>(sd->status_key.char_id) : 0;
             break;
         case SP::ELTLVL:
-            val = static_cast<int>(battle_get_element(sd).level);
+            val = static_cast<int>(battle_get_element(bl).level);
             break;
         case SP::ELTTYPE:
-            val = static_cast<int>(battle_get_element(sd).element);
+            val = static_cast<int>(battle_get_element(bl).element);
             break;
     }
 
-- 
cgit v1.2.3-70-g09d2