diff options
-rw-r--r-- | src/ast/quest.cpp | 125 | ||||
-rw-r--r-- | src/ast/quest.hpp | 65 | ||||
-rw-r--r-- | src/char/char.cpp | 49 | ||||
-rw-r--r-- | src/login/login.cpp | 87 | ||||
-rw-r--r-- | src/map/atcommand.cpp | 88 | ||||
-rw-r--r-- | src/map/chrif.cpp | 58 | ||||
-rw-r--r-- | src/map/clif.cpp | 60 | ||||
-rw-r--r-- | src/map/clif.hpp | 4 | ||||
-rw-r--r-- | src/map/fwd.hpp | 1 | ||||
-rw-r--r-- | src/map/globals.cpp | 2 | ||||
-rw-r--r-- | src/map/globals.hpp | 1 | ||||
-rw-r--r-- | src/map/map.cpp | 3 | ||||
-rw-r--r-- | src/map/npc-parse.cpp | 2 | ||||
-rw-r--r-- | src/map/pc.cpp | 81 | ||||
-rw-r--r-- | src/map/pc.hpp | 3 | ||||
-rw-r--r-- | src/map/quest.cpp | 135 | ||||
-rw-r--r-- | src/map/quest.hpp | 51 | ||||
-rw-r--r-- | src/map/script-call.cpp | 4 | ||||
-rw-r--r-- | src/map/script-fun.cpp | 78 | ||||
-rw-r--r-- | src/mmo/fwd.hpp | 1 | ||||
-rw-r--r-- | src/mmo/ids.hpp | 1 | ||||
-rw-r--r-- | src/mmo/ids.py | 1 | ||||
-rwxr-xr-x | tools/protocol.py | 32 |
23 files changed, 659 insertions, 273 deletions
diff --git a/src/ast/quest.cpp b/src/ast/quest.cpp new file mode 100644 index 0000000..bd339c2 --- /dev/null +++ b/src/ast/quest.cpp @@ -0,0 +1,125 @@ +#include "quest.hpp" +// ast/quest.cpp - Structure of tmwa questdb +// +// Copyright © 2015 Ed Pasek <pasekei@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 "../io/extract.hpp" +#include "../io/line.hpp" + +#include "../mmo/extract_enums.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace ast +{ +namespace quest +{ + using io::respan; + + static + void skip_comma_space(io::LineCharReader& lr) + { + io::LineChar c; + if (lr.get(c) && c.ch() == ',') + { + lr.adv(); + while (lr.get(c) && c.ch() == ' ') + { + lr.adv(); + } + } + } + static + Option<Spanned<RString>> lex_nonscript(io::LineCharReader& lr, bool first) + { + io::LineChar c; + if (first) + { + while (lr.get(c) && c.ch() == '\n') + { + lr.adv(); + } + } + if (!lr.get(c)) + { + return None; + } + io::LineSpan span; + MString accum; + accum += c.ch(); + span.begin = c; + span.end = c; + lr.adv(); + if (c.ch() != '/') + first = false; + + if (first && lr.get(c) && c.ch() == '/') + { + accum += c.ch(); + span.end = c; + lr.adv(); + while (lr.get(c) && c.ch() != '\n') + { + accum += c.ch(); + span.end = c; + lr.adv(); + } + return Some(respan(span, RString(accum))); + } + + while (lr.get(c) && c.ch() != ',' && c.ch() != '\n') + { + accum += c.ch(); + span.end = c; + lr.adv(); + } + skip_comma_space(lr); + return Some(respan(span, RString(accum))); + } + +#define SPAN_EXTRACT(bitexpr, var) ({ auto bit = bitexpr; if (!extract(bit.data, &var.data)) return Err(bit.span.error_str("failed to extract "_s #var)); var.span = bit.span; }) + +#define EOL_ERROR(lr) ({ io::LineChar c; lr.get(c) ? Err(c.error_str("unexpected EOL"_s)) : Err("unexpected EOF before unexpected EOL"_s); }) + Option<Result<QuestOrComment>> parse_quest(io::LineCharReader& lr) + { + Spanned<RString> first = TRY_UNWRAP(lex_nonscript(lr, true), return None); + if (first.data.startswith("//"_s)) + { + Comment comment; + comment.comment = first.data; + QuestOrComment rv = std::move(comment); + rv.span = first.span; + return Some(Ok(std::move(rv))); + } + Quest quest; + SPAN_EXTRACT(first, quest.questid); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), quest.quest_var); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), quest.quest_vr); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), quest.quest_shift); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), quest.quest_mask); + QuestOrComment rv = std::move(quest); + rv.span.begin = quest.questid.span.begin; + rv.span.end = quest.quest_mask.span.end; + return Some(Ok(std::move(rv))); + } +} // namespace quest +} // namespace ast +} // namespace tmwa diff --git a/src/ast/quest.hpp b/src/ast/quest.hpp new file mode 100644 index 0000000..5112524 --- /dev/null +++ b/src/ast/quest.hpp @@ -0,0 +1,65 @@ +#pragma once +// ast/quest.hpp - Structure of tmwa questdb +// +// Copyright © 2015 Ed Pasek <pasekei@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 Affero 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include "../compat/result.hpp" + +#include "../io/span.hpp" + +#include "../sexpr/variant.hpp" + +#include "../mmo/clif.t.hpp" +#include "../mmo/ids.hpp" +#include "../mmo/strs.hpp" + +namespace tmwa +{ +namespace ast +{ +namespace quest +{ + using io::Spanned; + + struct Comment + { + RString comment; + }; + struct Quest + { + Spanned<QuestId> questid; + Spanned<VarName> quest_var; + Spanned<VarName> quest_vr; + Spanned<int> quest_shift; + Spanned<int> quest_mask; + }; + + using QuestOrCommentBase = Variant<Comment, Quest>; + struct QuestOrComment : QuestOrCommentBase + { + QuestOrComment(Comment o) : QuestOrCommentBase(std::move(o)) {} + QuestOrComment(Quest o) : QuestOrCommentBase(std::move(o)) {} + io::LineSpan span; + }; + + Option<Result<QuestOrComment>> parse_quest(io::LineCharReader& lr); +} // namespace quest +} // namespace ast +} // namespace tmwa diff --git a/src/char/char.cpp b/src/char/char.cpp index a9b6834..ed9e369 100644 --- a/src/char/char.cpp +++ b/src/char/char.cpp @@ -1223,28 +1223,6 @@ void parse_tologin(Session *ls) break; } - case 0x2721: // gm reply - { - Packet_Fixed<0x2721> fixed; - rv = recv_fpacket<0x2721, 10>(ls, fixed); - if (rv != RecvResult::Complete) - break; - - { - AccountId acc = fixed.account_id; - GmLevel gml = fixed.gm_level; - - Packet_Fixed<0x2b0b> fixed_2b; - fixed_2b.account_id = acc; - fixed_2b.gm_level = gml; - for (Session *ss : iter_map_sessions()) - { - send_fpacket<0x2b0b, 10>(ss, fixed_2b); - } - } - break; - } - case 0x2723: // changesex reply (modified by [Yor]) { Packet_Fixed<0x2723> fixed; @@ -1857,33 +1835,6 @@ void parse_frommap(Session *ms) break; } - // it is a request to become GM - case 0x2b0a: - { - Packet_Head<0x2b0a> head; - AString repeat; - rv = recv_vpacket<0x2b0a, 8, 1>(ms, head, repeat); - if (rv != RecvResult::Complete) - break; - - AccountId account_id = head.account_id; - if (login_session) - { // don't send request if no login-server - Packet_Head<0x2720> head_20; - head_20.account_id = account_id; - AString& repeat_20 = repeat; - send_vpacket<0x2720, 8, 1>(login_session, head_20, repeat_20); - } - else - { - Packet_Fixed<0x2b0b> fixed_0b; - fixed_0b.account_id = account_id; - fixed_0b.gm_level = GmLevel(); - send_fpacket<0x2b0b, 10>(ms, fixed_0b); - } - break; - } - // Map server send information to change an email of an account -> login-server case 0x2b0c: { diff --git a/src/login/login.cpp b/src/login/login.cpp index 66b3ea0..92f8cc0 100644 --- a/src/login/login.cpp +++ b/src/login/login.cpp @@ -982,93 +982,6 @@ void parse_fromchar(Session *s) break; } - case 0x2720: // To become GM request - { - Packet_Head<0x2720> head; - AString repeat; - rv = recv_vpacket<0x2720, 8, 1>(s, head, repeat); - if (rv != RecvResult::Complete) - break; - - { - AccountId acc = head.account_id; - - Packet_Fixed<0x2721> fixed_21; - fixed_21.account_id = acc; - fixed_21.gm_level = GmLevel(); - - AString pass = repeat; - - if (pass == login_conf.gm_pass) - { - // only non-GM can become GM - if (!isGM(acc)) - { - // if we autorise creation - if (login_conf.level_new_gm) - { - // if we can open the file to add the new GM - io::AppendFile fp(login_conf.gm_account_filename); - if (fp.is_open()) - { - timestamp_seconds_buffer tmpstr; - stamp_time(tmpstr); - FPRINTF(fp, - "\n// %s: @GM command on account %d\n%d %d\n"_fmt, - tmpstr, - acc, acc, login_conf.level_new_gm); - if (!fp.close()) - { - PRINTF("warning: didn't actually save GM file\n"_fmt); - } - fixed_21.gm_level = login_conf.level_new_gm; - read_gm_account(); - send_GM_accounts(); - PRINTF("GM Change of the account %d: level 0 -> %d.\n"_fmt, - acc, login_conf.level_new_gm); - LOGIN_LOG("Char-server '%s': GM Change of the account %d: level 0 -> %d (ip: %s).\n"_fmt, - server[id].name, acc, - login_conf.level_new_gm, ip); - } - else - { - PRINTF("Error of GM change (suggested account: %d, correct password, unable to add a GM account in GM accounts file)\n"_fmt, - acc); - LOGIN_LOG("Char-server '%s': Error of GM change (suggested account: %d, correct password, unable to add a GM account in GM accounts file, ip: %s).\n"_fmt, - server[id].name, acc, ip); - } - } - else - { - PRINTF("Error of GM change (suggested account: %d, correct password, but GM creation is disable (level_new_gm = 0))\n"_fmt, - acc); - LOGIN_LOG("Char-server '%s': Error of GM change (suggested account: %d, correct password, but GM creation is disable (level_new_gm = 0), ip: %s).\n"_fmt, - server[id].name, acc, ip); - } - } - else - { - PRINTF("Error of GM change (suggested account: %d (already GM), correct password).\n"_fmt, - acc); - LOGIN_LOG("Char-server '%s': Error of GM change (suggested account: %d (already GM), correct password, ip: %s).\n"_fmt, - server[id].name, acc, ip); - } - } - else - { - PRINTF("Error of GM change (suggested account: %d, invalid password).\n"_fmt, - acc); - LOGIN_LOG("Char-server '%s': Error of GM change (suggested account: %d, invalid password, ip: %s).\n"_fmt, - server[id].name, acc, ip); - } - for (Session *ss : iter_char_sessions()) - { - send_fpacket<0x2721, 10>(ss, fixed_21); - } - } - break; - } - // Map server send information to change an email of an account via char-server case 0x2722: // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B { diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp index 9a35d30..7739966 100644 --- a/src/map/atcommand.cpp +++ b/src/map/atcommand.cpp @@ -701,6 +701,69 @@ ATCE atcommand_goto(Session *s, dumb_ptr<map_session_data> sd, } static +ATCE atcommand_npc(Session *s, dumb_ptr<map_session_data> sd, + ZString message) +{ + NpcName npc; + + if (!asplit(message, &npc)) + { + clif_displaymessage(s, + "Please, enter a npc name (usage: @npc/@warptonpc/@gotonpc <npc>)."_s); + return ATCE::USAGE; + } + + dumb_ptr<npc_data> nd = npc_name2id(npc); + if (nd != nullptr) + { + if (nd->bl_m->flag.get(MapFlag::NOWARPTO) + && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level))) + { + clif_displaymessage(s, + "You are not authorised to warp you to the map of this npc."_s); + return ATCE::PERM; + } + if (sd->bl_m->flag.get(MapFlag::NOWARP) + && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level))) + { + clif_displaymessage(s, + "You are not authorised to warp you from your actual map."_s); + return ATCE::PERM; + } + + int x = nd->bl_x, y = nd->bl_y, x0 = (x >= 5)? (x - 5): 0, j = 0, + y0 = (y >= 5)? (y - 5): 0, x1 = (x + 5), y1 = (y + 5), max; + max = (y1 - y0 + 1) * (x1 - x0 + 1) * 3; + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(nd->bl_m->name_), return ATCE::OKAY); + if (max > 1000) + max = 1000; + if (bool(map_getcell(m, x, y) & MapCell::UNWALKABLE)){ + do + { + x = random_::in(x0, x1); + y = random_::in(y0, y1); + } + while (bool(map_getcell(m, x, y) & MapCell::UNWALKABLE) + && (++j) < max); + if (j >= max) + { + return ATCE::OKAY; // Since reference of the place which boils first went wrong, it stops. + } + } + pc_setpos(sd, nd->bl_m->name_, x, y, BeingRemoveWhy::WARPED); + AString output = STRPRINTF("Jump to %s"_fmt, npc); + clif_displaymessage(s, output); + } + else + { + clif_displaymessage(s, "Npc not found."_s); + return ATCE::EXIST; + } + + return ATCE::OKAY; +} + +static ATCE atcommand_jump(Session *s, dumb_ptr<map_session_data> sd, ZString message) { @@ -1528,25 +1591,6 @@ ATCE atcommand_joblevelup(Session *s, dumb_ptr<map_session_data> sd, } static -ATCE atcommand_gm(Session *s, dumb_ptr<map_session_data> sd, - ZString message) -{ - if (!message) - return ATCE::USAGE; - - if (pc_isGM(sd)) - { - // a GM can not use this function. only a normal player (become gm is not for gm!) - clif_displaymessage(s, "You already have some GM powers."_s); - return ATCE::PERM; - } - else - chrif_changegm(sd->status_key.account_id, message); - - return ATCE::OKAY; -} - -static ATCE atcommand_pvpoff(Session *s, dumb_ptr<map_session_data> sd, ZString) { @@ -4926,6 +4970,9 @@ Map<XString, AtCommandInfo> atcommand_info = {"goto"_s, {"<charname>"_s, 40, atcommand_goto, "Warp yourself to another character"_s}}, + {"npc"_s, {"<npc>"_s, + 40, atcommand_npc, + "Warp yourself to a npc"_s}}, {"jump"_s, {"[x] [y]"_s, 40, atcommand_jump, "Warp yourself within a map"_s}}, @@ -4995,9 +5042,6 @@ Map<XString, AtCommandInfo> atcommand_info = {"jlvl"_s, {"<delta>"_s, 60, atcommand_joblevelup, "Adjust your job level"_s}}, - {"gm"_s, {"<password>"_s, - 100, atcommand_gm, - "Receive GM powers"_s}}, {"pvpoff"_s, {""_s, 60, atcommand_pvpoff, "Enable PvP on your map"_s}}, diff --git a/src/map/chrif.cpp b/src/map/chrif.cpp index bf4ae4e..3ffeaf2 100644 --- a/src/map/chrif.cpp +++ b/src/map/chrif.cpp @@ -345,23 +345,6 @@ int chrif_charselectreq(dumb_ptr<map_session_data> sd) } /*========================================== - * GMに変化要求 - *------------------------------------------ - */ -void chrif_changegm(AccountId id, ZString pass) -{ - if (!char_session) - return; - - if (battle_config.etc_log) - PRINTF("chrif_changegm: account: %d, password: '%s'.\n"_fmt, id, pass); - - Packet_Head<0x2b0a> head_0a; - head_0a.account_id = id; - send_vpacket<0x2b0a, 8, 1>(char_session, head_0a, pass); -} - -/*========================================== * Change Email *------------------------------------------ */ @@ -559,30 +542,6 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed) } /*========================================== - * End of GM change(@GM) (modified by Yor) - *------------------------------------------ - */ -static -void chrif_changedgm(Session *, const Packet_Fixed<0x2b0b>& fixed) -{ - AccountId acc = fixed.account_id; - GmLevel level = fixed.gm_level; - - dumb_ptr<map_session_data> sd = map_id2sd(account_to_block(acc)); - - if (battle_config.etc_log) - PRINTF("chrif_changedgm: account: %d, GM level 0 -> %d.\n"_fmt, acc, - level); - if (sd != nullptr) - { - if (level) - clif_displaymessage(sd->sess, "GM modification success."_s); - else - clif_displaymessage(sd->sess, "Failure of GM modification."_s); - } -} - -/*========================================== * 性別変化終了 (modified by Yor) *------------------------------------------ */ @@ -609,15 +568,14 @@ void chrif_changedsex(Session *, const Packet_Fixed<0x2b0d>& fixed) { if (sd->status.inventory[i].nameid && bool(sd->status.inventory[i].equip)) - pc_unequipitem(sd, i, CalcStatus::NOW); + pc_unequipitem(sd, i, CalcStatus::LATER); } + pc_calcstatus(sd, 0); // save character chrif_save(sd); sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters // do same modify in login-server for the account, but no in char-server (it ask again login_id1 to login, and don't remember it) - clif_displaymessage(sd->sess, - "Your sex has been changed (need disconexion by the server)..."_s); - clif_setwaitclose(sd->sess); // forced to disconnect for the change + clif_fixpcpos(sd); // use clif_set0078_main_1d8 to send new sex to the client } } else @@ -973,16 +931,6 @@ void chrif_parse(Session *s) chrif_changemapserverack(s, fixed); break; } - case 0x2b0b: - { - Packet_Fixed<0x2b0b> fixed; - rv = recv_fpacket<0x2b0b, 10>(s, fixed); - if (rv != RecvResult::Complete) - break; - - chrif_changedgm(s, fixed); - break; - } case 0x2b0d: { Packet_Fixed<0x2b0d> fixed; diff --git a/src/map/clif.cpp b/src/map/clif.cpp index 577d7be..e3cd55f 100644 --- a/src/map/clif.cpp +++ b/src/map/clif.cpp @@ -198,11 +198,6 @@ int is_deaf(dumb_ptr<block_list> bl) return sd->special_state.deaf; } -static -void clif_emotion_towards(dumb_ptr<block_list> bl, - dumb_ptr<block_list> target, int type); - - enum class ChatType { Party, @@ -3113,7 +3108,6 @@ void clif_emotion(dumb_ptr<block_list> bl, int type) clif_send(buf, bl, SendWho::AREA); } -static void clif_emotion_towards(dumb_ptr<block_list> bl, dumb_ptr<block_list> target, int type) { @@ -4671,6 +4665,60 @@ RecvResult clif_parse_PartyMessage(Session *s, dumb_ptr<map_session_data> sd) return rv; } +void clif_sendallquest(dumb_ptr<map_session_data> sd) +{ + int i; + QuestId questid; + if (!sd) + return; + + if (!sd->sess) + return; + + Session *s = sd->sess; + Packet_Head<0x0215> head_215; + std::vector<Packet_Repeat<0x0215>> repeat_215; + + assert (sd->status.global_reg_num < GLOBAL_REG_NUM); + for (QuestId q = wrap<QuestId>(0); q < wrap<QuestId>(-1); q = next(q)) + { + P<struct quest_data> quest_data_ = TRY_UNWRAP(questdb_exists(q), continue); + for (i = 0; i < sd->status.global_reg_num; i++) + { + if (sd->status.global_reg[i].str == quest_data_->quest_vr) + { + int val = ((sd->status.global_reg[i].value & (((1 << quest_data_->quest_mask) - 1) << (quest_data_->quest_shift * quest_data_->quest_mask))) >> (quest_data_->quest_shift * quest_data_->quest_mask)); + Packet_Repeat<0x0215> info; + info.variable = unwrap<QuestId>(quest_data_->questid); + info.value = val; + repeat_215.push_back(info); + break; + } + } + } + + send_vpacket<0x0215, 4, 6>(s, head_215, repeat_215); + return; +} + +void clif_sendquest(dumb_ptr<map_session_data> sd, QuestId questid, int value) +{ + if (!sd) + return; + + if (!sd->sess) + return; + + Session *s = sd->sess; + + Packet_Fixed<0x0214> fixed; + fixed.variable = unwrap<QuestId>(questid); + fixed.value = value; + send_fpacket<0x0214, 8>(s, fixed); + return; +} + + func_table clif_parse_func_table[0x0220] = { {0, 10, nullptr, }, // 0x0000 diff --git a/src/map/clif.hpp b/src/map/clif.hpp index 153cc7c..5117ff3 100644 --- a/src/map/clif.hpp +++ b/src/map/clif.hpp @@ -96,6 +96,7 @@ int clif_changeoption(dumb_ptr<block_list>); // area int clif_useitemack(dumb_ptr<map_session_data>, IOff0, int, int); // self void clif_emotion(dumb_ptr<block_list> bl, int type); +void clif_emotion_towards(dumb_ptr<block_list> bl, dumb_ptr<block_list> target, int type); void clif_sitting(Session *, dumb_ptr<map_session_data> sd); // trade @@ -174,6 +175,9 @@ int clif_GM_kick(dumb_ptr<map_session_data> sd, dumb_ptr<map_session_data> tsd, int type); int clif_foreachclient(std::function<void(dumb_ptr<map_session_data>)>); +// quest +void clif_sendallquest(dumb_ptr<map_session_data> sd); +void clif_sendquest(dumb_ptr<map_session_data> sd, QuestId questid, int value); void do_init_clif(void); } // namespace map diff --git a/src/map/fwd.hpp b/src/map/fwd.hpp index 1db4ed0..911d566 100644 --- a/src/map/fwd.hpp +++ b/src/map/fwd.hpp @@ -65,6 +65,7 @@ class npc_data_warp; class npc_data_message; struct item_data; +struct quest_data; struct ScriptState; struct str_data_t; diff --git a/src/map/globals.cpp b/src/map/globals.cpp index 09ff157..dce3906 100644 --- a/src/map/globals.cpp +++ b/src/map/globals.cpp @@ -26,6 +26,7 @@ #include "battle_conf.hpp" #include "itemdb.hpp" +#include "quest.hpp" #include "magic-interpreter.hpp" #include "map_conf.hpp" #include "mob.hpp" @@ -49,6 +50,7 @@ namespace tmwa int chrif_state; std::map<MapName, RString> resnametable; Map<ItemNameId, item_data> item_db; + Map<QuestId, quest_data> quest_db; namespace magic { // Global magic conf diff --git a/src/map/globals.hpp b/src/map/globals.hpp index 33cfec8..b457b4e 100644 --- a/src/map/globals.hpp +++ b/src/map/globals.hpp @@ -48,6 +48,7 @@ namespace tmwa extern int chrif_state; 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 diff --git a/src/map/map.cpp b/src/map/map.cpp index d502fbb..c1d760a 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -74,6 +74,7 @@ #include "magic-v2.hpp" #include "map_conf.hpp" #include "mob.hpp" +#include "quest.hpp" #include "npc.hpp" #include "npc-parse.hpp" #include "party.hpp" @@ -1489,6 +1490,8 @@ bool map_confs(io::Spanned<XString> key, io::Spanned<ZString> value) return itemdb_readdb(value.data); if (key.data == "mob_db"_s) return mob_readdb(value.data); + if (key.data == "quest_db"_s) + return quest_readdb(value.data); if (key.data == "mob_skill_db"_s) return mob_readskilldb(value.data); if (key.data == "skill_db"_s) diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp index 443a1e7..9c9a22c 100644 --- a/src/map/npc-parse.cpp +++ b/src/map/npc-parse.cpp @@ -339,7 +339,7 @@ bool npc_load_mapflag(ast::npc::MapFlag& mapflag) return false; } - if (battle_config.pk_mode && mf == MapFlag::NOPVP) + if (mf == MapFlag::NOPVP) { if (mapflag.vec_extra.data.size()) { diff --git a/src/map/pc.cpp b/src/map/pc.cpp index 00acadf..e8e526a 100644 --- a/src/map/pc.cpp +++ b/src/map/pc.cpp @@ -64,6 +64,7 @@ #include "skill.hpp" #include "storage.hpp" #include "trade.hpp" +#include "quest.hpp" #include "../poison.hpp" @@ -794,6 +795,9 @@ int pc_authok(AccountId id, int login_id2, pc_calcstatus(sd, 1); + npc_event_doall_l(stringish<ScriptLabel>("OnPCLoginEvent"_s), sd->bl_id, nullptr); + // Init Quest Log + clif_sendallquest(sd); return 0; } @@ -3520,8 +3524,7 @@ int pc_setparam(dumb_ptr<map_session_data> sd, SP type, int val) } break; case SP::SEX: - // this is a really bad idea - sd->sex = static_cast<SEX>(val); + chrif_char_ask_name(AccountId(), sd->status_key.name, 5, HumanTimeDiff()); break; case SP::WEIGHT: sd->weight = val; @@ -3827,14 +3830,37 @@ void pc_setregstr(dumb_ptr<map_session_data> sd, SIR reg, RString str) int pc_readglobalreg(dumb_ptr<map_session_data> sd, VarName reg) { int i; - + int quest_shift = 0; + int quest_mask = 0; nullpo_retz(sd); + QuestId questid; + XString var = reg; + VarName vr; assert (sd->status.global_reg_num < GLOBAL_REG_NUM); + Option<P<struct quest_data>> quest_data_ = questdb_searchname(var); + OMATCH_BEGIN_SOME(quest_data, quest_data_) + { + questid = quest_data->questid; + reg = quest_data->quest_vr; + vr = quest_data->quest_var; + quest_shift = quest_data->quest_shift; + quest_mask = quest_data->quest_mask; + } + OMATCH_END (); for (i = 0; i < sd->status.global_reg_num; i++) { if (sd->status.global_reg[i].str == reg) - return sd->status.global_reg[i].value; + { + if (questid) + { + return ((sd->status.global_reg[i].value & (((1 << quest_mask) - 1) << (quest_shift * quest_mask))) >> (quest_shift * quest_mask)); + } + else + { + return sd->status.global_reg[i].value; + } + } } return 0; @@ -3847,8 +3873,13 @@ int pc_readglobalreg(dumb_ptr<map_session_data> sd, VarName reg) int pc_setglobalreg(dumb_ptr<map_session_data> sd, VarName reg, int val) { int i; - + int quest_shift = 0; + int quest_mask = 0; + int bitval = val; nullpo_retz(sd); + QuestId questid; + XString var = reg; + VarName vr; //PC_DIE_COUNTERがスクリプトなどで変更された時の処理 if (reg == stringish<VarName>("PC_DIE_COUNTER"_s) && sd->die_counter != val) @@ -3856,6 +3887,17 @@ int pc_setglobalreg(dumb_ptr<map_session_data> sd, VarName reg, int val) sd->die_counter = val; pc_calcstatus(sd, 0); } + Option<P<struct quest_data>> quest_data_ = questdb_searchname(var); + OMATCH_BEGIN_SOME(quest_data, quest_data_) + { + questid = quest_data->questid; + reg = quest_data->quest_vr; + vr = quest_data->quest_var; + quest_shift = quest_data->quest_shift; + quest_mask = quest_data->quest_mask; + assert (((1 << quest_mask) - 1) >= val); + } + OMATCH_END (); assert (sd->status.global_reg_num < GLOBAL_REG_NUM); if (val == 0) { @@ -3863,9 +3905,18 @@ int pc_setglobalreg(dumb_ptr<map_session_data> sd, VarName reg, int val) { if (sd->status.global_reg[i].str == reg) { - sd->status.global_reg[i] = - sd->status.global_reg[sd->status.global_reg_num - 1]; - sd->status.global_reg_num--; + if (questid) + { + bitval = ((sd->status.global_reg[i].value & ~(((1 << quest_mask) - 1) << (quest_shift * quest_mask))) | (val << (quest_shift * quest_mask))); + clif_sendquest(sd, questid, val); + } + sd->status.global_reg[i].value = bitval; + if (sd->status.global_reg[i].value == 0) + { + sd->status.global_reg[i] = + sd->status.global_reg[sd->status.global_reg_num - 1]; + sd->status.global_reg_num--; + } break; } } @@ -3875,14 +3926,24 @@ int pc_setglobalreg(dumb_ptr<map_session_data> sd, VarName reg, int val) { if (sd->status.global_reg[i].str == reg) { - sd->status.global_reg[i].value = val; + if (questid) + { + bitval = ((sd->status.global_reg[i].value & ~(((1 << quest_mask) - 1) << (quest_shift * quest_mask))) | (val << (quest_shift * quest_mask))); + clif_sendquest(sd, questid, val); + } + sd->status.global_reg[i].value = bitval; return 0; } } if (sd->status.global_reg_num < GLOBAL_REG_NUM) { sd->status.global_reg[i].str = reg; - sd->status.global_reg[i].value = val; + if (questid) + { + bitval = ((sd->status.global_reg[i].value & ~(((1 << quest_mask) - 1) << (quest_shift * quest_mask))) | (val << (quest_shift * quest_mask))); + clif_sendquest(sd, questid, val); + } + sd->status.global_reg[i].value = bitval; sd->status.global_reg_num++; return 0; } diff --git a/src/map/pc.hpp b/src/map/pc.hpp index 14829d1..6c0803f 100644 --- a/src/map/pc.hpp +++ b/src/map/pc.hpp @@ -28,6 +28,7 @@ #include "../mmo/clif.t.hpp" #include "map.hpp" +#include "quest.hpp" namespace tmwa @@ -146,6 +147,8 @@ 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); +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 ); int pc_setglobalreg(dumb_ptr<map_session_data>, VarName , int); int pc_readaccountreg(dumb_ptr<map_session_data>, VarName ); diff --git a/src/map/quest.cpp b/src/map/quest.cpp new file mode 100644 index 0000000..dfe19ff --- /dev/null +++ b/src/map/quest.cpp @@ -0,0 +1,135 @@ +#include "quest.hpp" +// quest.cpp - Quest Log. +// +// Copyright © 2015 Ed Pasek <pasekei@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/zstring.hpp" +#include "../strings/xstring.hpp" + +#include "../generic/db.hpp" + +#include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" +#include "../io/line.hpp" + +#include "../mmo/config_parse.hpp" +#include "../mmo/extract_enums.hpp" + +#include "../ast/quest.hpp" + +#include "../poison.hpp" +#include "globals.hpp" +#include "script-parse.hpp" + +namespace tmwa +{ +namespace map +{ +// Function declarations + +static +void questdb_searchname_sub(Borrowed<struct quest_data> quest, VarName str, Borrowed<Option<Borrowed<struct quest_data>>> dst) +{ + if (quest->quest_var == str) + *dst = Some(quest); +} + +Option<Borrowed<struct quest_data>> questdb_searchname(XString str_) +{ + VarName str = stringish<VarName>(str_); + if (XString(str) != str_) + return None; + Option<P<struct quest_data>> quest = None; + for (auto& pair : quest_db) + questdb_searchname_sub(borrow(pair.second), str, borrow(quest)); + return quest; +} + +Borrowed<struct quest_data> questdb_search(QuestId questid) +{ + Option<P<struct quest_data>> id_ = quest_db.search(questid); + OMATCH_BEGIN_SOME (id, id_) + { + return id; + } + OMATCH_END (); + + P<struct quest_data> id = quest_db.init(questid); + + id->questid = questid; + + return id; +} + +Option<Borrowed<struct quest_data>> questdb_exists(QuestId questid) +{ + return quest_db.search(questid); +} + +bool quest_readdb(ZString filename) +{ + io::LineCharReader in(filename); + + if (!in.is_open()) + { + PRINTF("can't read %s\n"_fmt, filename); + return false; + } + + int ln = 0; + + while (true) + { + auto res = TRY_UNWRAP(ast::quest::parse_quest(in), + { + PRINTF("read %s done (count=%d)\n"_fmt, filename, ln); + return true; + }); + if (res.get_failure()) + PRINTF("%s\n"_fmt, res.get_failure()); + ast::quest::QuestOrComment ioc = TRY_UNWRAP(std::move(res.get_success()), return false); + + MATCH_BEGIN (ioc) + { + MATCH_CASE (const ast::quest::Comment&, c) + { + (void)c; + } + MATCH_CASE (const ast::quest::Quest&, quest) + { + ln++; + + quest_data qdv {}; + qdv.questid = quest.questid.data; + qdv.quest_var = quest.quest_var.data; + qdv.quest_vr = quest.quest_vr.data; + qdv.quest_shift = quest.quest_shift.data; + qdv.quest_mask = quest.quest_mask.data; + + Borrowed<struct quest_data> id = questdb_search(qdv.questid); + *id = std::move(qdv); + } + } + MATCH_END (); + } +} +} // namespace map +} // namespace tmwa diff --git a/src/map/quest.hpp b/src/map/quest.hpp new file mode 100644 index 0000000..65e6f4e --- /dev/null +++ b/src/map/quest.hpp @@ -0,0 +1,51 @@ +#pragma once +// quest.hpp - Quest Log. +// +// Copyright © 2015 Ed Pasek <pasekei@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 "../mmo/ids.hpp" +#include "../high/mmo.hpp" + +#include "map.hpp" +#include "script-buffer.hpp" + +namespace tmwa +{ +namespace map +{ +constexpr int MAX_QUEST_DB (60355+1); +struct quest_data +{ + QuestId questid; + VarName quest_var; + VarName quest_vr; + int quest_shift; + int quest_mask; +}; +inline +Option<Borrowed<struct quest_data>> questdb_searchname(VarName) = delete; +Option<Borrowed<struct quest_data>> questdb_searchname(XString quest_var); +Borrowed<struct quest_data> questdb_search(QuestId questid); +Option<Borrowed<struct quest_data>> questdb_exists(QuestId questid); + +// get quest var by quest name / mask / bit +bool quest_readdb(ZString filename); +} // namespace map +} // namespace tmwa diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp index f412328..c3c6aa1 100644 --- a/src/map/script-call.cpp +++ b/src/map/script-call.cpp @@ -757,7 +757,7 @@ void run_script_main(ScriptState *st, Borrowed<const ScriptBuffer> rootscript) { rerun_pos = st->scriptp.pos; st->state = ScriptEndState::ZERO; - if (!st->freeloop && gotocount > 0 && (--gotocount) <= 0) + if (st->freeloop != 1 && gotocount > 0 && (--gotocount) <= 0) { PRINTF("run_script: infinity loop !\n"_fmt); st->state = ScriptEndState::END; @@ -806,7 +806,7 @@ void run_script_main(ScriptState *st, Borrowed<const ScriptBuffer> rootscript) st->state = ScriptEndState::END; break; } - if (!st->freeloop && cmdcount > 0 && (--cmdcount) <= 0) + if (st->freeloop != 1 && cmdcount > 0 && (--cmdcount) <= 0) { PRINTF("run_script: infinity loop !\n"_fmt); st->state = ScriptEndState::END; diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp index a7f9fcc..010b095 100644 --- a/src/map/script-fun.cpp +++ b/src/map/script-fun.cpp @@ -677,6 +677,24 @@ void builtin_getelementofarray(ScriptState *st) } } +static +void builtin_wgm(ScriptState *st) +{ + ZString message = ZString(conv_str(st, &AARG(0))); + + intif_wis_message_to_gm(WISP_SERVER_NAME, + battle_config.hack_info_GM_level, + STRPRINTF("[GM] %s"_fmt, message)); +} + +static +void builtin_gmlog(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + ZString message = ZString(conv_str(st, &AARG(0))); + log_atcommand(sd, STRPRINTF("{SCRIPT} %s"_fmt, message)); +} + /*========================================== * *------------------------------------------ @@ -1948,20 +1966,6 @@ void builtin_resetstatus(ScriptState *st) } /*========================================== - * 性別変換 - *------------------------------------------ - */ -static -void builtin_changesex(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = nullptr; - sd = script_rid2sd(st); - - chrif_char_ask_name(AccountId(), sd->status_key.name, 5, HumanTimeDiff()); // type: 5 - changesex - chrif_save(sd); -} - -/*========================================== * RIDのアタッチ *------------------------------------------ */ @@ -2140,11 +2144,22 @@ void builtin_getpvpflag(ScriptState *st) static void builtin_emotion(ScriptState *st) { - int type; - type = conv_num(st, &AARG(0)); + ZString str; + dumb_ptr<map_session_data> pl_sd = nullptr; + int type = conv_num(st, &AARG(0)); + if (HARG(1)) { + str = ZString(conv_str(st, &AARG(1))); + CharName player = stringish<CharName>(str); + pl_sd = map_nick2sd(player); + } if (type < 0 || type > 200) return; - clif_emotion(map_id2bl(st->oid), type); + if (pl_sd != nullptr) + clif_emotion_towards(map_id2bl(st->oid), pl_sd, type); + else if (st->rid && str == "self"_s) + clif_emotion(map_id2sd(st->rid), type); + else + clif_emotion(map_id2bl(st->oid), type); } static @@ -2272,11 +2287,11 @@ void builtin_getitemlink(ScriptState *st) { OMATCH_CASE_SOME (item_data) { - buf = STRPRINTF("[@@%d|%s@@]"_fmt, item_data->nameid, item_data->jname); + buf = STRPRINTF("@@%d|@@"_fmt, item_data->nameid); } OMATCH_CASE_NONE () { - buf = STRPRINTF("Unknown Item: %s"_fmt, name); + buf = "Unknown Item"_s; } } OMATCH_END (); @@ -2553,25 +2568,6 @@ void builtin_unequipbyid(ScriptState *st) } /*========================================== - * gmcommand [MouseJstr] - * - * suggested on the forums... - *------------------------------------------ - */ - -static -void builtin_gmcommand(ScriptState *st) -{ - dumb_ptr<map_session_data> sd; - - sd = script_rid2sd(st); - RString cmd = conv_str(st, &AARG(0)); - - is_atcommand(sd->sess, sd, cmd, GmLevel::from(-1U)); - -} - -/*========================================== * npcwarp [remoitnane] * Move NPC to a new position on the same map. *------------------------------------------ @@ -3125,8 +3121,9 @@ BuiltinFunction builtin_functions[] = 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'), BUILTIN(resetstatus, ""_s, '\0'), - BUILTIN(changesex, ""_s, '\0'), BUILTIN(attachrid, "i"_s, 'i'), BUILTIN(detachrid, ""_s, '\0'), BUILTIN(isloggedin, "i"_s, 'i'), @@ -3137,7 +3134,7 @@ BuiltinFunction builtin_functions[] = BUILTIN(pvpoff, "M"_s, '\0'), BUILTIN(setpvpchannel, "i"_s, '\0'), BUILTIN(getpvpflag, "i"_s, 'i'), - BUILTIN(emotion, "i"_s, '\0'), + BUILTIN(emotion, "i?"_s, '\0'), BUILTIN(mapwarp, "MMxy"_s, '\0'), BUILTIN(mobcount, "ME"_s, 'i'), BUILTIN(marriage, "P"_s, 'i'), @@ -3156,7 +3153,6 @@ BuiltinFunction builtin_functions[] = BUILTIN(specialeffect2, "i"_s, '\0'), BUILTIN(nude, ""_s, '\0'), BUILTIN(unequipbyid, "i"_s, '\0'), - BUILTIN(gmcommand, "s"_s, '\0'), BUILTIN(npcwarp, "xys"_s, '\0'), BUILTIN(npcareawarp, "xyxyis"_s, '\0'), BUILTIN(message, "Ps"_s, '\0'), diff --git a/src/mmo/fwd.hpp b/src/mmo/fwd.hpp index f51767d..434885e 100644 --- a/src/mmo/fwd.hpp +++ b/src/mmo/fwd.hpp @@ -51,6 +51,7 @@ class AccountCrypt; class AccountEmail; class ServerName; class PartyName; +class QuestId; class VarName; class MapName; class CharName; diff --git a/src/mmo/ids.hpp b/src/mmo/ids.hpp index c1482ef..28b146a 100644 --- a/src/mmo/ids.hpp +++ b/src/mmo/ids.hpp @@ -40,6 +40,7 @@ class PartyId : public Wrapped<uint32_t> { public: constexpr PartyId() : Wrapped class ItemNameId : public Wrapped<uint16_t> { public: constexpr ItemNameId() : Wrapped<uint16_t>() {} protected: constexpr explicit ItemNameId(uint16_t a) : Wrapped<uint16_t>(a) {} }; class BlockId : public Wrapped<uint32_t> { public: constexpr BlockId() : Wrapped<uint32_t>() {} protected: constexpr explicit BlockId(uint32_t a) : Wrapped<uint32_t>(a) {} }; +class QuestId : public Wrapped<uint16_t> { public: constexpr QuestId() : Wrapped<uint16_t>() {} protected: constexpr explicit QuestId(uint16_t a) : Wrapped<uint16_t>(a) {} }; bool impl_extract(XString str, GmLevel *lvl); class GmLevel diff --git a/src/mmo/ids.py b/src/mmo/ids.py index 89392b1..a98920f 100644 --- a/src/mmo/ids.py +++ b/src/mmo/ids.py @@ -5,6 +5,7 @@ for s in [ 'PartyId', 'ItemNameId', 'BlockId', + 'QuestId', ]: class OtherId(object): __slots__ = ('_value') diff --git a/tools/protocol.py b/tools/protocol.py index 5d4dc37..db14f6d 100755 --- a/tools/protocol.py +++ b/tools/protocol.py @@ -4617,7 +4617,39 @@ def build_context(): ) # 0x0213 define='CMSG_SET_STATUS', # 0x0214 define='SMSG_QUEST_SET_VAR', + map_user.s(0x0214, 'send quest', + define='SMSG_QUEST_SET_VAR', + fixed=[ + at(0, u16, 'packet id'), + at(2, u16, 'variable'), + at(4, u32, 'value'), + ], + fixed_size=8, + pre=[NOTHING], + post=[PRETTY], + desc=''' + Set Quest Log Variable to Value. + ''', + ) # 0x0215 define='SMSG_QUEST_PLAYER_VARS', + map_user.s(0x0215, 'send all quest', + define='SMSG_QUEST_PLAYER_VARS', + head=[ + at(0, u16, 'packet id'), + at(2, u16, 'packet length'), + ], + head_size=4, + repeat=[ + at(0, u16, 'variable'), + at(2, u32, 'value'), + ], + repeat_size=6, + pre=[NOTHING], + post=[PRETTY], + desc=''' + Set All Quest Log Variable to Value. + ''', + ) # 0x0220 define='SMSG_BEING_NAME_RESPONSE2', # 0x0221 define='SMSG_CHAR_CREATE_SUCCEEDED2', # 0x0222 define='CMSG_CHAT_MESSAGE2', |