From b7ec6e535d314a64aeaf426e2e4c068cd7c4bb34 Mon Sep 17 00:00:00 2001
From: gumi <git@gumi.ca>
Date: Mon, 1 Jan 2018 16:09:22 -0500
Subject: release v18.1.1

---
 CHANGELOG               |  3 +++
 deps/attoconf           |  2 +-
 src/char/int_party.cpp  | 47 +++++++++++++++++++++++++++++++++++++++
 src/map/atcommand.cpp   | 59 ++++++++++++++++++++++++++++++++++++++++++-------
 src/map/intif.cpp       | 50 +++++++++++++++++++++++++++++++++++++++++
 src/map/intif.hpp       |  1 +
 src/map/map.hpp         |  2 ++
 src/map/npc-parse.cpp   |  8 +++++++
 src/map/npc.cpp         |  8 +++++--
 src/map/script-call.cpp |  9 ++++++++
 src/map/script-fun.cpp  |  5 ++++-
 tools/protocol.py       | 28 +++++++++++++++++++++++
 12 files changed, 210 insertions(+), 12 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index adcc69d..af0cf28 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+v18.1.1
+  - increase party limit to 120
+  - add a command to change party leader
 v17.11.12
   - fix a bug that did not decrease the npc counter on npc despawn (REDUX)
 v16.11.28
diff --git a/deps/attoconf b/deps/attoconf
index d33fcf6..7b939e7 160000
--- a/deps/attoconf
+++ b/deps/attoconf
@@ -1 +1 @@
-Subproject commit d33fcf6cc7380633329fdaf2e7f7543bbdd2490e
+Subproject commit 7b939e7e4ce36e8b62b10025e567f871731cbf4d
diff --git a/src/char/int_party.cpp b/src/char/int_party.cpp
index 5ee65ad..9fdd9bb 100644
--- a/src/char/int_party.cpp
+++ b/src/char/int_party.cpp
@@ -416,6 +416,20 @@ void mapif_party_optionchanged(Session *s, PartyPair p, AccountId account_id,
             account_id, p->exp, p->item, flag);
 }
 
+static
+void mapif_party_leaderchanged(Session *s, PartyPair p, AccountId account_id,
+                               int leader)
+{
+    Packet_Fixed<0x3828> fixed_28;
+    fixed_28.party_id = p.party_id;
+    fixed_28.account_id = account_id;
+    fixed_28.leader = leader;
+    for (Session *ss : iter_map_sessions())
+    {
+        send_fpacket<0x3828, 11>(ss, fixed_28);
+    }
+}
+
 // パーティ脱退通知
 static
 void mapif_party_leaved(PartyId party_id, AccountId account_id, CharName name)
@@ -603,6 +617,23 @@ void mapif_parse_PartyChangeOption(Session *s, PartyId party_id, AccountId accou
     mapif_party_optionchanged(s, p, account_id, flag);
 }
 
+static
+void mapif_parse_PartyChangeLeader(Session *s, PartyId party_id, AccountId account_id,
+        int leader)
+{
+    PartyPair p{party_id, TRY_UNWRAP(party_db.search(party_id), return)};
+
+    for (int i = 0; i < MAX_PARTY; i++)
+    {
+        if (p->member[i].account_id != account_id)
+            continue;
+
+        mapif_party_leaderchanged(s, p, account_id, leader);
+        p->member[i].leader = leader;
+        return;
+    }
+}
+
 // パーティ脱退要求
 void mapif_parse_PartyLeave(Session *, PartyId party_id, AccountId account_id)
 {
@@ -777,6 +808,22 @@ RecvResult inter_party_parse_frommap(Session *ms, uint16_t packet_id)
                     lv);
             break;
         }
+        case 0x3026:
+        {
+            Packet_Fixed<0x3026> fixed;
+            rv = recv_fpacket<0x3026, 11>(ms, fixed);
+            if (rv != RecvResult::Complete)
+                break;
+
+            PartyId party_id = fixed.party_id;
+            AccountId account_id = fixed.account_id;
+            uint8_t leader = fixed.leader;
+            mapif_parse_PartyChangeLeader(ms,
+                    party_id,
+                    account_id,
+                    leader);
+            break;
+        }
         case 0x3027:
         {
             Packet_Head<0x3027> head;
diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp
index ad3529d..2e11227 100644
--- a/src/map/atcommand.cpp
+++ b/src/map/atcommand.cpp
@@ -902,8 +902,8 @@ ATCE atcommand_whogroup(Session *s, dumb_ptr<map_session_data> sd,
                     AString output;
                     if (pl_gm_level)
                         output = STRPRINTF(
-                                "Name: %s (GM:%d) | Party: '%s'"_fmt,
-                                pl_sd->status_key.name, pl_gm_level, temp0);
+                                "Name: %s (GM:%d) | Party: '%s' (%i)"_fmt,
+                                pl_sd->status_key.name, pl_gm_level, temp0, pl_sd->status.party_id);
                     clif_displaymessage(s, output);
                     count++;
                 }
@@ -1019,11 +1019,11 @@ ATCE atcommand_whomapgroup(Session *s, dumb_ptr<map_session_data> sd,
                     PartyName temp0 = p_.pmd_pget(&PartyMost::name).copy_or(stringish<PartyName>("None"_s));
                     AString output;
                     if (pl_gm_level)
-                        output = STRPRINTF("Name: %s (GM:%d) | Party: '%s'"_fmt,
-                                pl_sd->status_key.name, pl_gm_level, temp0);
+                        output = STRPRINTF("Name: %s (GM:%d) | Party: '%s' (%i)"_fmt,
+                                pl_sd->status_key.name, pl_gm_level, temp0, pl_sd->status.party_id);
                     else
-                        output = STRPRINTF("Name: %s | Party: '%s'"_fmt,
-                                pl_sd->status_key.name, temp0);
+                        output = STRPRINTF("Name: %s | Party: '%s' (%i)"_fmt,
+                                pl_sd->status_key.name, temp0, pl_sd->status.party_id);
                     clif_displaymessage(s, output);
                     count++;
                 }
@@ -1092,8 +1092,8 @@ ATCE atcommand_whogm(Session *s, dumb_ptr<map_session_data> sd,
                         Option<PartyPair> p_ = party_search(pl_sd->status.party_id);
                         PartyName temp0 = p_.pmd_pget(&PartyMost::name).copy_or(stringish<PartyName>("None"_s));
                         output = STRPRINTF(
-                                "       Party: '%s'"_fmt,
-                                temp0);
+                                "       Party: '%s' (%i)"_fmt,
+                                temp0, pl_sd->status.party_id);
                         clif_displaymessage(s, output);
                         count++;
                     }
@@ -3708,6 +3708,46 @@ ATCE atcommand_partyspy(Session *s, dumb_ptr<map_session_data> sd,
     return ATCE::OKAY;
 }
 
+static
+ATCE atcommand_setpartyleader(Session *s, dumb_ptr<map_session_data> sd,
+        ZString message)
+{
+    CharName character;
+    PartyName party_name;
+    int value = 0;
+
+    if (!asplit(message, &party_name, &value, &character))
+        return ATCE::USAGE;
+
+
+    // name first to avoid error when name begin with a number
+    Option<PartyPair> p_ = party_searchname(party_name);
+    dumb_ptr<map_session_data> pl_sd = map_nick2sd(character);
+
+    // try with party id
+    if (p_.is_none())
+        p_ = party_search(wrap<PartyId>(static_cast<uint32_t>(atoi(party_name.c_str()))));
+
+    if (p_.is_none() || pl_sd == nullptr)
+        return ATCE::EXIST;
+
+    OMATCH_BEGIN (p_)
+    {
+        OMATCH_CASE_SOME (p)
+        {
+            intif_party_changeleader(p.party_id, pl_sd->status_key.account_id, value < 1 ? 0 : 1);
+            clif_displaymessage(s, "Party leader changed."_s);
+        }
+        OMATCH_CASE_NONE ()
+        {
+            clif_displaymessage(s, "Incorrect party name, or no one from the party is online."_s);
+            return ATCE::EXIST;
+        }
+    }
+    OMATCH_END ();
+    return ATCE::OKAY;
+}
+
 static
 ATCE atcommand_enablenpc(Session *s, dumb_ptr<map_session_data>,
         ZString message)
@@ -5295,6 +5335,9 @@ Map<XString, AtCommandInfo> atcommand_info =
     {"party"_s, {"<name>"_s,
         99, atcommand_party,
         "Create a new party"_s}},
+    {"setpartyleader"_s, {"<party-name-or-id> <flag> <player>"_s,
+        40, atcommand_setpartyleader,
+        "Change the leader of a party"_s}},
     {"mapexit"_s, {""_s,
         99, atcommand_mapexit,
         "Try to kill the server kindly"_s}},
diff --git a/src/map/intif.cpp b/src/map/intif.cpp
index d08b94f..fc34a64 100644
--- a/src/map/intif.cpp
+++ b/src/map/intif.cpp
@@ -234,6 +234,18 @@ void intif_party_changeoption(PartyId party_id, AccountId account_id, int exp, i
     send_fpacket<0x3023, 14>(char_session, fixed_23);
 }
 
+void intif_party_changeleader(PartyId party_id, AccountId account_id, int leader)
+{
+    if (!char_session)
+        return;
+
+    Packet_Fixed<0x3026> fixed_26;
+    fixed_26.party_id = party_id;
+    fixed_26.account_id = account_id;
+    fixed_26.leader = leader;
+    send_fpacket<0x3026, 11>(char_session, fixed_26);
+}
+
 // パーティ脱退要求
 void intif_party_leave(PartyId party_id, AccountId account_id)
 {
@@ -488,6 +500,34 @@ void intif_parse_PartyOptionChanged(Session *, const Packet_Fixed<0x3823>& fixed
             fixed.item, fixed.flag);
 }
 
+static
+void intif_parse_PartyLeaderChanged(Session *, const Packet_Fixed<0x3828>& fixed)
+{
+    int i;
+    PartyPair p = TRY_UNWRAP(party_search(fixed.party_id), return);
+
+    for (i = 0; i < MAX_PARTY; i++)
+    {
+        PartyMember *m = &p->member[i];
+
+        if (m->account_id == fixed.account_id)
+        {
+            dumb_ptr<map_session_data> sd = map_id2sd(wrap<BlockId>(unwrap<AccountId>(fixed.account_id)));
+            m->leader = (fixed.leader > 0 ? 1 : 0);
+
+            if (sd != nullptr)
+            {
+                AString msg = STRPRINTF("You are %s a leader of %s."_fmt,
+                    fixed.leader > 0 ? "now"_s : "no longer"_s, p->name);
+                clif_displaymessage(sd->sess, msg);
+            }
+            break;
+        }
+    }
+
+    clif_party_info(p, nullptr);
+}
+
 // パーティ脱退通知
 static
 void intif_parse_PartyMemberLeaved(Session *, const Packet_Fixed<0x3824>& fixed)
@@ -693,6 +733,16 @@ RecvResult intif_parse(Session *s, uint16_t packet_id)
             intif_parse_PartyMessage(s, head, repeat);
             break;
         }
+        case 0x3828:
+        {
+            Packet_Fixed<0x3828> fixed;
+            rv = recv_fpacket<0x3828, 11>(s, fixed);
+            if (rv != RecvResult::Complete)
+                return rv;
+
+            intif_parse_PartyLeaderChanged(s, fixed);
+            break;
+        }
         default:
             return RecvResult::Error;
     }
diff --git a/src/map/intif.hpp b/src/map/intif.hpp
index ac68040..a9d66fa 100644
--- a/src/map/intif.hpp
+++ b/src/map/intif.hpp
@@ -45,6 +45,7 @@ void intif_request_partyinfo(PartyId party_id);
 void intif_party_addmember(PartyId party_id, AccountId account_id);
 void intif_party_changeoption(PartyId party_id, AccountId account_id, int exp,
         int item);
+void intif_party_changeleader(PartyId party_id, AccountId account_id, int leader);
 void intif_party_leave(PartyId party_id, AccountId accound_id);
 void intif_party_changemap(dumb_ptr<map_session_data> sd, int online);
 void intif_party_message(PartyId party_id, AccountId account_id, XString mes);
diff --git a/src/map/map.hpp b/src/map/map.hpp
index 897787e..44c5bfb 100644
--- a/src/map/map.hpp
+++ b/src/map/map.hpp
@@ -340,6 +340,8 @@ struct npc_data : block_list
     Opt0 option;
     short flag;
 
+    bool deletion_pending;
+
     std::list<RString> eventqueuel;
     Array<Timer, MAX_EVENTTIMER> eventtimer;
     short arenaflag;
diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp
index 99e6267..47b851c 100644
--- a/src/map/npc-parse.cpp
+++ b/src/map/npc-parse.cpp
@@ -164,6 +164,8 @@ bool npc_load_warp(ast::npc::Warp& warp)
     nd->warp.xs = xs;
     nd->warp.ys = ys;
 
+    nd->deletion_pending = false;
+
     npc_warp++;
     nd->bl_type = BL::NPC;
     nd->npc_subtype = NpcSubtype::WARP;
@@ -226,6 +228,8 @@ bool npc_load_shop(ast::npc::Shop& shop)
     nd->opt2 = Opt2::ZERO;
     nd->opt3 = Opt3::ZERO;
 
+    nd->deletion_pending = false;
+
     npc_shop++;
     nd->bl_type = BL::NPC;
     nd->npc_subtype = NpcSubtype::SHOP;
@@ -454,6 +458,8 @@ bool npc_load_script_none(ast::script::ScriptBody& body, ast::npc::ScriptNone& s
     nd->opt2 = Opt2::ZERO;
     nd->opt3 = Opt3::ZERO;
 
+    nd->deletion_pending = false;
+
     npc_script++;
     nd->bl_type = BL::NPC;
     nd->npc_subtype = NpcSubtype::SCRIPT;
@@ -562,6 +568,8 @@ bool npc_load_script_map(ast::script::ScriptBody& body, ast::npc::ScriptMap& scr
     nd->opt2 = Opt2::ZERO;
     nd->opt3 = Opt3::ZERO;
 
+    nd->deletion_pending = false;
+
     npc_script++;
     nd->bl_type = BL::NPC;
     nd->npc_subtype = NpcSubtype::SCRIPT;
diff --git a/src/map/npc.cpp b/src/map/npc.cpp
index 32a5a54..a7cbf5d 100644
--- a/src/map/npc.cpp
+++ b/src/map/npc.cpp
@@ -358,7 +358,7 @@ void npc_eventtimer(TimerData *, tick_t, BlockId id, NpcEvent data)
                     data);
         return;
     });
-    if ((nd = ev->nd) == nullptr)
+    if ((nd = ev->nd) == nullptr || nd->deletion_pending == true)
     {
         if (battle_config.error_log)
             PRINTF("npc_event: event not found [%s]\n"_fmt,
@@ -611,7 +611,7 @@ int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname,
         ev.pos = ev2->pos;
     }
 
-    if ((nd = ev.nd) == nullptr)
+    if ((nd = ev.nd) == nullptr || nd->deletion_pending == true)
     {
         if (!mob_kill && battle_config.error_log)
             PRINTF("npc_event: event not found [%s]\n"_fmt,
@@ -1078,6 +1078,10 @@ void npc_propagate_update(dumb_ptr<npc_data> nd)
 
 void npc_free(dumb_ptr<npc_data> nd)
 {
+    if (nd == nullptr || nd->deletion_pending == true)
+        return;
+
+    nd->deletion_pending = true;
     clif_clearchar(nd, BeingRemoveWhy::GONE);
     npc_propagate_update(nd);
     map_deliddb(nd);
diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp
index d54e234..7c7d4e6 100644
--- a/src/map/script-call.cpp
+++ b/src/map/script-call.cpp
@@ -383,6 +383,9 @@ void pop_stack(struct script_stack *stack, int start, int end)
 static
 ByteCode get_com(ScriptPointer *script)
 {
+    if (script == nullptr)
+        return ByteCode::NOP;
+
     if (static_cast<uint8_t>(script->peek()) >= 0x80)
     {
         // synthetic! Does not advance pos yet.
@@ -742,10 +745,16 @@ void run_func(ScriptState *st)
 static
 void run_script_main(ScriptState *st, Borrowed<const ScriptBuffer> rootscript)
 {
+    if (st == nullptr)
+        return;
+
     int cmdcount = script_config.check_cmdcount;
     int gotocount = script_config.check_gotocount;
     struct script_stack *stack = st->stack;
 
+    if (stack == nullptr)
+        return;
+
     st->defsp = stack->stack_datav.size();
 
     int rerun_pos = st->scriptp.pos;
diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp
index 2036782..8aa8b5c 100644
--- a/src/map/script-fun.cpp
+++ b/src/map/script-fun.cpp
@@ -1130,8 +1130,9 @@ void builtin_destroy(ScriptState *st)
         return;
 
     nd = nd->is_script();
-    //assert(nd->disposable == true); we don't care about it anymore
     npc_free(nd);
+    st->oid = BlockId();
+
     if (!HARG(0))
         st->state = ScriptEndState::END;
 }
@@ -1196,6 +1197,8 @@ void builtin_puppet(ScriptState *st)
     nd->npc_subtype = NpcSubtype::SCRIPT;
     npc_script++;
 
+    nd->deletion_pending = false;
+
     nd->n = map_addnpc(nd->bl_m, nd);
 
     map_addblock(nd);
diff --git a/tools/protocol.py b/tools/protocol.py
index 1a87263..0c64e6f 100755
--- a/tools/protocol.py
+++ b/tools/protocol.py
@@ -5830,6 +5830,20 @@ def build_context():
             3. logged out
         ''',
     )
+    char_map.r(0x3026, 'party change leader',
+        fixed=[
+            at(0, u16, 'packet id'),
+            at(2, party_id, 'party id'),
+            at(6, account_id, 'account id'),
+            at(10, u8, 'leader'),
+        ],
+        fixed_size=11,
+        pre=[],
+        post=[0x3828],
+        desc='''
+            Explicitly request a change of party leader.
+        ''',
+    )
     char_map.r(0x3027, 'party message remote begin',
         head=[
             at(0, u16, 'packet id'),
@@ -6108,6 +6122,20 @@ def build_context():
             Actually send a party message to other map servers.
         ''',
     )
+    char_map.s(0x3828, 'party change leader notify',
+        fixed=[
+            at(0, u16, 'packet id'),
+            at(2, party_id, 'party id'),
+            at(6, account_id, 'account id'),
+            at(10, u8, 'leader'),
+        ],
+        fixed_size=11,
+        pre=[0x3026],
+        post=[],
+        desc='''
+            Party leader was changed.
+        ''',
+    )
 
     # TOC_MISC
     # any client
-- 
cgit v1.2.3-70-g09d2