From cf4007f34bf5feee7903506e1a569f60aec5b795 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 21 Apr 2016 10:46:15 -0400
Subject: make `get` return -1 when getting a param fails

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

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index beabef3..13a635d 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -3813,7 +3813,10 @@ void builtin_get(ScriptState *st)
         }
 
         if (bl == nullptr)
+        {
+            push_int<ScriptDataInt>(st->stack, -1);
             return;
+        }
         int var = pc_readparam(bl, reg.sp());
         push_int<ScriptDataInt>(st->stack, var);
         return;
@@ -3869,9 +3872,8 @@ void builtin_get(ScriptState *st)
 
     if (!bl)
     {
-        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)));
+            push_str<ScriptDataStr>(st->stack, ""_s);
         else
             push_int<ScriptDataInt>(st->stack, 0);
         return;
-- 
cgit v1.2.3-70-g09d2


From 48b9900248f74b8dc5179e34e851047e05c862b3 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Thu, 21 Apr 2016 12:32:12 -0400
Subject: allow to set params on npcs and mobs

---
 src/map/pc.cpp | 293 ++++++++++++++++++++++++++++++---------------------------
 src/map/pc.hpp |   2 +-
 2 files changed, 155 insertions(+), 140 deletions(-)

diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index d65297b..2954ca8 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -3544,155 +3544,170 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
  * script用PCステータス設定
  *------------------------------------------
  */
-int pc_setparam(dumb_ptr<map_session_data> sd, SP type, int val)
+int pc_setparam(dumb_ptr<block_list> bl, SP type, int val)
 {
-    int i = 0, up_level = 50;
+    nullpo_retz(bl);
+    dumb_ptr<map_session_data> sd;
+    dumb_ptr<npc_data> nd;
+    dumb_ptr<mob_data> md;
 
-    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 0;
 
-    switch (type)
+    int i = 0, up_level = 50;
+
+    if (sd)
     {
-        case SP::BASELEVEL:
-            if (val > sd->status.base_level)
-            {
-                for (i = 1; i <= (val - sd->status.base_level); i++)
-                    sd->status.status_point +=
-                        (sd->status.base_level + i + 14) / 4;
-            }
-            sd->status.base_level = val;
-            sd->status.base_exp = 0;
-            clif_updatestatus(sd, SP::BASELEVEL);
-            clif_updatestatus(sd, SP::NEXTBASEEXP);
-            clif_updatestatus(sd, SP::STATUSPOINT);
-            clif_updatestatus(sd, SP::BASEEXP);
-            pc_calcstatus(sd, 0);
-            pc_heal(sd, sd->status.max_hp, sd->status.max_sp);
-            break;
-        case SP::JOBLEVEL:
-            up_level -= 40;
-            if (val >= sd->status.job_level)
-            {
-                if (val > up_level)
-                    val = up_level;
-                sd->status.skill_point += (val - sd->status.job_level);
-                sd->status.job_level = val;
-                sd->status.job_exp = 0;
-                clif_updatestatus(sd, SP::JOBLEVEL);
-                clif_updatestatus(sd, SP::NEXTJOBEXP);
-                clif_updatestatus(sd, SP::JOBEXP);
-                clif_updatestatus(sd, SP::SKILLPOINT);
-                pc_calcstatus(sd, 0);
-                clif_misceffect(sd, 1);
-            }
-            else
-            {
-                sd->status.job_level = val;
-                sd->status.job_exp = 0;
-                clif_updatestatus(sd, SP::JOBLEVEL);
-                clif_updatestatus(sd, SP::NEXTJOBEXP);
-                clif_updatestatus(sd, SP::JOBEXP);
+        switch (type)
+        {
+            case SP::BASELEVEL:
+                if (val > sd->status.base_level)
+                {
+                    for (i = 1; i <= (val - sd->status.base_level); i++)
+                        sd->status.status_point +=
+                            (sd->status.base_level + i + 14) / 4;
+                }
+                sd->status.base_level = val;
+                sd->status.base_exp = 0;
+                clif_updatestatus(sd, SP::BASELEVEL);
+                clif_updatestatus(sd, SP::NEXTBASEEXP);
+                clif_updatestatus(sd, SP::STATUSPOINT);
+                clif_updatestatus(sd, SP::BASEEXP);
                 pc_calcstatus(sd, 0);
-            }
-            clif_updatestatus(sd, type);
-            break;
-        case SP::CLASS:
-            sd->status.species = wrap<Species>(val);
-            clif_changelook(sd, LOOK::BASE, val);
-            return 0;
-        case SP::SKILLPOINT:
-            sd->status.skill_point = val;
-            break;
-        case SP::STATUSPOINT:
-            sd->status.status_point = val;
-            break;
-        case SP::ZENY:
-            sd->status.zeny = val;
-            break;
-        case SP::BASEEXP:
-            if (pc_nextbaseexp(sd) > 0)
-            {
-                sd->status.base_exp = val;
-                if (sd->status.base_exp < 0)
-                    sd->status.base_exp = 0;
-                pc_checkbaselevelup(sd);
-            }
-            break;
-        case SP::JOBEXP:
-            if (pc_nextjobexp(sd) > 0)
-            {
-                sd->status.job_exp = val;
-                if (sd->status.job_exp < 0)
+                pc_heal(sd, sd->status.max_hp, sd->status.max_sp);
+                break;
+            case SP::JOBLEVEL:
+                up_level -= 40;
+                if (val >= sd->status.job_level)
+                {
+                    if (val > up_level)
+                        val = up_level;
+                    sd->status.skill_point += (val - sd->status.job_level);
+                    sd->status.job_level = val;
                     sd->status.job_exp = 0;
-                pc_checkjoblevelup(sd);
-            }
-            break;
-        case SP::SEX:
-            switch (val)
-            {
-            case 0:
-                sd->sex = sd->status.sex = SEX::FEMALE;
+                    clif_updatestatus(sd, SP::JOBLEVEL);
+                    clif_updatestatus(sd, SP::NEXTJOBEXP);
+                    clif_updatestatus(sd, SP::JOBEXP);
+                    clif_updatestatus(sd, SP::SKILLPOINT);
+                    pc_calcstatus(sd, 0);
+                    clif_misceffect(sd, 1);
+                }
+                else
+                {
+                    sd->status.job_level = val;
+                    sd->status.job_exp = 0;
+                    clif_updatestatus(sd, SP::JOBLEVEL);
+                    clif_updatestatus(sd, SP::NEXTJOBEXP);
+                    clif_updatestatus(sd, SP::JOBEXP);
+                    pc_calcstatus(sd, 0);
+                }
+                clif_updatestatus(sd, type);
                 break;
-            case 1:
-                sd->sex = sd->status.sex = SEX::MALE;
+            case SP::CLASS:
+                sd->status.species = wrap<Species>(val);
+                clif_changelook(sd, LOOK::BASE, val);
+                return 0;
+            case SP::SKILLPOINT:
+                sd->status.skill_point = val;
                 break;
-            default:
-                sd->sex = sd->status.sex = SEX::NEUTRAL;
+            case SP::STATUSPOINT:
+                sd->status.status_point = val;
                 break;
-            }
-            for (IOff0 j : IOff0::iter())
-            {
-                if (sd->status.inventory[j].nameid
-                    && bool(sd->status.inventory[j].equip)
-                    && !pc_isequip(sd, j))
-                    pc_unequipitem(sd, j, CalcStatus::LATER);
-            }
-            pc_calcstatus(sd, 0);
-            chrif_save(sd);
-            clif_fixpcpos(sd);
-            break;
-        case SP::WEIGHT:
-            sd->weight = val;
-            break;
-        case SP::MAXWEIGHT:
-            sd->max_weight = val;
-            break;
-        case SP::HP:
-            sd->status.hp = val;
-            break;
-        case SP::MAXHP:
-            sd->status.max_hp = val;
-            break;
-        case SP::SP:
-            sd->status.sp = val;
-            break;
-        case SP::MAXSP:
-            sd->status.max_sp = val;
-            break;
-        case SP::STR:
-        case SP::AGI:
-        case SP::VIT:
-        case SP::INT:
-        case SP::DEX:
-        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;
+            case SP::ZENY:
+                sd->status.zeny = val;
+                break;
+            case SP::BASEEXP:
+                if (pc_nextbaseexp(sd) > 0)
+                {
+                    sd->status.base_exp = val;
+                    if (sd->status.base_exp < 0)
+                        sd->status.base_exp = 0;
+                    pc_checkbaselevelup(sd);
+                }
+                break;
+            case SP::JOBEXP:
+                if (pc_nextjobexp(sd) > 0)
+                {
+                    sd->status.job_exp = val;
+                    if (sd->status.job_exp < 0)
+                        sd->status.job_exp = 0;
+                    pc_checkjoblevelup(sd);
+                }
+                break;
+            case SP::SEX:
+                switch (val)
+                {
+                case 0:
+                    sd->sex = sd->status.sex = SEX::FEMALE;
+                    break;
+                case 1:
+                    sd->sex = sd->status.sex = SEX::MALE;
+                    break;
+                default:
+                    sd->sex = sd->status.sex = SEX::NEUTRAL;
+                    break;
+                }
+                for (IOff0 j : IOff0::iter())
+                {
+                    if (sd->status.inventory[j].nameid
+                        && bool(sd->status.inventory[j].equip)
+                        && !pc_isequip(sd, j))
+                        pc_unequipitem(sd, j, CalcStatus::LATER);
+                }
+                pc_calcstatus(sd, 0);
+                chrif_save(sd);
+                clif_fixpcpos(sd);
+                break;
+            case SP::WEIGHT:
+                sd->weight = val;
+                break;
+            case SP::MAXWEIGHT:
+                sd->max_weight = val;
+                break;
+            case SP::HP:
+                sd->status.hp = val;
+                break;
+            case SP::MAXHP:
+                sd->status.max_hp = val;
+                break;
+            case SP::SP:
+                sd->status.sp = val;
+                break;
+            case SP::MAXSP:
+                sd->status.max_sp = val;
+                break;
+            case SP::STR:
+            case SP::AGI:
+            case SP::VIT:
+            case SP::INT:
+            case SP::DEX:
+            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);
     }
-    clif_updatestatus(sd, type);
 
     return 0;
 }
diff --git a/src/map/pc.hpp b/src/map/pc.hpp
index 0f04698..2e63c26 100644
--- a/src/map/pc.hpp
+++ b/src/map/pc.hpp
@@ -143,7 +143,7 @@ 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<block_list>, SP);
-int pc_setparam(dumb_ptr<map_session_data>, SP, int);
+int pc_setparam(dumb_ptr<block_list>, SP, int);
 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);
-- 
cgit v1.2.3-70-g09d2


From becc8312ff9a373ba22760ba89cdff6fe5e4895a Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sat, 23 Apr 2016 14:42:18 -0400
Subject: forbid puppet creation if name already exists

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

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 13a635d..b39a763 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -1121,6 +1121,13 @@ void builtin_puppet(ScriptState *st)
 {
     int x, y;
 
+    NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(3))));
+    if (npc_name2id(npc) != nullptr)
+    {
+        push_int<ScriptDataInt>(st->stack, 0);
+        return;
+    }
+
     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;
@@ -1137,7 +1144,6 @@ void builtin_puppet(ScriptState *st)
     nd->scr.event_needs_map = false;
 
     // PlayerName::SpellName
-    NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(3))));
     nd->name = npc;
 
     // Dynamically set location
-- 
cgit v1.2.3-70-g09d2


From 5a92a6817378f69785b77bb9993c9c950d6e0eb9 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sat, 23 Apr 2016 14:59:48 -0400
Subject: dispose orphan puppet if the parent is gone

---
 src/map/npc.cpp | 34 +++++++++++++++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index 47bf820..3eb13b3 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -366,6 +366,12 @@ void npc_eventtimer(TimerData *, tick_t, BlockId id, NpcEvent data)
         return;
     }
 
+    if (nd->scr.parent && map_id2bl(nd->scr.parent) == nullptr)
+    {
+        npc_free(nd);
+        return;
+    }
+
     if (nd->scr.event_needs_map)
     {
         int xs = nd->scr.xs;
@@ -436,6 +442,12 @@ void npc_timerevent(TimerData *, tick_t tick, BlockId id, interval_t data)
     assert (nd->npc_subtype == NpcSubtype::SCRIPT);
     assert (nd->scr.next_event != nd->scr.timer_eventv.end());
 
+    if (nd->scr.parent && map_id2bl(nd->scr.parent) == nullptr)
+    {
+        npc_free(nd);
+        return;
+    }
+
     nd->scr.timertick = tick;
     const auto te = nd->scr.next_event;
     // nd->scr.timerid = nullptr;
@@ -606,6 +618,13 @@ int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname,
                     eventname);
         return 0;
     }
+
+    if (nd->scr.parent && map_id2bl(nd->scr.parent) == nullptr)
+    {
+        npc_free(nd);
+        return 0;
+    }
+
     if (sd)
     {
         if (nd->scr.event_needs_map)
@@ -774,7 +793,13 @@ 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(script_or_parent(nd->is_script()), 0), sd->bl_id, id);
+            dumb_ptr<npc_data_script> nds = nd->is_script();
+            if (nds->scr.parent && map_id2bl(nds->scr.parent) == nullptr)
+            {
+                npc_free(nds);
+                return 1;
+            }
+            sd->npc_pos = run_script(ScriptPointer(script_or_parent(nds), 0), sd->bl_id, id);
             break;
     }
 
@@ -808,6 +833,13 @@ int npc_scriptcont(dumb_ptr<map_session_data> sd, BlockId id)
         return 0;
     }
 
+    if (nd->is_script()->scr.parent &&
+        map_id2bl(nd->is_script()->scr.parent) == nullptr)
+    {
+        npc_free(nd);
+        return 0;
+    }
+
     sd->npc_pos = run_script(ScriptPointer(script_or_parent(nd->is_script()), sd->npc_pos), sd->bl_id, id);
 
     return 0;
-- 
cgit v1.2.3-70-g09d2


From 881e3c06581f566f626f0c7d4da7724ff7a6d6a4 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sun, 24 Apr 2016 12:24:17 -0400
Subject: add INVISIBLE param

---
 src/map/pc.cpp          | 298 ++++++++++++++++++++++++++++--------------------
 src/map/script-call.cpp |   2 +-
 src/mmo/clif.t.hpp      |   1 +
 3 files changed, 176 insertions(+), 125 deletions(-)

diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 2954ca8..8c2bc01 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -3535,6 +3535,12 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
         case SP::ELTTYPE:
             val = static_cast<int>(battle_get_element(bl).element);
             break;
+        case SP::INVISIBLE:
+            if (sd)
+                val = bool(sd->status.option & Opt0::INVISIBILITY);
+            if (nd)
+                val = bool(nd->flag & 1);
+            break;
     }
 
     return val;
@@ -3560,137 +3566,176 @@ int pc_setparam(dumb_ptr<block_list> bl, SP type, int val)
     else
         return 0;
 
-    int i = 0, up_level = 50;
+    int i = 0;
 
-    if (sd)
+    switch (type)
     {
-        switch (type)
-        {
-            case SP::BASELEVEL:
-                if (val > sd->status.base_level)
-                {
-                    for (i = 1; i <= (val - sd->status.base_level); i++)
-                        sd->status.status_point +=
-                            (sd->status.base_level + i + 14) / 4;
-                }
-                sd->status.base_level = val;
-                sd->status.base_exp = 0;
-                clif_updatestatus(sd, SP::BASELEVEL);
-                clif_updatestatus(sd, SP::NEXTBASEEXP);
-                clif_updatestatus(sd, SP::STATUSPOINT);
-                clif_updatestatus(sd, SP::BASEEXP);
-                pc_calcstatus(sd, 0);
-                pc_heal(sd, sd->status.max_hp, sd->status.max_sp);
-                break;
-            case SP::JOBLEVEL:
-                up_level -= 40;
-                if (val >= sd->status.job_level)
-                {
-                    if (val > up_level)
-                        val = up_level;
-                    sd->status.skill_point += (val - sd->status.job_level);
-                    sd->status.job_level = val;
-                    sd->status.job_exp = 0;
-                    clif_updatestatus(sd, SP::JOBLEVEL);
-                    clif_updatestatus(sd, SP::NEXTJOBEXP);
-                    clif_updatestatus(sd, SP::JOBEXP);
-                    clif_updatestatus(sd, SP::SKILLPOINT);
-                    pc_calcstatus(sd, 0);
-                    clif_misceffect(sd, 1);
-                }
-                else
-                {
-                    sd->status.job_level = val;
-                    sd->status.job_exp = 0;
-                    clif_updatestatus(sd, SP::JOBLEVEL);
-                    clif_updatestatus(sd, SP::NEXTJOBEXP);
-                    clif_updatestatus(sd, SP::JOBEXP);
-                    pc_calcstatus(sd, 0);
-                }
-                clif_updatestatus(sd, type);
-                break;
-            case SP::CLASS:
+        case SP::BASELEVEL:
+            nullpo_retz(sd);
+            // TODO: mob mutation
+            if (val > sd->status.base_level)
+            {
+                if (val > MAX_LEVEL)
+                    val = MAX_LEVEL;
+                for (i = 1; i <= (val - sd->status.base_level); i++)
+                    sd->status.status_point +=
+                        (sd->status.base_level + i + 14) / 4;
+            }
+            sd->status.base_level = val;
+            sd->status.base_exp = 0;
+            clif_updatestatus(sd, SP::BASELEVEL);
+            clif_updatestatus(sd, SP::NEXTBASEEXP);
+            clif_updatestatus(sd, SP::STATUSPOINT);
+            clif_updatestatus(sd, SP::BASEEXP);
+            pc_calcstatus(sd, 0);
+            pc_heal(sd, sd->status.max_hp, sd->status.max_sp);
+            break;
+        case SP::JOBLEVEL:
+            nullpo_retz(sd);
+            if (val > sd->status.job_level)
+            {
+                if (val > MAX_LEVEL)
+                    val = MAX_LEVEL;
+                sd->status.skill_point += (val - sd->status.job_level);
+                clif_updatestatus(sd, SP::SKILLPOINT);
+                clif_misceffect(sd, 1);
+            }
+            sd->status.job_level = val;
+            sd->status.job_exp = 0;
+            clif_updatestatus(sd, SP::JOBLEVEL);
+            clif_updatestatus(sd, SP::NEXTJOBEXP);
+            clif_updatestatus(sd, SP::JOBEXP);
+            pc_calcstatus(sd, 0);
+            break;
+        case SP::CLASS:
+            // TODO: mob class change
+            if (sd)
+            {
                 sd->status.species = wrap<Species>(val);
                 clif_changelook(sd, LOOK::BASE, val);
-                return 0;
-            case SP::SKILLPOINT:
-                sd->status.skill_point = val;
-                break;
-            case SP::STATUSPOINT:
-                sd->status.status_point = val;
-                break;
-            case SP::ZENY:
-                sd->status.zeny = val;
-                break;
-            case SP::BASEEXP:
-                if (pc_nextbaseexp(sd) > 0)
-                {
-                    sd->status.base_exp = val;
-                    if (sd->status.base_exp < 0)
-                        sd->status.base_exp = 0;
-                    pc_checkbaselevelup(sd);
-                }
-                break;
-            case SP::JOBEXP:
-                if (pc_nextjobexp(sd) > 0)
+            }
+            else if (nd)
+            {
+                if (unwrap<Species>(nd->npc_class) != val)
                 {
-                    sd->status.job_exp = val;
-                    if (sd->status.job_exp < 0)
-                        sd->status.job_exp = 0;
-                    pc_checkjoblevelup(sd);
+                    nd->npc_class = wrap<Species>(val);
+                    npc_enable(nd->name, 0);
+                    npc_enable(nd->name, 1);
                 }
-                break;
-            case SP::SEX:
-                switch (val)
+            }
+            return 0;
+        case SP::SKILLPOINT:
+            nullpo_retz(sd);
+            sd->status.skill_point = val;
+            clif_updatestatus(sd, type);
+            break;
+        case SP::STATUSPOINT:
+            nullpo_retz(sd);
+            sd->status.status_point = val;
+            clif_updatestatus(sd, type);
+            break;
+        case SP::ZENY:
+            nullpo_retz(sd);
+            sd->status.zeny = val;
+            clif_updatestatus(sd, type);
+            break;
+        case SP::BASEEXP:
+            nullpo_retz(sd);
+            if (pc_nextbaseexp(sd) > 0)
+            {
+                sd->status.base_exp = val;
+                if (sd->status.base_exp < 0)
+                    sd->status.base_exp = 0;
+                pc_checkbaselevelup(sd);
+            }
+            clif_updatestatus(sd, type);
+            break;
+        case SP::JOBEXP:
+            nullpo_retz(sd);
+            if (pc_nextjobexp(sd) > 0)
+            {
+                sd->status.job_exp = val;
+                if (sd->status.job_exp < 0)
+                    sd->status.job_exp = 0;
+                pc_checkjoblevelup(sd);
+            }
+            clif_updatestatus(sd, type);
+            break;
+        case SP::SEX:
+            {
+                SEX sex = SEX::NEUTRAL;
+                if (val == 0)
+                    sex = SEX::FEMALE;
+                if (val == 1)
+                    sex = SEX::MALE;
+
+                if (nd)
                 {
-                case 0:
-                    sd->sex = sd->status.sex = SEX::FEMALE;
-                    break;
-                case 1:
-                    sd->sex = sd->status.sex = SEX::MALE;
-                    break;
-                default:
-                    sd->sex = sd->status.sex = SEX::NEUTRAL;
-                    break;
+                    nd->sex = sex;
+                    npc_enable(nd->name, 0);
+                    npc_enable(nd->name, 1);
                 }
-                for (IOff0 j : IOff0::iter())
+                else if (sd)
                 {
-                    if (sd->status.inventory[j].nameid
-                        && bool(sd->status.inventory[j].equip)
-                        && !pc_isequip(sd, j))
-                        pc_unequipitem(sd, j, CalcStatus::LATER);
+                    sd->sex = sd->status.sex = sex;
+                    for (IOff0 j : IOff0::iter())
+                    {
+                        if (sd->status.inventory[j].nameid
+                            && bool(sd->status.inventory[j].equip)
+                            && !pc_isequip(sd, j))
+                            pc_unequipitem(sd, j, CalcStatus::LATER);
+                    }
+                    pc_calcstatus(sd, 0);
+                    chrif_save(sd);
+                    clif_fixpcpos(sd);
                 }
-                pc_calcstatus(sd, 0);
-                chrif_save(sd);
-                clif_fixpcpos(sd);
-                break;
-            case SP::WEIGHT:
-                sd->weight = val;
-                break;
-            case SP::MAXWEIGHT:
-                sd->max_weight = val;
-                break;
-            case SP::HP:
-                sd->status.hp = val;
-                break;
-            case SP::MAXHP:
-                sd->status.max_hp = val;
-                break;
-            case SP::SP:
-                sd->status.sp = val;
-                break;
-            case SP::MAXSP:
-                sd->status.max_sp = val;
-                break;
-            case SP::STR:
-            case SP::AGI:
-            case SP::VIT:
-            case SP::INT:
-            case SP::DEX:
-            case SP::LUK:
-                pc_statusup2(sd, type, (val - sd->status.attrs[sp_to_attr(type)]));
-                break;
-            case SP::PARTNER:
+            }
+            break;
+        case SP::WEIGHT:
+            nullpo_retz(sd);
+            sd->weight = val;
+            clif_updatestatus(sd, type);
+            break;
+        case SP::MAXWEIGHT:
+            nullpo_retz(sd);
+            sd->max_weight = val;
+            clif_updatestatus(sd, type);
+            break;
+        case SP::HP:
+            nullpo_retz(sd);
+            // TODO: mob mutation
+            sd->status.hp = val;
+            clif_updatestatus(sd, type);
+            break;
+        case SP::MAXHP:
+            nullpo_retz(sd);
+            // TODO: mob mutation
+            sd->status.max_hp = val;
+            clif_updatestatus(sd, type);
+            break;
+        case SP::SP:
+            nullpo_retz(sd);
+            sd->status.sp = val;
+            clif_updatestatus(sd, type);
+            break;
+        case SP::MAXSP:
+            nullpo_retz(sd);
+            sd->status.max_sp = val;
+            clif_updatestatus(sd, type);
+            break;
+        case SP::STR:
+        case SP::AGI:
+        case SP::VIT:
+        case SP::INT:
+        case SP::DEX:
+        case SP::LUK:
+            nullpo_retz(sd);
+            // TODO: mob mutation
+            pc_statusup2(sd, type, (val - sd->status.attrs[sp_to_attr(type)]));
+            break;
+        case SP::PARTNER:
+            if (sd)
+            {
                 dumb_ptr<block_list> p_bl;
                 if (val < 2000000 && val >= 150000)
                 {
@@ -3704,9 +3749,14 @@ int pc_setparam(dumb_ptr<block_list> bl, SP type, int val)
                     pc_divorce(sd);
                 else
                     p_bl ? pc_marriage(sd, p_bl->is_player()) : 0;
-                break;
-        }
-        clif_updatestatus(sd, type);
+            }
+            break;
+        case SP::INVISIBLE:
+            if (sd)
+                pc_invisibility(sd, (val > 0) ? 1 : 0);
+            else if (nd)
+                npc_enable(nd->name, (val > 0) ? false : true);
+            break;
     }
 
     return 0;
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index 085d90a..2e90432 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -219,7 +219,7 @@ void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, struct script_
     if (type == VariableCode::PARAM)
     {
         int val = vd.get_if<ScriptDataInt>()->numi;
-        pc_setparam(sd->is_player(), reg.sp(), val);
+        pc_setparam(sd, reg.sp(), val);
         return;
     }
     assert (type == VariableCode::VARIABLE);
diff --git a/src/mmo/clif.t.hpp b/src/mmo/clif.t.hpp
index c1b222f..1dfbd67 100644
--- a/src/mmo/clif.t.hpp
+++ b/src/mmo/clif.t.hpp
@@ -474,6 +474,7 @@ enum class SP : uint16_t
     BL_ID                       = 1077,
     BL_TYPE                     = 1078,
     CHAR_ID                     = 1079,
+    INVISIBLE                   = 1080,
 };
 
 constexpr
-- 
cgit v1.2.3-70-g09d2


From d007bb6c7b519137585c53373ec085b57528b17d Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sun, 24 Apr 2016 13:06:34 -0400
Subject: fix npc gender for ManaPlus

---
 src/map/clif.cpp       | 10 +++++++++-
 src/map/npc-parse.cpp  |  5 ++++-
 src/map/script-fun.cpp |  1 +
 src/mmo/enums.hpp      |  1 +
 4 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/src/map/clif.cpp b/src/map/clif.cpp
index 31ecaf8..81ae02f 100644
--- a/src/map/clif.cpp
+++ b/src/map/clif.cpp
@@ -913,7 +913,15 @@ void clif_npc0078(dumb_ptr<npc_data> nd, Buffer& buf)
     fixed_78.pos.x = nd->bl_x;
     fixed_78.pos.y = nd->bl_y;
     fixed_78.pos.dir = nd->dir;
-    fixed_78.sex = nd->sex;
+
+    // ManaPlus uses a different sex enum for npcs...
+    if (nd->sex == SEX::FEMALE)
+        fixed_78.sex = SEX::UNSPECIFIED;
+    else if (nd->sex == SEX::MALE)
+        fixed_78.sex = SEX::NEUTRAL;
+    else if (nd->sex == SEX::NEUTRAL)
+        fixed_78.sex = SEX::__OTHER;
+
     buf = create_fpacket<0x0078, 54>(fixed_78);
 }
 
diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp
index 164d793..99e6267 100644
--- a/src/map/npc-parse.cpp
+++ b/src/map/npc-parse.cpp
@@ -142,6 +142,7 @@ bool npc_load_warp(ast::npc::Warp& warp)
     nd->bl_id = npc_get_new_npc_id();
     nd->n = map_addnpc(m, nd);
 
+    nd->sex = SEX::UNSPECIFIED;
     nd->bl_prev = nd->bl_next = nullptr;
     nd->bl_m = m;
     nd->bl_x = x;
@@ -208,6 +209,7 @@ bool npc_load_shop(ast::npc::Shop& shop)
         }
     }
 
+    nd->sex = SEX::UNSPECIFIED;
     nd->bl_prev = nd->bl_next = nullptr;
     nd->bl_m = m;
     nd->bl_x = x;
@@ -435,6 +437,7 @@ bool npc_load_script_none(ast::script::ScriptBody& body, ast::npc::ScriptNone& s
 
     nd->name = script_none.name.data;
 
+    nd->sex = SEX::UNSPECIFIED;
     nd->bl_prev = nd->bl_next = nullptr;
     nd->bl_m = borrow(undefined_gat);
     nd->bl_x = 0;
@@ -542,7 +545,7 @@ bool npc_load_script_map(ast::script::ScriptBody& body, ast::npc::ScriptMap& scr
     }
 
     nd->name = script_map.name.data;
-
+    nd->sex = SEX::UNSPECIFIED;
     nd->bl_prev = nd->bl_next = nullptr;
     nd->bl_m = m;
     nd->bl_x = x;
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index b39a763..fc555ad 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -1145,6 +1145,7 @@ void builtin_puppet(ScriptState *st)
 
     // PlayerName::SpellName
     nd->name = npc;
+    nd->sex = SEX::UNSPECIFIED;
 
     // Dynamically set location
     nd->bl_m = m;
diff --git a/src/mmo/enums.hpp b/src/mmo/enums.hpp
index 9a8f8ea..2564ec9 100644
--- a/src/mmo/enums.hpp
+++ b/src/mmo/enums.hpp
@@ -115,6 +115,7 @@ enum class SEX : uint8_t
     // TODO switch to Option<SEX> where appropriate.
     UNSPECIFIED = 2,
     NEUTRAL = 3,
+    __OTHER = 4, // used in ManaPlus only
 };
 inline
 char sex_to_char(SEX sex)
-- 
cgit v1.2.3-70-g09d2


From 543bdef8acecf4456d42c73fb712e570b01dc741 Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sun, 24 Apr 2016 13:21:31 -0400
Subject: add HIDDEN param

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

diff --git a/src/map/pc.cpp b/src/map/pc.cpp
index 8c2bc01..63b1497 100644
--- a/src/map/pc.cpp
+++ b/src/map/pc.cpp
@@ -3541,6 +3541,10 @@ int pc_readparam(dumb_ptr<block_list> bl, SP type)
             if (nd)
                 val = bool(nd->flag & 1);
             break;
+        case SP::HIDDEN:
+            if (sd)
+                val = bool(sd->status.option & Opt0::HIDE);
+            break;
     }
 
     return val;
@@ -3757,6 +3761,14 @@ int pc_setparam(dumb_ptr<block_list> bl, SP type, int val)
             else if (nd)
                 npc_enable(nd->name, (val > 0) ? false : true);
             break;
+        case SP::HIDDEN:
+            nullpo_retz(sd);
+            if (val == 1)
+                sd->status.option |= Opt0::HIDE;
+            else
+                sd->status.option &= ~Opt0::HIDE;
+            clif_changeoption(sd);
+            break;
     }
 
     return 0;
diff --git a/src/mmo/clif.t.hpp b/src/mmo/clif.t.hpp
index 1dfbd67..c23842e 100644
--- a/src/mmo/clif.t.hpp
+++ b/src/mmo/clif.t.hpp
@@ -475,6 +475,7 @@ enum class SP : uint16_t
     BL_TYPE                     = 1078,
     CHAR_ID                     = 1079,
     INVISIBLE                   = 1080,
+    HIDDEN                      = 1081,
 };
 
 constexpr
-- 
cgit v1.2.3-70-g09d2


From 6fac3210f0de07ab1ff483875ac517a3eea7367a Mon Sep 17 00:00:00 2001
From: mekolat <mekolat@users.noreply.github.com>
Date: Sun, 24 Apr 2016 13:46:11 -0400
Subject: check for null pointer where npc_dialog_mes is used

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

diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index fc555ad..b73a8fc 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -90,6 +90,8 @@ static
 void builtin_mes(ScriptState *st)
 {
     dumb_ptr<map_session_data> sd = script_rid2sd(st);
+    if (sd == nullptr)
+        return;
     sd->state.npc_dialog_mes = 1;
     RString mes = HARG(0) ? conv_str(st, &AARG(0)) : ""_s;
     clif_scriptmes(sd, st->oid, mes);
@@ -327,6 +329,8 @@ void builtin_close(ScriptState *st)
     }
     st->state = ScriptEndState::END;
     dumb_ptr<map_session_data> sd = script_rid2sd(st);
+    if (sd == nullptr)
+        return;
     if (sd->state.npc_dialog_mes)
         clif_scriptclose(sd, st->oid);
     else
@@ -338,6 +342,8 @@ void builtin_close2(ScriptState *st)
 {
     st->state = ScriptEndState::STOP;
     dumb_ptr<map_session_data> sd = script_rid2sd(st);
+    if (sd == nullptr)
+        return;
     if (sd->state.npc_dialog_mes)
         clif_scriptclose(sd, st->oid);
     else
-- 
cgit v1.2.3-70-g09d2