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/map')

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