diff options
Diffstat (limited to 'src')
259 files changed, 16709 insertions, 13675 deletions
diff --git a/src/admin/fwd.hpp b/src/admin/fwd.hpp index 46674c8..0beaf50 100644 --- a/src/admin/fwd.hpp +++ b/src/admin/fwd.hpp @@ -20,8 +20,21 @@ #include "../sanity.hpp" +#include "../strings/fwd.hpp" // rank 1 +#include "../io/fwd.hpp" // rank 4 +#include "../net/fwd.hpp" // rank 5 +#include "../mmo/fwd.hpp" // rank 6 +#include "../proto2/fwd.hpp" // rank 8 +#include "../high/fwd.hpp" // rank 9 +#include "../wire/fwd.hpp" // rank 9 +// admin/fwd.hpp is rank ∞ because it is an executable + namespace tmwa { +namespace admin +{ + struct AdminConf; // meh, add more when I feel like it +} // namespace admin } // namespace tmwa diff --git a/src/admin/globals.cpp b/src/admin/globals.cpp new file mode 100644 index 0000000..e61128a --- /dev/null +++ b/src/admin/globals.cpp @@ -0,0 +1,48 @@ +#include "globals.hpp" +// globals.cpp - Evil global variables for tmwa-admin. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../strings/tstring.hpp" + +#include "../mmo/ids.hpp" + +#include "admin_conf.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ + namespace admin + { + bool eathena_interactive_session; + AdminConf admin_conf; + Session *login_session; + // flag to know if we waiting bytes from login-server + bool bytes_to_read = false; + // needs to be global since it's passed to the parse function + // really should be added to session data + TString parameters; + // parameters to display a list of accounts + AccountId list_first, list_last; + int list_type, list_count; + // sometimes, the exit function is called twice... so, don't log twice the message + bool already_exit_function = false; + } // namespace admin +} // namespace tmwa diff --git a/src/ints/cmp.cpp b/src/admin/globals.hpp index 94ff0e3..3935499 100644 --- a/src/ints/cmp.cpp +++ b/src/admin/globals.hpp @@ -1,5 +1,5 @@ -#include "cmp.hpp" -// cmp.cpp - comparison related operations +#pragma once +// globals.hpp - Evil global variables for tmwa-admin. // // Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> // @@ -18,9 +18,20 @@ // 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 "../poison.hpp" +#include "fwd.hpp" namespace tmwa { + namespace admin + { + extern bool eathena_interactive_session; + extern AdminConf admin_conf; + extern Session *login_session; + extern bool bytes_to_read; + extern TString parameters; + extern AccountId list_first, list_last; + extern int list_type, list_count; + extern bool already_exit_function; + } // namespace admin } // namespace tmwa diff --git a/src/admin/ladmin.cpp b/src/admin/ladmin.cpp index 9dae089..e0d6b06 100644 --- a/src/admin/ladmin.cpp +++ b/src/admin/ladmin.cpp @@ -35,49 +35,48 @@ #include "../strings/vstring.hpp" #include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" #include "../io/read.hpp" +#include "../io/span.hpp" #include "../io/tty.hpp" #include "../io/write.hpp" #include "../net/ip.hpp" -#include "../net/packets.hpp" +#include "../net/timestamp-utils.hpp" #include "../proto2/any-user.hpp" #include "../proto2/login-admin.hpp" #include "../mmo/config_parse.hpp" -#include "../mmo/core.hpp" #include "../mmo/human_time_diff.hpp" -#include "../mmo/mmo.hpp" -#include "../mmo/utils.hpp" +#include "../high/mmo.hpp" #include "../mmo/version.hpp" +#include "../high/core.hpp" +#include "../high/utils.hpp" + +#include "../wire/packets.hpp" + +#include "admin_conf.hpp" +#include "globals.hpp" + #include "../poison.hpp" namespace tmwa { -static -int eathena_interactive_session; -#define Iprintf if (eathena_interactive_session) PRINTF - -//-------------------------------INSTRUCTIONS------------------------------ -// Set the variables below: -// IP of the login server. -// Port where the login-server listens incoming packets. -// Password of administration (same of config_athena.conf). -// IMPORTANT: -// Be sure that you authorize remote administration in login-server -// (see login_athena.conf, 'admin_state' parameter) -//------------------------------------------------------------------------- -static -IP4Address login_ip = IP4_LOCALHOST; // IP of login-server -static -int login_port = 6900; // Port of login-server -static -AccountPass admin_pass = stringish<AccountPass>("admin"_s); // Administration password -static -AString ladmin_log_filename = "log/ladmin.log"_s; +DIAG_PUSH(); +DIAG_I(missing_noreturn); +void SessionDeleter::operator()(SessionData *) +{ + assert(false && "ladmin does not have sessions"_s); +} +DIAG_POP(); + +namespace admin +{ +#define Iprintf if (tmwa::admin::eathena_interactive_session) PRINTF + //------------------------------------------------------------------------- // LIST of COMMANDs that you can type at the prompt: // To use these commands you can only type only the first letters. @@ -220,29 +219,6 @@ AString ladmin_log_filename = "log/ladmin.log"_s; // all other values are 'No MSG', then use state 9 please. // 'error_message_#7': message of the code error 6 = Your are Prohibited to log in until %s (packet 0x006a) // -// timeadd <account_name> <modifier> -// Adds or substracts time from the validity limit of an account. -// Modifier is done as follows: -// Adjustment value (-1, 1, +1, etc...) -// Modified element: -// a or y: year -// m: month -// j or d: day -// h: hour -// mn: minute -// s: second -// <example> timeadd testname +1m-2mn1s-6y -// this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time. -// NOTE: You can not modify a unlimited validity limit. -// If you want modify it, you want probably create a limited validity limit. -// So, at first, you must set the validity limit to a date/time. -// -// timeset <account_name> yyyy/mm/dd [hh:mm:ss] -// Changes the validity limit of an account. -// Default time [hh:mm:ss]: 23:59:59. -// timeset <account_name> 0 -// Gives an unlimited validity limit (0 = unlimited). -// // unban/unbanish <account name> // Unban an account. // Like banset <account name> 0. @@ -258,37 +234,16 @@ AString ladmin_log_filename = "log/ladmin.log"_s; // Displays complete information of an account. // //------------------------------------------------------------------------- -static -Session *login_session; -static -int bytes_to_read = 0; // flag to know if we waiting bytes from login-server -static -TString parameters; // needs to be global since it's passed to the parse function -// really should be added to session data -static -AccountId list_first, list_last; -static -int list_type, list_count; // parameter to display a list of accounts -static -int already_exit_function = 0; // sometimes, the exit function is called twice... so, don't log twice the message - -DIAG_PUSH(); -DIAG_I(missing_noreturn); -void SessionDeleter::operator()(SessionData *) -{ - assert(false && "ladmin does not have sessions"_s); -} -DIAG_POP(); //------------------------------ // Writing function of logs file //------------------------------ #define LADMIN_LOG(fmt, ...) \ - ladmin_log(STRPRINTF(fmt, ## __VA_ARGS__)) + tmwa::admin::ladmin_log(STRPRINTF(fmt, ## __VA_ARGS__)) static void ladmin_log(XString line) { - io::AppendFile logfp(ladmin_log_filename); + io::AppendFile logfp(admin_conf.ladmin_log_filename); if (!logfp.is_open()) return; log_with_timestamp(logfp, line); @@ -300,9 +255,9 @@ void delete_fromlogin(Session *) login_session = nullptr; { PRINTF("Impossible to have a connection with the login-server [%s:%d] !\n"_fmt, - login_ip, login_port); + admin_conf.login_ip, admin_conf.login_port); LADMIN_LOG("Impossible to have a connection with the login-server [%s:%d] !\n"_fmt, - login_ip, login_port); + admin_conf.login_ip, admin_conf.login_port); exit(0); } } @@ -487,13 +442,6 @@ void display_help(ZString param) PRINTF(" Research by name is not possible with this command.\n"_fmt); PRINTF(" <example> list 10 9999999\n"_fmt); } - else if (command == "itemfrob"_s) - { - PRINTF("itemfrob <source-id> <dest-id>\n"_fmt); - PRINTF(" Translates item IDs for all accounts.\n"_fmt); - PRINTF(" Any items matching the source item ID will be mapped to the dest-id.\n"_fmt); - PRINTF(" <example> itemfrob 500 700\n"_fmt); - } else if (command == "listban"_s) { PRINTF("listban [start_id [end_id]]\n"_fmt); @@ -565,34 +513,6 @@ void display_help(ZString param) PRINTF(" 'error_message_#7': message of the code error 6\n"_fmt); PRINTF(" = Your are Prohibited to log in until... (packet 0x006a)\n"_fmt); } - else if (command == "timeadd"_s) - { - PRINTF("timeadd <account_name> <modifier>\n"_fmt); - PRINTF(" Adds or substracts time from the validity limit of an account.\n"_fmt); - PRINTF(" Modifier is done as follows:\n"_fmt); - PRINTF(" Adjustment value (-1, 1, +1, etc...)\n"_fmt); - PRINTF(" Modified element:\n"_fmt); - PRINTF(" a or y: year\n"_fmt); - PRINTF(" m: month\n"_fmt); - PRINTF(" j or d: day\n"_fmt); - PRINTF(" h: hour\n"_fmt); - PRINTF(" mn: minute\n"_fmt); - PRINTF(" s: second\n"_fmt); - PRINTF(" <example> timeadd testname +1m-2mn1s-6y\n"_fmt); - PRINTF(" this example adds 1 month and 1 second, and substracts 2 minutes\n"_fmt); - PRINTF(" and 6 years at the same time.\n"_fmt); - PRINTF("NOTE: You can not modify a unlimited validity limit.\n"_fmt); - PRINTF(" If you want modify it, you want probably create a limited validity limit.\n"_fmt); - PRINTF(" So, at first, you must set the validity limit to a date/time.\n"_fmt); - } - else if (command == "timeadd"_s) - { - PRINTF("timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n"_fmt); - PRINTF(" Changes the validity limit of an account.\n"_fmt); - PRINTF(" Default time [hh:mm:ss]: 23:59:59.\n"_fmt); - PRINTF("timeset <account_name> 0\n"_fmt); - PRINTF(" Gives an unlimited validity limit (0 = unlimited).\n"_fmt); - } else if (command == "unban"_s) { PRINTF("unban/unbanish <account name>\n"_fmt); @@ -644,7 +564,6 @@ void display_help(ZString param) PRINTF(" gm <account_name> [GM_level] -- Modify the GM level of an account\n"_fmt); PRINTF(" id <account name> -- Give the id of an account\n"_fmt); PRINTF(" info <account_id> -- Display all information of an account\n"_fmt); - PRINTF(" itemfrob <source-id> <dest-id> -- Map all items from one item ID to another\n"_fmt); PRINTF(" kami <message> -- Sends a broadcast message (in yellow)\n"_fmt); PRINTF(" kamib <message> -- Sends a broadcast message (in blue)\n"_fmt); PRINTF(" list [First_id [Last_id]] -- Display a list of accounts\n"_fmt); @@ -661,10 +580,6 @@ void display_help(ZString param) PRINTF(" search <expression> -- Seek accounts\n"_fmt); PRINTF(" sex <nomcompte> <sexe> -- Modify the sex of an account\n"_fmt); PRINTF(" state <account_name> <new_state> <error_message_#7> -- Change the state\n"_fmt); - PRINTF(" timeadd <account_name> <modifier> -- Add or substract time from the\n"_fmt); - PRINTF(" example: ta apple +1m-2mn1s-2y validity limit of an account\n"_fmt); - PRINTF(" timeset <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the validify limit\n"_fmt); - PRINTF(" timeset <account_name> 0 -- Give a unlimited validity limit\n"_fmt); PRINTF(" unban <account name> -- Remove the banishment of an account\n"_fmt); PRINTF(" unblock <account name> -- Set state 0 (Account ok) to an account\n"_fmt); PRINTF(" version -- Gives the version of the login-server\n"_fmt); @@ -1260,29 +1175,6 @@ void listaccount(ZString param, int type) list_count = 0; } -//-------------------------------------------------------- -// Sub-function: Frobnicate items -//-------------------------------------------------------- -static -int itemfrob(ZString param) -{ - ItemNameId source_id, dest_id; - - if (!extract(param, record<' '>(&source_id, &dest_id))) - { - PRINTF("You must provide the source and destination item IDs.\n"_fmt); - return 1; - } - - Packet_Fixed<0x7924> fixed_24; - fixed_24.source_item_id = source_id; - fixed_24.dest_item_id = dest_id; - send_fpacket<0x7924, 10>(login_session, fixed_24); - bytes_to_read = 1; // all logging is done to the three main servers - - return 0; -} - //-------------------------------------------- // Sub-function: Asking to modify a memo field //-------------------------------------------- @@ -1552,179 +1444,6 @@ void blockaccount(ZString param) changestatesub(name, 5, "-"_s); // state 5, no error message } -//--------------------------------------------------------------------- -// Sub-function: Add/substract time to the validity limit of an account -//--------------------------------------------------------------------- -static -void timeaddaccount(ZString param) -{ - AccountName name; - HumanTimeDiff modif {}; - - if (!qsplit(param, &name, &modif)) - { - PRINTF("Please input an account name and a modifier.\n"_fmt); - PRINTF(" <example>: timeadd testname +1m-2mn1s-6y\n"_fmt); - PRINTF(" this example adds 1 month and 1 second, and substracts 2 minutes\n"_fmt); - PRINTF(" and 6 years at the same time.\n"_fmt); - LADMIN_LOG("Incomplete parameters to modify a limit time ('timeadd' command).\n"_fmt); - return; - } - if (name.is_print()) - { - return; - } - - if (!modif) - { - PRINTF("Please give an adjustment with this command:\n"_fmt); - PRINTF(" Adjustment value (-1, 1, +1, etc...)\n"_fmt); - PRINTF(" Modified element:\n"_fmt); - PRINTF(" a or y: year\n"_fmt); - PRINTF(" m: month\n"_fmt); - PRINTF(" j or d: day\n"_fmt); - PRINTF(" h: hour\n"_fmt); - PRINTF(" mn: minute\n"_fmt); - PRINTF(" s: second\n"_fmt); - PRINTF(" <example> timeadd testname +1m-2mn1s-6y\n"_fmt); - PRINTF(" this example adds 1 month and 1 second, and substracts 2 minutes\n"_fmt); - PRINTF(" and 6 years at the same time.\n"_fmt); - LADMIN_LOG("No adjustment isn't an adjustment ('timeadd' command).\n"_fmt); - return; - } - - LADMIN_LOG("Request to login-server to modify a time limit.\n"_fmt); - - Packet_Fixed<0x7950> fixed_50; - fixed_50.account_name = name; - fixed_50.valid_add = modif; - send_fpacket<0x7950, 38>(login_session, fixed_50); - bytes_to_read = 1; -} - -//------------------------------------------------- -// Sub-function: Set a validity limit of an account -//------------------------------------------------- -static -void timesetaccount(ZString param) -{ - AccountName name; - XString date; - XString time_; - int year, month, day, hour, minute, second; - - year = month = day = hour = minute = second = 0; - - // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) - TimeT connect_until_time = TimeT(); - struct tm tmtime = connect_until_time; // initialize - - if (!qsplit(param, &name, &date, &time_) - && !qsplit(param, &name, &date)) - { - PRINTF("Please input an account name, a date and a hour.\n"_fmt); - PRINTF("<example>: timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n"_fmt); - PRINTF(" timeset <account_name> 0 (0 = unlimited)\n"_fmt); - PRINTF(" Default time [hh:mm:ss]: 23:59:59.\n"_fmt); - LADMIN_LOG("Incomplete parameters to set a limit time ('timeset' command).\n"_fmt); - return; - } - if (!name.is_print()) - return; - - if (!time_) - time_ = "23:59:59"_s; - - if (date != "0"_s - && ((!extract(date, record<'/'>(&year, &month, &day)) - && !extract(date, record<'-'>(&year, &month, &day)) - && !extract(date, record<'.'>(&year, &month, &day))) - || !extract(time_, record<':'>(&hour, &minute, &second)))) - { - PRINTF("Please input 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n"_fmt); - LADMIN_LOG("Invalid format for the date/time ('timeset' command).\n"_fmt); - return; - } - - if (date == "0"_s) - { - connect_until_time = TimeT(); - } - else - { - if (year < 70) - { - year = year + 100; - } - if (year >= 1900) - { - year = year - 1900; - } - if (month < 1 || month > 12) - { - PRINTF("Please give a correct value for the month (from 1 to 12).\n"_fmt); - LADMIN_LOG("Invalid month for the date ('timeset' command).\n"_fmt); - return; - } - month = month - 1; - if (day < 1 || day > 31) - { - PRINTF("Please give a correct value for the day (from 1 to 31).\n"_fmt); - LADMIN_LOG("Invalid day for the date ('timeset' command).\n"_fmt); - return; - } - if (((month == 3 || month == 5 || month == 8 || month == 10) - && day > 30) ||(month == 1 && day > 29)) - { - PRINTF("Please give a correct value for a day of this month (%d).\n"_fmt, - month); - LADMIN_LOG("Invalid day for this month ('timeset' command).\n"_fmt); - return; - } - if (hour < 0 || hour > 23) - { - PRINTF("Please give a correct value for the hour (from 0 to 23).\n"_fmt); - LADMIN_LOG("Invalid hour for the time ('timeset' command).\n"_fmt); - return; - } - if (minute < 0 || minute > 59) - { - PRINTF("Please give a correct value for the minutes (from 0 to 59).\n"_fmt); - LADMIN_LOG("Invalid minute for the time ('timeset' command).\n"_fmt); - return; - } - if (second < 0 || second > 59) - { - PRINTF("Please give a correct value for the seconds (from 0 to 59).\n"_fmt); - LADMIN_LOG("Invalid second for the time ('timeset' command).\n"_fmt); - return; - } - tmtime.tm_year = year; - tmtime.tm_mon = month; - tmtime.tm_mday = day; - tmtime.tm_hour = hour; - tmtime.tm_min = minute; - tmtime.tm_sec = second; - tmtime.tm_isdst = -1; // -1: no winter/summer time modification - connect_until_time = tmtime; - if (connect_until_time.error()) - { - PRINTF("Invalid date.\n"_fmt); - PRINTF("Please add 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n"_fmt); - LADMIN_LOG("Invalid date. ('timeset' command).\n"_fmt); - return; - } - } - - LADMIN_LOG("Request to login-server to set a time limit.\n"_fmt); - - Packet_Fixed<0x7948> fixed_48; - fixed_48.account_name = name; - fixed_48.valid_until = connect_until_time; - send_fpacket<0x7948, 30>(login_session, fixed_48); - bytes_to_read = 1; -} - //------------------------------------------------------------------------------ // Sub-function: Asking to displaying information about an account (by its name) //------------------------------------------------------------------------------ @@ -1874,8 +1593,6 @@ void prompt(void) infoaccount(wrap<AccountId>(static_cast<uint32_t>(atoi(parameters.c_str())))); else if (command == "kami"_s) sendbroadcast(parameters); // flag for normal - else if (command == "itemfrob"_s) - itemfrob(parameters); // 0: to list all else if (command == "list"_s) listaccount(parameters, 0); // 0: to list all else if (command == "listban"_s) @@ -1898,10 +1615,6 @@ void prompt(void) changesex(parameters); else if (command == "state"_s) changestate(parameters); - else if (command == "timeadd"_s) - timeaddaccount(parameters); - else if (command == "timeset"_s) - timesetaccount(parameters); else if (command == "unban"_s) unbanaccount(parameters); else if (command == "unblock"_s) @@ -1974,7 +1687,7 @@ void parse_fromlogin(Session *s) break; Iprintf(" Login-Server [%s:%d]\n"_fmt, - login_ip, login_port); + admin_conf.login_ip, admin_conf.login_port); Version version = fixed.version; Iprintf(" tmwA version %hhu.%hhu.%hhu (dev? %hhu) (flags %hhx) (which %hhx) (vend %hu)\n"_fmt, version.major, version.minor, version.patch, @@ -1986,17 +1699,6 @@ void parse_fromlogin(Session *s) break; } - case 0x7925: // Itemfrob-OK - { - Packet_Fixed<0x7925> fixed; - rv = recv_fpacket<0x7925, 2>(s, fixed); - if (rv != RecvResult::Complete) - break; - - bytes_to_read = 0; - break; - } - case 0x7921: // Displaying of the list of accounts { std::vector<Packet_Repeat<0x7921>> repeat; @@ -2487,47 +2189,6 @@ void parse_fromlogin(Session *s) break; } - case 0x7949: // answer of an account validity limit set - { - Packet_Fixed<0x7949> fixed; - rv = recv_fpacket<0x7949, 34>(s, fixed); - if (rv != RecvResult::Complete) - break; - - AccountId account_id = fixed.account_id; - AccountName name = fixed.account_name; - if (!account_id) - { - PRINTF("Account [%s] validity limit changing failed. Account doesn't exist.\n"_fmt, - name); - LADMIN_LOG("Account [%s] validity limit changing failed. Account doesn't exist.\n"_fmt, - name); - } - else - { - TimeT timestamp = fixed.valid_until; - if (!timestamp) - { - PRINTF("Validity Limit of the account [%s][id: %d] successfully changed to [unlimited].\n"_fmt, - name, account_id); - LADMIN_LOG("Validity Limit of the account [%s][id: %d] successfully changed to [unlimited].\n"_fmt, - name, account_id); - } - else - { - timestamp_seconds_buffer tmpstr; - stamp_time(tmpstr, ×tamp); - PRINTF("Validity Limit of the account [%s][id: %d] successfully changed to be until %s.\n"_fmt, - name, account_id, tmpstr); - LADMIN_LOG("Validity Limit of the account [%s][id: %d] successfully changed to be until %s.\n"_fmt, - name, account_id, - tmpstr); - } - } - bytes_to_read = 0; - break; - } - case 0x794b: // answer of an account ban set { Packet_Fixed<0x794b> fixed; @@ -2632,50 +2293,6 @@ void parse_fromlogin(Session *s) break; } - case 0x7951: // answer of an account validity limit changing - { - Packet_Fixed<0x7951> fixed; - rv = recv_fpacket<0x7951, 34>(s, fixed); - if (rv != RecvResult::Complete) - break; - - AccountId account_id = fixed.account_id; - AccountName name = fixed.account_name; - if (!account_id) - { - PRINTF("Account [%s] validity limit changing failed. Account doesn't exist.\n"_fmt, - name); - LADMIN_LOG("Account [%s] validity limit changing failed. Account doesn't exist.\n"_fmt, - name); - } - else - { - TimeT timestamp = fixed.valid_until; - if (!timestamp) - { - PRINTF("Validity limit of the account [%s][id: %d] unchanged.\n"_fmt, - name, account_id); - PRINTF("The account have an unlimited validity limit or\n"_fmt); - PRINTF("the changing is impossible with the proposed adjustments.\n"_fmt); - LADMIN_LOG("Validity limit of the account [%s][id: %d] unchanged. The account have an unlimited validity limit or the changing is impossible with the proposed adjustments.\n"_fmt, - name, account_id); - } - else - { - timestamp_seconds_buffer tmpstr; - stamp_time(tmpstr, ×tamp); - PRINTF("Validity limit of the account [%s][id: %d] successfully changed to be until %s.\n"_fmt, - name, account_id, - tmpstr); - LADMIN_LOG("Validity limit of the account [%s][id: %d] successfully changed to be until %s.\n"_fmt, - name, account_id, - tmpstr); - } - } - bytes_to_read = 0; - break; - } - case 0x7953: // answer of a request about informations of an account (by account name/id) { Packet_Head<0x7953> head; @@ -2696,7 +2313,6 @@ void parse_fromlogin(Session *s) timestamp_milliseconds_buffer lastlogin = head.last_login_string; VString<15> last_ip_ = head.ip_string; AccountEmail email = head.email; - TimeT connect_until_time = head.connect_until; TimeT ban_until_time = head.ban_until; AString& memo = repeat; if (!account_id) @@ -2790,17 +2406,9 @@ void parse_fromlogin(Session *s) connections); PRINTF(" Last connection at: %s (ip: %s)\n"_fmt, lastlogin, last_ip_); - if (!connect_until_time) { PRINTF(" Validity limit: unlimited.\n"_fmt); } - else - { - timestamp_seconds_buffer tmpstr; - stamp_time(tmpstr, &connect_until_time); - PRINTF(" Validity limit: until %s.\n"_fmt, - tmpstr); - } PRINTF(" Memo: '%s'\n"_fmt, memo); } } @@ -2836,12 +2444,12 @@ void parse_fromlogin(Session *s) // Function to connect to login-server //------------------------------------ static -int Connect_login_server(void) +int connect_login_server(void) { Iprintf("Attempt to connect to login-server...\n"_fmt); LADMIN_LOG("Attempt to connect to login-server...\n"_fmt); - login_session = make_connection(login_ip, login_port, SessionParsers{.func_parse= parse_fromlogin, .func_delete= delete_fromlogin}); + login_session = make_connection(admin_conf.login_ip, admin_conf.login_port, SessionParsers{.func_parse= parse_fromlogin, .func_delete= delete_fromlogin}); if (!login_session) return 0; @@ -2849,7 +2457,7 @@ int Connect_login_server(void) { Packet_Fixed<0x7918> fixed_18; fixed_18.encryption_zero = 0; - fixed_18.account_pass = admin_pass; + fixed_18.account_pass = admin_conf.admin_pass; send_fpacket<0x7918, 28>(login_session, fixed_18); bytes_to_read = 1; @@ -2861,59 +2469,36 @@ int Connect_login_server(void) } static -bool admin_confs(XString w1, ZString w2) +bool admin_config(io::Spanned<XString> key, io::Spanned<ZString> value) { + return parse_admin_conf(admin_conf, key, value); +} + +static +bool admin_confs(io::Spanned<XString> key, io::Spanned<ZString> value) +{ + if (key.data == "admin_conf"_s) { - if (w1 == "login_ip"_s) - { - struct hostent *h = gethostbyname(w2.c_str()); - if (h != nullptr) - { - Iprintf("Login server IP address: %s -> %s\n"_fmt, - w2, login_ip); - login_ip = IP4Address({ - static_cast<uint8_t>(h->h_addr[0]), - static_cast<uint8_t>(h->h_addr[1]), - static_cast<uint8_t>(h->h_addr[2]), - static_cast<uint8_t>(h->h_addr[3]), - }); - } - } - else if (w1 == "login_port"_s) - { - login_port = atoi(w2.c_str()); - } - else if (w1 == "admin_pass"_s) - { - admin_pass = stringish<AccountPass>(w2); - } - else if (w1 == "ladmin_log_filename"_s) - { - ladmin_log_filename = w2; - } - else - { - PRINTF("WARNING: unknown ladmin config key: %s\n"_fmt, AString(w1)); - return false; - } + return load_config_file(value.data, admin_config); } - return true; + key.span.error("Unknown meta-key for admin nonserver"_s); + return false; } +} // namespace admin //-------------------------------------- // Function called at exit of the server //-------------------------------------- void term_func(void) { - - if (already_exit_function == 0) + if (admin::already_exit_function == 0) { - delete_session(login_session); + delete_session(admin::login_session); Iprintf(SGR_RESET "----End of Ladmin (normal end with closing of all files).\n"_fmt); LADMIN_LOG("----End of Ladmin (normal end with closing of all files).\n"_fmt); - already_exit_function = 1; + admin::already_exit_function = 1; } } @@ -2949,14 +2534,14 @@ int do_init(Slice<ZString> argv) else { loaded_config_yet = true; - runflag &= load_config_file(argvi, admin_confs); + runflag &= load_config_file(argvi, admin::admin_confs); } } if (!loaded_config_yet) - runflag &= load_config_file("conf/tmwa-admin.conf"_s, admin_confs); + runflag &= load_config_file("conf/tmwa-admin.conf"_s, admin::admin_confs); - eathena_interactive_session = isatty(0); + admin::eathena_interactive_session = isatty(0); LADMIN_LOG(""_fmt); LADMIN_LOG("Configuration file readed.\n"_fmt); @@ -2973,8 +2558,9 @@ int do_init(Slice<ZString> argv) LADMIN_LOG("Ladmin is ready.\n"_fmt); Iprintf("Ladmin is " SGR_BOLD SGR_GREEN "ready" SGR_RESET ".\n\n"_fmt); - Connect_login_server(); + admin::connect_login_server(); return 0; } +// namespace admin ends before term_func and do_init } // namespace tmwa diff --git a/src/admin/main.cpp b/src/admin/main.cpp index 1849d80..babf195 100644 --- a/src/admin/main.cpp +++ b/src/admin/main.cpp @@ -17,7 +17,7 @@ // 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 "../mmo/core.hpp" +#include "../high/core.hpp" #include "ladmin.hpp" diff --git a/src/ast/fwd.hpp b/src/ast/fwd.hpp new file mode 100644 index 0000000..24bf545 --- /dev/null +++ b/src/ast/fwd.hpp @@ -0,0 +1,45 @@ +#pragma once +// ast/fwd.hpp - list of type names for new independent tmwa ast +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../sanity.hpp" + +#include "../compat/fwd.hpp" // rank 2 +#include "../io/fwd.hpp" // rank 4 +#include "../net/fwd.hpp" // rank 5 +#include "../sexpr/fwd.hpp" // rank 5 +#include "../mmo/fwd.hpp" // rank 6 +#include "../high/fwd.hpp" // rank 9 +// ast/fwd.hpp is rank 10 + +namespace tmwa +{ +namespace ast +{ +namespace npc +{ +class Warp; +} // namespace npc +namespace script +{ +class ScriptBody; +} // namespace script +} // namespace ast +// meh, add more when I feel like it +} // namespace tmwa diff --git a/src/ast/item.cpp b/src/ast/item.cpp new file mode 100644 index 0000000..bd4f295 --- /dev/null +++ b/src/ast/item.cpp @@ -0,0 +1,155 @@ +#include "item.hpp" +// ast/item.cpp - Structure of itemdb +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../io/extract.hpp" +#include "../io/line.hpp" + +#include "../mmo/extract_enums.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace ast +{ +namespace item +{ + 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))); + } + + static + Result<ast::script::ScriptBody> lex_script(io::LineCharReader& lr) + { + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.implicit_end = true; + opt.one_line = true; + opt.no_event = true; + auto rv = ast::script::parse_script_body(lr, opt); + if (rv.get_success().is_some()) + { + skip_comma_space(lr); + } + return rv; + } + +#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<ItemOrComment>> parse_item(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; + ItemOrComment rv = std::move(comment); + rv.span = first.span; + return Some(Ok(std::move(rv))); + } + Item item; + SPAN_EXTRACT(first, item.id); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.name); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.jname); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.type); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.buy_price); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.sell_price); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.weight); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.atk); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.def); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.range); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.magic_bonus); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.slot_unused); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.gender); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.loc); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.wlv); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.elv); + SPAN_EXTRACT(TRY_UNWRAP(lex_nonscript(lr, false), return EOL_ERROR(lr)), item.view); + item.use_script = TRY(lex_script(lr)); + item.equip_script = TRY(lex_script(lr)); + ItemOrComment rv = std::move(item); + rv.span.begin = item.id.span.begin; + rv.span.end = item.equip_script.span.end; + return Some(Ok(std::move(rv))); + } +} // namespace item +} // namespace ast +} // namespace tmwa diff --git a/src/ast/item.hpp b/src/ast/item.hpp new file mode 100644 index 0000000..a8fe908 --- /dev/null +++ b/src/ast/item.hpp @@ -0,0 +1,82 @@ +#pragma once +// ast/item.hpp - Structure of tmwa itemdb +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU 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" + +#include "script.hpp" + + +namespace tmwa +{ +namespace ast +{ +namespace item +{ + using io::Spanned; + + struct Comment + { + RString comment; + }; + struct Item + { + Spanned<ItemNameId> id; + Spanned<ItemName> name; + Spanned<ItemName> jname; + Spanned<ItemType> type; + Spanned<int> buy_price; + Spanned<int> sell_price; + Spanned<int> weight; + Spanned<int> atk; + Spanned<int> def; + Spanned<int> range; + Spanned<int> magic_bonus; + Spanned<RString> slot_unused; + Spanned<SEX> gender; + Spanned<EPOS> loc; + Spanned<int> wlv; + Spanned<int> elv; + Spanned<ItemLook> view; + ast::script::ScriptBody use_script; + ast::script::ScriptBody equip_script; + }; + + using ItemOrCommentBase = Variant<Comment, Item>; + struct ItemOrComment : ItemOrCommentBase + { + ItemOrComment(Comment o) : ItemOrCommentBase(std::move(o)) {} + ItemOrComment(Item o) : ItemOrCommentBase(std::move(o)) {} + io::LineSpan span; + }; + + Option<Result<ItemOrComment>> parse_item(io::LineCharReader& lr); +} // namespace item +} // namespace ast +} // namespace tmwa diff --git a/src/ast/item_test.cpp b/src/ast/item_test.cpp new file mode 100644 index 0000000..a77662e --- /dev/null +++ b/src/ast/item_test.cpp @@ -0,0 +1,150 @@ +#include "item.hpp" +// ast/item_test.cpp - Testsuite for itemdb parser. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include <gtest/gtest.h> + +#include "../io/line.hpp" + +#include "../tests/fdhack.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace ast +{ +namespace item +{ +#define EXPECT_SPAN(span, bl,bc, el,ec) \ + ({ \ + EXPECT_EQ((span).begin.line, bl); \ + EXPECT_EQ((span).begin.column, bc); \ + EXPECT_EQ((span).end.line, el); \ + EXPECT_EQ((span).end.column, ec); \ + }) + + TEST(itemast, eof) + { + QuietFd q; + LString inputs[] = + { + ""_s, + "\n"_s, + "\n\n"_s, + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = parse_item(lr); + EXPECT_TRUE(res.is_none()); + } + } + TEST(itemast, comment) + { + QuietFd q; + LString inputs[] = + { + //23456789 + "// hello"_s, + "// hello\n "_s, + "// hello\nabc"_s, + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = TRY_UNWRAP(parse_item(lr), FAIL()); + EXPECT_TRUE(res.get_success().is_some()); + auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); + EXPECT_SPAN(top.span, 1,1, 1,8); + auto p = top.get_if<Comment>(); + EXPECT_TRUE(p); + if (p) + { + EXPECT_EQ(p->comment, "// hello"_s); + } + } + } + TEST(itemast, item) + { + QuietFd q; + LString inputs[] = + { + // 1 2 3 4 5 + //2345678901234567890123456789012345678901234567890123456789 + "1,abc , def,3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}"_s, + "1,abc , def,3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}\n"_s, + "1,abc , def,3,4,5,6,7,8,9,10,xx,2,16,12,13,11, {end;}, {}\nabc"_s, + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = TRY_UNWRAP(parse_item(lr), FAIL()); + EXPECT_TRUE(res.get_success().is_some()); + auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); + EXPECT_SPAN(top.span, 1,1, 1,58); + auto p = top.get_if<Item>(); + EXPECT_TRUE(p); + if (p) + { + EXPECT_SPAN(p->id.span, 1,1, 1,1); + EXPECT_EQ(p->id.data, wrap<ItemNameId>(1)); + EXPECT_SPAN(p->name.span, 1,3, 1,6); + EXPECT_EQ(p->name.data, stringish<ItemName>("abc "_s)); + EXPECT_SPAN(p->jname.span, 1,10, 1,12); + EXPECT_EQ(p->jname.data, stringish<ItemName>("def"_s)); + EXPECT_SPAN(p->type.span, 1,14, 1,14); + EXPECT_EQ(p->type.data, ItemType::JUNK); + EXPECT_SPAN(p->buy_price.span, 1,16, 1,16); + EXPECT_EQ(p->buy_price.data, 4); + EXPECT_SPAN(p->sell_price.span, 1,18, 1,18); + EXPECT_EQ(p->sell_price.data, 5); + EXPECT_SPAN(p->weight.span, 1,20, 1,20); + EXPECT_EQ(p->weight.data, 6); + EXPECT_SPAN(p->atk.span, 1,22, 1,22); + EXPECT_EQ(p->atk.data, 7); + EXPECT_SPAN(p->def.span, 1,24, 1,24); + EXPECT_EQ(p->def.data, 8); + EXPECT_SPAN(p->range.span, 1,26, 1,26); + EXPECT_EQ(p->range.data, 9); + EXPECT_SPAN(p->magic_bonus.span, 1,28, 1,29); + EXPECT_EQ(p->magic_bonus.data, 10); + EXPECT_SPAN(p->slot_unused.span, 1,31, 1,32); + EXPECT_EQ(p->slot_unused.data, "xx"_s); + EXPECT_SPAN(p->gender.span, 1,34, 1,34); + EXPECT_EQ(p->gender.data, SEX::NEUTRAL); + EXPECT_SPAN(p->loc.span, 1,36, 1,37); + EXPECT_EQ(p->loc.data, EPOS::MISC1); + EXPECT_SPAN(p->wlv.span, 1,39, 1,40); + EXPECT_EQ(p->wlv.data, 12); + EXPECT_SPAN(p->elv.span, 1,42, 1,43); + EXPECT_EQ(p->elv.data, 13); + EXPECT_SPAN(p->view.span, 1,45, 1,46); + EXPECT_EQ(p->view.data, ItemLook::BOW); + EXPECT_SPAN(p->use_script.span, 1,49, 1,54); + EXPECT_EQ(p->use_script.braced_body, "{end;}"_s); + EXPECT_SPAN(p->equip_script.span, 1,57, 1,58); + EXPECT_EQ(p->equip_script.braced_body, "{}"_s); + } + } + } +} // namespace item +} // namespace ast +} // namespace tmwa diff --git a/src/ast/npc.cpp b/src/ast/npc.cpp new file mode 100644 index 0000000..8d4a43e --- /dev/null +++ b/src/ast/npc.cpp @@ -0,0 +1,620 @@ +#include "npc.hpp" +// ast/npc.cpp - Structure of non-player characters (including mobs). +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" +#include "../io/line.hpp" + +#include "../mmo/extract_enums.hpp" + +#include "../high/extract_mmo.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace ast +{ +namespace npc +{ + using io::respan; + +#define TRY_EXTRACT(bit, var) ({ if (!extract(bit.data, &var.data)) return Err(bit.span.error_str("failed to extract "_s #var)); var.span = bit.span; }) + + static + Result<Warp> parse_warp(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) + { + if (bits.size() != 4) + { + return Err(span.error_str("expect 4 |component|s"_s)); + } + if (bits[0].data.size() != 3) + { + return Err(bits[0].span.error_str("in |component 1| expect 3 ,component,s"_s)); + } + assert(bits[1].data.size() == 1); + assert(bits[1].data[0].data == "warp"_s); + if (bits[2].data.size() != 1) + { + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); + } + if (bits[3].data.size() != 5) + { + return Err(bits[3].span.error_str("in |component 4| expect 5 ,component,s"_s)); + } + + Warp warp; + TRY_EXTRACT(bits[0].data[0], warp.m); + TRY_EXTRACT(bits[0].data[1], warp.x); + TRY_EXTRACT(bits[0].data[2], warp.y); + warp.key_span = bits[1].data[0].span; + TRY_EXTRACT(bits[2].data[0], warp.name); + if (bits[3].data[0].data == "-1"_s) + bits[3].data[0].data = "4294967295"_s; + TRY_EXTRACT(bits[3].data[0], warp.xs); + warp.xs.data += 2; + if (bits[3].data[1].data == "-1"_s) + bits[3].data[1].data = "4294967295"_s; + TRY_EXTRACT(bits[3].data[1], warp.ys); + warp.ys.data += 2; + TRY_EXTRACT(bits[3].data[2], warp.to_m); + TRY_EXTRACT(bits[3].data[3], warp.to_x); + TRY_EXTRACT(bits[3].data[4], warp.to_y); + return Ok(std::move(warp)); + } + static + Result<Shop> parse_shop(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) + { + if (bits.size() != 4) + { + return Err(span.error_str("expect 4 |component|s"_s)); + } + if (bits[0].data.size() != 4) + { + return Err(bits[0].span.error_str("in |component 1| expect 4 ,component,s"_s)); + } + assert(bits[1].data.size() == 1); + assert(bits[1].data[0].data == "shop"_s); + if (bits[2].data.size() != 1) + { + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); + } + if (bits[3].data.size() < 2) + { + return Err(bits[3].span.error_str("in |component 4| expect at least 2 ,component,s"_s)); + } + + Shop shop; + TRY_EXTRACT(bits[0].data[0], shop.m); + TRY_EXTRACT(bits[0].data[1], shop.x); + TRY_EXTRACT(bits[0].data[2], shop.y); + TRY_EXTRACT(bits[0].data[3], shop.d); + shop.key_span = bits[1].data[0].span; + TRY_EXTRACT(bits[2].data[0], shop.name); + TRY_EXTRACT(bits[3].data[0], shop.npc_class); + shop.items.span = bits[3].span; + shop.items.span.begin = bits[3].data[1].span.begin; + for (size_t i = 1; i < bits[3].data.size(); ++i) + { + shop.items.data.emplace_back(); + auto& item = shop.items.data.back(); + auto& data = bits[3].data[i]; + assert(data.span.begin.line == data.span.end.line); + item.span = data.span; + + XString name, value; + if (!extract(data.data, record<':'>(&name, &value))) + return Err(data.span.error_str("Failed to split item:value"_s)); + item.data.name.span = item.span; + item.data.name.span.end.column = item.data.name.span.begin.column + name.size() - 1; + item.data.value.span = item.span; + item.data.value.span.begin.column = item.data.name.span.begin.column + name.size() + 1; + if (name.endswith(' ')) + { + item.data.name.span.warning("Shop item has useless space before the colon"_s); + name = name.rstrip(); + } + if (name.is_digit10()) + { + item.data.name.span.warning("Shop item is an id; should be a name"_s); + } + if (!extract(name, &item.data.name.data)) + { + return Err(item.data.name.span.error_str("item name problem (too long?)"_s)); + } + item.data.value_multiply = false; + if (value.startswith('-')) + { + item.data.value.span.begin.warning("Shop value multiplier should use '*' instead of '-' now"_s); + item.data.value_multiply = true; + item.data.value.span.begin.column += 1; + value = value.xslice_t(1); + } + else if (value.startswith('*')) + { + item.data.value_multiply = true; + item.data.value.span.begin.column += 1; + value = value.xslice_t(1); + } + if (!extract(value, &item.data.value.data)) + { + return Err(item.data.value.span.error_str("invalid item value"_s)); + } + } + return Ok(std::move(shop)); + } + static + Result<Monster> parse_monster(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) + { + if (bits.size() != 4) + { + return Err(span.error_str("expect 4 |component|s"_s)); + } + if (bits[0].data.size() != 3 && bits[0].data.size() != 5) + { + return Err(bits[0].span.error_str("in |component 1| expect 3 or 5 ,component,s"_s)); + } + assert(bits[1].data.size() == 1); + assert(bits[1].data[0].data == "monster"_s); + if (bits[2].data.size() != 1) + { + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); + } + if (bits[3].data.size() != 2 && bits[3].data.size() != 4 && bits[3].data.size() != 5) + { + return Err(bits[3].span.error_str("in |component 4| expect 2, 4, or 5 ,component,s"_s)); + } + + Monster mob; + TRY_EXTRACT(bits[0].data[0], mob.m); + TRY_EXTRACT(bits[0].data[1], mob.x); + TRY_EXTRACT(bits[0].data[2], mob.y); + if (bits[0].data.size() >= 5) + { + TRY_EXTRACT(bits[0].data[3], mob.xs); + TRY_EXTRACT(bits[0].data[4], mob.ys); + } + else + { + mob.xs.data = 0; + mob.xs.span = bits[0].data[2].span; + mob.xs.span.end.column++; + mob.xs.span.begin.column = mob.xs.span.end.column; + mob.ys.data = 0; + mob.ys.span = mob.xs.span; + } + mob.key_span = bits[1].data[0].span; + TRY_EXTRACT(bits[2].data[0], mob.name); + TRY_EXTRACT(bits[3].data[0], mob.mob_class); + TRY_EXTRACT(bits[3].data[1], mob.num); + if (bits[3].data.size() >= 4) + { + static_assert(std::is_same<decltype(mob.delay1.data)::period, std::chrono::milliseconds::period>::value, "delay1 is milliseconds"); + static_assert(std::is_same<decltype(mob.delay2.data)::period, std::chrono::milliseconds::period>::value, "delay2 is milliseconds"); + if (bits[3].data[2].data.is_digit10()) + bits[3].data[2].span.warning("delay1 lacks units; defaulting to ms"_s); + if (bits[3].data[3].data.is_digit10()) + bits[3].data[3].span.warning("delay2 lacks units; defaulting to ms"_s); + TRY_EXTRACT(bits[3].data[2], mob.delay1); + TRY_EXTRACT(bits[3].data[3], mob.delay2); + if (bits[3].data.size() >= 5) + { + TRY_EXTRACT(bits[3].data[4], mob.event); + } + else + { + mob.event.data = NpcEvent(); + mob.event.span = bits[3].data[3].span; + mob.event.span.end.column++; + mob.event.span.begin.column = mob.event.span.end.column; + } + } + else + { + mob.delay1.data = std::chrono::milliseconds::zero(); + mob.delay1.span = bits[3].data[1].span; + mob.delay1.span.end.column++; + mob.delay1.span.begin.column = mob.delay1.span.end.column; + mob.delay2.data = std::chrono::milliseconds::zero(); + mob.delay2.span = mob.delay1.span; + mob.event.data = NpcEvent(); + mob.event.span = mob.delay1.span; + } + return Ok(std::move(mob)); + } + static + Result<MapFlag> parse_mapflag(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) + { + if (bits.size() != 3 && bits.size() != 4) + { + return Err(span.error_str("expect 3 or 4 |component|s"_s)); + } + if (bits[0].data.size() != 1) + { + return Err(bits[0].span.error_str("in |component 1| expect 1 ,component,s"_s)); + } + assert(bits[1].data.size() == 1); + assert(bits[1].data[0].data == "mapflag"_s); + if (bits[2].data.size() != 1) + { + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); + } + + MapFlag mapflag; + TRY_EXTRACT(bits[0].data[0], mapflag.m); + mapflag.key_span = bits[1].data[0].span; + TRY_EXTRACT(bits[2].data[0], mapflag.name); + if (bits.size() >= 4) + { + mapflag.vec_extra.span = bits[3].span; + for (auto& bit : bits[3].data) + { + mapflag.vec_extra.data.emplace_back(); + TRY_EXTRACT(bit, mapflag.vec_extra.data.back()); + } + } + else + { + mapflag.vec_extra.data = {}; + mapflag.vec_extra.span = bits[2].span; + mapflag.vec_extra.span.end.column++; + mapflag.vec_extra.span.begin.column = mapflag.vec_extra.span.end.column; + } + return Ok(std::move(mapflag)); + } + static + Result<ScriptFunction> parse_script_function_head(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) + { + // ScriptFunction: function|script|Fun Name{code} + if (bits.size() != 3) + { + return Err(span.error_str("expect 3 |component|s"_s)); + } + assert(bits[0].data.size() == 1); + assert(bits[0].data[0].data == "function"_s); + assert(bits[1].data.size() == 1); + assert(bits[1].data[0].data == "script"_s); + if (bits[2].data.size() != 1) + { + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); + } + + ScriptFunction script_function; + script_function.key1_span = bits[0].data[0].span; + TRY_EXTRACT(bits[2].data[0], script_function.name); + // also expect '{' and parse real script (in caller) + return Ok(std::move(script_function)); + } + static + Result<ScriptNone> parse_script_none_head(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) + { + // ScriptNone: -|script|script name|32767{code} + if (bits.size() != 4) + { + return Err(span.error_str("expect 4 |component|s"_s)); + } + assert(bits[0].data.size() == 1); + assert(bits[0].data[0].data == "-"_s); + assert(bits[1].data.size() == 1); + assert(bits[1].data[0].data == "script"_s); + if (bits[2].data.size() != 1) + { + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); + } + assert(bits[3].data[0].data == "32767"_s); + if (bits[3].data.size() != 1) + { + return Err(bits[3].span.error_str("in |component 4| should be just 32767"_s)); + } + + ScriptNone script_none; + script_none.key1_span = bits[0].data[0].span; + TRY_EXTRACT(bits[2].data[0], script_none.name); + script_none.key4_span = bits[3].data[0].span; + // also expect '{' and parse real script (in caller) + return Ok(std::move(script_none)); + } + static + Result<ScriptMap> parse_script_map_head(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits) + { + // ScriptMap: m,x,y,d|script|script name|class,xs,ys{code} + if (bits.size() != 4) + { + return Err(span.error_str("expect 4 |component|s"_s)); + } + if (bits[0].data.size() != 4) + { + return Err(bits[0].span.error_str("in |component 1| expect 3 ,component,s"_s)); + } + assert(bits[1].data.size() == 1); + assert(bits[1].data[0].data == "script"_s); + if (bits[2].data.size() != 1) + { + return Err(bits[2].span.error_str("in |component 3| expect 1 ,component,s"_s)); + } + if (bits[3].data.size() != 1 && bits[3].data.size() != 3) + { + return Err(bits[3].span.error_str("in |component 4| expect 1 or 3 ,component,s"_s)); + } + + ScriptMap script_map; + TRY_EXTRACT(bits[0].data[0], script_map.m); + TRY_EXTRACT(bits[0].data[1], script_map.x); + TRY_EXTRACT(bits[0].data[2], script_map.y); + TRY_EXTRACT(bits[0].data[3], script_map.d); + TRY_EXTRACT(bits[2].data[0], script_map.name); + TRY_EXTRACT(bits[3].data[0], script_map.npc_class); + if (bits[3].data.size() >= 3) + { + TRY_EXTRACT(bits[3].data[1], script_map.xs); + script_map.xs.data = script_map.xs.data * 2 + 1; + TRY_EXTRACT(bits[3].data[2], script_map.ys); + script_map.ys.data = script_map.ys.data * 2 + 1; + } + else + { + script_map.xs.data = 0; + script_map.xs.span = script_map.npc_class.span; + script_map.xs.span.end.column++; + script_map.xs.span.begin = script_map.xs.span.end; + script_map.ys.data = 0; + script_map.ys.span = script_map.xs.span; + } + // also expect '{' and parse real script (in caller) + return Ok(std::move(script_map)); + } + static + Result<Script> parse_script_any(io::LineSpan span, std::vector<Spanned<std::vector<Spanned<RString>>>>& bits, io::LineCharReader& lr) + { + // 3 cases: + // ScriptFunction: function|script|Fun Name{code} + // ScriptNone: -|script|script name|32767{code} + // ScriptMap: m,x,y,d|script|script name|class,xs,ys{code} + if (bits[0].data[0].data == "function"_s) + { + Script rv = TRY(parse_script_function_head(span, bits)); + rv.key_span = bits[1].data[0].span; + + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.default_label = "OnCall"_s; + opt.no_event = true; + rv.body = TRY(ast::script::parse_script_body(lr, opt)); + return Ok(std::move(rv)); + } + else if (bits[0].data[0].data == "-"_s) + { + Script rv = TRY(parse_script_none_head(span, bits)); + rv.key_span = bits[1].data[0].span; + + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.no_start = true; + rv.body = TRY(ast::script::parse_script_body(lr, opt)); + return Ok(std::move(rv)); + } + else + { + ScriptMap script_map = TRY(parse_script_map_head(span, bits)); + bool no_touch = !script_map.xs.data && !script_map.ys.data; + Script rv = std::move(script_map); + rv.key_span = bits[1].data[0].span; + + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.default_label = "OnClick"_s; + opt.no_touch = no_touch; + rv.body = TRY(ast::script::parse_script_body(lr, opt)); + return Ok(std::move(rv)); + } + } + + /// Try to extract a top-level token + /// Return None at EOL, or Some(span) + /// This will alternate betweeen returning words and separators + static + Option<Spanned<RString>> lex(io::LineCharReader& lr, bool first) + { + // you know, I just realized a lot of the if (.get()) checks are not + // actually going to fail, since LineCharReader guarantees the \n + // occurs before EOF + io::LineChar c; + // at start of line, skip whitespace + if (first) + { + while (lr.get(c) && (/*c.ch() == ' ' ||*/ c.ch() == '\n')) + { + lr.adv(); + } + } + // at end of file, end of line, or start of script, signal end + if (!lr.get(c) || c.ch() == '\n' || c.ch() == '{') + { + return None; + } + // separators are simple ... NOT. + // Reasonably, we should only ever eat a single char here. + // Unfortunately, we aren't dealing with reasonable people here. + if (c.ch() == '|' || c.ch() == ',') + { + lr.adv(); + while (true) + { + io::LineChar c2; + if (!lr.get(c2) || c2.ch() == '\n' || c2.ch() == '{') + { + c.warning("Separator at EOL"_s); + return None; + } + if (c2.ch() == ',' || c2.ch() == '|') + { + lr.adv(); + c = c2; + c.warning("Adjacent separators"_s); + continue; + } + break; + } + return Some(respan({c, c}, RString(VString<1>(c.ch())))); + } + io::LineSpan span; + MString accum; + accum += c.ch(); + span.begin = c; + span.end = c; + lr.adv(); + if (c.ch() != '/') + first = false; + + // if one-char token followed by an end-of-line or separator, stop + if (!lr.get(c) || c.ch() == '\n' || c.ch() == '{' || c.ch() == ',' || c.ch() == '|') + { + return Some(respan(span, RString(accum))); + } + + accum += c.ch(); + span.end = c; + lr.adv(); + + // if first token on line, can get comment + if (first && c.ch() == '/') + { + while (lr.get(c) && c.ch() != '\n') + { + accum += c.ch(); + span.end = c; + lr.adv(); + } + return Some(respan(span, RString(accum))); + } + // otherwise, collect until an end of line or separator + while (lr.get(c) && c.ch() != '\n' && c.ch() != '{' && c.ch() != ',' && c.ch() != '|') + { + accum += c.ch(); + span.end = c; + lr.adv(); + } + return Some(respan(span, RString(accum))); + } + + Option<Result<TopLevel>> parse_top(io::LineCharReader& in) + { + Spanned<std::vector<Spanned<std::vector<Spanned<RString>>>>> bits; + + // special logic for the first 'bit' + { + Spanned<RString> mayc = TRY_UNWRAP(lex(in, true), + { + io::LineChar c; + if (in.get(c) && c.ch() == '{') + { + return Err(c.error_str("Unexpected script open"_s)); + } + return None; + }); + if (mayc.data.startswith("//"_s)) + { + Comment com; + com.comment = std::move(mayc.data); + TopLevel rv = std::move(com); + rv.span = std::move(mayc.span); + return Some(Ok(std::move(rv))); + } + + if (mayc.data == "|"_s || mayc.data == ","_s) + return Err(mayc.span.error_str("Unexpected separator"_s)); + bits.span = mayc.span; + bits.data.emplace_back(); + bits.data.back().span = mayc.span; + bits.data.back().data.push_back(mayc); + } + + while (true) + { + Spanned<RString> sep = TRY_UNWRAP(lex(in, false), + break); + if (sep.data == "|"_s) + { + bits.data.emplace_back(); + } + else if (sep.data != ","_s) + { + return Err(sep.span.error_str("Expected separator"_s)); + } + + Spanned<RString> word = TRY_UNWRAP(lex(in, false), + return Err(sep.span.error_str("Expected word after separator"_s))); + if (bits.data.back().data.empty()) + bits.data.back().span = word.span; + else + bits.data.back().span.end = word.span.end; + bits.span.end = word.span.end; + bits.data.back().data.push_back(std::move(word)); + } + + if (bits.data.size() < 2) + return Err(bits.span.error_str("Expected a line with |s in it"_s)); + for (auto& bit : bits.data) + { + if (bit.data.empty()) + return Err(bit.span.error_str("Empty components are not cool"_s)); + } + if (bits.data[1].data.size() != 1) + return Err(bits.data[1].span.error_str("Expected a single word in type position"_s)); + Spanned<RString>& w2 = bits.data[1].data[0]; + if (w2.data == "warp"_s) + { + TopLevel rv = TRY(parse_warp(bits.span, bits.data)); + rv.span = bits.span; + return Some(Ok(std::move(rv))); + } + else if (w2.data == "shop"_s) + { + TopLevel rv = TRY(parse_shop(bits.span, bits.data)); + rv.span = bits.span; + return Some(Ok(std::move(rv))); + } + else if (w2.data == "monster"_s) + { + TopLevel rv = TRY(parse_monster(bits.span, bits.data)); + rv.span = bits.span; + return Some(Ok(std::move(rv))); + } + else if (w2.data == "mapflag"_s) + { + TopLevel rv = TRY(parse_mapflag(bits.span, bits.data)); + rv.span = bits.span; + return Some(Ok(std::move(rv))); + } + else if (w2.data == "script"_s) + { + TopLevel rv = TRY_MOVE(parse_script_any(bits.span, bits.data, in)); + rv.span = bits.span; + return Some(Ok(std::move(rv))); + } + else + { + return Err(w2.span.error_str("Unknown type"_s)); + } + } +} // namespace npc +} // namespace ast +} // namespace tmwa diff --git a/src/ast/npc.hpp b/src/ast/npc.hpp new file mode 100644 index 0000000..ca6479e --- /dev/null +++ b/src/ast/npc.hpp @@ -0,0 +1,142 @@ +#pragma once +// ast/npc.hpp - Structure of non-player characters (including mobs). +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include "../compat/result.hpp" + +#include "../io/span.hpp" + +#include "../net/timer.t.hpp" + +#include "../sexpr/variant.hpp" + +#include "../mmo/clif.t.hpp" +#include "../mmo/ids.hpp" +#include "../mmo/strs.hpp" + +#include "script.hpp" + + +namespace tmwa +{ +namespace ast +{ +namespace npc +{ + using io::Spanned; + + struct Comment + { + RString comment; + }; + struct Warp + { + Spanned<MapName> m; + Spanned<unsigned> x, y; + io::LineSpan key_span; + Spanned<NpcName> name; + Spanned<unsigned> xs, ys; + Spanned<MapName> to_m; + Spanned<unsigned> to_x, to_y; + }; + struct ShopItem + { + Spanned<ItemName> name; + bool value_multiply; + Spanned<int> value; + }; + struct Shop + { + Spanned<MapName> m; + Spanned<unsigned> x, y; + Spanned<DIR> d; + io::LineSpan key_span; + Spanned<NpcName> name; + Spanned<Species> npc_class; + Spanned<std::vector<Spanned<ShopItem>>> items; + }; + struct Monster + { + Spanned<MapName> m; + Spanned<unsigned> x, y; + Spanned<unsigned> xs, ys; + io::LineSpan key_span; + Spanned<MobName> name; + Spanned<Species> mob_class; + Spanned<unsigned> num; + Spanned<interval_t> delay1, delay2; + Spanned<NpcEvent> event; + }; + struct MapFlag + { + Spanned<MapName> m; + io::LineSpan key_span; + Spanned<RString> name; + Spanned<std::vector<Spanned<RString>>> vec_extra; + }; + struct ScriptFunction + { + io::LineSpan key1_span; + Spanned<RString> name; + }; + struct ScriptNone + { + io::LineSpan key1_span; + Spanned<NpcName> name; + io::LineSpan key4_span; + }; + struct ScriptMap + { + Spanned<MapName> m; + Spanned<unsigned> x, y; + Spanned<DIR> d; + Spanned<NpcName> name; + Spanned<Species> npc_class; + Spanned<unsigned> xs, ys; + }; + using ScriptBase = Variant<ScriptFunction, ScriptNone, ScriptMap>; + struct Script : ScriptBase + { + Script() = default; + Script(ScriptFunction s) : ScriptBase(std::move(s)) {} + Script(ScriptNone s) : ScriptBase(std::move(s)) {} + Script(ScriptMap s) : ScriptBase(std::move(s)) {} + + io::LineSpan key_span; + ast::script::ScriptBody body; + }; + using TopLevelBase = Variant<Comment, Warp, Shop, Monster, MapFlag, Script>; + struct TopLevel : TopLevelBase + { + TopLevel() = default; + TopLevel(Comment t) : TopLevelBase(std::move(t)) {} + TopLevel(Warp t) : TopLevelBase(std::move(t)) {} + TopLevel(Shop t) : TopLevelBase(std::move(t)) {} + TopLevel(Monster t) : TopLevelBase(std::move(t)) {} + TopLevel(MapFlag t) : TopLevelBase(std::move(t)) {} + TopLevel(Script t) : TopLevelBase(std::move(t)) {} + io::LineSpan span; + }; + + Option<Result<TopLevel>> parse_top(io::LineCharReader& in); +} // namespace npc +} // namespace ast +} // namespace tmwa diff --git a/src/ast/npc_test.cpp b/src/ast/npc_test.cpp new file mode 100644 index 0000000..dadeba7 --- /dev/null +++ b/src/ast/npc_test.cpp @@ -0,0 +1,556 @@ +#include "npc.hpp" +// ast/npc_test.cpp - Testsuite for npc parser. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include <gtest/gtest.h> + +#include "../io/line.hpp" + +#include "../tests/fdhack.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace ast +{ +namespace npc +{ +#define EXPECT_SPAN(span, bl,bc, el,ec) \ + ({ \ + EXPECT_EQ((span).begin.line, bl); \ + EXPECT_EQ((span).begin.column, bc); \ + EXPECT_EQ((span).end.line, el); \ + EXPECT_EQ((span).end.column, ec); \ + }) + + TEST(npcast, eof) + { + QuietFd q; + LString inputs[] = + { + ""_s, + "\n"_s, + "\n\n"_s, + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = parse_top(lr); + EXPECT_TRUE(res.is_none()); + } + } + TEST(npcast, comment) + { + QuietFd q; + LString inputs[] = + { + //23456789 + "// hello"_s, + "// hello\n "_s, + "// hello\nabc"_s, + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); + EXPECT_TRUE(res.get_success().is_some()); + auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); + EXPECT_SPAN(top.span, 1,1, 1,8); + auto p = top.get_if<Comment>(); + EXPECT_TRUE(p); + if (p) + { + EXPECT_EQ(p->comment, "// hello"_s); + } + } + } + TEST(npcast, warp) + { + QuietFd q; + LString inputs[] = + { + // 1 2 3 4 + //234567890123456789012345678901234567890123456789 + "map.gat,1,2|warp|To Other Map|3,4,other.gat,7,8"_s, + "map.gat,1,2|warp|To Other Map|3,4,other.gat,7,8\n"_s, + "map.gat,1,2|warp|To Other Map|3,4,other.gat,7,8{"_s, + // no optional fields in warp + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); + EXPECT_TRUE(res.get_success().is_some()); + auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); + EXPECT_SPAN(top.span, 1,1, 1,47); + auto p = top.get_if<Warp>(); + EXPECT_TRUE(p); + if (p) + { + EXPECT_SPAN(p->m.span, 1,1, 1,7); + EXPECT_EQ(p->m.data, stringish<MapName>("map"_s)); + EXPECT_SPAN(p->x.span, 1,9, 1,9); + EXPECT_EQ(p->x.data, 1); + EXPECT_SPAN(p->y.span, 1,11, 1,11); + EXPECT_EQ(p->y.data, 2); + EXPECT_SPAN(p->key_span, 1,13, 1,16); + EXPECT_SPAN(p->name.span, 1,18, 1,29); + EXPECT_EQ(p->name.data, stringish<NpcName>("To Other Map"_s)); + EXPECT_SPAN(p->xs.span, 1,31, 1,31); + EXPECT_EQ(p->xs.data, 5); + EXPECT_SPAN(p->ys.span, 1,33, 1,33); + EXPECT_EQ(p->ys.data, 6); + EXPECT_SPAN(p->to_m.span, 1,35, 1,43); + EXPECT_EQ(p->to_m.data, stringish<MapName>("other"_s)); + EXPECT_SPAN(p->to_x.span, 1,45, 1,45); + EXPECT_EQ(p->to_x.data, 7); + EXPECT_SPAN(p->to_y.span, 1,47, 1,47); + EXPECT_EQ(p->to_y.data, 8); + } + } + } + TEST(npcast, shop) + { + QuietFd q; + LString inputs[] = + { + // 1 2 3 4 5 + //2345678901234567890123456789012345678901234567890123456789 + "map.gat,1,2,3|shop|Flower Shop|4,5:6,Named:7,Spaced :*8"_s, + "map.gat,1,2,3|shop|Flower Shop|4,5:6,Named:7,Spaced :*8\n"_s, + "map.gat,1,2,3|shop|Flower Shop|4,5:6,Named:7,Spaced :*8{"_s, + // no optional fields in shop + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); + EXPECT_TRUE(res.get_success().is_some()); + auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); + EXPECT_SPAN(top.span, 1,1, 1,55); + auto p = top.get_if<Shop>(); + EXPECT_TRUE(p); + if (p) + { + EXPECT_SPAN(p->m.span, 1,1, 1,7); + EXPECT_EQ(p->m.data, stringish<MapName>("map"_s)); + EXPECT_SPAN(p->x.span, 1,9, 1,9); + EXPECT_EQ(p->x.data, 1); + EXPECT_SPAN(p->y.span, 1,11, 1,11); + EXPECT_EQ(p->y.data, 2); + EXPECT_SPAN(p->d.span, 1,13, 1,13); + EXPECT_EQ(p->d.data, DIR::NW); + EXPECT_SPAN(p->key_span, 1,15, 1,18); + EXPECT_SPAN(p->name.span, 1,20, 1,30); + EXPECT_EQ(p->name.data, stringish<NpcName>("Flower Shop"_s)); + EXPECT_SPAN(p->npc_class.span, 1,32, 1,32); + EXPECT_EQ(p->npc_class.data, wrap<Species>(4)); + EXPECT_SPAN(p->items.span, 1,34, 1,55); + EXPECT_EQ(p->items.data.size(), 3); + EXPECT_SPAN(p->items.data[0].span, 1,34, 1,36); + EXPECT_SPAN(p->items.data[0].data.name.span, 1,34, 1,34); + EXPECT_EQ(p->items.data[0].data.name.data, stringish<ItemName>("5"_s)); + EXPECT_EQ(p->items.data[0].data.value_multiply, false); + EXPECT_SPAN(p->items.data[0].data.value.span, 1,36, 1,36); + EXPECT_EQ(p->items.data[0].data.value.data, 6); + EXPECT_SPAN(p->items.data[1].span, 1,38, 1,44); + EXPECT_SPAN(p->items.data[1].data.name.span, 1,38, 1,42); + EXPECT_EQ(p->items.data[1].data.name.data, stringish<ItemName>("Named"_s)); + EXPECT_EQ(p->items.data[1].data.value_multiply, false); + EXPECT_SPAN(p->items.data[1].data.value.span, 1,44, 1,44); + EXPECT_EQ(p->items.data[1].data.value.data, 7); + EXPECT_SPAN(p->items.data[2].span, 1,46, 1,55); + EXPECT_SPAN(p->items.data[2].data.name.span, 1,46, 1,52); + EXPECT_EQ(p->items.data[2].data.name.data, stringish<ItemName>("Spaced"_s)); + EXPECT_EQ(p->items.data[2].data.value_multiply, true); + EXPECT_SPAN(p->items.data[2].data.value.span, 1,55, 1,55); + EXPECT_EQ(p->items.data[2].data.value.data, 8); + } + } + } + TEST(npcast, monster) + { + QuietFd q; + LString inputs[] = + { + // 1 2 3 4 5 6 + //23456789012345678901234567890123456789012345678901234567890123456789 + "map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000,Npc::Event"_s, + "map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000,Npc::Event\n"_s, + "map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000,Npc::Event{"_s, + "Map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000"_s, + "Map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000\n"_s, + "Map.gat,1,2,3,4|monster|Feeping Creature|5,6,7000,8000{"_s, + "nap.gat,1,20304|monster|Feeping Creature|506,700008000"_s, + "nap.gat,1,20304|monster|Feeping Creature|506,700008000\n"_s, + "nap.gat,1,20304|monster|Feeping Creature|506,700008000{"_s, + }; + for (auto input : inputs) + { + bool first = input.startswith('m'); + bool second = input.startswith('M'); + bool third = input.startswith('n'); + assert(first + second + third == 1); + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); + EXPECT_TRUE(res.get_success().is_some()); + auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); + EXPECT_SPAN(top.span, 1,1, 1,first?65:54); + auto p = top.get_if<Monster>(); + EXPECT_TRUE(p); + if (p) + { + EXPECT_SPAN(p->m.span, 1,1, 1,7); + if (first) + { + EXPECT_EQ(p->m.data, stringish<MapName>("map"_s)); + } + else if (second) + { + EXPECT_EQ(p->m.data, stringish<MapName>("Map"_s)); + } + else + { + EXPECT_EQ(p->m.data, stringish<MapName>("nap"_s)); + } + EXPECT_SPAN(p->x.span, 1,9, 1,9); + EXPECT_EQ(p->x.data, 1); + if (!third) + { + EXPECT_SPAN(p->y.span, 1,11, 1,11); + EXPECT_EQ(p->y.data, 2); + EXPECT_SPAN(p->xs.span, 1,13, 1,13); + EXPECT_EQ(p->xs.data, 3); + EXPECT_SPAN(p->ys.span, 1,15, 1,15); + EXPECT_EQ(p->ys.data, 4); + } + else + { + EXPECT_SPAN(p->y.span, 1,11, 1,15); + EXPECT_EQ(p->y.data, 20304); + EXPECT_SPAN(p->xs.span, 1,16, 1,16); + EXPECT_EQ(p->xs.data, 0); + EXPECT_SPAN(p->ys.span, 1,16, 1,16); + EXPECT_EQ(p->ys.data, 0); + } + EXPECT_SPAN(p->key_span, 1,17, 1,23); + EXPECT_SPAN(p->name.span, 1,25, 1,40); + EXPECT_EQ(p->name.data, stringish<MobName>("Feeping Creature"_s)); + if (!third) + { + EXPECT_SPAN(p->mob_class.span, 1,42, 1,42); + EXPECT_EQ(p->mob_class.data, wrap<Species>(5)); + EXPECT_SPAN(p->num.span, 1,44, 1,44); + EXPECT_EQ(p->num.data, 6); + EXPECT_SPAN(p->delay1.span, 1,46, 1,49); + EXPECT_EQ(p->delay1.data, 7_s); + EXPECT_SPAN(p->delay2.span, 1,51, 1,54); + EXPECT_EQ(p->delay2.data, 8_s); + } + else + { + EXPECT_SPAN(p->mob_class.span, 1,42, 1,44); + EXPECT_EQ(p->mob_class.data, wrap<Species>(506)); + EXPECT_SPAN(p->num.span, 1,46, 1,54); + EXPECT_EQ(p->num.data, 700008000); + EXPECT_SPAN(p->delay1.span, 1,55, 1,55); + EXPECT_EQ(p->delay1.data, 0_s); + EXPECT_SPAN(p->delay2.span, 1,55, 1,55); + EXPECT_EQ(p->delay2.data, 0_s); + } + if (first) + { + EXPECT_SPAN(p->event.span, 1,56, 1,65); + EXPECT_EQ(p->event.data.npc, stringish<NpcName>("Npc"_s)); + EXPECT_EQ(p->event.data.label, stringish<ScriptLabel>("Event"_s)); + } + else + { + EXPECT_SPAN(p->event.span, 1,55, 1,55); + EXPECT_EQ(p->event.data.npc, NpcName()); + EXPECT_EQ(p->event.data.label, ScriptLabel()); + } + } + } + } + TEST(npcast, mapflag) + { + QuietFd q; + LString inputs[] = + { + // 1 2 3 + //23456789012345678901234567890123456789 + "map.gat|mapflag|flagname"_s, + "map.gat|mapflag|flagname\n"_s, + "map.gat|mapflag|flagname{"_s, + "Map.gat|mapflag|flagname|optval"_s, + "Map.gat|mapflag|flagname|optval\n"_s, + "Map.gat|mapflag|flagname|optval{"_s, + "nap.gat|mapflag|flagname|aa,b,c"_s, + "nap.gat|mapflag|flagname|aa,b,c\n"_s, + "nap.gat|mapflag|flagname|aa,b,c{"_s, + }; + for (auto input : inputs) + { + bool first = input.startswith('m'); + bool second = input.startswith('M'); + bool third = input.startswith('n'); + EXPECT_EQ(first + second + third, 1); + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); + EXPECT_TRUE(res.get_success().is_some()); + auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); + EXPECT_SPAN(top.span, 1,1, 1,first?24:31); + auto p = top.get_if<MapFlag>(); + EXPECT_TRUE(p); + if (p) + { + EXPECT_SPAN(p->m.span, 1,1, 1,7); + if (first) + { + EXPECT_EQ(p->m.data, stringish<MapName>("map"_s)); + } + if (second) + { + EXPECT_EQ(p->m.data, stringish<MapName>("Map"_s)); + } + if (third) + { + EXPECT_EQ(p->m.data, stringish<MapName>("nap"_s)); + } + EXPECT_SPAN(p->key_span, 1,9, 1,15); + EXPECT_SPAN(p->name.span, 1,17, 1,24); + EXPECT_EQ(p->name.data, "flagname"_s); + if (first) + { + EXPECT_SPAN(p->vec_extra.span, 1,25, 1,25); + EXPECT_EQ(p->vec_extra.data.size(), 0); + } + if (second) + { + EXPECT_SPAN(p->vec_extra.span, 1,26, 1,31); + EXPECT_EQ(p->vec_extra.data.size(), 1); + EXPECT_SPAN(p->vec_extra.data[0].span, 1,26, 1,31); + EXPECT_EQ(p->vec_extra.data[0].data, "optval"_s); + } + if (third) + { + EXPECT_SPAN(p->vec_extra.span, 1,26, 1,31); + EXPECT_EQ(p->vec_extra.data.size(), 3); + EXPECT_SPAN(p->vec_extra.data[0].span, 1,26, 1,27); + EXPECT_EQ(p->vec_extra.data[0].data, "aa"_s); + EXPECT_SPAN(p->vec_extra.data[1].span, 1,29, 1,29); + EXPECT_EQ(p->vec_extra.data[1].data, "b"_s); + EXPECT_SPAN(p->vec_extra.data[2].span, 1,31, 1,31); + EXPECT_EQ(p->vec_extra.data[2].data, "c"_s); + } + } + } + } + + TEST(npcast, scriptfun) + { + QuietFd q; + LString inputs[] = + { + // 1 2 3 + //23456789012345678901234567890123456789 + "function|script|Fun Name{end;}"_s, + // 123456 + "function|script|Fun Name\n{end;}\n"_s, + // 1234567 + "function|script|Fun Name\n \n {end;} "_s, + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); + EXPECT_TRUE(res.get_success().is_some()); + auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); + EXPECT_SPAN(top.span, 1,1, 1,24); + auto script = top.get_if<Script>(); + EXPECT_TRUE(script); + auto p = script->get_if<ScriptFunction>(); + EXPECT_TRUE(p); + if (p) + { + EXPECT_SPAN(p->key1_span, 1,1, 1,8); + EXPECT_SPAN(script->key_span, 1,10, 1,15); + EXPECT_SPAN(p->name.span, 1,17, 1,24); + EXPECT_EQ(p->name.data, "Fun Name"_s); + if (input.endswith('}')) + { + EXPECT_SPAN(script->body.span, 1,25, 1,30); + } + else if (input.endswith('\n')) + { + EXPECT_SPAN(script->body.span, 2,1, 2,6); + } + else if (input.endswith(' ')) + { + EXPECT_SPAN(script->body.span, 3,2, 3,7); + } + else + { + FAIL(); + } + EXPECT_EQ(script->body.braced_body, "{end;}"_s); + } + } + } + TEST(npcast, scriptnone) + { + QuietFd q; + LString inputs[] = + { + // 1 2 3 + //23456789012345678901234567890123456789 + "-|script|#config|32767{end;}"_s, + // 123456 + "-|script|#config|32767\n{end;}\n"_s, + // 1234567 + "-|script|#config|32767\n \n {end;} "_s, + }; + for (auto input : inputs) + { + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); + EXPECT_TRUE(res.get_success().is_some()); + auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); + EXPECT_SPAN(top.span, 1,1, 1,22); + auto script = top.get_if<Script>(); + EXPECT_TRUE(script); + auto p = script->get_if<ScriptNone>(); + EXPECT_TRUE(p); + if (p) + { + EXPECT_SPAN(p->key1_span, 1,1, 1,1); + EXPECT_SPAN(script->key_span, 1,3, 1,8); + EXPECT_SPAN(p->name.span, 1,10, 1,16); + EXPECT_EQ(p->name.data, stringish<NpcName>("#config"_s)); + EXPECT_SPAN(p->key4_span, 1,18, 1,22); + if (input.endswith('}')) + { + EXPECT_SPAN(script->body.span, 1,23, 1,28); + } + else if (input.endswith('\n')) + { + EXPECT_SPAN(script->body.span, 2,1, 2,6); + } + else if (input.endswith(' ')) + { + EXPECT_SPAN(script->body.span, 3,2, 3,7); + } + else + { + FAIL(); + } + EXPECT_EQ(script->body.braced_body, "{end;}"_s); + } + } + } + TEST(npcast, scriptmap) + { + QuietFd q; + LString inputs[] = + { + // 1 2 3 + //23456789012345678901234567890123456789 + "map.gat,1,2,3|script|Asdf|4,5,6{end;}"_s, + "map.gat,1,2,3|script|Asdf|4,5,6\n{end;}\n"_s, + "map.gat,1,2,3|script|Asdf|4,5,6\n \n {end;} "_s, + "Map.gat,1,2,3|script|Asdf|40506{end;}"_s, + "Map.gat,1,2,3|script|Asdf|40506\n{end;}\n"_s, + "Map.gat,1,2,3|script|Asdf|40506\n \n {end;} "_s, + }; + for (auto input : inputs) + { + bool second = input.startswith('M'); + io::LineCharReader lr(io::from_string, "<string>"_s, input); + auto res = TRY_UNWRAP(parse_top(lr), FAIL()); + EXPECT_TRUE(res.get_success().is_some()); + auto top = TRY_UNWRAP(std::move(res.get_success()), FAIL()); + EXPECT_SPAN(top.span, 1,1, 1,31); + auto script = top.get_if<Script>(); + EXPECT_TRUE(script); + auto p = script->get_if<ScriptMap>(); + EXPECT_TRUE(p); + if (p) + { + EXPECT_SPAN(p->m.span, 1,1, 1,7); + if (!second) + { + EXPECT_EQ(p->m.data, stringish<MapName>("map"_s)); + } + else + { + EXPECT_EQ(p->m.data, stringish<MapName>("Map"_s)); + } + EXPECT_SPAN(p->x.span, 1,9, 1,9); + EXPECT_EQ(p->x.data, 1); + EXPECT_SPAN(p->y.span, 1,11, 1,11); + EXPECT_EQ(p->y.data, 2); + EXPECT_SPAN(p->d.span, 1,13, 1,13); + EXPECT_EQ(p->d.data, DIR::NW); + EXPECT_SPAN(script->key_span, 1,15, 1,20); + EXPECT_SPAN(p->name.span, 1,22, 1,25); + EXPECT_EQ(p->name.data, stringish<NpcName>("Asdf"_s)); + if (!second) + { + EXPECT_SPAN(p->npc_class.span, 1,27, 1,27); + EXPECT_EQ(p->npc_class.data, wrap<Species>(4)); + EXPECT_SPAN(p->xs.span, 1,29, 1,29); + EXPECT_EQ(p->xs.data, 11); + EXPECT_SPAN(p->ys.span, 1,31, 1,31); + EXPECT_EQ(p->ys.data, 13); + } + else + { + EXPECT_SPAN(p->npc_class.span, 1,27, 1,31); + EXPECT_EQ(p->npc_class.data, wrap<Species>(40506)); + EXPECT_SPAN(p->xs.span, 1,32, 1,32); + EXPECT_EQ(p->xs.data, 0); + EXPECT_SPAN(p->ys.span, 1,32, 1,32); + EXPECT_EQ(p->ys.data, 0); + } + if (input.endswith('}')) + { + EXPECT_SPAN(script->body.span, 1,32, 1,37); + } + else if (input.endswith('\n')) + { + EXPECT_SPAN(script->body.span, 2,1, 2,6); + } + else if (input.endswith(' ')) + { + EXPECT_SPAN(script->body.span, 3,2, 3,7); + } + else + { + FAIL(); + } + EXPECT_EQ(script->body.braced_body, "{end;}"_s); + } + } + } +} // namespace npc +} // namespace ast +} // namespace tmwa 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/ast/script.cpp b/src/ast/script.cpp new file mode 100644 index 0000000..ec958e1 --- /dev/null +++ b/src/ast/script.cpp @@ -0,0 +1,75 @@ +#include "script.hpp" +// ast/script.cpp - Structure of tmwa-script +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU 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 "../io/line.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace ast +{ +namespace script +{ + Result<ScriptBody> parse_script_body(io::LineCharReader& lr, ScriptOptions opt) + { + io::LineSpan span; + io::LineChar c; + while (true) + { + if (!lr.get(c)) + { + return Err("error: unexpected EOF before '{' in parse_script_body"_s); + } + if (c.ch() == ' ' || (!opt.one_line && c.ch() == '\n')) + { + lr.adv(); + continue; + } + break; + } + if (c.ch() != '{') + { + return Err(c.error_str("expected opening '{'"_s)); + } + + MString accum; + accum += c.ch(); + span.begin = c; + lr.adv(); + while (true) + { + if (!lr.get(c)) + return Err(c.error_str("unexpected EOF before '}' in parse_script_body"_s)); + if (opt.one_line && c.ch() == '\n') + return Err(c.error_str("unexpected EOL before '}' in parse_script_body"_s)); + accum += c.ch(); + span.end = c; + lr.adv(); + if (c.ch() == '}') + { + return Ok(ScriptBody{RString(accum), std::move(span)}); + } + } + } +} // namespace script +} // namespace ast +} // namespace tmwa diff --git a/src/ast/script.hpp b/src/ast/script.hpp new file mode 100644 index 0000000..74b11e1 --- /dev/null +++ b/src/ast/script.hpp @@ -0,0 +1,112 @@ +#pragma once +// ast/script.hpp - Structure of tmwa-script +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU 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" + + +namespace tmwa +{ +namespace ast +{ +namespace script +{ + using io::Spanned; + + struct ScriptBody + { + RString braced_body; + io::LineSpan span; + }; + + struct ScriptOptions + { + // don't require a label at the beginning + bool implicit_start = false; + // label to generate at the beginning if not already present + RString default_label; + // beginning must be only 'end;' + bool no_start; + // don't requite an 'end;' at the end + bool implicit_end = false; + // forbid newlines anywhere between { and } + bool one_line = false; + // forbid the OnTouch event + bool no_touch = false; + // forbid all events + bool no_event = false; + }; + + Result<ScriptBody> parse_script_body(io::LineCharReader& lr, ScriptOptions opt); + + /* + (-- First bare-body-chunk only allowed for npcs, items, magic, functions. + It is not allowed for events. Basically it's an implicit label at times. + Last normal-lines is only permitted on item and magic scripts. --) + { script-body }: "{" bare-body-chunk? body-chunk* normal-lines? "}" + body-chunk: (comment* labelname ":")+ bare-body-chunk + bare-body-chunk: normal-lines terminator-line + normal-lines: normal-line* + any-line: normal-line + any-line: terminator-line + normal-line: "if" "(" expr ")" any-line + normal-line: normal-command ((expr ",")* expr)? ";" + terminator-line: "menu" (expr, labelname)* expr, labelname ";" + terminator-line: "goto" labelname ";" + terminator-line: terminator ((expr ",")* expr)? ";" + terminator: "return" + terminator: "close" + terminator: "end" + terminator: "mapexit" + terminator: "shop" + + expr: subexpr_-1 + subexpr_N: ("+" | "-" | "!" | "~") subexpr_7 + subexpr_N: simple-expr (op_M subexpr_M | "(" ((expr ",")+ expr)? ")")* if N < M; function call only if N < 8 and preceding simple-expr (op sub)* is a known function + op_0: "||" + op_1: "&&" + op_2: "==" + op_2: "!=" + op_2: ">=" + op_2: ">" + op_2: "<=" + op_2: "<" + op_3: "^" + op_4: "|" + op_5: "&" + op_5: ">>" + op_5: "<<" + op_6: "+" + op_6: "-" + op_7: "*" + op_7: "/" + op_7: "%" + simple-expr: "(" expr ")" + simple-expr: integer + simple-expr: string + simple-expr: variable ("[" expr "]")? + simple-expr: function // no longer command/label though + */ +} // namespace script +} // namespace ast +} // namespace tmwa diff --git a/src/char/char.cpp b/src/char/char.cpp index d5e887b..ed9e369 100644 --- a/src/char/char.cpp +++ b/src/char/char.cpp @@ -49,13 +49,13 @@ #include "../generic/array.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" +#include "../io/extract.hpp" #include "../io/lock.hpp" #include "../io/read.hpp" +#include "../io/span.hpp" #include "../io/tty.hpp" #include "../io/write.hpp" -#include "../net/packets.hpp" #include "../net/socket.hpp" #include "../net/timer.hpp" @@ -66,15 +66,23 @@ #include "../proto2/char-user.hpp" #include "../mmo/config_parse.hpp" -#include "../mmo/core.hpp" -#include "../mmo/extract.hpp" +#include "../mmo/cxxstdio_enums.hpp" #include "../mmo/extract_enums.hpp" #include "../mmo/human_time_diff.hpp" -#include "../mmo/mmo.hpp" -#include "../mmo/utils.hpp" #include "../mmo/version.hpp" +#include "../high/core.hpp" +#include "../high/extract_mmo.hpp" +#include "../high/mmo.hpp" +#include "../high/utils.hpp" + +#include "../wire/packets.hpp" + +#include "char_conf.hpp" +#include "char_lan_conf.hpp" +#include "globals.hpp" #include "inter.hpp" +#include "inter_conf.hpp" #include "int_party.hpp" #include "int_storage.hpp" @@ -83,58 +91,8 @@ namespace tmwa { -static -Array<struct mmo_map_server, MAX_MAP_SERVERS> server; -static -Array<Session *, MAX_MAP_SERVERS> server_session; -static -Array<int, MAX_MAP_SERVERS> server_freezeflag; // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed -static -int anti_freeze_enable = 0; -static -std::chrono::seconds anti_freeze_interval = 6_s; - -constexpr -std::chrono::milliseconds DEFAULT_AUTOSAVE_INTERVAL = - 5_min; - -static -Session *login_session, *char_session; -static -AccountName userid; -static -AccountPass passwd; -static -ServerName server_name; -static -CharName wisp_server_name = stringish<CharName>("Server"_s); -static -IP4Address login_ip; -static -int login_port = 6900; -static -IP4Address char_ip; -static -int char_port = 6121; -static -AString char_txt; -static -CharName unknown_char_name = stringish<CharName>("Unknown"_s); -static -AString char_log_filename = "log/char.log"_s; -//Added for lan support -static -IP4Address lan_map_ip = IP4_LOCALHOST; -static -IP4Mask lan_subnet = IP4Mask(IP4_LOCALHOST, IP4_BROADCAST); -static -int char_name_option = 0; // Option to know which letters/symbols are authorised in the name of a character (0: all, 1: only those in char_name_letters, 2: all EXCEPT those in char_name_letters) by [Yor] -static -std::bitset<256> char_name_letters; // list of letters/symbols authorised (or not) in a character name. by [Yor] -static constexpr -GmLevel default_gm_level = GmLevel::from(0_u32); - - +namespace char_ +{ struct char_session_data : SessionData { AccountId account_id; @@ -142,69 +100,16 @@ struct char_session_data : SessionData SEX sex; unsigned short packet_tmw_version; AccountEmail email; - TimeT connect_until_time; }; +} // namespace char_ void SessionDeleter::operator()(SessionData *sd) { - really_delete1 static_cast<char_session_data *>(sd); + really_delete1 static_cast<char_::char_session_data *>(sd); } -struct AuthFifoEntry +namespace char_ { - AccountId account_id; - CharId char_id; - int login_id1, login_id2; - IP4Address ip; - int delflag; - SEX sex; - unsigned short packet_tmw_version; - TimeT connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) -}; -static -std::array<AuthFifoEntry, 256> auth_fifo; -static -auto auth_fifo_iter = auth_fifo.begin(); - -static -int check_ip_flag = 1; // It's to check IP of a player between char-server and other servers (part of anti-hacking system) - -static -CharId char_id_count = wrap<CharId>(150000); -static -std::vector<CharPair> char_keys; -static -int max_connect_user = 0; -static -std::chrono::milliseconds autosave_time = DEFAULT_AUTOSAVE_INTERVAL; - -// Initial position (it's possible to set it in conf file) -static -Point start_point = { {"001-1.gat"_s}, 273, 354 }; - -static -std::vector<GM_Account> gm_accounts; - -// online players by [Yor] -static -AString online_txt_filename = "online.txt"_s; -static -AString online_html_filename = "online.html"_s; -static -int online_sorting_option = 0; // sorting option to display online players in online files -static -int online_refresh_html = 20; // refresh time (in sec) of the html file in the explorer -static -GmLevel online_gm_display_min_level = GmLevel::from(20_u32); // minimum GM level to display 'GM' when we want to display it - -static -std::vector<Session *> online_chars; // same size of char_keys, and id value of current server (or -1) -static -TimeT update_online; // to update online files when we receiving information from a server (not less than 8 seconds) - -static -pid_t pid = 0; // For forked DB writes - auto iter_map_sessions() -> decltype(filter_iterator<Session *>(std::declval<Array<Session *, MAX_MAP_SERVERS> *>())) { @@ -250,7 +155,7 @@ void delete_frommap(Session *sess) //------------------------------ void char_log(XString line) { - io::AppendFile logfp(char_log_filename, true); + io::AppendFile logfp(char_conf.char_log_filename, true); if (!logfp.is_open()) return; log_with_timestamp(logfp, line); @@ -327,7 +232,7 @@ AString mmo_char_tostr(struct CharPair *cp) // on multi-map server, sometimes it's posssible that last_point become void. (reason???) We check that to not lost character at restart. if (!p->last_point.map_) { - p->last_point = start_point; + p->last_point = char_conf.start_point; } MString str_p; @@ -414,12 +319,6 @@ AString mmo_char_tostr(struct CharPair *cp) return AString(str_p); } -static -bool extract(XString str, Point *p) -{ - return extract(str, record<','>(&p->map_, &p->x, &p->y)); -} - struct skill_loader { SkillID id; @@ -428,7 +327,7 @@ struct skill_loader }; static -bool extract(XString str, struct skill_loader *s) +bool impl_extract(XString str, struct skill_loader *s) { uint32_t flags_and_level; if (!extract(str, @@ -438,13 +337,16 @@ bool extract(XString str, struct skill_loader *s) s->flags = SkillFlags(flags_and_level >> 16); return true; } +} // namespace char //------------------------------------------------------------------------- // Function to set the character from the line (at read of characters file) //------------------------------------------------------------------------- static -bool extract(XString str, CharPair *cp) +bool impl_extract(XString str, CharPair *cp) { + using namespace tmwa::char_; + CharKey *k = &cp->key; CharData *p = cp->data.get(); @@ -489,7 +391,7 @@ bool extract(XString str, CharPair *cp) else if (!extract(hair_style, &p->hair)) return false; - if (wisp_server_name == k->name) + if (WISP_SERVER_NAME == k->name) return false; // TODO replace *every* lookup with a map lookup @@ -532,6 +434,8 @@ bool extract(XString str, CharPair *cp) return true; } +namespace char_ +{ //--------------------------------- // Function to read characters file //--------------------------------- @@ -541,11 +445,11 @@ int mmo_char_init(void) char_keys.clear(); online_chars.clear(); - io::ReadFile in(char_txt); + io::ReadFile in(char_conf.char_txt); if (!in.is_open()) { - PRINTF("Characters file not found: %s.\n"_fmt, char_txt); - CHAR_LOG("Characters file not found: %s.\n"_fmt, char_txt); + PRINTF("Characters file not found: %s.\n"_fmt, char_conf.char_txt); + CHAR_LOG("Characters file not found: %s.\n"_fmt, char_conf.char_txt); CHAR_LOG("Id for the next created character: %d.\n"_fmt, char_id_count); return 0; @@ -583,9 +487,9 @@ int mmo_char_init(void) } PRINTF("mmo_char_init: %zu characters read in %s.\n"_fmt, - char_keys.size(), char_txt); + char_keys.size(), char_conf.char_txt); CHAR_LOG("mmo_char_init: %zu characters read in %s.\n"_fmt, - char_keys.size(), char_txt); + char_keys.size(), char_conf.char_txt); CHAR_LOG("Id for the next created character: %d.\n"_fmt, char_id_count); @@ -599,7 +503,7 @@ int mmo_char_init(void) static void mmo_char_sync(void) { - io::WriteLock fp(char_txt); + io::WriteLock fp(char_conf.char_txt); if (!fp.is_open()) { PRINTF("WARNING: Server can't not save characters.\n"_fmt); @@ -687,28 +591,18 @@ CharPair *make_new_char(Session *s, CharName name, const Stats6& stats, uint8_t } // Check Authorised letters/symbols in the name of the character - if (char_name_option == 1) { // only letters/symbols in char_name_letters are authorised for (uint8_t c : name.to__actual()) - if (!char_name_letters[c]) + { + if (!char_conf.char_name_letters[c]) { CHAR_LOG("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c.\n"_fmt, s, sd->account_id, name, c); return nullptr; } + } } - else if (char_name_option == 2) - { - // letters/symbols in char_name_letters are forbidden - for (uint8_t c : name.to__actual()) - if (char_name_letters[c]) - { - CHAR_LOG("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c.\n"_fmt, - s, sd->account_id, name, c); - return nullptr; - } - } // else, all letters/symbols are authorised (except control char removed before) // TODO this comment is obsolete // this is why it needs to be unsigned @@ -763,10 +657,10 @@ CharPair *make_new_char(Session *s, CharName name, const Stats6& stats, uint8_t } } - if (wisp_server_name == name) + if (WISP_SERVER_NAME == name) { CHAR_LOG("Make new char error (name used is wisp name for server): (connection #%d, account: %d) slot %d, name: %s (actual name whisper server: %s), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d.\n"_fmt, - s, sd->account_id, slot, name, wisp_server_name, + s, sd->account_id, slot, name, WISP_SERVER_NAME, stats.str, stats.agi, stats.vit, stats.int_, stats.dex, stats.luk, stats.str + stats.agi + stats.vit + stats.int_ + stats.dex + stats.luk, hair_style, hair_color); @@ -807,7 +701,7 @@ CharPair *make_new_char(Session *s, CharName name, const Stats6& stats, uint8_t cd.sp = cd.max_sp; cd.status_point = 0; cd.skill_point = 0; - cd.option = static_cast<Option>(0x0000); // Option is only declared + cd.option = static_cast<Opt0>(0x0000); // Opt0 is only declared cd.karma = 0; cd.manner = 0; cd.party_id = PartyId(); @@ -821,8 +715,8 @@ CharPair *make_new_char(Session *s, CharName name, const Stats6& stats, uint8_t cd.head_top = ItemNameId(); cd.head_mid = ItemNameId(); cd.head_bottom = ItemNameId(); - cd.last_point = start_point; - cd.save_point = start_point; + cd.last_point = char_conf.start_point; + cd.save_point = char_conf.start_point; char_keys.push_back(std::move(cp)); online_chars.push_back(nullptr); @@ -836,10 +730,10 @@ static void create_online_files(void) { // write files - io::WriteFile fp(online_txt_filename); + io::WriteFile fp(char_conf.online_txt_filename); if (fp.is_open()) { - io::WriteFile fp2(online_html_filename); + io::WriteFile fp2(char_conf.online_html_filename); if (fp2.is_open()) { // get time @@ -847,15 +741,15 @@ void create_online_files(void) stamp_time(timetemp); // write heading FPRINTF(fp2, "<HTML>\n"_fmt); - FPRINTF(fp2, " <META http-equiv=\"Refresh\" content=\"%d\">\n"_fmt, online_refresh_html); // update on client explorer every x seconds + FPRINTF(fp2, " <META http-equiv=\"Refresh\" content=\"%d\">\n"_fmt, char_conf.online_refresh_html); // update on client explorer every x seconds FPRINTF(fp2, " <HEAD>\n"_fmt); FPRINTF(fp2, " <TITLE>Online Players on %s</TITLE>\n"_fmt, - server_name); + char_conf.server_name); FPRINTF(fp2, " </HEAD>\n"_fmt); FPRINTF(fp2, " <BODY>\n"_fmt); FPRINTF(fp2, " <H3>Online Players on %s (%s):</H3>\n"_fmt, - server_name, timetemp); - FPRINTF(fp, "Online Players on %s (%s):\n"_fmt, server_name, timetemp); + char_conf.server_name, timetemp); + FPRINTF(fp, "Online Players on %s (%s):\n"_fmt, char_conf.server_name, timetemp); FPRINTF(fp, "\n"_fmt); int players = 0; @@ -891,14 +785,14 @@ void create_online_files(void) // without/with 'GM' display GmLevel gml = isGM(cd.key.account_id); { - if (gml.satisfies(online_gm_display_min_level)) + if (gml.satisfies(char_conf.online_gm_display_min_level)) FPRINTF(fp, "%-24s (GM) "_fmt, cd.key.name); else FPRINTF(fp, "%-24s "_fmt, cd.key.name); } // name of the character in the html (no < >, because that create problem in html code) FPRINTF(fp2, " <td>"_fmt); - if (gml.satisfies(online_gm_display_min_level)) + if (gml.satisfies(char_conf.online_gm_display_min_level)) FPRINTF(fp2, "<b>"_fmt); for (char c : cd.key.name.to__actual()) { @@ -918,7 +812,7 @@ void create_online_files(void) break; }; } - if (gml.satisfies(online_gm_display_min_level)) + if (gml.satisfies(char_conf.online_gm_display_min_level)) FPRINTF(fp2, "</b> (GM)"_fmt); FPRINTF(fp2, "</td>\n"_fmt); } @@ -1278,13 +1172,12 @@ void parse_tologin(Session *ls) fixed_6c.code = 0x42; send_fpacket<0x006c, 3>(s2, fixed_6c); } - else if (max_connect_user == 0 - || count_users() < max_connect_user) + else if (char_conf.max_connect_user == 0 + || count_users() < char_conf.max_connect_user) { sd->email = stringish<AccountEmail>(fixed.email); if (!e_mail_check(sd->email)) sd->email = DEFAULT_EMAIL; - sd->connect_until_time = fixed.connect_until; // send characters to player mmo_char_send006b(s2, sd); } @@ -1323,7 +1216,6 @@ void parse_tologin(Session *ls) sd->email = fixed.email; if (!e_mail_check(sd->email)) sd->email = DEFAULT_EMAIL; - sd->connect_until_time = fixed.connect_until; break; } } @@ -1331,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; @@ -1481,65 +1351,6 @@ void parse_tologin(Session *ls) break; } - case 0x7924: - { // [Fate] Itemfrob package: forwarded from login-server - Packet_Fixed<0x7924> fixed; - rv = recv_fpacket<0x7924, 10>(ls, fixed); - if (rv != RecvResult::Complete) - break; - - ItemNameId source_id = fixed.source_item_id; - ItemNameId dest_id = fixed.dest_item_id; - - Packet_Fixed<0x2afa> fixed_fa; - fixed_fa.source_item_id = source_id; - fixed_fa.dest_item_id = dest_id; - - // forward package to map servers - for (Session *ss : iter_map_sessions()) - { - send_fpacket<0x2afa, 10>(ss, fixed_fa); - } - - for (CharPair& cp : char_keys) - { - CharKey *k = &cp.key; - CharData& cd = *cp.data.get(); - CharData *c = &cd; - Storage *s = account2storage(k->account_id); - int changes = 0; -#define FIX(v) if (v == source_id) {v = dest_id; ++changes; } - for (IOff0 j : IOff0::iter()) - { - FIX(c->inventory[j].nameid); - } - // used to FIX cart, but it's no longer supported - // FIX(c->weapon); - FIX(c->shield); - FIX(c->head_top); - FIX(c->head_mid); - FIX(c->head_bottom); - - if (s) - { - for (SOff0 j : SOff0::iter()) - { - FIX(s->storage_[j].nameid); - } - } -#undef FIX - if (changes) - CHAR_LOG("itemfrob(%d -> %d): `%s'(%d, account %d): changed %d times\n"_fmt, - source_id, dest_id, k->name, k->char_id, - k->account_id, changes); - - } - - mmo_char_sync(); - inter_storage_save(); - break; - } - // Account deletion notification (from login-server) case 0x2730: { @@ -1737,22 +1548,6 @@ void parse_frommap(Session *ms) { switch (packet_id) { - // request from map-server to reload GM accounts. Transmission to login-server (by Yor) - case 0x2af7: - { - Packet_Fixed<0x2af7> fixed; - rv = recv_fpacket<0x2af7, 2>(ms, fixed); - if (rv != RecvResult::Complete) - break; - - if (login_session) - { // don't send request if no login-server - Packet_Fixed<0x2709> fixed_09; - send_fpacket<0x2709, 2>(login_session, fixed_09); - } - break; - } - // Receiving map names list from the map-server case 0x2afa: { @@ -1781,7 +1576,6 @@ void parse_frommap(Session *ms) Packet_Fixed<0x2afb> fixed_fb; fixed_fb.unknown = 0; - fixed_fb.whisper_name = wisp_server_name; send_fpacket<0x2afb, 27>(ms, fixed_fb); { @@ -1857,7 +1651,7 @@ void parse_frommap(Session *ms) afi.login_id1 == login_id1 && // here, it's the only area where it's possible that we doesn't know login_id2 (map-server asks just after 0x72 packet, that doesn't given the value) (afi.login_id2 == login_id2 || login_id2 == 0) && // relate to the versions higher than 18 - (!check_ip_flag || afi.ip == ip) + afi.ip == ip && !afi.delflag) { CharPair *cp = nullptr; @@ -1878,7 +1672,6 @@ void parse_frommap(Session *ms) Packet_Payload<0x2afd> payload_fd; // not file descriptor payload_fd.account_id = account_id; payload_fd.login_id2 = afi.login_id2; - payload_fd.connect_until = afi.connect_until_time; cd->sex = afi.sex; payload_fd.packet_tmw_version = afi.packet_tmw_version; FPRINTF(stderr, @@ -1912,7 +1705,7 @@ void parse_frommap(Session *ms) server[id].users = head.users; assert (head.users == repeat.size()); - if (anti_freeze_enable) + if (char_conf.anti_freeze_enable) server_freezeflag[id] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed // remove all previously online players of the server for (Session *& oci : online_chars) @@ -1984,7 +1777,6 @@ void parse_frommap(Session *ms) auth_fifo_iter->login_id1 = fixed.login_id1; auth_fifo_iter->login_id2 = fixed.login_id2; auth_fifo_iter->delflag = 2; - auth_fifo_iter->connect_until_time = TimeT(); // unlimited/unknown time by default (not display in map-server) auth_fifo_iter->ip = fixed.ip; auth_fifo_iter++; @@ -2023,7 +1815,6 @@ void parse_frommap(Session *ms) auth_fifo_iter->char_id = fixed.char_id; auth_fifo_iter->delflag = 0; auth_fifo_iter->sex = fixed.sex; - auth_fifo_iter->connect_until_time = TimeT(); // unlimited/unknown time by default (not display in map-server) auth_fifo_iter->ip = fixed.client_ip; // default, if not found in the loop @@ -2044,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: { @@ -2336,7 +2100,7 @@ int search_mapserver(XString map) static int lan_ip_check(IP4Address addr) { - bool lancheck = lan_subnet.covers(addr); + bool lancheck = char_lan_conf.lan_subnet.covers(addr); PRINTF("LAN test (result): %s.\n"_fmt, (lancheck) ? SGR_BOLD SGR_CYAN "LAN source" SGR_RESET ""_s : SGR_BOLD SGR_GREEN "WAN source" SGR_RESET ""_s); @@ -2401,7 +2165,7 @@ void handle_x0066(Session *s, struct char_session_data *sd, uint8_t rfifob_2, IP sd->account_id, ck->char_num, ip); PRINTF("--Send IP of map-server. "_fmt); if (lan_ip_check(ip)) - fixed_71.ip = lan_map_ip; + fixed_71.ip = char_lan_conf.lan_map_ip; else fixed_71.ip = server[i].ip; fixed_71.port = server[i].port; @@ -2415,7 +2179,6 @@ void handle_x0066(Session *s, struct char_session_data *sd, uint8_t rfifob_2, IP auth_fifo_iter->login_id2 = sd->login_id2; auth_fifo_iter->delflag = 0; auth_fifo_iter->sex = sd->sex; - auth_fifo_iter->connect_until_time = sd->connect_until_time; auth_fifo_iter->ip = s->client_ip; auth_fifo_iter->packet_tmw_version = sd->packet_tmw_version; auth_fifo_iter++; @@ -2484,7 +2247,6 @@ void parse_char(Session *s) s->session_data = make_unique<char_session_data, SessionDeleter>(); sd = static_cast<char_session_data *>(s->session_data.get()); sd->email = stringish<AccountEmail>("no mail"_s); // put here a mail without '@' to refuse deletion if we don't receive the e-mail - sd->connect_until_time = TimeT(); // unknow or illimited (not displaying on map-server) } sd->account_id = account_id; sd->login_id1 = fixed.login_id1; @@ -2503,13 +2265,12 @@ void parse_char(Session *s) if (afi.account_id == sd->account_id && afi.login_id1 == sd->login_id1 && afi.login_id2 == sd->login_id2 - && (!check_ip_flag - || afi.ip == s->client_ip) + && afi.ip == s->client_ip && afi.delflag == 2) { afi.delflag = 1; - if (max_connect_user == 0 - || count_users() < max_connect_user) + if (char_conf.max_connect_user == 0 + || count_users() < char_conf.max_connect_user) { { // there is always a login server @@ -2608,7 +2369,7 @@ void parse_char(Session *s) fixed_6d.char_select.gloves = ItemNameId(); fixed_6d.char_select.cape = ItemNameId(); fixed_6d.char_select.misc1 = ItemNameId(); - fixed_6d.char_select.option = Option(); + fixed_6d.char_select.option = Opt0(); fixed_6d.char_select.unused = 0; // this was buggy until the protocol became generated @@ -2663,9 +2424,6 @@ void parse_char(Session *s) s->set_eof(); return; } - AccountEmail email = fixed.email; - if (!e_mail_check(email)) - email = DEFAULT_EMAIL; { { @@ -2723,8 +2481,8 @@ void parse_char(Session *s) } AccountName userid_ = fixed.account_name; AccountPass passwd_ = fixed.account_pass; - if (i == MAX_MAP_SERVERS || userid_ != userid - || passwd_ != passwd) + if (i == MAX_MAP_SERVERS || userid_ != char_conf.userid + || passwd_ != char_conf.passwd) { fixed_f9.code = 3; send_fpacket<0x2af9, 3>(s, fixed_f9); @@ -2734,7 +2492,7 @@ void parse_char(Session *s) fixed_f9.code = 0; s->set_parsers(SessionParsers{.func_parse= parse_frommap, .func_delete= delete_frommap}); server_session[i] = s; - if (anti_freeze_enable) + if (char_conf.anti_freeze_enable) server_freezeflag[i] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed // ignore fixed.unknown server[i].ip = fixed.ip; @@ -2822,19 +2580,19 @@ void check_connect_login_server(TimerData *, tick_t) if (!login_session) { PRINTF("Attempt to connect to login-server...\n"_fmt); - login_session = make_connection(login_ip, login_port, + login_session = make_connection(char_conf.login_ip, char_conf.login_port, SessionParsers{.func_parse= parse_tologin, .func_delete= delete_tologin}); if (!login_session) return; realloc_fifo(login_session, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); Packet_Fixed<0x2710> fixed_10; - fixed_10.account_name = userid; - fixed_10.account_pass = passwd; + fixed_10.account_name = char_conf.userid; + fixed_10.account_pass = char_conf.passwd; fixed_10.unknown = 0; - fixed_10.ip = char_ip; - fixed_10.port = char_port; - fixed_10.server_name = server_name; + fixed_10.ip = char_conf.char_ip; + fixed_10.port = char_conf.char_port; + fixed_10.server_name = char_conf.server_name; fixed_10.unknown2 = 0; fixed_10.maintenance = 0; fixed_10.is_new = 0; @@ -2842,61 +2600,13 @@ void check_connect_login_server(TimerData *, tick_t) } } -//------------------------------------------- -// Reading Lan Support configuration by [Yor] -//------------------------------------------- -static -bool char_lan_config(XString w1, ZString w2) -{ - struct hostent *h = nullptr; - - { - if (w1 == "lan_map_ip"_s) - { - // Read map-server Lan IP Address - h = gethostbyname(w2.c_str()); - if (h != nullptr) - { - lan_map_ip = IP4Address({ - static_cast<uint8_t>(h->h_addr[0]), - static_cast<uint8_t>(h->h_addr[1]), - static_cast<uint8_t>(h->h_addr[2]), - static_cast<uint8_t>(h->h_addr[3]), - }); - } - else - { - PRINTF("Bad IP value: %s\n"_fmt, w2); - return false; - } - PRINTF("LAN IP of map-server: %s.\n"_fmt, lan_map_ip); - } - else if (w1 == "subnet"_s /*backward compatibility*/ - || w1 == "lan_subnet"_s) - { - if (!extract(w2, &lan_subnet)) - { - PRINTF("Bad IP mask: %s\n"_fmt, w2); - return false; - } - PRINTF("Sub-network of the map-server: %s.\n"_fmt, - lan_subnet); - } - else - { - return false; - } - } - return true; -} - static bool lan_check() { // sub-network check of the map-server { PRINTF("LAN test of LAN IP of the map-server: "_fmt); - if (!lan_ip_check(lan_map_ip)) + if (!lan_ip_check(char_lan_conf.lan_map_ip)) { PRINTF(SGR_BOLD SGR_RED "***ERROR: LAN IP of the map-server doesn't belong to the specified Sub-network." SGR_RESET "\n"_fmt); return false; @@ -2907,161 +2617,46 @@ bool lan_check() } static -bool char_config(XString w1, ZString w2) +bool char_config(io::Spanned<XString> key, io::Spanned<ZString> value) { - struct hostent *h = nullptr; + return parse_char_conf(char_conf, key, value); +} +static +bool char_lan_config(io::Spanned<XString> key, io::Spanned<ZString> value) +{ + return parse_char_lan_conf(char_lan_conf, key, value); +} + +static +bool inter_config(io::Spanned<XString> key, io::Spanned<ZString> value) +{ + return parse_inter_conf(inter_conf, key, value); +} + +static +bool char_confs(io::Spanned<XString> key, io::Spanned<ZString> value) +{ + if (key.data == "char_conf"_s) { - if (w1 == "userid"_s) - userid = stringish<AccountName>(w2); - else if (w1 == "passwd"_s) - passwd = stringish<AccountPass>(w2); - else if (w1 == "server_name"_s) - { - server_name = stringish<ServerName>(w2); - PRINTF("%s server has been intialized\n"_fmt, w2); - } - else if (w1 == "wisp_server_name"_s) - { - if (w2.size() >= 4) - wisp_server_name = stringish<CharName>(w2); - } - else if (w1 == "login_ip"_s) - { - h = gethostbyname(w2.c_str()); - if (h != nullptr) - { - login_ip = IP4Address({ - static_cast<uint8_t>(h->h_addr[0]), - static_cast<uint8_t>(h->h_addr[1]), - static_cast<uint8_t>(h->h_addr[2]), - static_cast<uint8_t>(h->h_addr[3]), - }); - PRINTF("Login server IP address : %s -> %s\n"_fmt, - w2, login_ip); - } - else - { - PRINTF("Bad IP value: %s\n"_fmt, w2); - return false; - } - } - else if (w1 == "login_port"_s) - { - login_port = atoi(w2.c_str()); - } - else if (w1 == "char_ip"_s) - { - h = gethostbyname(w2.c_str()); - if (h != nullptr) - { - char_ip = IP4Address({ - static_cast<uint8_t>(h->h_addr[0]), - static_cast<uint8_t>(h->h_addr[1]), - static_cast<uint8_t>(h->h_addr[2]), - static_cast<uint8_t>(h->h_addr[3]), - }); - PRINTF("Character server IP address : %s -> %s\n"_fmt, - w2, char_ip); - } - else - { - PRINTF("Bad IP value: %s\n"_fmt, w2); - return false; - } - } - else if (w1 == "char_port"_s) - { - char_port = atoi(w2.c_str()); - } - else if (w1 == "char_txt"_s) - { - char_txt = w2; - } - else if (w1 == "max_connect_user"_s) - { - max_connect_user = atoi(w2.c_str()); - if (max_connect_user < 0) - max_connect_user = 0; // unlimited online players - } - else if (w1 == "check_ip_flag"_s) - { - check_ip_flag = config_switch(w2); - } - else if (w1 == "autosave_time"_s) - { - autosave_time = std::chrono::seconds(atoi(w2.c_str())); - if (autosave_time <= std::chrono::seconds::zero()) - autosave_time = DEFAULT_AUTOSAVE_INTERVAL; - } - else if (w1 == "start_point"_s) - { - extract(w2, &start_point); - } - else if (w1 == "unknown_char_name"_s) - { - unknown_char_name = stringish<CharName>(w2); - } - else if (w1 == "char_log_filename"_s) - { - char_log_filename = w2; - } - else if (w1 == "char_name_option"_s) - { - char_name_option = atoi(w2.c_str()); - } - else if (w1 == "char_name_letters"_s) - { - if (!w2) - char_name_letters.reset(); - else - for (uint8_t c : w2) - char_name_letters[c] = true; - } - else if (w1 == "online_txt_filename"_s) - { - online_txt_filename = w2; - } - else if (w1 == "online_html_filename"_s) - { - online_html_filename = w2; - } - else if (w1 == "online_sorting_option"_s) - { - online_sorting_option = atoi(w2.c_str()); - } - else if (w1 == "online_gm_display_min_level"_s) - { - // minimum GM level to display 'GM' when we want to display it - return extract(w2, &online_gm_display_min_level); - } - else if (w1 == "online_refresh_html"_s) - { - online_refresh_html = atoi(w2.c_str()); - if (online_refresh_html < 1) - online_refresh_html = 1; - } - else if (w1 == "anti_freeze_enable"_s) - { - anti_freeze_enable = config_switch(w2); - } - else if (w1 == "anti_freeze_interval"_s) - { - anti_freeze_interval = std::max( - std::chrono::seconds(atoi(w2.c_str())), - 5_s); - } - else - { - return false; - } + return load_config_file(value.data, char_config); } - - return true; + if (key.data == "char_lan_conf"_s) + { + return load_config_file(value.data, char_lan_config); + } + if (key.data == "inter_conf"_s) + { + return load_config_file(value.data, inter_config); + } + key.span.error("Unknown meta-key for char server"_s); + return false; } +} // namespace char_ void term_func(void) { + using namespace tmwa::char_; // write online players files with no player std::fill(online_chars.begin(), online_chars.end(), nullptr); create_online_files(); @@ -3079,20 +2674,9 @@ void term_func(void) CHAR_LOG("----End of char-server (normal end with closing of all files).\n"_fmt); } -static -bool char_confs(XString key, ZString value) -{ - unsigned sum = 0; - sum += char_config(key, value); - sum += char_lan_config(key, value); - sum += inter_config(key, value); - if (sum >= 2) - abort(); - return sum; -} - int do_init(Slice<ZString> argv) { + using namespace tmwa::char_; ZString argv0 = argv.pop_front(); bool loaded_config_yet = false; @@ -3121,12 +2705,12 @@ int do_init(Slice<ZString> argv) else { loaded_config_yet = true; - runflag &= load_config_file(argvi, char_confs); + runflag &= load_config_file(argvi, char_::char_confs); } } if (!loaded_config_yet) - runflag &= load_config_file("conf/tmwa-char.conf"_s, char_confs); + runflag &= load_config_file("conf/tmwa-char.conf"_s, char_::char_confs); // a newline in the log... CHAR_LOG(""_fmt); @@ -3140,7 +2724,7 @@ int do_init(Slice<ZString> argv) update_online = TimeT::now(); create_online_files(); // update online players files at start of the server - char_session = make_listen_port(char_port, SessionParsers{parse_char, delete_char}); + char_session = make_listen_port(char_conf.char_port, SessionParsers{parse_char, delete_char}); Timer(gettick() + 1_s, check_connect_login_server, @@ -3150,25 +2734,26 @@ int do_init(Slice<ZString> argv) send_users_tologin, 5_s ).detach(); - Timer(gettick() + autosave_time, + Timer(gettick() + char_conf.autosave_time, mmo_char_sync_timer, - autosave_time + char_conf.autosave_time ).detach(); - if (anti_freeze_enable > 0) + if (char_conf.anti_freeze_enable > 0) { Timer(gettick() + 1_s, map_anti_freeze_system, - anti_freeze_interval + char_conf.anti_freeze_interval ).detach(); } CHAR_LOG("The char-server is ready (Server is listening on the port %d).\n"_fmt, - char_port); + char_conf.char_port); PRINTF("The char-server is " SGR_BOLD SGR_GREEN "ready" SGR_RESET " (Server is listening on the port %d).\n\n"_fmt, - char_port); + char_conf.char_port); return 0; } +// namespace char_ ends before term_func and do_init } // namespace tmwa diff --git a/src/char/char.hpp b/src/char/char.hpp index a9c786f..4f55c04 100644 --- a/src/char/char.hpp +++ b/src/char/char.hpp @@ -22,18 +22,36 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" +#include "../ints/udl.hpp" #include "../generic/array.hpp" #include "../net/ip.hpp" -#include "../mmo/mmo.hpp" +#include "../high/mmo.hpp" + +#include "consts.hpp" namespace tmwa { -constexpr int MAX_MAP_SERVERS = 30; +namespace char_ +{ +constexpr +std::chrono::seconds DEFAULT_AUTOSAVE_INTERVAL = 5_min; +constexpr +GmLevel default_gm_level = GmLevel::from(0_u32); + +struct AuthFifoEntry +{ + AccountId account_id; + CharId char_id; + int login_id1, login_id2; + IP4Address ip; + int delflag; + SEX sex; + unsigned short packet_tmw_version; +}; struct mmo_map_server { @@ -53,4 +71,5 @@ void char_log(XString line); #define CHAR_LOG(fmt, ...) \ char_log(STRPRINTF(fmt, ## __VA_ARGS__)) +} // namespace char_ } // namespace tmwa diff --git a/src/map/magic-interpreter.cpp b/src/char/consts.hpp index 389a821..e3b6c57 100644 --- a/src/map/magic-interpreter.cpp +++ b/src/char/consts.hpp @@ -1,5 +1,5 @@ -#include "magic-interpreter.hpp" -// magic-interpreter.cpp - Old magic. +#pragma once +// consts.hpp - Constants for tmwa-char. // // Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> // @@ -18,12 +18,14 @@ // 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 "../poison.hpp" +#include "fwd.hpp" namespace tmwa { -namespace magic +namespace char_ { -} // namespace magic +constexpr +int MAX_MAP_SERVERS = 30; +} // namespace char_ } // namespace tmwa diff --git a/src/char/fwd.hpp b/src/char/fwd.hpp index 31cd1ba..8086083 100644 --- a/src/char/fwd.hpp +++ b/src/char/fwd.hpp @@ -20,8 +20,31 @@ #include "../sanity.hpp" +#include "../ints/fwd.hpp" // rank 1 +#include "../strings/fwd.hpp" // rank 1 +#include "../compat/fwd.hpp" // rank 2 +#include "../generic/fwd.hpp" // rank 3 +#include "../io/fwd.hpp" // rank 4 +#include "../net/fwd.hpp" // rank 5 +#include "../mmo/fwd.hpp" // rank 6 +#include "../proto2/fwd.hpp" // rank 8 +#include "../high/fwd.hpp" // rank 9 +#include "../wire/fwd.hpp" // rank 9 +// char/fwd.hpp is rank ∞ because it is an executable + namespace tmwa { -// meh, add more when I feel like it +namespace char_ +{ + struct CharConf; + struct CharLanConf; + struct InterConf; + + struct AuthFifoEntry; + struct mmo_map_server; + + struct accreg; + // meh, add more when I feel like it +} // namespace char_ } // namespace tmwa diff --git a/src/char/globals.cpp b/src/char/globals.cpp new file mode 100644 index 0000000..6733ad5 --- /dev/null +++ b/src/char/globals.cpp @@ -0,0 +1,68 @@ +#include "globals.hpp" +// globals.cpp - Evil global variables for tmwa-char. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../compat/time_t.hpp" + +#include "../generic/db.hpp" + +#include "../proto2/net-Storage.hpp" + +#include "char.hpp" +#include "char_conf.hpp" +#include "char_lan_conf.hpp" +#include "inter.hpp" +#include "inter_conf.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ + namespace char_ + { + CharConf char_conf; + CharLanConf char_lan_conf; + InterConf inter_conf; + Array<mmo_map_server, MAX_MAP_SERVERS> server; + Array<Session *, MAX_MAP_SERVERS> server_session; + // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed + Array<int, MAX_MAP_SERVERS> server_freezeflag; + Session *login_session, *char_session; + const CharName WISP_SERVER_NAME = stringish<CharName>("Server"_s); + std::array<AuthFifoEntry, 256> auth_fifo; + decltype(auth_fifo)::iterator auth_fifo_iter = auth_fifo.begin(); + CharId char_id_count = wrap<CharId>(150000); + std::vector<CharPair> char_keys; + std::vector<GM_Account> gm_accounts; + // same size of char_keys, and id value of current server (or -1) + std::vector<Session *> online_chars; + // to update online files when we receiving information from a server (not less than 8 seconds) + TimeT update_online; + // For forked DB writes + pid_t pid = 0; + + Map<AccountId, accreg> accreg_db; + + Map<PartyId, PartyMost> party_db; + PartyId party_newid = wrap<PartyId>(100_u32); + + Map<AccountId, Storage> storage_db; + } // namespace char_ +} // namespace tmwa diff --git a/src/char/globals.hpp b/src/char/globals.hpp new file mode 100644 index 0000000..2df3f21 --- /dev/null +++ b/src/char/globals.hpp @@ -0,0 +1,59 @@ +#pragma once +// globals.hpp - Evil global variables for tmwa-char. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include <sys/types.h> + +#include <array> +#include <vector> + +#include "consts.hpp" + + +namespace tmwa +{ + namespace char_ + { + extern CharConf char_conf; + extern CharLanConf char_lan_conf; + extern InterConf inter_conf; + extern Array<mmo_map_server, MAX_MAP_SERVERS> server; + extern Array<Session *, MAX_MAP_SERVERS> server_session; + extern Array<int, MAX_MAP_SERVERS> server_freezeflag; + extern Session *login_session, *char_session; + extern const CharName WISP_SERVER_NAME; + extern std::array<AuthFifoEntry, 256> auth_fifo; + extern AuthFifoEntry *auth_fifo_iter; + extern CharId char_id_count; + extern std::vector<CharPair> char_keys; + extern std::vector<GM_Account> gm_accounts; + extern std::vector<Session *> online_chars; + extern TimeT update_online; + extern pid_t pid; + + extern Map<AccountId, accreg> accreg_db; + + extern Map<PartyId, PartyMost> party_db; + extern PartyId party_newid; + + extern Map<AccountId, Storage> storage_db; + } // namespace char_ +} // namespace tmwa diff --git a/src/char/int_party.cpp b/src/char/int_party.cpp index c9ab5a4..5ee65ad 100644 --- a/src/char/int_party.cpp +++ b/src/char/int_party.cpp @@ -29,33 +29,32 @@ #include "../generic/db.hpp" #include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" #include "../io/lock.hpp" #include "../io/read.hpp" #include "../io/write.hpp" -#include "../net/packets.hpp" - #include "../proto2/char-map.hpp" -#include "../mmo/extract.hpp" #include "../mmo/ids.hpp" -#include "../mmo/mmo.hpp" + +#include "../high/extract_mmo.hpp" +#include "../high/mmo.hpp" + +#include "../wire/packets.hpp" #include "char.hpp" +#include "globals.hpp" #include "inter.hpp" +#include "inter_conf.hpp" #include "../poison.hpp" namespace tmwa { -AString party_txt = "save/party.txt"_s; - -static -Map<PartyId, PartyMost> party_db; -static -PartyId party_newid = wrap<PartyId>(100_u32); - +namespace char_ +{ static void mapif_party_broken(PartyId party_id, int flag); static @@ -89,9 +88,10 @@ AString inter_party_tostr(PartyPair p) return AString(str); } +} // namespace char_ static -bool extract(XString str, PartyPair *pp) +bool impl_extract(XString str, PartyPair *pp) { PartyPair& p = *pp; @@ -129,6 +129,8 @@ bool extract(XString str, PartyPair *pp) return true; } +namespace char_ +{ static void party_check_deleted_init(PartyPair p) { @@ -153,7 +155,7 @@ void party_check_deleted_init(PartyPair p) // パーティデータのロード void inter_party_init(void) { - io::ReadFile in(party_txt); + io::ReadFile in(inter_conf.party_txt); if (!in.is_open()) return; @@ -171,19 +173,20 @@ void inter_party_init(void) } PartyMost pm; - PartyPair pp; - pp.party_most = ± + PartyPair pp{PartyId(), borrow(pm)}; if (extract(line, &pp) && pp.party_id) { if (party_newid < next(pp.party_id)) party_newid = next(pp.party_id); party_check_deleted_init(pp); party_db.insert(pp.party_id, pm); + // note: this is still referring to the noncanonical copy of + // the PartyMost pointer. This is okay, though. party_check_empty(pp); } else { - PRINTF("int_party: broken data [%s] line %d\n"_fmt, party_txt, + PRINTF("int_party: broken data [%s] line %d\n"_fmt, inter_conf.party_txt, c + 1); } c++; @@ -201,18 +204,16 @@ void inter_party_save_sub(PartyPair data, io::WriteFile& fp) // パーティーデータのセーブ int inter_party_save(void) { - io::WriteLock fp(party_txt); + io::WriteLock fp(inter_conf.party_txt); if (!fp.is_open()) { PRINTF("int_party: cant write [%s] !!! data is lost !!!\n"_fmt, - party_txt); + inter_conf.party_txt); return 1; } for (auto& pair : party_db) { - PartyPair tmp; - tmp.party_id = pair.first; - tmp.party_most = &pair.second; + PartyPair tmp{pair.first, borrow(pair.second)}; inter_party_save_sub(tmp, fp); } @@ -221,23 +222,21 @@ int inter_party_save(void) // パーティ名検索用 static -void search_partyname_sub(PartyPair p, PartyName str, PartyPair *dst) +void search_partyname_sub(PartyPair p, PartyName str, Borrowed<Option<PartyPair>> dst) { if (p->name == str) - *dst = p; + *dst = Some(p); } // パーティ名検索 static -PartyPair search_partyname(PartyName str) +Option<PartyPair> search_partyname(PartyName str) { - PartyPair p; + Option<PartyPair> p = None; for (auto& pair : party_db) { - PartyPair tmp; - tmp.party_id = pair.first; - tmp.party_most = &pair.second; - search_partyname_sub(tmp, str, &p); + PartyPair tmp{pair.first, borrow(pair.second)}; + search_partyname_sub(tmp, str, borrow(p)); } return p; @@ -262,11 +261,11 @@ int party_check_exp_share(PartyPair p) } } - return (maxlv == 0 || maxlv - minlv <= party_share_level); + return (maxlv == 0 || maxlv - minlv <= inter_conf.party_share_level); } // パーティが空かどうかチェック -int party_check_empty(PartyPair p) +int party_check_empty(const PartyPair p) { int i; @@ -313,9 +312,7 @@ void party_check_conflict(PartyId party_id, AccountId account_id, CharName nick) { for (auto& pair : party_db) { - PartyPair tmp; - tmp.party_id = pair.first; - tmp.party_most = &pair.second; + PartyPair tmp{pair.first, borrow(pair.second)}; party_check_conflict_sub(tmp, party_id, account_id, nick); } @@ -326,23 +323,27 @@ void party_check_conflict(PartyId party_id, AccountId account_id, CharName nick) // パーティ作成可否 static -void mapif_party_created(Session *s, AccountId account_id, PartyPair p) +void mapif_party_created(Session *s, AccountId account_id, Option<PartyPair> p_) { Packet_Fixed<0x3820> fixed_20; fixed_20.account_id = account_id; - if (p) - { - fixed_20.error = 0; - fixed_20.party_id = p.party_id; - fixed_20.party_name = p->name; - PRINTF("int_party: created! %d %s\n"_fmt, p.party_id, p->name); - } - else + OMATCH_BEGIN (p_) { - fixed_20.error = 1; - fixed_20.party_id = PartyId(); - fixed_20.party_name = stringish<PartyName>("error"_s); + OMATCH_CASE_SOME (p) + { + fixed_20.error = 0; + fixed_20.party_id = p.party_id; + fixed_20.party_name = p->name; + PRINTF("int_party: created! %d %s\n"_fmt, p.party_id, p->name); + } + OMATCH_CASE_NONE () + { + fixed_20.error = 1; + fixed_20.party_id = PartyId(); + fixed_20.party_name = stringish<PartyName>("error"_s); + } } + OMATCH_END (); send_fpacket<0x3820, 35>(s, fixed_20); } @@ -359,7 +360,7 @@ void mapif_party_noinfo(Session *s, PartyId party_id) // パーティ情報まとめ送り static -void mapif_party_info(Session *s, PartyPair p) +void mapif_party_info(Session *s, const PartyPair p) { Packet_Head<0x3821> head_21; head_21.party_id = p.party_id; @@ -490,22 +491,21 @@ void mapif_parse_CreateParty(Session *s, AccountId account_id, PartyName name, C if (!name.is_print()) { PRINTF("int_party: illegal party name [%s]\n"_fmt, name); - mapif_party_created(s, account_id, PartyPair()); + mapif_party_created(s, account_id, None); return; } } - if (search_partyname(name)) + if (search_partyname(name).is_some()) { PRINTF("int_party: same name party exists [%s]\n"_fmt, name); - mapif_party_created(s, account_id, PartyPair()); + mapif_party_created(s, account_id, None); return; } + PartyMost p {}; - PartyPair pp; - pp.party_most = &p; party_newid = next(party_newid); - pp.party_id = party_newid; + PartyPair pp{party_newid, borrow(p)}; p.name = name; p.exp = 0; p.item = 0; @@ -518,7 +518,8 @@ void mapif_parse_CreateParty(Session *s, AccountId account_id, PartyName name, C party_db.insert(pp.party_id, p); - mapif_party_created(s, account_id, pp); + // pointer to noncanonical version + mapif_party_created(s, account_id, Some(pp)); mapif_party_info(s, pp); } @@ -526,12 +527,19 @@ void mapif_parse_CreateParty(Session *s, AccountId account_id, PartyName name, C static void mapif_parse_PartyInfo(Session *s, PartyId party_id) { - PartyPair p; - p.party_most = party_db.search(party_id); - if (p) - mapif_party_info(s, p); - else - mapif_party_noinfo(s, party_id); + Option<P<PartyMost>> maybe_party_most = party_db.search(party_id); + OMATCH_BEGIN (maybe_party_most) + { + OMATCH_CASE_SOME (party_most) + { + mapif_party_info(s, PartyPair{party_id, party_most}); + } + OMATCH_CASE_NONE () + { + mapif_party_noinfo(s, party_id); + } + } + OMATCH_END (); } // パーティ追加要求 @@ -539,13 +547,13 @@ static void mapif_parse_PartyAddMember(Session *s, PartyId party_id, AccountId account_id, CharName nick, MapName map, int lv) { - PartyPair p; - p.party_most = party_db.search(party_id); - if (!p) - { - mapif_party_memberadded(s, party_id, account_id, 1); - return; - } + Option<P<PartyMost>> maybe_party_most = party_db.search(party_id); + P<PartyMost> party_most = TRY_UNWRAP(maybe_party_most, + { + mapif_party_memberadded(s, party_id, account_id, 1); + return; + }); + PartyPair p{party_id, party_most}; for (int i = 0; i < MAX_PARTY; i++) { @@ -580,10 +588,7 @@ static void mapif_parse_PartyChangeOption(Session *s, PartyId party_id, AccountId account_id, int exp, int item) { - PartyPair p; - p.party_most = party_db.search(party_id); - if (!p) - return; + PartyPair p{party_id, TRY_UNWRAP(party_db.search(party_id), return)}; p->exp = exp; int flag = 0; @@ -601,10 +606,8 @@ void mapif_parse_PartyChangeOption(Session *s, PartyId party_id, AccountId accou // パーティ脱退要求 void mapif_parse_PartyLeave(Session *, PartyId party_id, AccountId account_id) { - PartyPair p; - p.party_most = party_db.search(party_id); - if (!p) - return; + 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) @@ -623,10 +626,7 @@ static void mapif_parse_PartyChangeMap(Session *s, PartyId party_id, AccountId account_id, MapName map, int online, int lv) { - PartyPair p; - p.party_most = party_db.search(party_id); - if (!p) - return; + PartyPair p{party_id, TRY_UNWRAP(party_db.search(party_id), return)}; for (int i = 0; i < MAX_PARTY; i++) { @@ -822,4 +822,5 @@ void inter_party_leave(PartyId party_id, AccountId account_id) { mapif_parse_PartyLeave(nullptr, party_id, account_id); } +} // namespace char_ } // namespace tmwa diff --git a/src/char/int_party.hpp b/src/char/int_party.hpp index f33fc0d..d66afbf 100644 --- a/src/char/int_party.hpp +++ b/src/char/int_party.hpp @@ -22,21 +22,16 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" - -#include "../net/fwd.hpp" - -#include "../mmo/fwd.hpp" - namespace tmwa { +namespace char_ +{ void inter_party_init(void); int inter_party_save(void); RecvResult inter_party_parse_frommap(Session *ms, uint16_t); void inter_party_leave(PartyId party_id, AccountId account_id); - -extern AString party_txt; +} // namespace char_ } // namespace tmwa diff --git a/src/char/int_storage.cpp b/src/char/int_storage.cpp index 76eff34..32af231 100644 --- a/src/char/int_storage.cpp +++ b/src/char/int_storage.cpp @@ -28,30 +28,30 @@ #include "../generic/db.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" +#include "../io/extract.hpp" #include "../io/lock.hpp" #include "../io/read.hpp" #include "../io/write.hpp" -#include "../net/packets.hpp" - #include "../proto2/char-map.hpp" -#include "../mmo/extract.hpp" -#include "../mmo/mmo.hpp" +#include "../mmo/cxxstdio_enums.hpp" + +#include "../high/extract_mmo.hpp" +#include "../high/mmo.hpp" + +#include "../wire/packets.hpp" + +#include "globals.hpp" +#include "inter_conf.hpp" #include "../poison.hpp" namespace tmwa { -// ファイル名のデフォルト -// inter_config_read()で再設定される -AString storage_txt = "save/storage.txt"_s; - -static -Map<AccountId, Storage> storage_db; - +namespace char_ +{ // 倉庫データを文字列に変換 static AString storage_tostr(Storage *p) @@ -90,10 +90,11 @@ AString storage_tostr(Storage *p) return AString(); return AString(str); } +} // namespace char_ // 文字列を倉庫データに変換 static -bool extract(XString str, Storage *p) +bool impl_extract(XString str, Storage *p) { std::vector<Item> storage_items; if (!extract(str, @@ -116,15 +117,13 @@ bool extract(XString str, Storage *p) return true; } +namespace char_ +{ // アカウントから倉庫データインデックスを得る(新規倉庫追加可能) -Storage *account2storage(AccountId account_id) +Borrowed<Storage> account2storage(AccountId account_id) { - Storage *s = storage_db.search(account_id); - if (s == nullptr) - { - s = storage_db.init(account_id); - s->account_id = account_id; - } + P<Storage> s = storage_db.init(account_id); + s->account_id = account_id; return s; } @@ -134,10 +133,10 @@ void inter_storage_init(void) { int c = 0; - io::ReadFile in(storage_txt); + io::ReadFile in(inter_conf.storage_txt); if (!in.is_open()) { - PRINTF("cant't read : %s\n"_fmt, storage_txt); + PRINTF("cant't read : %s\n"_fmt, inter_conf.storage_txt); return; } @@ -152,7 +151,7 @@ void inter_storage_init(void) else { PRINTF("int_storage: broken data [%s] line %d\n"_fmt, - storage_txt, c); + inter_conf.storage_txt, c); } c++; } @@ -170,12 +169,12 @@ void inter_storage_save_sub(Storage *data, io::WriteFile& fp) // 倉庫データを書き込む int inter_storage_save(void) { - io::WriteLock fp(storage_txt); + io::WriteLock fp(inter_conf.storage_txt); if (!fp.is_open()) { PRINTF("int_storage: cant write [%s] !!! data is lost !!!\n"_fmt, - storage_txt); + inter_conf.storage_txt); return 1; } for (auto& pair : storage_db) @@ -196,7 +195,7 @@ void inter_storage_delete(AccountId account_id) static void mapif_load_storage(Session *ss, AccountId account_id) { - Storage *st = account2storage(account_id); + P<Storage> st = account2storage(account_id); Packet_Payload<0x3810> payload_10; payload_10.account_id = account_id; payload_10.storage = *st; @@ -240,11 +239,10 @@ RecvResult mapif_parse_SaveStorage(Session *ss) if (rv != RecvResult::Complete) return rv; - Storage *st; AccountId account_id = payload.account_id; { - st = account2storage(account_id); + P<Storage> st = account2storage(account_id); *st = payload.storage; mapif_save_storage_ack(ss, account_id); } @@ -273,4 +271,5 @@ RecvResult inter_storage_parse_frommap(Session *ms, uint16_t packet_id) } return rv; } +} // namespace char_ } // namespace tmwa diff --git a/src/char/int_storage.hpp b/src/char/int_storage.hpp index b8ec9db..3741061 100644 --- a/src/char/int_storage.hpp +++ b/src/char/int_storage.hpp @@ -22,21 +22,16 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" - -#include "../net/fwd.hpp" - -#include "../mmo/fwd.hpp" - namespace tmwa { +namespace char_ +{ void inter_storage_init(void); int inter_storage_save(void); void inter_storage_delete(AccountId account_id); -Storage *account2storage(AccountId account_id); +Borrowed<Storage> account2storage(AccountId account_id); RecvResult inter_storage_parse_frommap(Session *ms, uint16_t); - -extern AString storage_txt; +} // namespace char_ } // namespace tmwa diff --git a/src/char/inter.cpp b/src/char/inter.cpp index f757991..3bf3bfc 100644 --- a/src/char/inter.cpp +++ b/src/char/inter.cpp @@ -34,18 +34,24 @@ #include "../generic/db.hpp" #include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" #include "../io/lock.hpp" #include "../io/read.hpp" +#include "../io/span.hpp" #include "../io/write.hpp" -#include "../net/packets.hpp" +#include "../mmo/config_parse.hpp" #include "../proto2/char-map.hpp" -#include "../mmo/extract.hpp" -#include "../mmo/mmo.hpp" +#include "../high/extract_mmo.hpp" +#include "../high/mmo.hpp" + +#include "../wire/packets.hpp" #include "char.hpp" +#include "globals.hpp" +#include "inter_conf.hpp" #include "int_party.hpp" #include "int_storage.hpp" @@ -54,20 +60,8 @@ namespace tmwa { -static -AString accreg_txt = "save/accreg.txt"_s; - -struct accreg +namespace char_ { - AccountId account_id; - int reg_num; - Array<GlobalReg, ACCOUNT_REG_NUM> reg; -}; -static -Map<AccountId, struct accreg> accreg_db; - -int party_share_level = 10; - //-------------------------------------------------------- // アカウント変数を文字列へ変換 @@ -84,7 +78,7 @@ AString inter_accreg_tostr(struct accreg *reg) // アカウント変数を文字列から変換 static -bool extract(XString str, struct accreg *reg) +bool impl_extract(XString str, struct accreg *reg) { std::vector<GlobalReg> vars; if (!extract(str, @@ -108,7 +102,7 @@ void inter_accreg_init(void) { int c = 0; - io::ReadFile in(accreg_txt); + io::ReadFile in(inter_conf.accreg_txt); if (!in.is_open()) return; AString line; @@ -121,7 +115,7 @@ void inter_accreg_init(void) } else { - PRINTF("inter: accreg: broken data [%s] line %d\n"_fmt, accreg_txt, + PRINTF("inter: accreg: broken data [%s] line %d\n"_fmt, inter_conf.accreg_txt, c); } c++; @@ -143,11 +137,11 @@ void inter_accreg_save_sub(struct accreg *reg, io::WriteFile& fp) static int inter_accreg_save(void) { - io::WriteLock fp(accreg_txt); + io::WriteLock fp(inter_conf.accreg_txt); if (!fp.is_open()) { PRINTF("int_accreg: cant write [%s] !!! data is lost !!!\n"_fmt, - accreg_txt); + inter_conf.accreg_txt); return 1; } for (auto& pair : accreg_db) @@ -156,36 +150,6 @@ int inter_accreg_save(void) return 0; } -bool inter_config(XString w1, ZString w2) -{ - { - if (w1 == "storage_txt"_s) - { - storage_txt = w2; - } - else if (w1 == "party_txt"_s) - { - party_txt = w2; - } - else if (w1 == "accreg_txt"_s) - { - accreg_txt = w2; - } - else if (w1 == "party_share_level"_s) - { - party_share_level = atoi(w2.c_str()); - if (party_share_level < 0) - party_share_level = 0; - } - else - { - return false; - } - } - - return true; -} - // セーブ void inter_save(void) { @@ -264,12 +228,12 @@ void mapif_account_reg(Session *s, AccountId account_id, const std::vector<Packe static void mapif_account_reg_reply(Session *s, AccountId account_id) { - struct accreg *reg = accreg_db.search(account_id); + Option<P<struct accreg>> reg_ = accreg_db.search(account_id); Packet_Head<0x3804> head_04; head_04.account_id = account_id; std::vector<Packet_Repeat<0x3804>> repeat_04; - if (reg) + OMATCH_BEGIN_SOME (reg, reg_) { repeat_04.resize(reg->reg_num); assert (reg->reg_num < ACCOUNT_REG_NUM); @@ -279,6 +243,7 @@ void mapif_account_reg_reply(Session *s, AccountId account_id) repeat_04[j].value = reg->reg[j].value; } } + OMATCH_END (); send_vpacket<0x3804, 8, 36>(s, head_04, repeat_04); } @@ -408,13 +373,9 @@ RecvResult mapif_parse_AccReg(Session *s) if (rv != RecvResult::Complete) return rv; - struct accreg *reg = accreg_db.search(head.account_id); - - if (reg == nullptr) + P<struct accreg> reg = accreg_db.init(head.account_id); { - AccountId account_id = head.account_id; - reg = accreg_db.init(account_id); - reg->account_id = account_id; + reg->account_id = head.account_id; } size_t jlim = std::min(repeat.size(), ACCOUNT_REG_NUM); @@ -488,4 +449,5 @@ RecvResult inter_parse_frommap(Session *ms, uint16_t packet_id) return rv; } +} // namespace char_ } // namespace tmwa diff --git a/src/char/inter.hpp b/src/char/inter.hpp index 19900f9..c641254 100644 --- a/src/char/inter.hpp +++ b/src/char/inter.hpp @@ -22,17 +22,27 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" +#include "../generic/array.hpp" -#include "../net/fwd.hpp" +#include "../mmo/consts.hpp" +#include "../mmo/ids.hpp" + +#include "../proto2/net-GlobalReg.hpp" namespace tmwa { -bool inter_config(XString key, ZString value); +namespace char_ +{ +struct accreg +{ + AccountId account_id; + int reg_num; + Array<GlobalReg, ACCOUNT_REG_NUM> reg; +}; + void inter_init2(); void inter_save(void); RecvResult inter_parse_frommap(Session *ms, uint16_t packet_id); - -extern int party_share_level; +} // namespace char_ } // namespace tmwa diff --git a/src/char/main.cpp b/src/char/main.cpp index 3648a74..7d6fee3 100644 --- a/src/char/main.cpp +++ b/src/char/main.cpp @@ -17,7 +17,7 @@ // 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 "../mmo/core.hpp" +#include "../high/core.hpp" #include "char.hpp" diff --git a/src/compat/attr.hpp b/src/compat/attr.hpp index 238a5d5..5ebef6d 100644 --- a/src/compat/attr.hpp +++ b/src/compat/attr.hpp @@ -1,7 +1,7 @@ #pragma once // attr.hpp - Attributes. // -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com> // // This file is part of The Mana World (Athena server) // @@ -28,4 +28,6 @@ namespace tmwa #else # define FALLTHROUGH /* fallthrough */ #endif + +#define JOIN(a, b) a##b } // namespace tmwa diff --git a/src/compat/borrow.hpp b/src/compat/borrow.hpp new file mode 100644 index 0000000..0ea6a26 --- /dev/null +++ b/src/compat/borrow.hpp @@ -0,0 +1,111 @@ +#pragma once +// borrow.hpp - a non-null, unowned, pointer +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include <cstdlib> + +#include <iterator> + +#include "option.hpp" + +// unit tests currention in option_test.cpp + +namespace tmwa +{ + // TODO see if const-by-default is a thing + template<class T> + class Borrowed + { + T *stupid; + public: + Borrowed() = delete; + explicit + Borrowed(T *p) : stupid(p) + { + if (!p) abort(); + } + + T& operator *() const + { + return *stupid; + } + + T *operator ->() const + { + return stupid; + } + + template<class U> + Borrowed<U> downcast_to() const + { + static_assert(std::is_base_of<T, U>::value, "base check"); + static_assert(!std::is_same<T, U>::value, "same check"); + return Borrowed<U>(static_cast<U *>(stupid)); + } + + template<class U> + Borrowed<U> upcast_to() const + { + static_assert(std::is_base_of<U, T>::value, "base check"); + static_assert(!std::is_same<T, U>::value, "same check"); + return Borrowed<U>(stupid); + } + + friend bool operator == (Borrowed l, Borrowed r) + { + return l.stupid == r.stupid; + } + friend bool operator != (Borrowed l, Borrowed r) + { + return !(l == r); + } + }; + + namespace option + { + template<class T> + class OptionRepr<Borrowed<T>> + { + T *stupider; + public: + void set_none() { stupider = nullptr; } + void post_set_some() {} + bool is_some() const { return stupider != nullptr; } + Borrowed<T> *ptr() { return reinterpret_cast<Borrowed<T> *>(&stupider); } + const Borrowed<T> *ptr() const { return reinterpret_cast<const Borrowed<T> *>(&stupider); } + }; + } + + template<class T> + using P = Borrowed<T>; + + template<class T> + Borrowed<T> borrow(T& ref) + { + return Borrowed<T>(&ref); + } + + template<class T> + T *as_raw_pointer(Option<Borrowed<T>> ptr) + { + return &*TRY_UNWRAP(ptr, return nullptr); + } +} // namespace tmwa diff --git a/src/compat/borrow.py b/src/compat/borrow.py new file mode 100644 index 0000000..58cd19b --- /dev/null +++ b/src/compat/borrow.py @@ -0,0 +1,18 @@ +class Borrowed(object): + __slots__ = ('_value') + name = 'tmwa::Borrowed' + enabled = True + + def __init__(self, value): + self._value = value['stupid'] + + def to_string(self): + return self._value + + test_extra = ''' + static int borrow_thingy; + ''' + + tests = [ + ('tmwa::borrow(borrow_thingy)', '<borrow_thingy>'), + ] diff --git a/src/compat/fwd.hpp b/src/compat/fwd.hpp index 45f3c24..3fa0dd2 100644 --- a/src/compat/fwd.hpp +++ b/src/compat/fwd.hpp @@ -20,8 +20,23 @@ #include "../sanity.hpp" +#include "../ints/fwd.hpp" // rank 1 +#include "../strings/fwd.hpp" // rank 1 +// compat/fwd.hpp is rank 2 + namespace tmwa { + namespace option + { + template<class T> + class Option; + } + using option::Option; + + template<class T> + class Borrowed; + + struct TimeT; // meh, add more when I feel like it } // namespace tmwa diff --git a/src/compat/nullpo.hpp b/src/compat/nullpo.hpp index 5be674a..38c8e92 100644 --- a/src/compat/nullpo.hpp +++ b/src/compat/nullpo.hpp @@ -52,13 +52,15 @@ bool nullpo_chk(const char *file, int line, const char *func, const void *target); template<class T> -bool nullpo_chk(const char *file, int line, const char *func, T target) -{ - return nullpo_chk(file, line, func, target.operator->()); -} +bool nullpo_chk(const char *, int, const char *, Borrowed<T>) = delete; template<class T> bool nullpo_chk(const char *file, int line, const char *func, T *target) { return nullpo_chk(file, line, func, static_cast<const void *>(target)); } +template<class T> +bool nullpo_chk(const char *file, int line, const char *func, T target) +{ + return nullpo_chk(file, line, func, target.operator->()); +} } // namespace tmwa diff --git a/src/generic/operators.hpp b/src/compat/operators.hpp index bb05765..bb05765 100644 --- a/src/generic/operators.hpp +++ b/src/compat/operators.hpp diff --git a/src/compat/option.hpp b/src/compat/option.hpp new file mode 100644 index 0000000..b6e7655 --- /dev/null +++ b/src/compat/option.hpp @@ -0,0 +1,469 @@ +#pragma once +// option.hpp - a data type that may or may not exist +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include <cassert> + +#include <utility> + + +namespace tmwa +{ +namespace option +{ + enum option_hack_type { option_hack_value }; + + template<class T> + class OptionRepr + { + __attribute__((aligned(alignof(T)))) + char _data[sizeof(T)]; + bool _some; + public: + void set_none() { _some = false; } + // maybe add pre_set_some if it is useful for other specializations + void post_set_some() { _some = true; } + bool is_some() const { return _some; } + T *ptr() { return reinterpret_cast<T *>(&_data); } + const T *ptr() const { return reinterpret_cast<const T *>(&_data); } + }; + template<class T> + class OptionRepr<T&>; + template<class T> + class OptionRepr<T&&>; + + template<class T> + Option<T> None(option_hack_type=option_hack_value) + { + return None; + } + + template<class T> + Option<T> Some(T v) + { + Option<T> rv = None; + rv.do_construct(std::move(v)); + return rv; + } + + // TODO all *_or and *_set methods should have a lazy version too + template<class T> + class Option + { + static_assert(std::is_pod<OptionRepr<T>>::value, "repr should itself be pod, copies are done manually"); + OptionRepr<T> repr; + + friend Option<T> Some<T>(T); + + void do_init() + { + repr.set_none(); + } + template<class... U> + void do_construct(U&&... u) + { + new(repr.ptr()) T(std::forward<U>(u)...); + repr.post_set_some(); + } + void do_move_construct(Option&& r) + { + if (r.repr.is_some()) + { + do_construct(std::move(*r.repr.ptr())); + r.do_destruct(); + } + } + void do_copy_construct(const Option& r) + { + if (r.repr.is_some()) + { + do_construct(*r.repr.ptr()); + } + } + void do_move_assign(Option&& r) + { + if (repr.is_some()) + { + if (r.repr.is_some()) + { + *repr.ptr() = std::move(*r.repr.ptr()); + } + else + { + do_destruct(); + } + return; + } + else + { + do_move_construct(std::move(r)); + } + } + void do_copy_assign(const Option& r) + { + if (repr.is_some()) + { + if (r.repr.is_some()) + { + *repr.ptr() = *r.repr.ptr(); + } + else + { + do_destruct(); + } + return; + } + else + { + do_copy_construct(r); + } + } + void do_destruct() + { + repr.ptr()->~T(); + repr.set_none(); + } + public: + Option() = delete; + Option(Option(*)(option_hack_type)) + { + do_init(); + } + Option(Option&& r) + { + do_init(); + do_move_construct(std::move(r)); + } + Option(const Option& r) + { + do_init(); + do_copy_construct(r); + } + Option& operator = (Option&& r) + { + do_move_assign(std::move(r)); + return *this; + } + Option& operator = (const Option& r) + { + do_copy_assign(r); + return *this; + } + ~Option() + { + if (repr.is_some()) + { + do_destruct(); + } + } + + T move_or(T def) + { + if (repr.is_some()) + { + def = std::move(*repr.ptr()); + do_destruct(); + } + return def; + } + T copy_or(T def) const + { + if (repr.is_some()) + { + def = *repr.ptr(); + } + return def; + } + T& ref_or(T& def) + { + return repr.is_some() ? *repr.ptr() : def; + } + const T& ref_or(const T& def) const + { + return repr.is_some() ? *repr.ptr() : def; + } + T *ptr_or(T *def) + { + return repr.is_some() ? repr.ptr() : def; + } + const T *ptr_or(const T *def) const + { + return repr.is_some() ? repr.ptr() : def; + } + bool is_some() const + { + return repr.is_some(); + } + bool is_none() const + { + return !is_some(); + } + + template<class F> + auto move_map(F&& f) -> Option<decltype(std::forward<F>(f)(std::move(*repr.ptr())))> + { + if (repr.is_some()) + { + auto rv = Some(std::forward<F>(f)(std::move(*repr.ptr()))); + do_destruct(); + return rv; + } + else + { + return None; + } + } + template<class F> + auto map(F&& f) -> Option<decltype(std::forward<F>(f)(*repr.ptr()))> + { + if (repr.is_some()) + { + return Some(std::forward<F>(f)(*repr.ptr())); + } + else + { + return None; + } + } + template<class F> + auto map(F&& f) const -> Option<decltype(std::forward<F>(f)(*repr.ptr()))> + { + if (repr.is_some()) + { + return Some(std::forward<F>(f)(*repr.ptr())); + } + else + { + return None; + } + } + // shortcut for flatten(o.map()) that avoids explicit Some's inside + template<class B, class F> + auto cmap(B&& b, F&& f) const -> Option<decltype(std::forward<F>(f)(*repr.ptr()))> + { + if (repr.is_some() && std::forward<B>(b)(*repr.ptr())) + { + return Some(std::forward<F>(f)(*repr.ptr())); + } + else + { + return None; + } + } + // wanting members is *so* common + template<class M, class B> + Option<M> pmd_get(const M B::*pmd) const + { + if (repr.is_some()) + { + return Some((*repr.ptr()).*pmd); + } + else + { + return None; + } + } + template<class M, class B> + void pmd_set(M B::*pmd, M value) + { + if (repr.is_some()) + { + ((*repr.ptr()).*pmd) = std::move(value); + } + } + template<class M, class B> + Option<M> pmd_pget(const M B::*pmd) const + { + if (repr.is_some()) + { + return Some((**repr.ptr()).*pmd); + } + else + { + return None; + } + } + template<class M, class B> + void pmd_pset(M B::*pmd, M value) + { + if (repr.is_some()) + { + ((**repr.ptr()).*pmd) = std::move(value); + } + } + }; + + template<class T> + struct most_flattened_type + { + using type = T; + + static Option<type> flatten(Option<T> o) + { + return std::move(o); + } + }; + template<class T> + struct most_flattened_type<Option<T>> + { + using type = typename most_flattened_type<T>::type; + + static Option<type> flatten(Option<Option<T>> o) + { + return most_flattened_type<T>::flatten(o.move_or(None)); + } + }; + + template<class T> + Option<typename most_flattened_type<T>::type> flatten(Option<T> o) + { + return most_flattened_type<T>::flatten(std::move(o)); + } + + template<class T> + bool operator == (const Option<T>& l, const Option<T>& r) + { + const T *l2 = l.ptr_or(nullptr); + const T *r2 = r.ptr_or(nullptr); + if (!l2 && !r2) + return true; + if (l2 && r2) + { + return *l2 == *r2; + } + return false; + } + template<class T> + bool operator != (const Option<T>& l, const Option<T>& r) + { + return !(l == r); + } + template<class T> + bool operator < (const Option<T>& l, const Option<T>& r) + { + const T *l2 = l.ptr_or(nullptr); + const T *r2 = r.ptr_or(nullptr); + + if (!l2 && r2) + return true; + if (l2 && r2) + { + return *l2 < *r2; + } + return false; + } + template<class T> + bool operator > (const Option<T>& l, const Option<T>& r) + { + return (r < l); + } + template<class T> + bool operator <= (const Option<T>& l, const Option<T>& r) + { + return !(r < l); + } + template<class T> + bool operator >= (const Option<T>& l, const Option<T>& r) + { + return !(l < r); + } + + // workaround for the fact that most references can't escape + template<class T> + struct RefWrapper + { + T maybe_ref; + + T maybe_ref_fun() { return std::forward<T>(maybe_ref); } + }; + + template<class T> + RefWrapper<T> option_unwrap(RefWrapper<Option<T>> o) + { return RefWrapper<T>{std::move(*reinterpret_cast<OptionRepr<T>&>(o.maybe_ref).ptr())}; } + template<class T> + RefWrapper<T&> option_unwrap(RefWrapper<Option<T>&> o) + { return RefWrapper<T&>{*reinterpret_cast<OptionRepr<T>&>(o.maybe_ref).ptr()}; } + template<class T> + RefWrapper<T&&> option_unwrap(RefWrapper<Option<T>&&> o) + { return RefWrapper<T&&>{std::move(*reinterpret_cast<OptionRepr<T>&>(o.maybe_ref).ptr())}; } + template<class T> + RefWrapper<T> option_unwrap(RefWrapper<const Option<T>> o) + { return RefWrapper<T>{std::move(*reinterpret_cast<const OptionRepr<T>&>(o.maybe_ref).ptr())}; } + template<class T> + RefWrapper<const T&> option_unwrap(RefWrapper<const Option<T>&> o) + { return RefWrapper<const T&>{*reinterpret_cast<const OptionRepr<T>&>(o.maybe_ref).ptr()}; } + template<class T> + RefWrapper<const T&&> option_unwrap(RefWrapper<const Option<T>&&> o) + { return RefWrapper<const T&&>{std::move(*reinterpret_cast<const OptionRepr<T>&>(o.maybe_ref).ptr())}; } + + // if you think you understand this, you're not trying hard enough. +#define TRY_UNWRAP(opt, falsy) \ + ({ \ + tmwa::option::RefWrapper<decltype((opt))> o = {(opt)}; \ + if (o.maybe_ref.is_none()) falsy; \ + tmwa::option::option_unwrap(std::move(o)); \ + }).maybe_ref_fun() + +#define OMATCH_BEGIN(expr) \ + { \ + auto&& _omatch_var = (expr); \ + switch (_omatch_var.is_some()) \ + { \ + { \ + { \ + /*}}}}*/ +#define OMATCH_END() \ + /*{{{{*/ \ + } \ + } \ + } \ + (void) _omatch_var; \ + } + +#define OMATCH_BEGIN_SOME(var, expr) \ + OMATCH_BEGIN (expr) \ + OMATCH_CASE_SOME (var) + +#define OMATCH_CASE_SOME(var) \ + /*{{{{*/ \ + } \ + break; \ + } \ + { \ + case true: \ + { \ + auto&& var = *_omatch_var.ptr_or(nullptr); \ + /*}}}}*/ +#define OMATCH_CASE_NONE() \ + /*{{{{*/ \ + } \ + break; \ + } \ + { \ + case false: \ + { \ + /*}}}}*/ +} // namespace option + +//using option::Option; +using option::None; +using option::Some; +} // namespace tmwa diff --git a/src/compat/option.py b/src/compat/option.py new file mode 100644 index 0000000..7704174 --- /dev/null +++ b/src/compat/option.py @@ -0,0 +1,39 @@ +class Option(object): + __slots__ = ('_value') + name = 'tmwa::option::Option' + enabled = True + + def __init__(self, value): + self._value = value['repr'] + + def to_string(self): + value = self._value + ty = value.type.template_argument(0) + try: + some = bool(value['_some']) + except gdb.error: + stupider = value['stupider'] + if stupider: + return 'Some<%s>(%s)' % (ty, stupider) + else: + return 'None<%s>' % ty + else: + if some: + data = value['_data'] + data = data.address.cast(ty.pointer()).dereference() + return 'Some<%s>(%s)' % (ty, data) + else: + return 'None<%s>' % ty + + test_extra = ''' + #include "../compat/borrow.hpp" + + static int option_borrow_thingy; + ''' + + tests = [ + ('tmwa::None<int>()', 'None<int>'), + ('tmwa::Some(1)', 'Some<int>(1)'), + ('tmwa::Option<tmwa::Borrowed<int>>(tmwa::None)', 'None<tmwa::Borrowed<int>>'), + ('tmwa::Some(tmwa::borrow(option_borrow_thingy))', 'Some<tmwa::Borrowed<int>>(<option_borrow_thingy>)'), + ] diff --git a/src/compat/option_test.cpp b/src/compat/option_test.cpp new file mode 100644 index 0000000..69f3a60 --- /dev/null +++ b/src/compat/option_test.cpp @@ -0,0 +1,499 @@ +#include "option.hpp" +// option_test.cpp - Testsuite for a type that may or may not exist +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include <gtest/gtest.h> + +#include "../strings/literal.hpp" + +#include "borrow.hpp" + +#include "../diagnostics.hpp" +//#include "../poison.hpp" + + +namespace tmwa +{ +TEST(Option, somenone) +{ + { + option::Option<int> opt = option::None; + opt = option::None; + } + { + option::Option<int> opt = option::None<int>; + opt = option::None<int>; + } + { + option::Option<int> opt = option::None<int>(); + opt = option::None<int>(); + } + { + option::Option<int> opt = option::Some(123); + opt = option::Some(123); + } + { + option::Option<int> opt = option::Some<int>(123); + opt = option::Some<int>(123); + } +} +TEST(Option, somenonenocopy) +{ + struct Foo + { + Foo() = default; + Foo(Foo&&) = default; + Foo(const Foo&) = delete; + Foo& operator = (Foo&&) = default; + Foo& operator = (const Foo&) = delete; + }; + { + option::Option<Foo> opt = option::None; + opt = option::None; + } + // clang <= 3.4 is buggy + // since clang doesn't version, there is no way to restrict it to clang 3.5+ +#ifndef __clang__ + { + option::Option<Foo> opt = option::None<Foo>; + opt = option::None<Foo>; + } +#endif + { + option::Option<Foo> opt = option::None<Foo>(); + opt = option::None<Foo>(); + } + { + option::Option<Foo> opt = option::Some(Foo()); + opt = option::Some(Foo()); + } + { + option::Option<Foo> opt = option::Some<Foo>(Foo()); + opt = option::Some<Foo>(Foo()); + } +} +TEST(Option, customrepr) +{ + int iv = 123; + Borrowed<int> i = borrow(iv); + + EXPECT_EQ(&iv, as_raw_pointer(Some(i))); + + { + option::Option<Borrowed<int>> opt = option::None; + opt = option::None; + } + { + option::Option<Borrowed<int>> opt = option::None<Borrowed<int>>; + opt = option::None<Borrowed<int>>; + } + { + option::Option<Borrowed<int>> opt = option::None<Borrowed<int>>(); + opt = option::None<Borrowed<int>>(); + } + { + option::Option<Borrowed<int>> opt = option::Some(i); + opt = option::Some(i); + } + { + option::Option<Borrowed<int>> opt = option::Some<Borrowed<int>>(i); + opt = option::Some<Borrowed<int>>(i); + } +} + +TEST(Option, destruct) +{ + struct BugCheck + { + bool *destroyed; + + BugCheck(bool *d) + : destroyed(d) + {} + BugCheck(BugCheck&& r) + : destroyed(r.destroyed) + { + r.destroyed = nullptr; + } + BugCheck& operator = (BugCheck&& r) + { + std::swap(destroyed, r.destroyed); + return *this; + } + ~BugCheck() + { + if (!destroyed) + return; + if (*destroyed) + abort(); + *destroyed = true; + } + }; + + bool destroyed = false; + + Option<BugCheck> bug = Some(BugCheck(&destroyed)); + bug = None; +} + +TEST(Option, def) +{ + struct Tracked + { + int id; + int gen; + + Tracked(int i, int g=0) : id(i), gen(g) {} + Tracked(Tracked&&) = default; + Tracked(const Tracked& r) : id(r.id), gen(r.gen + 1) {} + Tracked& operator = (Tracked&&) = default; + Tracked& operator = (const Tracked& r) { id = r.id; gen = r.gen + 1; return *this; } + + bool operator == (const Tracked& r) const + { + return this->id == r.id && this->gen == r.gen; + } + bool operator != (const Tracked& r) const + { + return !(*this == r); + } + }; + + { + option::Option<Tracked> o = option::None; + EXPECT_EQ(o.move_or(Tracked(1)), Tracked(1)); + EXPECT_EQ(o.copy_or(Tracked(2)), Tracked(2)); + Tracked t3(3); + Tracked& r3 = o.ref_or(t3); + EXPECT_EQ(&r3, &t3); + Tracked t4(4); + Tracked *r4 = o.ptr_or(&t4); + EXPECT_EQ(r4, &t4); + EXPECT_EQ(o.ptr_or(nullptr), nullptr); + } + { + const option::Option<Tracked> o = option::None; + EXPECT_EQ(o.copy_or(Tracked(2)), Tracked(2)); + Tracked t3(3); + const Tracked& r3 = o.ref_or(t3); + EXPECT_EQ(&r3, &t3); + Tracked t4(4); + const Tracked *r4 = o.ptr_or(&t4); + EXPECT_EQ(r4, &t4); + EXPECT_EQ(o.ptr_or(nullptr), nullptr); + } + { + option::Option<Tracked> o = option::Some(Tracked(0)); + EXPECT_EQ(o.move_or(Tracked(1)), Tracked(0)); + EXPECT_EQ(o.ptr_or(nullptr), nullptr); + o = option::Some(Tracked(0)); + EXPECT_EQ(o.copy_or(Tracked(2)), Tracked(0, 1)); + Tracked t3(3); + Tracked& r3 = o.ref_or(t3); + EXPECT_NE(&r3, &t3); + Tracked t4(4); + Tracked *r4 = o.ptr_or(&t4); + EXPECT_NE(r4, &t4); + EXPECT_NE(o.ptr_or(nullptr), nullptr); + EXPECT_EQ(&r3, r4); + EXPECT_EQ(r4, reinterpret_cast<Tracked *>(&o)); + } + { + const option::Option<Tracked> o = option::Some(Tracked(0)); + EXPECT_EQ(o.copy_or(Tracked(2)), Tracked(0, 1)); + Tracked t3(3); + const Tracked& r3 = o.ref_or(t3); + EXPECT_NE(&r3, &t3); + Tracked t4(4); + const Tracked *r4 = o.ptr_or(&t4); + EXPECT_NE(r4, &t4); + EXPECT_NE(o.ptr_or(nullptr), nullptr); + EXPECT_EQ(&r3, r4); + EXPECT_EQ(r4, reinterpret_cast<const Tracked *>(&o)); + } +} + +TEST(Option, map) +{ + struct Foo + { + Foo() = default; + Foo(Foo&&) = default; + Foo(const Foo&) = delete; + Foo& operator = (Foo&&) = default; + Foo& operator = (const Foo&) = delete; + }; + + // move + { + option::Option<Foo> o = option::None; + EXPECT_EQ(o.ptr_or(nullptr), nullptr); + option::Option<int> i = o.move_map([](Foo){ return 0; }); + EXPECT_EQ(o.ptr_or(nullptr), nullptr); + EXPECT_EQ(i.ptr_or(nullptr), nullptr); + } + { + option::Option<Foo> o = option::Some(Foo()); + EXPECT_NE(o.ptr_or(nullptr), nullptr); + option::Option<int> i = o.move_map([](Foo){ return 1; }); + EXPECT_EQ(o.ptr_or(nullptr), nullptr); + EXPECT_NE(i.ptr_or(nullptr), nullptr); + EXPECT_EQ(i.copy_or(0), 1); + } + // mut ref + { + option::Option<Foo> o = option::None; + EXPECT_EQ(o.ptr_or(nullptr), nullptr); + option::Option<int> i = o.map([](Foo&){ return 0; }); + EXPECT_EQ(o.ptr_or(nullptr), nullptr); + EXPECT_EQ(i.ptr_or(nullptr), nullptr); + } + { + option::Option<Foo> o = option::Some(Foo()); + EXPECT_NE(o.ptr_or(nullptr), nullptr); + option::Option<int> i = o.map([](Foo&){ return 1; }); + EXPECT_NE(o.ptr_or(nullptr), nullptr); + EXPECT_NE(i.ptr_or(nullptr), nullptr); + EXPECT_EQ(i.copy_or(0), 1); + } + // const ref + { + option::Option<Foo> o = option::None; + EXPECT_EQ(o.ptr_or(nullptr), nullptr); + option::Option<int> i = o.map([](const Foo&){ return 0; }); + EXPECT_EQ(o.ptr_or(nullptr), nullptr); + EXPECT_EQ(i.ptr_or(nullptr), nullptr); + } + { + option::Option<Foo> o = option::Some(Foo()); + EXPECT_NE(o.ptr_or(nullptr), nullptr); + option::Option<int> i = o.map([](const Foo&){ return 1; }); + EXPECT_NE(o.ptr_or(nullptr), nullptr); + EXPECT_NE(i.ptr_or(nullptr), nullptr); + EXPECT_EQ(i.copy_or(0), 1); + } +} + +TEST(Option, member) +{ + struct Foo + { + int bar = 404; + }; + + Option<Foo> vng = None; + EXPECT_EQ(vng.pmd_get(&Foo::bar).copy_or(42), 42); + Option<Foo> vsg = Some(Foo()); + EXPECT_EQ(vsg.pmd_get(&Foo::bar).copy_or(42), 404); + + Option<Foo> vns = None; + vns.pmd_set(&Foo::bar, 42); + EXPECT_EQ(vns.copy_or(Foo()).bar, 404); + Option<Foo> vss = Some(Foo()); + vss.pmd_set(&Foo::bar, 42); + EXPECT_EQ(vss.copy_or(Foo()).bar, 42); + + Foo foo, alt; + + Option<P<Foo>> png = None; + EXPECT_EQ(png.pmd_pget(&Foo::bar).copy_or(42), 42); + Option<P<Foo>> psg = Some(borrow(foo)); + EXPECT_EQ(psg.pmd_pget(&Foo::bar).copy_or(42), 404); + + Option<P<Foo>> pns = None; + pns.pmd_pset(&Foo::bar, 42); + EXPECT_EQ(pns.copy_or(borrow(alt))->bar, 404); + EXPECT_EQ(foo.bar, 404); + Option<P<Foo>> pss = Some(borrow(foo)); + pss.pmd_pset(&Foo::bar, 42); + EXPECT_EQ(pss.copy_or(borrow(alt))->bar, 42); + EXPECT_EQ(foo.bar, 42); + EXPECT_EQ(alt.bar, 404); +} + +#if __cplusplus >= 201300 // c++14 as given by gcc 4.9 +# define DECLTYPE_AUTO decltype(auto) +#else +# define DECLTYPE_AUTO auto&& +#endif + +TEST(Option, unwrap) +{ + int x; + + Option<int> v = Some(1); + Option<int>& l = v; + Option<int>&& r = std::move(v); + const Option<int> cv = v; + // significantly, see the mut + const Option<int>& cl = v; + const Option<int>&& cr = std::move(v); + + auto fv = [&]() -> Option<int> { return v; }; + auto fl = [&]() -> Option<int>& { return l; }; + auto fr = [&]() -> Option<int>&& { return std::move(r); }; + auto fcv = [&]() -> const Option<int> { return v; }; + auto fcl = [&]() -> const Option<int>& { return l; }; + auto fcr = [&]() -> const Option<int>&& { return std::move(r); }; + + DIAG_PUSH(); + DIAG_I(useless_cast); + +#define CHECK(v, t) \ + { \ + DECLTYPE_AUTO out = TRY_UNWRAP(v, abort() ); \ + DECLTYPE_AUTO cmp = static_cast<t>(x); \ + static_assert(std::is_same<decltype(out), decltype(cmp)>::value, #v); \ + } + + CHECK(v, int&); + CHECK(cv, const int&); + CHECK(l, int&); + CHECK(cl, const int&); + CHECK(r, int&); + CHECK(cr, const int&); + // repeat the same forcing expressions, since that matters with decltype + CHECK((v), int&); + CHECK((cv), const int&); + CHECK((l), int&); + CHECK((cl), const int&); + CHECK((r), int&); + CHECK((cr), const int&); + + CHECK(fv(), int); + CHECK(fcv(), int); + CHECK(fl(), int&); + CHECK(fcl(), const int&); + CHECK(fr(), int&&); + CHECK(fcr(), const int&&); + + DIAG_POP(); +#undef CHECK + + v = None; TRY_UNWRAP(v, v = Some(1)); + v = None; TRY_UNWRAP(l, v = Some(1)); + v = None; TRY_UNWRAP(cl, v = Some(1)); + v = None; TRY_UNWRAP(r, v = Some(1)); + v = None; TRY_UNWRAP(cr, v = Some(1)); + + v = None; TRY_UNWRAP(fl(), v = Some(1)); + v = None; TRY_UNWRAP(fcl(), v = Some(1)); + v = None; TRY_UNWRAP(fr(), v = Some(1)); + v = None; TRY_UNWRAP(fcr(), v = Some(1)); + + v = None; + OMATCH_BEGIN (v) + { + OMATCH_CASE_SOME (o) + { + EXPECT_NE(o, o); + } + OMATCH_CASE_NONE () + { + SUCCEED(); + } + } + OMATCH_END (); + + v = Some(1); + OMATCH_BEGIN (v) + { + OMATCH_CASE_SOME (o) + { + EXPECT_EQ(o, 1); + } + OMATCH_CASE_NONE () + { + FAIL(); + } + } + OMATCH_END (); +} + +TEST(Option, flatten) +{ + using option::Option; + using option::Some; + using option::None; + + struct Foo + { + int x; + }; + auto f1 = Some(Foo{42}); + auto f2 = Some(f1); + auto f3 = Some(f2); + EXPECT_EQ(flatten(f1).copy_or(Foo{404}).x, 42); + EXPECT_EQ(flatten(f2).copy_or(Foo{404}).x, 42); + EXPECT_EQ(flatten(f3).copy_or(Foo{404}).x, 42); + + decltype(f1) n1 = None; + decltype(f2) n2a = None; + decltype(f2) n2b = Some(n1); + decltype(f3) n3a = None; + decltype(f3) n3b = Some(n2a); + decltype(f3) n3c = Some(n2b); + EXPECT_EQ(flatten(n1).copy_or(Foo{404}).x, 404); + EXPECT_EQ(flatten(n2a).copy_or(Foo{404}).x, 404); + EXPECT_EQ(flatten(n2b).copy_or(Foo{404}).x, 404); + EXPECT_EQ(flatten(n3a).copy_or(Foo{404}).x, 404); + EXPECT_EQ(flatten(n3b).copy_or(Foo{404}).x, 404); + EXPECT_EQ(flatten(n3c).copy_or(Foo{404}).x, 404); +} + +#define EQ(a, b) ({ EXPECT_TRUE(a == b); EXPECT_FALSE(a != b); EXPECT_FALSE(a < b); EXPECT_TRUE(a <= b); EXPECT_FALSE(a > b); EXPECT_TRUE(a >= b); }) +#define LT(a, b) ({ EXPECT_FALSE(a == b); EXPECT_TRUE(a != b); EXPECT_TRUE(a < b); EXPECT_TRUE(a <= b); EXPECT_FALSE(a > b); EXPECT_FALSE(a >= b); }) +#define GT(a, b) ({ EXPECT_FALSE(a == b); EXPECT_TRUE(a != b); EXPECT_FALSE(a < b); EXPECT_FALSE(a <= b); EXPECT_TRUE(a > b); EXPECT_TRUE(a >= b); }) + +TEST(Option, cmp) +{ + using option::Option; + using option::Some; + using option::None; + + Option<int> none = None; + + EQ(none, none); + EQ(none, None); + LT(none, Some(-1)); + LT(none, Some(0)); + LT(none, Some(1)); + EQ((None), none); + // EQ((None), None); // actually a function template + LT((None), Some(-1)); + LT((None), Some(0)); + LT((None), Some(1)); + GT(Some(-1), none); + GT(Some(-1), None); + EQ(Some(-1), Some(-1)); + LT(Some(-1), Some(0)); + LT(Some(-1), Some(1)); + GT(Some(0), none); + GT(Some(0), None); + GT(Some(0), Some(-1)); + EQ(Some(0), Some(0)); + LT(Some(0), Some(1)); + GT(Some(1), none); + GT(Some(1), None); + GT(Some(1), Some(-1)); + GT(Some(1), Some(0)); + EQ(Some(1), Some(1)); +} + +} // namespace tmwa diff --git a/src/compat/rawmem.hpp b/src/compat/rawmem.hpp index c271a56..66af204 100644 --- a/src/compat/rawmem.hpp +++ b/src/compat/rawmem.hpp @@ -22,6 +22,8 @@ #include <cstdint> #include <cstring> +#include <type_traits> + #include "fwd.hpp" @@ -49,4 +51,23 @@ void really_memset0(uint8_t *dest, size_t n) { memset(dest, '\0', n); } + +template<class T> +struct is_trivially_copyable +: std::integral_constant<bool, + // come back when GCC actually implements the public traits properly + __has_trivial_copy(T) + && __has_trivial_assign(T) + && __has_trivial_destructor(T)> +{}; + +template<class T> +void really_memzero_this(T *v) +{ + static_assert(is_trivially_copyable<T>::value, "only for mostly-pod types"); + static_assert(std::is_class<T>::value || std::is_union<T>::value, "Only for user-defined structures (for now)"); + memset(v, '\0', sizeof(*v)); +} +template<class T, size_t n> +void really_memzero_this(T (&)[n]) = delete; } // namespace tmwa diff --git a/src/compat/result.hpp b/src/compat/result.hpp new file mode 100644 index 0000000..6adc552 --- /dev/null +++ b/src/compat/result.hpp @@ -0,0 +1,94 @@ +#pragma once +// result.hpp - A possibly failed return value +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include "../strings/rstring.hpp" + +#include "option.hpp" + +namespace tmwa +{ + namespace result + { + enum ResultMagicFlag { magic_flag }; + + template<class T> + class Result + { + Option<T> success; + RString failure; + public: + Result(ResultMagicFlag, T v) + : success(Some(std::move(v))), failure() + {} + Result(ResultMagicFlag, RString msg) + : success(None), failure(std::move(msg)) + {} + + bool is_ok() { return success.is_some(); } + bool is_err() { return !is_ok(); } + + Option<T>& get_success() { return success; } + RString& get_failure() { return failure; } + }; + + template<class T> + Result<T> Ok(T v) + { + return Result<T>(magic_flag, std::move(v)); + } + + struct Err + { + RString message; + Err(RString m) : message(std::move(m)) {} + + template<class T> + operator Result<T>() + { + return Result<T>(magic_flag, message); + } + template<class T> + operator Option<Result<T>>() + { + return Some(Result<T>(magic_flag, message)); + } + }; + + template<class T> + bool operator == (const Result<T>& l, const Result<T>& r) + { + return l.get_success() == r.get_success() && l.get_failure() == r.get_failure(); + } + template<class T> + bool operator != (const Result<T>& l, const Result<T>& r) + { + return !(l == r); + } + } // namespace result + using result::Result; + using result::Ok; + using result::Err; + +#define TRY(r) ({ auto _res = r; TRY_UNWRAP(_res.get_success(), return ::tmwa::Err(_res.get_failure())); }) + // TODO the existence of this as a separate macro is a bug. +#define TRY_MOVE(r) ({ auto _res = r; TRY_UNWRAP(std::move(_res.get_success()), return ::tmwa::Err(_res.get_failure())); }) +} // namespace tmwa diff --git a/src/compat/result_test.cpp b/src/compat/result_test.cpp new file mode 100644 index 0000000..0fcc181 --- /dev/null +++ b/src/compat/result_test.cpp @@ -0,0 +1,79 @@ +#include "result.hpp" +// result_test.cpp - Testsuite for possibly failing return values +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include <gtest/gtest.h> + +#include "../strings/literal.hpp" + +//#include "../poison.hpp" + + +namespace tmwa +{ +TEST(Result, inspect) +{ + struct Foo + { + int val; + + Foo(int v) : val(v) {} + Foo(Foo&&) = default; + Foo(const Foo&) = delete; + Foo& operator = (Foo&&) = default; + Foo& operator = (const Foo&) = delete; + + bool operator == (const Foo& other) const + { + return this->val == other.val; + } + }; + + Result<Foo> foo = Ok(Foo(1)); + EXPECT_TRUE(foo.is_ok()); + EXPECT_FALSE(foo.is_err()); + EXPECT_EQ(foo.get_success(), Some(Foo(1))); + EXPECT_EQ(foo.get_failure(), ""_s); + foo = Err("oops"_s); + EXPECT_FALSE(foo.is_ok()); + EXPECT_TRUE(foo.is_err()); + EXPECT_EQ(foo.get_success(), None<Foo>()); + EXPECT_EQ(foo.get_failure(), "oops"_s); +} + +static +Result<int> try_you(bool b) +{ + return b ? Ok(0) : Err("die"_s); +} + +static +Result<int> try_me(bool b) +{ + return Ok(TRY(try_you(b)) + 1); +} + +TEST(Result, try) +{ + Result<int> t = try_me(true); + EXPECT_EQ(t.get_success(), Some(1)); + Result<int> f = try_me(false); + EXPECT_EQ(f.get_failure(), "die"_s); +} +} // namespace tmwa diff --git a/src/compat/time_t.cpp b/src/compat/time_t.cpp deleted file mode 100644 index ee0bbde..0000000 --- a/src/compat/time_t.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "time_t.hpp" -// time_t.cpp - time_t with a reliable representation -// -// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/compat/time_t.hpp b/src/compat/time_t.hpp index 9e7cf25..e9c97c4 100644 --- a/src/compat/time_t.hpp +++ b/src/compat/time_t.hpp @@ -20,10 +20,90 @@ #include "fwd.hpp" -// TODO fix this ordering violation by promoting TimeT here -#include "../mmo/utils.hpp" +#include <ctime> + +#include "../ints/little.hpp" + +#include "operators.hpp" namespace tmwa { +// Exists in place of time_t, to give it a predictable printf-format. +// (on x86 and amd64, time_t == long, but not on x32) +static_assert(sizeof(long long) >= sizeof(time_t), "long long >= time_t"); +struct TimeT : Comparable +{ + long long value; + + // conversion + TimeT(time_t t=0) : value(t) {} + TimeT(struct tm t) : value(timegm(&t)) {} + operator time_t() const { return value; } + operator struct tm() const { time_t v = value; return *gmtime(&v); } + + explicit operator bool() const { return value; } + bool operator !() const { return !value; } + + // prevent surprises + template<class T> + TimeT(T) = delete; + template<class T> + operator T() const = delete; + + static + TimeT now() + { + // poisoned, but this is still in header-land + return time(nullptr); + } + + bool error() const + { + return value == -1; + } + bool okay() const + { + return !error(); + } +}; + +inline +long long convert_for_printf(TimeT t) +{ + return t.value; +} + +// 2038 problem +inline __attribute__((warn_unused_result)) +bool native_to_network(Little32 *net, TimeT nat) +{ + time_t tmp = nat; + return native_to_network(net, static_cast<uint32_t>(tmp)); +} + +inline __attribute__((warn_unused_result)) +bool network_to_native(TimeT *nat, Little32 net) +{ + uint32_t tmp; + bool rv = network_to_native(&tmp, net); + *nat = static_cast<time_t>(tmp); + return rv; +} + +inline __attribute__((warn_unused_result)) +bool native_to_network(Little64 *net, TimeT nat) +{ + time_t tmp = nat; + return native_to_network(net, static_cast<uint64_t>(tmp)); +} + +inline __attribute__((warn_unused_result)) +bool network_to_native(TimeT *nat, Little64 net) +{ + uint64_t tmp; + bool rv = network_to_native(&tmp, net); + *nat = static_cast<time_t>(tmp); + return rv; +} } // namespace tmwa diff --git a/src/diagnostics.hpp b/src/diagnostics.hpp index dab15b5..5eee323 100644 --- a/src/diagnostics.hpp +++ b/src/diagnostics.hpp @@ -600,7 +600,8 @@ namespace tmwa /// Warn about maybe uninitialized automatic variables #define DIAG_maybe_uninitialized "-Wmaybe-uninitialized" -#if GCC >= 407 +// buggy in 4.7 with tmwa::Option<> +#if GCC >= 408 # define HAS_DIAG_maybe_uninitialized 1 #else # define HAS_DIAG_maybe_uninitialized 0 diff --git a/src/generic/array.hpp b/src/generic/array.hpp index dccb91e..3575db6 100644 --- a/src/generic/array.hpp +++ b/src/generic/array.hpp @@ -26,6 +26,7 @@ #include "oops.hpp" +// half the important stuff is now in fwd.hpp !!! namespace tmwa { template<class I, I be, I en> @@ -39,9 +40,6 @@ struct ExclusiveIndexing constexpr static size_t alloc_size = index_to_offset(en) - index_to_offset(be); }; -template<size_t n> -using SimpleIndexing = ExclusiveIndexing<size_t, 0, n>; - template<class I, I lo, I hi> struct InclusiveIndexing { @@ -53,7 +51,7 @@ struct InclusiveIndexing constexpr static size_t alloc_size = index_to_offset(hi) - index_to_offset(lo) + 1; }; -template<class E, E n=E::COUNT> +template<class E, E n> struct EnumIndexing : ExclusiveIndexing<E, static_cast<E>(0), n> { }; @@ -112,7 +110,4 @@ public: return !(lhs == rhs); } }; - -template<class T, size_t n> -using Array = GenericArray<T, SimpleIndexing<n>>; } // namespace tmwa diff --git a/src/generic/db.cpp b/src/generic/db.cpp deleted file mode 100644 index 458068c..0000000 --- a/src/generic/db.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "db.hpp" -// db.cpp - convenience wrappers over std::map<K, V> -// -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/generic/db.hpp b/src/generic/db.hpp index 90c4f92..04ead79 100644 --- a/src/generic/db.hpp +++ b/src/generic/db.hpp @@ -23,6 +23,8 @@ #include <map> #include <memory> +#include "../compat/borrow.hpp" + namespace tmwa { @@ -45,19 +47,19 @@ public: const_iterator begin() const { return impl.begin(); } const_iterator end() const { return impl.end(); } - V *search(const K& k) + Option<Borrowed<V>> search(const K& k) { iterator it = impl.find(k); if (it == impl.end()) - return nullptr; - return &it->second; + return None; + return Some(borrow(it->second)); } - const V *search(const K& k) const + Option<Borrowed<const V>> search(const K& k) const { const_iterator it = impl.find(k); if (it == impl.end()) - return nullptr; - return &it->second; + return None; + return Some(borrow(it->second)); } void insert(const K& k, V v) { @@ -72,9 +74,9 @@ public: return (void)&it->second; } - V *init(const K& k) + Borrowed<V> init(const K& k) { - return &impl[k]; + return borrow(impl[k]); } void erase(const K& k) { @@ -112,8 +114,13 @@ public: // const V& ? with a static default V? V get(const K& k) { - V *vp = impl.search(k); - return vp ? *vp : V(); + Option<Borrowed<V>> vp = impl.search(k); + OMATCH_BEGIN_SOME (v, vp) + { + return *v; + } + OMATCH_END (); + return V(); } void put(const K& k, V v) { @@ -153,10 +160,15 @@ public: const_iterator end() const { return impl.end(); } // const V& ? with a static default V? - V *get(const K& k) - { - U *up = impl.search(k); - return up ? up->get() : nullptr; + Option<Borrowed<V>> get(const K& k) + { + Option<Borrowed<U>> up = impl.search(k); + OMATCH_BEGIN_SOME (u, up) + { + return Some(borrow(*u->get())); + } + OMATCH_END (); + return None; } void put(const K& k, U v) { diff --git a/src/generic/dumb_ptr.cpp b/src/generic/dumb_ptr.cpp deleted file mode 100644 index e690f7d..0000000 --- a/src/generic/dumb_ptr.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "dumb_ptr.hpp" -// dumb_ptr.cpp - dummy file to make Make dependencies work -// -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/generic/dumb_ptr.hpp b/src/generic/dumb_ptr.hpp index 72247d5..2ada93b 100644 --- a/src/generic/dumb_ptr.hpp +++ b/src/generic/dumb_ptr.hpp @@ -35,8 +35,11 @@ class dumb_ptr friend class dumb_ptr; T *impl; public: + dumb_ptr() noexcept + : impl(nullptr) + {} explicit - dumb_ptr(T *p=nullptr) noexcept + dumb_ptr(T *p) noexcept : impl(p) {} template<class U> diff --git a/src/generic/enum.hpp b/src/generic/enum.hpp index 81c9b12..d5d50ea 100644 --- a/src/generic/enum.hpp +++ b/src/generic/enum.hpp @@ -33,8 +33,7 @@ namespace tmwa { -template<class T, class E, E max> -using earray = GenericArray<T, EnumIndexing<E, max>>; +// part moved to fwd.hpp template<class T, class E, E max> class eptr diff --git a/src/generic/fwd.hpp b/src/generic/fwd.hpp index 3215903..31fb13a 100644 --- a/src/generic/fwd.hpp +++ b/src/generic/fwd.hpp @@ -20,10 +20,42 @@ #include "../sanity.hpp" +#include "../strings/fwd.hpp" // rank 1 +#include "../compat/fwd.hpp" // rank 2 +// generic/fwd.hpp is rank 3 + namespace tmwa { // meh, add more when I feel like it template<class T> class dumb_ptr; + +template<class K, class V> +class Map; +template<class K, class V> +class DMap; +template<class K, class V> +class UPMap; + +class InternPool; + +// arrays are complicated +template<class I, I be, I en> +struct ExclusiveIndexing; +template<size_t n> +using SimpleIndexing = ExclusiveIndexing<size_t, 0, n>; +template<class I, I lo, I hi> +struct InclusiveIndexing; +template<class E, E n=E::COUNT> +struct EnumIndexing; +template<class I, size_t limit> +struct InventoryIndexing; +template<class T, class I> +struct GenericArray; +template<class T, size_t n> +using Array = GenericArray<T, SimpleIndexing<n>>; + +template<class T, class E, E max> +using earray = GenericArray<T, EnumIndexing<E, max>>; } // namespace tmwa diff --git a/src/generic/matrix.cpp b/src/generic/matrix.cpp deleted file mode 100644 index b14ab7d..0000000 --- a/src/generic/matrix.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "matrix.hpp" -// matrix.cpp - A 2D array. -// -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/generic/md5.hpp b/src/generic/md5.hpp index 50bc987..2f07789 100644 --- a/src/generic/md5.hpp +++ b/src/generic/md5.hpp @@ -24,7 +24,6 @@ #include <array> -#include "../strings/fwd.hpp" #include "../strings/vstring.hpp" diff --git a/src/generic/operators.cpp b/src/generic/operators.cpp deleted file mode 100644 index 614ae51..0000000 --- a/src/generic/operators.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "operators.hpp" -// operators.cpp - ADL helper for value wrappers. -// -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/generic/random.hpp b/src/generic/random.hpp index 5d67236..897ad43 100644 --- a/src/generic/random.hpp +++ b/src/generic/random.hpp @@ -18,10 +18,10 @@ // 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 "random.t.hpp" +#include "fwd.hpp" + #include <random> diff --git a/src/generic/random2.hpp b/src/generic/random2.hpp index 23d165c..3d481f4 100644 --- a/src/generic/random2.hpp +++ b/src/generic/random2.hpp @@ -18,10 +18,10 @@ // 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 "random.hpp" +#include "fwd.hpp" + #include <algorithm> #include "../compat/iter.hpp" diff --git a/src/mmo/core.cpp b/src/high/core.cpp index ff12f17..8a4d7ca 100644 --- a/src/mmo/core.cpp +++ b/src/high/core.cpp @@ -23,6 +23,7 @@ #include <sys/wait.h> #include <alloca.h> +#include <unistd.h> #include <csignal> #include <cstdlib> @@ -106,8 +107,21 @@ void sig_proc(int) */ } // namespace tmwa +static +void check_caps() +{ + if (geteuid() == 0) + { + puts("Please don't run as root!"); + _exit(1); + } +} + int tmwa_main(int argc, char **argv) { + // run before anything else (except global constructors) + check_caps(); + using namespace tmwa; check_paths(); @@ -153,8 +167,8 @@ int tmwa_main(int argc, char **argv) // may wait too long in sendrecv tick_t now = milli_clock::now(); interval_t next = do_timer(now); - do_sendrecv(next); - do_parsepacket(); + runflag &= do_sendrecv(next); + runflag &= do_parsepacket(); } return 0; diff --git a/src/mmo/core.hpp b/src/high/core.hpp index 259dd90..0ded246 100644 --- a/src/mmo/core.hpp +++ b/src/high/core.hpp @@ -24,8 +24,6 @@ #include "../range/slice.hpp" -#include "../strings/fwd.hpp" - namespace tmwa { diff --git a/src/mmo/extract.cpp b/src/high/extract_mmo.cpp index a480984..ab4290d 100644 --- a/src/mmo/extract.cpp +++ b/src/high/extract_mmo.cpp @@ -1,5 +1,5 @@ -#include "extract.hpp" -// extract.cpp - a simple, hierarchical, tokenizer +#include "extract_mmo.hpp" +// extract_mmo.cpp - a simple, hierarchical, tokenizer // // Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> // @@ -20,37 +20,25 @@ #include <algorithm> -#include "../strings/astring.hpp" -#include "../strings/xstring.hpp" -#include "../strings/vstring.hpp" +#include "../io/extract.hpp" + +#include "../mmo/extract_enums.hpp" -#include "extract_enums.hpp" #include "mmo.hpp" #include "../poison.hpp" +// TODO also pass an io::LineSpan around. namespace tmwa { -bool extract(XString str, XString *rv) -{ - *rv = str; - return true; -} - -bool extract(XString str, AString *rv) -{ - *rv = str; - return true; -} - -bool extract(XString str, GlobalReg *var) +bool impl_extract(XString str, GlobalReg *var) { return extract(str, record<','>(&var->str, &var->value)); } -bool extract(XString str, Item *it) +bool impl_extract(XString str, Item *it) { XString ignored; XString corruption_hack_amount; @@ -78,7 +66,7 @@ bool extract(XString str, Item *it) return rv; } -bool extract(XString str, MapName *m) +bool impl_extract(XString str, MapName *m) { XString::iterator it = std::find(str.begin(), str.end(), '.'); str = str.xislice_h(it); @@ -88,7 +76,7 @@ bool extract(XString str, MapName *m) return rv; } -bool extract(XString str, CharName *out) +bool impl_extract(XString str, CharName *out) { VString<23> tmp; if (extract(str, &tmp)) @@ -98,4 +86,15 @@ bool extract(XString str, CharName *out) } return false; } + +bool impl_extract(XString str, NpcEvent *ev) +{ + XString mid; + return extract(str, record<':'>(&ev->npc, &mid, &ev->label)) && !mid; +} + +bool impl_extract(XString str, Point *p) +{ + return extract(str, record<','>(&p->map_, &p->x, &p->y)); +} } // namespace tmwa diff --git a/src/compat/rawmem.cpp b/src/high/extract_mmo.hpp index d322437..f374658 100644 --- a/src/compat/rawmem.cpp +++ b/src/high/extract_mmo.hpp @@ -1,7 +1,7 @@ -#include "rawmem.hpp" -// rawmem.cpp - Ignore poisoning and really frob this memory unsafely. +#pragma once +// extract_mmo.hpp - a simple, hierarchical, tokenizer // -// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © 2012-2013 Ben Longbons <b.r.longbons@gmail.com> // // This file is part of The Mana World (Athena server) // @@ -18,9 +18,16 @@ // 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 "../poison.hpp" +#include "fwd.hpp" namespace tmwa { +bool impl_extract(XString str, GlobalReg *var); +bool impl_extract(XString str, Item *it); +bool impl_extract(XString str, MapName *m); +bool impl_extract(XString str, CharName *out); + +bool impl_extract(XString str, NpcEvent *ev); +bool impl_extract(XString str, Point *p); } // namespace tmwa diff --git a/src/high/fwd.hpp b/src/high/fwd.hpp new file mode 100644 index 0000000..96875d3 --- /dev/null +++ b/src/high/fwd.hpp @@ -0,0 +1,40 @@ +#pragma once +// high/fwd.hpp - list of type names for mmo lib +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../sanity.hpp" + +#include "../range/fwd.hpp" // rank 1 +#include "../strings/fwd.hpp" // rank 1 +#include "../compat/fwd.hpp" // rank 2 +#include "../generic/fwd.hpp" // rank 3 +#include "../io/fwd.hpp" // rank 4 +#include "../net/fwd.hpp" // rank 5 +#include "../mmo/fwd.hpp" // rank 6 +#include "../proto2/fwd.hpp" // rank 8 +// high/fwd.hpp is rank 9 + + +namespace tmwa +{ +class CharPair; +class PartyPair; +struct GM_Account; +// meh, add more when I feel like it +} // namespace tmwa diff --git a/src/mmo/md5more.cpp b/src/high/md5more.cpp index 4e5d2da..05149ac 100644 --- a/src/mmo/md5more.cpp +++ b/src/high/md5more.cpp @@ -31,7 +31,7 @@ #include "../net/ip.hpp" -#include "../mmo/mmo.hpp" +#include "mmo.hpp" #include "../poison.hpp" diff --git a/src/mmo/md5more.hpp b/src/high/md5more.hpp index 7d50713..33ebe24 100644 --- a/src/mmo/md5more.hpp +++ b/src/high/md5more.hpp @@ -24,10 +24,6 @@ #include "../generic/md5.hpp" -#include "../io/fwd.hpp" - -#include "../net/fwd.hpp" - namespace tmwa { diff --git a/src/mmo/mmo.hpp b/src/high/mmo.hpp index cfa278d..b5bcac8 100644 --- a/src/mmo/mmo.hpp +++ b/src/high/mmo.hpp @@ -22,9 +22,13 @@ #include "fwd.hpp" +#include "../compat/borrow.hpp" #include "../compat/memory.hpp" -#include "../proto2/types.hpp" +#include "../proto2/net-CharData.hpp" +#include "../proto2/net-CharKey.hpp" +#include "../proto2/net-PartyMost.hpp" +#include "../proto2/net-SkillValue.hpp" namespace tmwa @@ -58,12 +62,10 @@ struct GM_Account struct PartyPair { - PartyId party_id = {}; - PartyMost *party_most = {}; + PartyId party_id; + Borrowed<PartyMost> party_most; - explicit - operator bool() const { return party_most; } - bool operator !() const { return !party_most; } - PartyMost *operator->() const { return party_most; } + PartyMost& operator *() const { return *party_most; } + Borrowed<PartyMost> operator->() const { return party_most; } }; } // namespace tmwa diff --git a/src/mmo/utils.cpp b/src/high/utils.cpp index f8aff2e..9b89c31 100644 --- a/src/mmo/utils.cpp +++ b/src/high/utils.cpp @@ -28,9 +28,7 @@ #include "../strings/xstring.hpp" #include "../io/cxxstdio.hpp" -#include "../io/write.hpp" - -#include "extract.hpp" +#include "../io/extract.hpp" #include "../poison.hpp" @@ -85,39 +83,4 @@ int config_switch(ZString str) FPRINTF(stderr, "Fatal: bad option value %s"_fmt, str); abort(); } - -static_assert(sizeof(timestamp_seconds_buffer) == 20, "seconds buffer"); -static_assert(sizeof(timestamp_milliseconds_buffer) == 24, "millis buffer"); - -void stamp_time(timestamp_seconds_buffer& out, const TimeT *t) -{ - struct tm when = t ? *t : TimeT::now(); - char buf[20]; - strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when); - out = stringish<timestamp_seconds_buffer>(VString<19>(strings::really_construct_from_a_pointer, buf)); -} -void stamp_time(timestamp_milliseconds_buffer& out) -{ - struct timeval tv; - gettimeofday(&tv, nullptr); - struct tm when = TimeT(tv.tv_sec); - char buf[24]; - strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when); - sprintf(buf + 19, ".%03d", static_cast<int>(tv.tv_usec / 1000)); - out = stringish<timestamp_milliseconds_buffer>(VString<23>(strings::really_construct_from_a_pointer, buf)); -} - -void log_with_timestamp(io::WriteFile& out, XString line) -{ - if (!line) - { - out.put('\n'); - return; - } - timestamp_milliseconds_buffer tmpstr; - stamp_time(tmpstr); - out.really_put(tmpstr.data(), tmpstr.size()); - out.really_put(": ", 2); - out.put_line(line); -} } // namespace tmwa diff --git a/src/compat/cast.cpp b/src/high/utils.hpp index 482529d..c815e03 100644 --- a/src/compat/cast.cpp +++ b/src/high/utils.hpp @@ -1,6 +1,8 @@ -#include "cast.hpp" -// cast.cpp - Change the type of a variable. +#pragma once +// utils.hpp - Useful stuff that hasn't been categorized. // +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team // Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> // // This file is part of The Mana World (Athena server) @@ -18,9 +20,10 @@ // 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 "../poison.hpp" - +#include "fwd.hpp" namespace tmwa { +bool e_mail_check(XString email); +int config_switch(ZString str); } // namespace tmwa diff --git a/src/ints/fwd.hpp b/src/ints/fwd.hpp index a08e546..536eba1 100644 --- a/src/ints/fwd.hpp +++ b/src/ints/fwd.hpp @@ -20,6 +20,8 @@ #include "../sanity.hpp" +// ints/fwd.hpp is rank 1 + namespace tmwa { diff --git a/src/ints/little.cpp b/src/ints/little.cpp deleted file mode 100644 index 0ae5bf7..0000000 --- a/src/ints/little.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "little.hpp" -// little.cpp - integers of known endianness -// -// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/ints/udl.cpp b/src/ints/udl.cpp deleted file mode 100644 index 3988903..0000000 --- a/src/ints/udl.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "udl.hpp" -// udl.cpp - user-defined literals for integers. -// -// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/ints/wrap.cpp b/src/ints/wrap.cpp deleted file mode 100644 index 84d4b33..0000000 --- a/src/ints/wrap.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "wrap.hpp" -// wrap.cpp - basic integer wrapper classes -// -// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/ints/wrap.py b/src/ints/wrap.py index c8a8c6d..d5a6e99 100644 --- a/src/ints/wrap.py +++ b/src/ints/wrap.py @@ -9,5 +9,10 @@ class Wrapped(object): def to_string(self): return self._value['_value'] + test_extra = ''' + void do_breakpoint(); + void do_breakpoint() {} + ''' + # tests are in src/mmo/ids.py instead tests = [] diff --git a/src/io/cxxstdio.cpp b/src/io/cxxstdio.cpp deleted file mode 100644 index ca4e880..0000000 --- a/src/io/cxxstdio.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "cxxstdio.hpp" -// cxxstdio.cpp - pass C++ types through printf -// -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/io/cxxstdio_enums.cpp b/src/io/cxxstdio_enums.cpp deleted file mode 100644 index 216da1d..0000000 --- a/src/io/cxxstdio_enums.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "cxxstdio_enums.hpp" -// cxxstdio_enums.cpp - Opt-in integer formatting support for enums. -// -// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/io/dir.hpp b/src/io/dir.hpp index 071f309..f6fedbf 100644 --- a/src/io/dir.hpp +++ b/src/io/dir.hpp @@ -20,8 +20,6 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" - #include "fd.hpp" diff --git a/src/io/extract.cpp b/src/io/extract.cpp new file mode 100644 index 0000000..fce4dab --- /dev/null +++ b/src/io/extract.cpp @@ -0,0 +1,220 @@ +#include "extract.hpp" +// extract.cpp - a simple, hierarchical, tokenizer +// +// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include <algorithm> + +#include "../strings/astring.hpp" +#include "../strings/xstring.hpp" +#include "../strings/vstring.hpp" + +#include "../poison.hpp" + + +// TODO also pass an io::LineSpan around. +namespace tmwa +{ +bool impl_extract(XString str, XString *rv) +{ + *rv = str; + return true; +} + +bool impl_extract(XString str, RString *rv) +{ + *rv = str; + return true; +} + +bool impl_extract(XString str, AString *rv) +{ + *rv = str; + return true; +} + +bool impl_extract(XString str, std::chrono::nanoseconds *ns) +{ + std::chrono::nanoseconds::rep rep; + if (extract(str, &rep)) + { + *ns = std::chrono::nanoseconds(rep); + return true; + } + if (str.endswith("ns"_s)) + { + if (extract(str.xrslice_h("ns"_s.size()), &rep)) + { + *ns = std::chrono::nanoseconds(rep); + return true; + } + return false; + } + std::chrono::microseconds bigger; + if (extract(str, &bigger)) + { + *ns = bigger; + return *ns == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::microseconds *us) +{ + std::chrono::microseconds::rep rep; + if (extract(str, &rep)) + { + *us = std::chrono::microseconds(rep); + return true; + } + if (str.endswith("us"_s)) + { + if (extract(str.xrslice_h("us"_s.size()), &rep)) + { + *us = std::chrono::microseconds(rep); + return true; + } + return false; + } + std::chrono::milliseconds bigger; + if (extract(str, &bigger)) + { + *us = bigger; + return *us == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::milliseconds *ms) +{ + std::chrono::milliseconds::rep rep; + if (extract(str, &rep)) + { + *ms = std::chrono::milliseconds(rep); + return true; + } + if (str.endswith("ms"_s)) + { + if (extract(str.xrslice_h("ms"_s.size()), &rep)) + { + *ms = std::chrono::milliseconds(rep); + return true; + } + return false; + } + std::chrono::seconds bigger; + if (extract(str, &bigger)) + { + *ms = bigger; + return *ms == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::seconds *s) +{ + std::chrono::seconds::rep rep; + if (extract(str, &rep)) + { + *s = std::chrono::seconds(rep); + return true; + } + if (str.endswith("s"_s)) + { + if (extract(str.xrslice_h("s"_s.size()), &rep)) + { + *s = std::chrono::seconds(rep); + return true; + } + return false; + } + std::chrono::minutes bigger; + if (extract(str, &bigger)) + { + *s = bigger; + return *s == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::minutes *min) +{ + std::chrono::minutes::rep rep; + if (extract(str, &rep)) + { + *min = std::chrono::minutes(rep); + return true; + } + if (str.endswith("min"_s)) + { + if (extract(str.xrslice_h("min"_s.size()), &rep)) + { + *min = std::chrono::minutes(rep); + return true; + } + return false; + } + std::chrono::hours bigger; + if (extract(str, &bigger)) + { + *min = bigger; + return *min == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::hours *h) +{ + std::chrono::hours::rep rep; + if (extract(str, &rep)) + { + *h = std::chrono::hours(rep); + return true; + } + if (str.endswith("h"_s)) + { + if (extract(str.xrslice_h("h"_s.size()), &rep)) + { + *h = std::chrono::hours(rep); + return true; + } + return false; + } + std::chrono::duration<int, std::ratio<60*60*24>> bigger; + if (extract(str, &bigger)) + { + *h = bigger; + return *h == bigger; + } + return false; +} +bool impl_extract(XString str, std::chrono::duration<int, std::ratio<60*60*24>> *d) +{ + std::chrono::duration<int, std::ratio<60*60*24>>::rep rep; + if (extract(str, &rep)) + { + *d = std::chrono::duration<int, std::ratio<60*60*24>>(rep); + return true; + } + if (str.endswith("d"_s)) + { + if (extract(str.xrslice_h("d"_s.size()), &rep)) + { + *d = std::chrono::duration<int, std::ratio<60*60*24>>(rep); + return true; + } + return false; + } + return false; +} +} // namespace tmwa diff --git a/src/mmo/extract.hpp b/src/io/extract.hpp index ed2eb78..897f50e 100644 --- a/src/mmo/extract.hpp +++ b/src/io/extract.hpp @@ -24,24 +24,25 @@ #include <cstdlib> #include <algorithm> +#include <chrono> #include <vector> #include "../ints/wrap.hpp" #include "../strings/xstring.hpp" -#include "../generic/enum.hpp" +#include "../compat/time_t.hpp" -#include "utils.hpp" +#include "../generic/enum.hpp" namespace tmwa { template<class T> -bool do_extract(XString str, T t); +bool extract(XString str, T t); template<class T, typename=typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value && !std::is_same<T, bool>::value>::type> -bool extract(XString str, T *iv) +bool impl_extract(XString str, T *iv) { if (!str || str.size() > 20) return false; @@ -74,7 +75,7 @@ bool extract(XString str, T *iv) } inline -bool extract(XString str, TimeT *tv) +bool impl_extract(XString str, TimeT *tv) { return extract(str, &tv->value); } @@ -92,12 +93,12 @@ bool extract_as_int(XString str, T *iv) return true; } -bool extract(XString str, XString *rv); - -bool extract(XString str, AString *rv); +bool impl_extract(XString str, XString *rv); +bool impl_extract(XString str, RString *rv); +bool impl_extract(XString str, AString *rv); template<uint8_t N> -bool extract(XString str, VString<N> *out) +bool impl_extract(XString str, VString<N> *out) { if (str.size() > N) return false; @@ -106,7 +107,7 @@ bool extract(XString str, VString<N> *out) } inline -bool extract(XString str, LString exact) +bool impl_extract(XString str, LString exact) { return str == exact; } @@ -125,7 +126,7 @@ LStripper<T> lstripping(T v) } template<class T> -bool extract(XString str, LStripper<T> out) +bool impl_extract(XString str, LStripper<T> out) { return extract(str.lstrip(), out.impl); } @@ -162,13 +163,13 @@ Record<split, n, T...> record(T... t) } template<char split, int n> -bool extract(XString str, Record<split, n>) +bool impl_extract(XString str, Record<split, n>) { return !str; } template<char split, int n, class F, class... R> -bool extract(XString str, Record<split, n, F, R...> rec) +bool impl_extract(XString str, Record<split, n, F, R...> rec) { XString::iterator s = std::find(str.begin(), str.end(), split); XString::iterator s2 = s; @@ -197,7 +198,7 @@ VRecord<split, T> vrec(std::vector<T> *arr) } template<char split, class T> -bool extract(XString str, VRecord<split, T> rec) +bool impl_extract(XString str, VRecord<split, T> rec) { if (!str) return true; @@ -209,23 +210,25 @@ bool extract(XString str, VRecord<split, T> rec) && extract(str.xislice_t(s + 1), rec); } -bool extract(XString str, GlobalReg *var); - -bool extract(XString str, Item *it); - -bool extract(XString str, MapName *m); - -bool extract(XString str, CharName *out); - -template<class T> -bool do_extract(XString str, T t) +template<class R> +bool impl_extract(XString str, Wrapped<R> *w) { - return extract(str, t); + return extract(str, &w->_value); } -template<class R> -bool extract(XString str, Wrapped<R> *w) +bool impl_extract(XString str, std::chrono::nanoseconds *ns); +bool impl_extract(XString str, std::chrono::microseconds *us); +bool impl_extract(XString str, std::chrono::milliseconds *ms); +bool impl_extract(XString str, std::chrono::seconds *s); +bool impl_extract(XString str, std::chrono::minutes *min); +bool impl_extract(XString str, std::chrono::hours *h); +bool impl_extract(XString str, std::chrono::duration<int, std::ratio<60*60*24>> *d); + +// this must come after all non-`namespace tmwa` `impl_extract`s. +// In particular, the ones for `*int*` and `std::chrono::*` +template<class T> +bool extract(XString str, T t) { - return extract(str, &w->_value); + return impl_extract(str, t); } } // namespace tmwa diff --git a/src/mmo/extract_test.cpp b/src/io/extract_test.cpp index e6dc7b2..ee4cb08 100644 --- a/src/mmo/extract_test.cpp +++ b/src/io/extract_test.cpp @@ -22,7 +22,11 @@ #include "../strings/xstring.hpp" -#include "mmo.hpp" +#include "../net/timer.t.hpp" + +#include "../mmo/strs.hpp" + +#include "../high/extract_mmo.hpp" #include "../poison.hpp" @@ -357,4 +361,92 @@ TEST(extract, mapname) EXPECT_TRUE(extract("abcdefghijklmno.gat"_s, &map)); EXPECT_EQ(map, "abcdefghijklmno"_s); } + +TEST(extract, chrono) +{ + std::chrono::nanoseconds ns; + std::chrono::microseconds us; + std::chrono::milliseconds ms; + std::chrono::seconds s; + std::chrono::minutes min; + std::chrono::hours h; + std::chrono::duration<int, std::ratio<60*60*24>> d; + + EXPECT_TRUE(extract("1"_s, &ns)); + EXPECT_EQ(ns, 1_ns); + EXPECT_TRUE(extract("3ns"_s, &ns)); + EXPECT_EQ(ns, 3_ns); + EXPECT_TRUE(extract("4us"_s, &ns)); + EXPECT_EQ(ns, 4_us); + EXPECT_TRUE(extract("5ms"_s, &ns)); + EXPECT_EQ(ns, 5_ms); + EXPECT_TRUE(extract("6s"_s, &ns)); + EXPECT_EQ(ns, 6_s); + EXPECT_TRUE(extract("7min"_s, &ns)); + EXPECT_EQ(ns, 7_min); + EXPECT_TRUE(extract("8h"_s, &ns)); + EXPECT_EQ(ns, 8_h); + EXPECT_TRUE(extract("9d"_s, &ns)); + EXPECT_EQ(ns, 9_d); + + EXPECT_TRUE(extract("1"_s, &us)); + EXPECT_EQ(us, 1_us); + EXPECT_TRUE(extract("4us"_s, &us)); + EXPECT_EQ(us, 4_us); + EXPECT_TRUE(extract("5ms"_s, &us)); + EXPECT_EQ(us, 5_ms); + EXPECT_TRUE(extract("6s"_s, &us)); + EXPECT_EQ(us, 6_s); + EXPECT_TRUE(extract("7min"_s, &us)); + EXPECT_EQ(us, 7_min); + EXPECT_TRUE(extract("8h"_s, &us)); + EXPECT_EQ(us, 8_h); + EXPECT_TRUE(extract("9d"_s, &us)); + EXPECT_EQ(us, 9_d); + + EXPECT_TRUE(extract("1"_s, &ms)); + EXPECT_EQ(ms, 1_ms); + EXPECT_TRUE(extract("5ms"_s, &ms)); + EXPECT_EQ(ms, 5_ms); + EXPECT_TRUE(extract("6s"_s, &ms)); + EXPECT_EQ(ms, 6_s); + EXPECT_TRUE(extract("7min"_s, &ms)); + EXPECT_EQ(ms, 7_min); + EXPECT_TRUE(extract("8h"_s, &ms)); + EXPECT_EQ(ms, 8_h); + EXPECT_TRUE(extract("9d"_s, &ms)); + EXPECT_EQ(ms, 9_d); + + EXPECT_TRUE(extract("1"_s, &s)); + EXPECT_EQ(s, 1_s); + EXPECT_TRUE(extract("6s"_s, &s)); + EXPECT_EQ(s, 6_s); + EXPECT_TRUE(extract("7min"_s, &s)); + EXPECT_EQ(s, 7_min); + EXPECT_TRUE(extract("8h"_s, &s)); + EXPECT_EQ(s, 8_h); + EXPECT_TRUE(extract("9d"_s, &s)); + EXPECT_EQ(s, 9_d); + + EXPECT_TRUE(extract("1"_s, &min)); + EXPECT_EQ(min, 1_min); + EXPECT_TRUE(extract("7min"_s, &min)); + EXPECT_EQ(min, 7_min); + EXPECT_TRUE(extract("8h"_s, &min)); + EXPECT_EQ(min, 8_h); + EXPECT_TRUE(extract("9d"_s, &min)); + EXPECT_EQ(min, 9_d); + + EXPECT_TRUE(extract("1"_s, &h)); + EXPECT_EQ(h, 1_h); + EXPECT_TRUE(extract("8h"_s, &h)); + EXPECT_EQ(h, 8_h); + EXPECT_TRUE(extract("9d"_s, &h)); + EXPECT_EQ(h, 9_d); + + EXPECT_TRUE(extract("1"_s, &d)); + EXPECT_EQ(d, 1_d); + EXPECT_TRUE(extract("9d"_s, &d)); + EXPECT_EQ(d, 9_d); +} } // namespace tmwa diff --git a/src/io/fd.cpp b/src/io/fd.cpp index c0b44e8..bb0bbd5 100644 --- a/src/io/fd.cpp +++ b/src/io/fd.cpp @@ -102,6 +102,11 @@ namespace io int FD::close() { + if (fd == -1) + { + errno = EBADF; + return -1; + } return ::close(fd); } int FD::shutdown(int how) diff --git a/src/io/fd.hpp b/src/io/fd.hpp index d04d5bf..03a8b44 100644 --- a/src/io/fd.hpp +++ b/src/io/fd.hpp @@ -23,8 +23,6 @@ #include <sys/select.h> #include <sys/socket.h> -#include "../strings/fwd.hpp" - #include "../diagnostics.hpp" diff --git a/src/io/fwd.hpp b/src/io/fwd.hpp index deeb08c..3b9452b 100644 --- a/src/io/fwd.hpp +++ b/src/io/fwd.hpp @@ -20,6 +20,12 @@ #include "../sanity.hpp" +#include "../ints/fwd.hpp" // rank 1 +#include "../strings/fwd.hpp" // rank 1 +#include "../compat/fwd.hpp" // rank 2 +#include "../generic/fwd.hpp" // rank 3 +// io/fwd.hpp is rank 4 + namespace tmwa { @@ -28,5 +34,12 @@ namespace io class ReadFile; class WriteFile; class AppendFile; + class LineReader; + class LineCharReader; + class Line; + class LineChar; + class LineSpan; + template<class T> + class Spanned; } // namespace io } // namespace tmwa diff --git a/src/io/line.cpp b/src/io/line.cpp index a1cdf42..5d7e792 100644 --- a/src/io/line.cpp +++ b/src/io/line.cpp @@ -19,9 +19,7 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. #include "../strings/astring.hpp" -#include "../strings/mstring.hpp" #include "../strings/zstring.hpp" -#include "../strings/xstring.hpp" #include "cxxstdio.hpp" @@ -32,71 +30,6 @@ namespace tmwa { namespace io { - AString Line::message_str(ZString cat, ZString msg) const - { - MString out; - if (column) - out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, - filename, line, column, cat, msg); - else - out += STRPRINTF("%s:%u: %s: %s\n"_fmt, - filename, line, cat, msg); - out += STRPRINTF("%s\n"_fmt, text); - out += STRPRINTF("%*c\n"_fmt, column, '^'); - return AString(out); - } - - void Line::message(ZString cat, ZString msg) const - { - if (column) - FPRINTF(stderr, "%s:%u:%u: %s: %s\n"_fmt, - filename, line, column, cat, msg); - else - FPRINTF(stderr, "%s:%u: %s: %s\n"_fmt, - filename, line, cat, msg); - FPRINTF(stderr, "%s\n"_fmt, text); - FPRINTF(stderr, "%*c\n"_fmt, column, '^'); - } - - AString LineSpan::message_str(ZString cat, ZString msg) const - { - assert (begin.column); - assert (end.column); - assert (begin.line < end.line || begin.column <= end.column); - - MString out; - if (begin.line == end.line) - { - out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, - begin.filename, begin.line, begin.column, cat, msg); - out += STRPRINTF("%s\n"_fmt, begin.text); - out += STRPRINTF("%*c"_fmt, begin.column, '^'); - for (unsigned c = begin.column; c != end.column; ++c) - out += '~'; - out += '\n'; - } - else - { - out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, - begin.filename, begin.line, begin.column, cat, msg); - out += STRPRINTF("%s\n"_fmt, begin.text); - out += STRPRINTF("%*c"_fmt, begin.column, '^'); - for (unsigned c = begin.column; c != begin.text.size(); ++c) - out += '~'; - out += " ...\n"_s; - out += STRPRINTF("%s\n"_fmt, end.text); - for (unsigned c = 0; c != end.column; ++c) - out += '~'; - out += '\n'; - } - return AString(out); - } - - void LineSpan::message(ZString cat, ZString msg) const - { - FPRINTF(stderr, "%s"_fmt, message_str(cat, msg)); - } - LineReader::LineReader(ZString name) : filename(name), line(0), column(0), rf(name) {} @@ -105,6 +38,14 @@ namespace io : filename(name), line(0), column(0), rf(fd) {} + LineReader::LineReader(read_file_from_string, ZString name, XString content, int startline, FD fd) + : filename(name), line(startline-1), column(0), rf(from_string, content, fd) + {} + + LineReader::LineReader(read_file_from_string, ZString name, LString content, int startline, FD fd) + : filename(name), line(startline-1), column(0), rf(from_string, content, fd) + {} + bool LineReader::read_line(Line& l) { AString text; @@ -145,6 +86,38 @@ namespace io column = 0; } + LineCharReader::LineCharReader(read_file_from_string, ZString name, XString content, int startline, int startcol, FD fd) + : LineReader(from_string, name, content, 1, fd) + { + column = 1; // not 0, not whole line + if (rf.is_open()) + adv(); + if (!line) + column = 0; + else + { + line = startline; + column = startcol; + line_text = STRPRINTF("%*s"_fmt, static_cast<int>(column-1 + line_text.size()), line_text); + } + } + + LineCharReader::LineCharReader(read_file_from_string, ZString name, LString content, int startline, int startcol, FD fd) + : LineReader(from_string, name, content, 1, fd) + { + column = 1; // not 0, not whole line + if (rf.is_open()) + adv(); + if (!line) + column = 0; + else + { + line = startline; + column = startcol; + line_text = STRPRINTF("%*s"_fmt, static_cast<int>(column-1 + line_text.size()), line_text); + } + } + bool LineCharReader::get(LineChar& c) { if (!column) diff --git a/src/io/line.hpp b/src/io/line.hpp index 8244c5e..c94eeb9 100644 --- a/src/io/line.hpp +++ b/src/io/line.hpp @@ -21,55 +21,15 @@ #include "fwd.hpp" #include "../strings/rstring.hpp" -#include "../strings/zstring.hpp" -#include "../strings/literal.hpp" #include "read.hpp" +#include "span.hpp" namespace tmwa { namespace io { - // TODO split this out - struct Line - { - RString text; - - RString filename; - // 1-based - uint16_t line, column; - - AString message_str(ZString cat, ZString msg) const; - void message(ZString cat, ZString msg) const; - void note(ZString msg) const { message("note"_s, msg); } - void warning(ZString msg) const { message("warning"_s, msg); } - void error(ZString msg) const { message("error"_s, msg); } - }; - - // psst, don't tell anyone - struct LineChar : Line - { - char ch() - { - size_t c = column - 1; - if (c == text.size()) - return '\n'; - return text[c]; - } - }; - - struct LineSpan - { - LineChar begin, end; - - AString message_str(ZString cat, ZString msg) const; - void message(ZString cat, ZString msg) const; - void note(ZString msg) const { message("note"_s, msg); } - void warning(ZString msg) const { message("warning"_s, msg); } - void error(ZString msg) const { message("error"_s, msg); } - }; - class LineReader { protected: @@ -83,6 +43,8 @@ namespace io LineReader& operator = (LineReader&&) = delete; // needed for unit tests LineReader(ZString name, FD fd); + LineReader(read_file_from_string, ZString name, XString content, int startline=1, FD fd=FD()); + LineReader(read_file_from_string, ZString name, LString content, int startline=1, FD fd=FD()); bool read_line(Line& l); bool is_open(); @@ -97,6 +59,8 @@ namespace io LineCharReader(LineCharReader&&) = delete; LineCharReader& operator = (LineCharReader&&) = delete; LineCharReader(ZString name, FD fd); + LineCharReader(read_file_from_string, ZString name, XString content, int startline=1, int startcol=1, FD fd=FD()); + LineCharReader(read_file_from_string, ZString name, LString content, int startline=1, int startcol=1, FD fd=FD()); bool get(LineChar& c); void adv(); diff --git a/src/io/line_test.cpp b/src/io/line_test.cpp index edf60bd..582ee81 100644 --- a/src/io/line_test.cpp +++ b/src/io/line_test.cpp @@ -23,6 +23,8 @@ #include "../strings/astring.hpp" #include "../strings/zstring.hpp" +#include "../tests/fdhack.hpp" + #include "../poison.hpp" @@ -57,6 +59,7 @@ TEST(io, line1) } TEST(io, line2) { + QuietFd q; io::LineReader lr("<string2>"_s, string_pipe("Hello\nWorld"_s)); io::Line hi; EXPECT_TRUE(lr.read_line(hi)); @@ -73,6 +76,7 @@ TEST(io, line2) } TEST(io, line3) { + QuietFd q; io::LineReader lr("<string3>"_s, string_pipe("Hello\rWorld"_s)); io::Line hi; EXPECT_TRUE(lr.read_line(hi)); @@ -89,6 +93,7 @@ TEST(io, line3) } TEST(io, line4) { + QuietFd q; io::LineReader lr("<string4>"_s, string_pipe("Hello\r\nWorld"_s)); io::Line hi; EXPECT_TRUE(lr.read_line(hi)); @@ -105,6 +110,7 @@ TEST(io, line4) } TEST(io, line5) { + QuietFd q; io::LineReader lr("<string5>"_s, string_pipe("Hello\n\rWorld"_s)); io::Line hi; EXPECT_TRUE(lr.read_line(hi)); @@ -124,6 +130,20 @@ TEST(io, line5) EXPECT_EQ(hi.column, 0); EXPECT_FALSE(lr.read_line(hi)); } +TEST(io, line1text) +{ + io::LineReader lr(io::from_string, "<string1text>"_s, "Hello\nWorld"_s, 2); + io::Line hi; + EXPECT_TRUE(lr.read_line(hi)); + EXPECT_EQ(hi.text, "Hello"_s); + EXPECT_EQ(hi.line, 2); + EXPECT_EQ(hi.column, 0); + EXPECT_TRUE(lr.read_line(hi)); + EXPECT_EQ(hi.text, "World"_s); + EXPECT_EQ(hi.line, 3); + EXPECT_EQ(hi.column, 0); + EXPECT_FALSE(lr.read_line(hi)); +} TEST(io, linechar1) { @@ -175,6 +195,7 @@ TEST(io, linechar1) } TEST(io, linechar2) { + QuietFd q; io::LineCharReader lr("<stringchar2>"_s, string_pipe("Hi\nWu"_s)); io::LineChar c; EXPECT_TRUE(lr.get(c)); @@ -223,6 +244,7 @@ TEST(io, linechar2) } TEST(io, linechar3) { + QuietFd q; io::LineCharReader lr("<stringchar3>"_s, string_pipe("Hi\rWu"_s)); io::LineChar c; EXPECT_TRUE(lr.get(c)); @@ -271,6 +293,7 @@ TEST(io, linechar3) } TEST(io, linechar4) { + QuietFd q; io::LineCharReader lr("<stringchar4>"_s, string_pipe("Hi\r\nWu"_s)); io::LineChar c; EXPECT_TRUE(lr.get(c)); @@ -319,6 +342,7 @@ TEST(io, linechar4) } TEST(io, linechar5) { + QuietFd q; io::LineCharReader lr("<stringchar5>"_s, string_pipe("Hi\n\rWu"_s)); io::LineChar c; EXPECT_TRUE(lr.get(c)); @@ -372,6 +396,42 @@ TEST(io, linechar5) lr.adv(); EXPECT_FALSE(lr.get(c)); } +TEST(io, linechar1text) +{ + io::LineCharReader lr(io::from_string, "<stringchar1text>"_s, "Hi\nWu\n"_s, 2, 3); + io::LineChar c; + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), 'H'); + EXPECT_EQ(c.line, 2); + EXPECT_EQ(c.column, 3); + lr.adv(); + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), 'i'); + EXPECT_EQ(c.line, 2); + EXPECT_EQ(c.column, 4); + lr.adv(); + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), '\n'); + EXPECT_EQ(c.line, 2); + EXPECT_EQ(c.column, 5); + lr.adv(); + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), 'W'); + EXPECT_EQ(c.line, 3); + EXPECT_EQ(c.column, 1); + lr.adv(); + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), 'u'); + EXPECT_EQ(c.line, 3); + EXPECT_EQ(c.column, 2); + lr.adv(); + EXPECT_TRUE(lr.get(c)); + EXPECT_EQ(c.ch(), '\n'); + EXPECT_EQ(c.line, 3); + EXPECT_EQ(c.column, 3); + lr.adv(); + EXPECT_FALSE(lr.get(c)); +} TEST(io, linespan) { @@ -402,17 +462,17 @@ TEST(io, linespan) } while (span.end.ch() != 'r'); - EXPECT_EQ(span.begin.message_str("note"_s, "foo"_s), + EXPECT_EQ(span.begin.note_str("foo"_s), "<span>:1:5: note: foo\n" "Hello,\n" " ^\n"_s ); - EXPECT_EQ(span.end.message_str("warning"_s, "bar"_s), + EXPECT_EQ(span.end.warning_str("bar"_s), "<span>:2:3: warning: bar\n" "World!\n" " ^\n"_s ); - EXPECT_EQ(span.message_str("error"_s, "qux"_s), + EXPECT_EQ(span.error_str("qux"_s), "<span>:1:5: error: qux\n" "Hello,\n" " ^~ ...\n" diff --git a/src/io/read.cpp b/src/io/read.cpp index 3ae5246..32974d6 100644 --- a/src/io/read.cpp +++ b/src/io/read.cpp @@ -1,7 +1,7 @@ #include "read.hpp" // io/read.cpp - Input from files // -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com> // // This file is part of The Mana World (Athena server) // @@ -25,7 +25,7 @@ #include "../strings/zstring.hpp" #include "../strings/literal.hpp" -#include "../io/cxxstdio.hpp" +#include "cxxstdio.hpp" #include "../poison.hpp" @@ -36,6 +36,8 @@ namespace io { ReadFile::ReadFile(FD f) : fd(f), start(0), end(0) + // only for debug-sanity + , buf{} { } ReadFile::ReadFile(ZString name) @@ -46,6 +48,32 @@ namespace io : fd(dir.open_fd(name, O_RDONLY | O_CLOEXEC)), start(0), end(0) { } + ReadFile::ReadFile(read_file_from_string, XString content, FD f) + : fd(f), start(0), end(), extra() + { + if (content.size() <= 4096) + { + end = content.size(); + auto z = std::copy(content.begin(), content.end(), buf); + // only for debug sanity + std::fill(z, std::end(buf), 0); + return; + } + auto base = content.base(); + if (!base) + { + extra = content; + end = content.size(); + return; + } + start = &*content.begin() - &*base->begin(); + end = &*content.end() - &*base->begin(); + extra = *base; + } + ReadFile::ReadFile(read_file_from_string, LString content, FD f) + : ReadFile(from_string, RString(content), f) + { + } ReadFile::~ReadFile() { fd.close(); @@ -54,6 +82,14 @@ namespace io bool ReadFile::get(char& c) { + if (extra) + { + c = extra[start]; + ++start; + if (start == end) + extra = ""_s; + return true; + } if (start == end) { if (fd == FD()) @@ -82,6 +118,7 @@ namespace io } bool ReadFile::getline(AString& line) { + bool was_real_file = fd != FD(); MString tmp; char c; bool anything = false; @@ -115,15 +152,17 @@ namespace io else FPRINTF(stderr, "warning: file contains bare CR\n"_fmt); } - else if (!happy && anything) + else if (!happy && anything && was_real_file) + { FPRINTF(stderr, "warning: file does not contain a trailing newline\n"_fmt); + } line = AString(tmp); return anything; } bool ReadFile::is_open() { - return fd != FD(); + return fd != FD() || start != end; } } // namespace io } // namespace tmwa diff --git a/src/io/read.hpp b/src/io/read.hpp index 1ec26ca..2e3611b 100644 --- a/src/io/read.hpp +++ b/src/io/read.hpp @@ -1,7 +1,7 @@ #pragma once // io/read.hpp - Input from files. // -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com> // // This file is part of The Mana World (Athena server) // @@ -20,7 +20,7 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" +#include "../strings/rstring.hpp" #include "dir.hpp" #include "fd.hpp" @@ -29,18 +29,28 @@ namespace tmwa { namespace io { + enum read_file_from_string + { + from_string, + }; + + // TODO - for internal warnings, it would be convenient if this class + // didn't exist at all, and instead everything was done with line info. class ReadFile { private: FD fd; unsigned short start, end; char buf[4096]; + RString extra; public: explicit ReadFile(FD fd); explicit ReadFile(ZString name); ReadFile(const DirFd& dir, ZString name); + ReadFile(read_file_from_string, XString content, FD fd=FD()); + ReadFile(read_file_from_string, LString content, FD fd=FD()); ReadFile& operator = (ReadFile&&) = delete; ReadFile(ReadFile&&) = delete; diff --git a/src/io/read_test.cpp b/src/io/read_test.cpp index 8fe84b7..22c67c8 100644 --- a/src/io/read_test.cpp +++ b/src/io/read_test.cpp @@ -24,6 +24,8 @@ #include "../strings/zstring.hpp" #include "../strings/literal.hpp" +#include "../tests/fdhack.hpp" + #include "../poison.hpp" @@ -47,6 +49,7 @@ io::FD string_pipe(ZString sz) TEST(io, read1) { + QuietFd q; io::ReadFile rf(string_pipe("Hello"_s)); AString hi; EXPECT_TRUE(rf.getline(hi)); @@ -63,6 +66,7 @@ TEST(io, read2) } TEST(io, read3) { + QuietFd q; io::ReadFile rf(string_pipe("Hello\r"_s)); AString hi; EXPECT_TRUE(rf.getline(hi)); @@ -71,6 +75,7 @@ TEST(io, read3) } TEST(io, read4) { + QuietFd q; io::ReadFile rf(string_pipe("Hello\r\n"_s)); AString hi; EXPECT_TRUE(rf.getline(hi)); @@ -79,6 +84,7 @@ TEST(io, read4) } TEST(io, read5) { + QuietFd q; io::ReadFile rf(string_pipe("Hello\n\r"_s)); AString hi; EXPECT_TRUE(rf.getline(hi)); @@ -87,4 +93,65 @@ TEST(io, read5) EXPECT_FALSE(hi); EXPECT_FALSE(rf.getline(hi)); } + +#define S15 "0123456789abcde"_s +#define S16 "0123456789abcdef"_s +#define S255 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S15 +#define S256 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 S16 +#define S4095 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S255 +#define S4096 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 S256 + +TEST(io, readstringr) +{ + LString tests[] = + { + S15, + S16, + S255, + S256, + S4095, + S4096, + S4096 S16, + }; + for (RString test : tests) + { + char buf[test.size() + 1]; + + io::ReadFile rf(io::from_string, test); + EXPECT_EQ(rf.get(buf, sizeof(buf)), test.size()); + EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr)); + + io::ReadFile rf2(io::from_string, test, string_pipe("\na"_s)); + EXPECT_EQ(rf2.get(buf, sizeof(buf)), test.size() + 1); + EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr)); + EXPECT_EQ('\n', buf[test.size()]); + } +} + +TEST(io, readstringx) +{ + LString tests[] = + { + S15, + S16, + S255, + S256, + S4095, + S4096, + S4096 S16, + }; + for (XString test : tests) + { + char buf[test.size() + 1]; + + io::ReadFile rf(io::from_string, test); + EXPECT_EQ(rf.get(buf, sizeof(buf)), test.size()); + EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr)); + + io::ReadFile rf2(io::from_string, test, string_pipe("\na"_s)); + EXPECT_EQ(rf2.get(buf, sizeof(buf)), test.size() + 1); + EXPECT_EQ(test, XString(buf + 0, buf + test.size(), nullptr)); + EXPECT_EQ('\n', buf[test.size()]); + } +} } // namespace tmwa diff --git a/src/io/span.cpp b/src/io/span.cpp new file mode 100644 index 0000000..6d116c7 --- /dev/null +++ b/src/io/span.cpp @@ -0,0 +1,113 @@ +#include "span.hpp" +// io/span.cpp - Tracking info about input +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../strings/astring.hpp" +#include "../strings/mstring.hpp" +#include "../strings/zstring.hpp" + +#include "cxxstdio.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace io +{ + io::LineSpan Line::to_span() const + { + io::LineSpan rv; + rv.begin.text = this->text; + rv.begin.filename = this->filename; + rv.begin.line = this->line; + rv.begin.column = 1; + rv.end.text = this->text; + rv.end.filename = this->filename; + rv.end.line = this->line; + rv.end.column = this->text.size(); + return rv; + } + + AString Line::message_str(ZString cat, ZString msg) const + { + MString out; + if (column) + out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, + filename, line, column, cat, msg); + else + out += STRPRINTF("%s:%u: %s: %s\n"_fmt, + filename, line, cat, msg); + out += STRPRINTF("%s\n"_fmt, text); + out += STRPRINTF("%*c\n"_fmt, column, '^'); + return AString(out); + } + + void Line::message(ZString cat, ZString msg) const + { + if (column) + FPRINTF(stderr, "%s:%u:%u: %s: %s\n"_fmt, + filename, line, column, cat, msg); + else + FPRINTF(stderr, "%s:%u: %s: %s\n"_fmt, + filename, line, cat, msg); + FPRINTF(stderr, "%s\n"_fmt, text); + FPRINTF(stderr, "%*c\n"_fmt, column, '^'); + } + + AString LineSpan::message_str(ZString cat, ZString msg) const + { + assert (begin.column); + assert (end.column); + assert (begin.line < end.line || begin.column <= end.column); + + MString out; + if (begin.line == end.line) + { + out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, + begin.filename, begin.line, begin.column, cat, msg); + out += STRPRINTF("%s\n"_fmt, begin.text); + out += STRPRINTF("%*c"_fmt, begin.column, '^'); + for (unsigned c = begin.column; c != end.column; ++c) + out += '~'; + out += '\n'; + } + else + { + out += STRPRINTF("%s:%u:%u: %s: %s\n"_fmt, + begin.filename, begin.line, begin.column, cat, msg); + out += STRPRINTF("%s\n"_fmt, begin.text); + out += STRPRINTF("%*c"_fmt, begin.column, '^'); + for (unsigned c = begin.column; c != begin.text.size(); ++c) + out += '~'; + out += " ...\n"_s; + out += STRPRINTF("%s\n"_fmt, end.text); + for (unsigned c = 0; c != end.column; ++c) + out += '~'; + out += '\n'; + } + return AString(out); + } + + void LineSpan::message(ZString cat, ZString msg) const + { + FPRINTF(stderr, "%s"_fmt, message_str(cat, msg)); + } +} // namespace io +} // namespace tmwa diff --git a/src/io/span.hpp b/src/io/span.hpp new file mode 100644 index 0000000..9962b7c --- /dev/null +++ b/src/io/span.hpp @@ -0,0 +1,92 @@ +#pragma once +// io/span.hpp - Tracking info about input +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include "../strings/rstring.hpp" +#include "../strings/zstring.hpp" +#include "../strings/literal.hpp" + + +namespace tmwa +{ +namespace io +{ + // TODO split this out + struct Line + { + RString text; + + RString filename; + // 1-based + uint16_t line, column; + + AString message_str(ZString cat, ZString msg) const; + AString note_str(ZString msg) const { return message_str("note"_s, msg); } + AString warning_str(ZString msg) const { return message_str("warning"_s, msg); } + AString error_str(ZString msg) const { return message_str("error"_s, msg); } + void message(ZString cat, ZString msg) const; + void note(ZString msg) const { message("note"_s, msg); } + void warning(ZString msg) const { message("warning"_s, msg); } + void error(ZString msg) const { message("error"_s, msg); } + + LineSpan to_span() const; + }; + + // psst, don't tell anyone + struct LineChar : Line + { + char ch() + { + size_t c = column - 1; + if (c == text.size()) + return '\n'; + return text[c]; + } + }; + + struct LineSpan + { + LineChar begin, end; + + AString message_str(ZString cat, ZString msg) const; + AString note_str(ZString msg) const { return message_str("note"_s, msg); } + AString warning_str(ZString msg) const { return message_str("warning"_s, msg); } + AString error_str(ZString msg) const { return message_str("error"_s, msg); } + void message(ZString cat, ZString msg) const; + void note(ZString msg) const { message("note"_s, msg); } + void warning(ZString msg) const { message("warning"_s, msg); } + void error(ZString msg) const { message("error"_s, msg); } + }; + + template<class T> + struct Spanned + { + T data; + LineSpan span; + }; + + template<class T> + Spanned<T> respan(LineSpan span, T data) + { + return Spanned<T>{std::move(data), std::move(span)}; + } +} // namespace io +} // namespace tmwa diff --git a/src/io/tty.cpp b/src/io/tty.cpp deleted file mode 100644 index c498740..0000000 --- a/src/io/tty.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "tty.hpp" -// io/tty.cpp - terminal escape sequences -// -// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -/* Nothing to see here, move along */ -} // namespace tmwa diff --git a/src/io/write.cpp b/src/io/write.cpp index 5359c7a..a98954b 100644 --- a/src/io/write.cpp +++ b/src/io/write.cpp @@ -37,6 +37,8 @@ namespace io { WriteFile::WriteFile(FD f, bool linebuffered) : fd(f), lb(linebuffered), buflen(0) + // only for debug-sanity + , buf{} {} WriteFile::WriteFile(ZString name, bool linebuffered) : fd(FD::open(name, O_WRONLY | O_CREAT | O_TRUNC, 0666)), lb(linebuffered), buflen(0) diff --git a/src/io/write.hpp b/src/io/write.hpp index 1ab05f3..d7d3699 100644 --- a/src/io/write.hpp +++ b/src/io/write.hpp @@ -22,8 +22,6 @@ #include <cstdarg> -#include "../strings/fwd.hpp" - #include "dir.hpp" #include "fd.hpp" diff --git a/src/map/magic-expr-eval.cpp b/src/login/consts.hpp index 9903600..4af2e41 100644 --- a/src/map/magic-expr-eval.cpp +++ b/src/login/consts.hpp @@ -1,5 +1,6 @@ -#include "magic-expr-eval.hpp" -// magic-expr-eval.cpp - Utilities for evaluating magic. + +#pragma once +// consts.hpp - Constants for tmwa-login. // // Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> // @@ -18,12 +19,14 @@ // 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 "../poison.hpp" +#include "fwd.hpp" namespace tmwa { -namespace magic +namespace login { -} // namespace magic +constexpr int MAX_SERVERS = 30; +constexpr int AUTH_FIFO_SIZE = 256; +} // namespace login } // namespace tmwa diff --git a/src/login/fwd.hpp b/src/login/fwd.hpp index 94fe3c0..6516404 100644 --- a/src/login/fwd.hpp +++ b/src/login/fwd.hpp @@ -20,8 +20,28 @@ #include "../sanity.hpp" +#include "../ints/fwd.hpp" // rank 1 +#include "../strings/fwd.hpp" // rank 1 +#include "../compat/fwd.hpp" +#include "../generic/fwd.hpp" // rank 3 +#include "../io/fwd.hpp" // rank 4 +#include "../net/fwd.hpp" // rank 5 +#include "../mmo/fwd.hpp" // rank 6 +#include "../proto2/fwd.hpp" // rank 8 +#include "../high/fwd.hpp" // rank 9 +#include "../wire/fwd.hpp" // rank 9 +// login/fwd.hpp is rank ∞ because it is an executable + namespace tmwa { -// meh, add more when I feel like it +namespace login +{ + struct LoginConf; + struct LoginLanConf; + struct AuthFifo; + struct mmo_char_server; + struct AuthData; + // meh, add more when I feel like it +} // namespace login } // namespace tmwa diff --git a/src/login/globals.cpp b/src/login/globals.cpp new file mode 100644 index 0000000..0c8a150 --- /dev/null +++ b/src/login/globals.cpp @@ -0,0 +1,54 @@ +#include "globals.hpp" +// globals.cpp - Evil global variables for tmwa-login. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../generic/array.hpp" +#include "../generic/db.hpp" + +#include "login.hpp" +#include "login_conf.hpp" +#include "login_lan_conf.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ + namespace login + { + LoginConf login_conf; + LoginLanConf login_lan_conf; + AccountId account_id_count = START_ACCOUNT_NUM; + tick_t creation_time_GM_account_file; + Array<mmo_char_server, MAX_SERVERS> server; + Array<Session *, MAX_SERVERS> server_session; + // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed + Array<int, MAX_SERVERS> server_freezeflag; + Session *login_session; + // minimum level of player/GM (0: player, 1-99: gm) to connect on the server + Array<AuthFifo, AUTH_FIFO_SIZE> auth_fifo; + // TODO replace with auto_fifo_it + int auth_fifo_pos = 0; + std::vector<AuthData> auth_data; + // TODO make this just be Map<AccountId, GmLevel> + Map<AccountId, GM_Account> gm_account_db; + // For forked DB writes + pid_t pid = 0; + } // namespace login +} // namespace tmwa diff --git a/src/mmo/consts.cpp b/src/login/globals.hpp index e49cdf5..541ea98 100644 --- a/src/mmo/consts.cpp +++ b/src/login/globals.hpp @@ -1,5 +1,5 @@ -#include "consts.hpp" -// consts.cpp - empty mess of constants +#pragma once +// globals.hpp - Evil global variables for tmwa-login. // // Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> // @@ -18,9 +18,31 @@ // 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 "../poison.hpp" +#include "fwd.hpp" + +#include <vector> + +#include "../net/timer.t.hpp" + +#include "consts.hpp" namespace tmwa { + namespace login + { + extern LoginConf login_conf; + extern LoginLanConf login_lan_conf; + extern AccountId account_id_count; + extern tick_t creation_time_GM_account_file; + extern Array<mmo_char_server, MAX_SERVERS> server; + extern Array<Session *, MAX_SERVERS> server_session; + extern Array<int, MAX_SERVERS> server_freezeflag; + extern Session *login_session; + extern Array<AuthFifo, AUTH_FIFO_SIZE> auth_fifo; + extern int auth_fifo_pos; + extern std::vector<AuthData> auth_data; + extern Map<AccountId, GM_Account> gm_account_db; + extern pid_t pid; + } // namespace login } // namespace tmwa diff --git a/src/login/login.cpp b/src/login/login.cpp index ccb68fc..92f8cc0 100644 --- a/src/login/login.cpp +++ b/src/login/login.cpp @@ -45,23 +45,19 @@ #include "../generic/random.hpp" #include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" #include "../io/lock.hpp" #include "../io/read.hpp" +#include "../io/span.hpp" #include "../io/tty.hpp" #include "../io/write.hpp" -#include "../net/packets.hpp" #include "../net/socket.hpp" #include "../net/timer.hpp" #include "../mmo/config_parse.hpp" -#include "../mmo/core.hpp" -#include "../mmo/extract.hpp" #include "../mmo/human_time_diff.hpp" #include "../mmo/ids.hpp" -#include "../mmo/md5more.hpp" -#include "../mmo/mmo.hpp" -#include "../mmo/utils.hpp" #include "../mmo/version.hpp" #include "../proto2/any-user.hpp" @@ -69,115 +65,23 @@ #include "../proto2/login-char.hpp" #include "../proto2/login-user.hpp" -#include "../poison.hpp" - +#include "../high/core.hpp" +#include "../high/extract_mmo.hpp" +#include "../high/md5more.hpp" +#include "../high/mmo.hpp" +#include "../high/utils.hpp" -namespace tmwa -{ -constexpr int MAX_SERVERS = 30; +#include "../wire/packets.hpp" -constexpr AccountId START_ACCOUNT_NUM = wrap<AccountId>(2000000); -constexpr AccountId END_ACCOUNT_NUM = wrap<AccountId>(100000000); - -struct mmo_account -{ - AccountName userid; - AccountPass passwd; - int passwdenc; +#include "globals.hpp" +#include "login_conf.hpp" +#include "login_lan_conf.hpp" - AccountId account_id; - int login_id1; - int login_id2; - AccountId char_id; - timestamp_milliseconds_buffer lastlogin; - SEX sex; -}; - -struct mmo_char_server -{ - ServerName name; - IP4Address ip; - uint16_t port; - uint16_t users; -}; - -static -AccountId account_id_count = START_ACCOUNT_NUM; -static -int new_account = 0; -static -int login_port = 6900; -static -IP4Address lan_char_ip = IP4_LOCALHOST; -static -IP4Mask lan_subnet = IP4Mask(IP4_LOCALHOST, IP4_BROADCAST); -static -AString update_host; -static -AccountName userid; -static -AccountPass passwd; -static -ServerName main_server; - -static -AString account_filename = "save/account.txt"_s; -static -AString gm_account_filename = "save/gm_account.txt"_s; -static -AString login_log_filename = "log/login.log"_s; -static -AString login_log_unknown_packets_filename = "log/login_unknown_packets.log"_s; -static -int save_unknown_packets = 0; -static -tick_t creation_time_GM_account_file; -static -std::chrono::seconds gm_account_filename_check_timer = 15_s; - -static -int display_parse_login = 0; // 0: no, 1: yes -static -int display_parse_admin = 0; // 0: no, 1: yes -static -int display_parse_fromchar = 0; // 0: no, 1: yes (without packet 0x2714), 2: all packets - -static -Array<struct mmo_char_server, MAX_SERVERS> server; -static -Array<Session *, MAX_SERVERS> server_session; -static -Array<int, MAX_SERVERS> server_freezeflag; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed -static -int anti_freeze_enable = 0; -static -std::chrono::seconds anti_freeze_interval = 15_s; +#include "../poison.hpp" -static -Session *login_session; -enum class ACO +namespace tmwa { - DENY_ALLOW, - ALLOW_DENY, - MUTUAL_FAILURE, -}; - -static -ACO access_order = ACO::DENY_ALLOW; -static -std::vector<IP4Mask> -access_allow, access_deny, access_ladmin; - -static -GmLevel min_level_to_connect = GmLevel::from(0_u32); // minimum level of player/GM (0: player, 1-99: gm) to connect on the server -static -int add_to_unlimited_account = 0; // Give possibility or not to adjust (ladmin command: timeadd) the time of an unlimited account. -static -int start_limited_time = -1; // Starting additional sec from now for the limited time at creation of accounts (-1: unlimited time, 0 or more: additional sec from now) -static -int check_ip_flag = 1; // It's to check IP of a player between login-server and char-server (part of anti-hacking system) - DIAG_PUSH(); DIAG_I(missing_noreturn); void SessionDeleter::operator()(SessionData *) @@ -186,67 +90,54 @@ void SessionDeleter::operator()(SessionData *) } DIAG_POP(); -constexpr int AUTH_FIFO_SIZE = 256; -struct AuthFifo +// out of namespace because ADL is dumb +bool extract(XString str, login::ACO *aco) { - AccountId account_id; - int login_id1, login_id2; - IP4Address ip; - SEX sex; - int delflag; -}; -static -Array<AuthFifo, AUTH_FIFO_SIZE> auth_fifo; -// TODO replace with auto_fifo_it -static -int auth_fifo_pos = 0; - -struct AuthData + using login::ACO; + if (str == "deny,allow"_s || str == "deny, allow"_s) + { + *aco = ACO::DENY_ALLOW; + return true; + } + if (str == "allow,deny"_s || str == "allow, deny"_s) + { + *aco = ACO::ALLOW_DENY; + return true; + } + // typo from old config files + if (str == "mutual-failture"_s || str == "mutual-failure"_s) + { + *aco = ACO::MUTUAL_FAILURE; + return true; + } + return false; +} +namespace login +{ +struct mmo_account { - AccountId account_id; - SEX sex; AccountName userid; - AccountCrypt pass; + AccountPass passwd; + int passwdenc; + + AccountId account_id; + int login_id1; + int login_id2; + AccountId char_id; timestamp_milliseconds_buffer lastlogin; - int logincount; - int state; // packet 0x006a value + 1 (0: compte OK) - AccountEmail email; // e-mail (by default: a@a.com) - timestamp_seconds_buffer error_message; // Message of error code #6 = Your are Prohibited to log in until %s (packet 0x006a) - TimeT ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) - TimeT connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) - IP4Address last_ip; // save of last IP of connection - VString<254> memo; // a memo field - int account_reg2_num; - Array<GlobalReg, ACCOUNT_REG2_NUM> account_reg2; + SEX sex; }; -static -std::vector<AuthData> auth_data; - -static -int admin_state = 0; -static -AccountPass admin_pass; -static -AString gm_pass; -static -GmLevel level_new_gm = GmLevel::from(60u); - -static -Map<AccountId, GM_Account> gm_account_db; - -static -pid_t pid = 0; // For forked DB writes //------------------------------ // Writing function of logs file //------------------------------ #define LOGIN_LOG(fmt, ...) \ - login_log(STRPRINTF(fmt, ## __VA_ARGS__)) + tmwa::login::login_log(STRPRINTF(fmt, ## __VA_ARGS__)) static void login_log(XString line) { - io::AppendFile logfp(login_log_filename); + io::AppendFile logfp(login_conf.login_log_filename); if (!logfp.is_open()) return; log_with_timestamp(logfp, line); @@ -287,10 +178,8 @@ void delete_admin(Session *s) static GmLevel isGM(AccountId account_id) { - GM_Account *p = gm_account_db.search(account_id); - if (p == nullptr) - return GmLevel(); - return p->level; + Option<P<GM_Account>> p = gm_account_db.search(account_id); + return TRY_UNWRAP(p, return GmLevel())->level; } //------------------------------------------------------- @@ -303,16 +192,16 @@ int read_gm_account(void) gm_account_db.clear(); - creation_time_GM_account_file = file_modified(gm_account_filename); + creation_time_GM_account_file = file_modified(login_conf.gm_account_filename); - io::ReadFile fp(gm_account_filename); + io::ReadFile fp(login_conf.gm_account_filename); if (!fp.is_open()) { PRINTF("read_gm_account: GM accounts file [%s] not found.\n"_fmt, - gm_account_filename); + login_conf.gm_account_filename); PRINTF(" Actually, there is no GM accounts on the server.\n"_fmt); LOGIN_LOG("read_gm_account: GM accounts file [%s] not found.\n"_fmt, - gm_account_filename); + login_conf.gm_account_filename); LOGIN_LOG(" Actually, there is no GM accounts on the server.\n"_fmt); return 1; } @@ -326,7 +215,7 @@ int read_gm_account(void) GM_Account p {}; if (!extract(line, record<' '>(&p.account_id, &p.level))) PRINTF("read_gm_account: file [%s], invalid 'id_acount level' format: '%s'\n"_fmt, - gm_account_filename, line); + login_conf.gm_account_filename, line); else { GmLevel GM_level = isGM(p.account_id); @@ -356,9 +245,9 @@ int read_gm_account(void) } PRINTF("read_gm_account: file '%s' readed (%d GM accounts found).\n"_fmt, - gm_account_filename, c); + login_conf.gm_account_filename, c); LOGIN_LOG("read_gm_account: file '%s' readed (%d GM accounts found).\n"_fmt, - gm_account_filename, c); + login_conf.gm_account_filename, c); return 0; } @@ -377,29 +266,29 @@ bool check_ip(IP4Address ip) }; ACF flag = ACF::DEF; - if (access_allow.empty() && access_deny.empty()) + if (login_conf.allow.empty() && login_conf.deny.empty()) return 1; // When there is no restriction, all IP are authorised. - if (std::find_if(access_allow.begin(), access_allow.end(), + if (std::find_if(login_conf.allow.begin(), login_conf.allow.end(), [&ip](IP4Mask m) { return m.covers(ip); - }) != access_allow.end()) + }) != login_conf.allow.end()) { { flag = ACF::ALLOW; - if (access_order == ACO::ALLOW_DENY) + if (login_conf.order == ACO::ALLOW_DENY) // With 'allow, deny' (deny if not allow), allow has priority return 1; } } - if (std::find_if(access_deny.begin(), access_deny.end(), + if (std::find_if(login_conf.deny.begin(), login_conf.deny.end(), [&ip](IP4Mask m) { return m.covers(ip); - }) != access_deny.end()) + }) != login_conf.deny.end()) { { flag = ACF::DENY; @@ -408,7 +297,7 @@ bool check_ip(IP4Address ip) } } - return flag == ACF::ALLOW || access_order == ACO::DENY_ALLOW; + return flag == ACF::ALLOW || login_conf.order == ACO::DENY_ALLOW; // With 'mutual-failture', only 'allow' and non 'deny' IP are authorised. // A non 'allow' (even non 'deny') IP is not authorised. It's like: if allowed and not denied, it's authorised. // So, it's disapproval if you have no description at the time of 'mutual-failture'. @@ -421,15 +310,15 @@ bool check_ip(IP4Address ip) static bool check_ladminip(IP4Address ip) { - if (access_ladmin.empty()) + if (login_conf.ladminallowip.empty()) // When there is no restriction, all IP are authorised. return true; - return std::find_if(access_ladmin.begin(), access_ladmin.end(), + return std::find_if(login_conf.ladminallowip.begin(), login_conf.ladminallowip.end(), [&ip](IP4Mask m) { return m.covers(ip); - }) != access_ladmin.end(); + }) != login_conf.ladminallowip.end(); } //----------------------------------------------- @@ -480,7 +369,7 @@ AString mmo_auth_tostr(const AuthData *p) p->state, p->email, p->error_message, - p->connect_until_time, + TimeT(), p->last_ip, p->memo, p->ban_until_time); @@ -495,8 +384,9 @@ AString mmo_auth_tostr(const AuthData *p) } static -bool extract(XString line, AuthData *ad) +bool impl_extract(XString line, AuthData *ad) { + TimeT unused_connect_until_time; std::vector<GlobalReg> vars; VString<1> sex; VString<15> ip; @@ -511,7 +401,7 @@ bool extract(XString line, AuthData *ad) &ad->state, &ad->email, &ad->error_message, - &ad->connect_until_time, + &unused_connect_until_time, &ip, &ad->memo, &ad->ban_until_time, @@ -577,13 +467,13 @@ int mmo_auth_init(void) { int gm_count = 0; - io::ReadFile in(account_filename); + io::ReadFile in(login_conf.account_filename); if (!in.is_open()) { // no account file -> no account -> no login, including char-server (ERROR) // not anymore! :-) PRINTF(SGR_BOLD SGR_RED "mmo_auth_init: Accounts file [%s] not found." SGR_RESET "\n"_fmt, - account_filename); + login_conf.account_filename); return 0; } @@ -620,7 +510,7 @@ int mmo_auth_init(void) } AString str = STRPRINTF("%s has %zu accounts (%d GMs)\n"_fmt, - account_filename, auth_data.size(), gm_count); + login_conf.account_filename, auth_data.size(), gm_count); PRINTF("mmo_auth_init: %s\n"_fmt, str); LOGIN_LOG("%s\n"_fmt, line); @@ -633,7 +523,7 @@ int mmo_auth_init(void) static void mmo_auth_sync(void) { - io::WriteLock fp(account_filename); + io::WriteLock fp(login_conf.account_filename); if (!fp.is_open()) { @@ -643,7 +533,7 @@ void mmo_auth_sync(void) FPRINTF(fp, "// Accounts file: here are saved all information about the accounts.\n"_fmt); FPRINTF(fp, - "// Structure: ID, account name, password, last login time, sex, # of logins, state, email, error message for state 7, validity time, last (accepted) login ip, memo field, ban timestamp, repeated(register text, register value)\n"_fmt); + "// Structure: ID, account name, password, last login time, sex, # of logins, state, email, error message for state 7, validity time (unused), last (accepted) login ip, memo field, ban timestamp, repeated(register text, register value)\n"_fmt); FPRINTF(fp, "// Some explanations:\n"_fmt); FPRINTF(fp, "// account name : between 4 to 23 char for a normal account (standard client can't send less than 4 char).\n"_fmt); @@ -754,11 +644,11 @@ static void check_GM_file(TimerData *, tick_t) { // if we would not check - if (gm_account_filename_check_timer == interval_t::zero()) + if (login_conf.gm_account_filename_check_timer == interval_t::zero()) return; // get last modify time/date - tick_t new_time = file_modified(gm_account_filename); + tick_t new_time = file_modified(login_conf.gm_account_filename); if (new_time != creation_time_GM_account_file) { @@ -795,17 +685,6 @@ AccountId mmo_auth_new(struct mmo_account *account, SEX sex, AccountEmail email) ad.error_message = stringish<timestamp_seconds_buffer>("-"_s); ad.ban_until_time = TimeT(); - if (start_limited_time < 0) - ad.connect_until_time = TimeT(); // unlimited - else - { - // limited time - TimeT timestamp = static_cast<time_t>(TimeT::now()) + start_limited_time; - // there used to be a silly overflow check here, but it wasn't - // correct, and we don't support time-limited accounts. - ad.connect_until_time = timestamp; - } - ad.last_ip = IP4Address(); ad.memo = "!"_s; ad.account_reg2_num = 0; @@ -827,7 +706,7 @@ int mmo_auth(struct mmo_account *account, Session *s) // Account creation with _M/_F if (account->passwdenc == 0 && (account->userid.endswith("_F"_s) || account->userid.endswith("_M"_s)) - && new_account == 1 && account_id_count < END_ACCOUNT_NUM + && login_conf.new_account && account_id_count < END_ACCOUNT_NUM && (account->userid.size() - 2) >= 4 && account->passwd.size() >= 4) { new_account_sex = account->userid.back(); @@ -899,14 +778,6 @@ int mmo_auth(struct mmo_account *account, Session *s) } } - if (ad->connect_until_time - && ad->connect_until_time < TimeT::now()) - { - LOGIN_LOG("Connection refused (account: %s, expired ID, ip: %s)\n"_fmt, - account->userid, ip); - return 2; // 2 = This ID is expired - } - LOGIN_LOG("Authentification accepted (account: %s (id: %d), ip: %s)\n"_fmt, account->userid, ad->account_id, ip); } @@ -989,28 +860,12 @@ void parse_fromchar(Session *s) uint16_t packet_id; while (rv == RecvResult::Complete && packet_peek_id(s, &packet_id)) { - if (display_parse_fromchar == 2 || (display_parse_fromchar == 1 && packet_id != 0x2714)) // 0x2714 is done very often (number of players) + if (login_conf.display_parse_fromchar == 2 || (login_conf.display_parse_fromchar == 1 && packet_id != 0x2714)) // 0x2714 is done very often (number of players) PRINTF("parse_fromchar: connection #%d, packet: 0x%x (with being read: %zu bytes).\n"_fmt, s, packet_id, packet_avail(s)); switch (packet_id) { - // request from map-server via char-server to reload GM accounts (by Yor). - case 0x2709: - { - Packet_Fixed<0x2709> fixed; - rv = recv_fpacket<0x2709, 2>(s, fixed); - if (rv != RecvResult::Complete) - break; - - LOGIN_LOG("Char-server '%s': Request to re-load GM configuration file (ip: %s).\n"_fmt, - server[id].name, ip); - read_gm_account(); - // send GM accounts to all char-servers - send_GM_accounts(); - break; - } - case 0x2712: // request from char-server to authentify an account { Packet_Fixed<0x2712> fixed; @@ -1027,8 +882,7 @@ void parse_fromchar(Session *s) auth_fifo[i].login_id1 == fixed.login_id1 && auth_fifo[i].login_id2 == fixed.login_id2 && // relate to the versions higher than 18 auth_fifo[i].sex == fixed.sex && - (!check_ip_flag - || auth_fifo[i].ip == fixed.ip) + auth_fifo[i].ip == fixed.ip && !auth_fifo[i].delflag) { auth_fifo[i].delflag = 1; @@ -1057,7 +911,6 @@ void parse_fromchar(Session *s) fixed_13.account_id = acc; fixed_13.invalid = 0; fixed_13.email = ad.email; - fixed_13.connect_until = ad.connect_until_time; send_fpacket<0x2713, 51>(s, fixed_13); break; @@ -1092,7 +945,7 @@ void parse_fromchar(Session *s) break; server[id].users = fixed.users; - if (anti_freeze_enable) + if (login_conf.anti_freeze_enable) server_freezeflag[id] = 5; // Char anti-freeze system. Counter. 5 ok, 4...0 freezed break; } @@ -1116,7 +969,6 @@ void parse_fromchar(Session *s) Packet_Fixed<0x2717> fixed_17; fixed_17.account_id = account_id; fixed_17.email = ad.email; - fixed_17.connect_until = ad.connect_until_time; send_fpacket<0x2717, 50>(s, fixed_17); if (rv != RecvResult::Complete) @@ -1130,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 == gm_pass) - { - // only non-GM can become GM - if (!isGM(acc)) - { - // if we autorise creation - if (level_new_gm) - { - // if we can open the file to add the new GM - io::AppendFile fp(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, level_new_gm); - if (!fp.close()) - { - PRINTF("warning: didn't actually save GM file\n"_fmt); - } - fixed_21.gm_level = level_new_gm; - read_gm_account(); - send_GM_accounts(); - PRINTF("GM Change of the account %d: level 0 -> %d.\n"_fmt, - acc, 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, - 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 { @@ -1603,19 +1368,17 @@ void parse_fromchar(Session *s) default: { - io::AppendFile logfp(login_log_unknown_packets_filename); - if (logfp.is_open()) { timestamp_milliseconds_buffer timestr; stamp_time(timestr); - FPRINTF(logfp, + FPRINTF(stderr, "%s: receiving of an unknown packet -> disconnection\n"_fmt, timestr); - FPRINTF(logfp, + FPRINTF(stderr, "parse_fromchar: connection #%d (ip: %s), packet: 0x%x (with being read: %zu).\n"_fmt, s, ip, packet_id, packet_avail(s)); - FPRINTF(logfp, "Detail (in hex):\n"_fmt); - packet_dump(logfp, s); + FPRINTF(stderr, "Detail (in hex):\n"_fmt); + packet_dump(s); } PRINTF("parse_fromchar: Unknown packet 0x%x (from a char-server)! -> disconnection.\n"_fmt, packet_id); @@ -1641,7 +1404,7 @@ void parse_admin(Session *s) uint16_t packet_id; while (rv == RecvResult::Complete && packet_peek_id(s, &packet_id)) { - if (display_parse_admin == 1) + if (login_conf.display_parse_admin) PRINTF("parse_admin: connection #%d, packet: 0x%x (with being read: %zu bytes).\n"_fmt, s, packet_id, packet_avail(s)); @@ -1718,23 +1481,6 @@ void parse_admin(Session *s) break; } - case 0x7924: - { // [Fate] Itemfrob package: change item IDs - Packet_Fixed<0x7924> fixed; - rv = recv_fpacket<0x7924, 10>(s, fixed); - if (rv != RecvResult::Complete) - break; - - for (Session *ss : iter_char_sessions()) - { - send_fpacket<0x7924, 10>(ss, fixed); - } - - Packet_Fixed<0x7925> fixed_25; - send_fpacket<0x7925, 2>(s, fixed_25); - break; - } - case 0x7930: // Request for an account creation { Packet_Fixed<0x7930> fixed; @@ -2096,10 +1842,10 @@ void parse_admin(Session *s) AccountId GM_account; GmLevel GM_level; int modify_flag; - io::WriteLock fp2(gm_account_filename); + io::WriteLock fp2(login_conf.gm_account_filename); if (fp2.is_open()) { - io::ReadFile fp(gm_account_filename); + io::ReadFile fp(login_conf.gm_account_filename); if (fp.is_open()) { timestamp_seconds_buffer tmpstr; @@ -2330,48 +2076,6 @@ void parse_admin(Session *s) break; } - case 0x7948: // Request to change the validity limit (timestamp) (absolute value) - { - Packet_Fixed<0x7948> fixed; - rv = recv_fpacket<0x7948, 30>(s, fixed); - if (rv != RecvResult::Complete) - break; - - Packet_Fixed<0x7949> fixed_49; - { - fixed_49.account_id = AccountId(); - AccountName account_name = stringish<AccountName>(fixed.account_name.to_print()); - TimeT timestamp = fixed.valid_until; - timestamp_seconds_buffer tmpstr = stringish<timestamp_seconds_buffer>("unlimited"_s); - if (timestamp) - stamp_time(tmpstr, ×tamp); - AuthData *ad = search_account(account_name); - if (ad) - { - fixed_49.account_name = ad->userid; - LOGIN_LOG("'ladmin': Change of a validity limit (account: %s, new validity: %lld (%s), ip: %s)\n"_fmt, - ad->userid, - timestamp, - tmpstr, - ip); - ad->connect_until_time = timestamp; - fixed_49.account_id = ad->account_id; - } - else - { - fixed_49.account_name = account_name; - LOGIN_LOG("'ladmin': Attempt to change the validity limit of an unknown account (account: %s, received validity: %lld (%s), ip: %s)\n"_fmt, - account_name, - timestamp, - tmpstr, - ip); - } - fixed_49.valid_until = timestamp; - } - send_fpacket<0x7949, 34>(s, fixed_49); - break; - } - case 0x794a: // Request to change the final date of a banishment (timestamp) (absolute value) { Packet_Fixed<0x794a> fixed; @@ -2475,7 +2179,7 @@ void parse_admin(Session *s) timestamp_seconds_buffer tmpstr = stringish<timestamp_seconds_buffer>("no banishment"_s); if (timestamp) stamp_time(tmpstr, ×tamp); - LOGIN_LOG("'ladmin': Adjustment of a final date of a banishment (account: %s, (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %lld (%s), ip: %s)\n"_fmt, + LOGIN_LOG("'ladmin': Adjustment of a final date of a banishment (account: %s, (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new ban: %lld (%s), ip: %s)\n"_fmt, ad->userid, ban_diff.year, ban_diff.month, ban_diff.day, ban_diff.hour, @@ -2583,99 +2287,6 @@ void parse_admin(Session *s) break; } - case 0x7950: // Request to change the validity limite (timestamp) (relative change) - { - Packet_Fixed<0x7950> fixed; - rv = recv_fpacket<0x7950, 38>(s, fixed); - if (rv != RecvResult::Complete) - break; - - Packet_Fixed<0x7951> fixed_51; - { - fixed_51.account_id = AccountId(); - AccountName account_name = stringish<AccountName>(fixed.account_name.to_print()); - AuthData *ad = search_account(account_name); - if (ad) - { - fixed_51.account_id = ad->account_id; - fixed_51.account_name = ad->userid; - if (add_to_unlimited_account == 0 && !ad->connect_until_time) - { - LOGIN_LOG("'ladmin': Attempt to adjust the validity limit of an unlimited account (account: %s, ip: %s)\n"_fmt, - ad->userid, ip); - fixed_51.valid_until = TimeT(); - } - else - { - TimeT now = TimeT::now(); - TimeT timestamp = ad->connect_until_time; - if (!timestamp || timestamp < now) - timestamp = now; - struct tm tmtime = timestamp; - HumanTimeDiff v_diff = fixed.valid_add; - tmtime.tm_year += v_diff.year; - tmtime.tm_mon += v_diff.month; - tmtime.tm_mday += v_diff.day; - tmtime.tm_hour += v_diff.hour; - tmtime.tm_min += v_diff.minute; - tmtime.tm_sec += v_diff.second; - timestamp = tmtime; - if (timestamp.okay()) - { - timestamp_seconds_buffer tmpstr = stringish<timestamp_seconds_buffer>("unlimited"_s); - timestamp_seconds_buffer tmpstr2 = stringish<timestamp_seconds_buffer>("unlimited"_s); - if (ad->connect_until_time) - stamp_time(tmpstr, &ad->connect_until_time); - if (timestamp) - stamp_time(tmpstr2, ×tamp); - LOGIN_LOG("'ladmin': Adjustment of a validity limit (account: %s, %lld (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %lld (%s), ip: %s)\n"_fmt, - ad->userid, - ad->connect_until_time, - tmpstr, - v_diff.year, - v_diff.month, - v_diff.day, - v_diff.hour, - v_diff.minute, - v_diff.second, - timestamp, - tmpstr2, - ip); - ad->connect_until_time = timestamp; - fixed_51.valid_until = timestamp; - } - else - { - timestamp_seconds_buffer tmpstr = stringish<timestamp_seconds_buffer>("unlimited"_s); - if (ad->connect_until_time) - stamp_time(tmpstr, &ad->connect_until_time); - LOGIN_LOG("'ladmin': Impossible to adjust a validity limit (account: %s, %lld (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> ???, ip: %s)\n"_fmt, - ad->userid, - ad->connect_until_time, - tmpstr, - v_diff.year, - v_diff.month, - v_diff.day, - v_diff.hour, - v_diff.minute, - v_diff.second, - ip); - fixed_51.valid_until = TimeT(); - } - } - } - else - { - fixed_51.account_name = account_name; - LOGIN_LOG("'ladmin': Attempt to adjust the validity limit of an unknown account (account: %s, ip: %s)\n"_fmt, - account_name, ip); - fixed_51.valid_until = TimeT(); - } - } - send_fpacket<0x7951, 34>(s, fixed_51); - break; - } - case 0x7952: // Request about informations of an account (by account name) { Packet_Fixed<0x7952> fixed; @@ -2699,7 +2310,6 @@ void parse_admin(Session *s) head_53.last_login_string = ad->lastlogin; head_53.ip_string = convert_for_printf(ad->last_ip); head_53.email = ad->email; - head_53.connect_until = ad->connect_until_time; head_53.ban_until = ad->ban_until_time; XString repeat_53 = ad->memo; @@ -2745,7 +2355,6 @@ void parse_admin(Session *s) head_53.last_login_string = ad.lastlogin; head_53.ip_string = convert_for_printf(ad.last_ip); head_53.email = ad.email; - head_53.connect_until = ad.connect_until_time; head_53.ban_until = ad.ban_until_time; XString repeat_53 = ad.memo; send_vpacket<0x7953, 150, 1>(s, head_53, repeat_53); @@ -2779,19 +2388,17 @@ void parse_admin(Session *s) default: { - io::AppendFile logfp(login_log_unknown_packets_filename); - if (logfp.is_open()) { timestamp_milliseconds_buffer timestr; stamp_time(timestr); - FPRINTF(logfp, + FPRINTF(stderr, "%s: receiving of an unknown packet -> disconnection\n"_fmt, timestr); - FPRINTF(logfp, + FPRINTF(stderr, "parse_admin: connection #%d (ip: %s), packet: 0x%x (with being read: %zu).\n"_fmt, s, ip, packet_id, packet_avail(s)); - FPRINTF(logfp, "Detail (in hex):\n"_fmt); - packet_dump(logfp, s); + FPRINTF(stderr, "Detail (in hex):\n"_fmt); + packet_dump(s); } LOGIN_LOG("'ladmin': End of connection, unknown packet (ip: %s)\n"_fmt, ip); @@ -2812,7 +2419,7 @@ void parse_admin(Session *s) static bool lan_ip_check(IP4Address p) { - bool lancheck = lan_subnet.covers(p); + bool lancheck = login_lan_conf.lan_subnet.covers(p); PRINTF("LAN test (result): %s.\n"_fmt, (lancheck) ? SGR_BOLD SGR_CYAN "LAN source" SGR_RESET ""_s : SGR_BOLD SGR_GREEN "WAN source" SGR_RESET ""_s); @@ -2833,7 +2440,7 @@ void parse_login(Session *s) uint16_t packet_id; while (rv == RecvResult::Complete && packet_peek_id(s, &packet_id)) { - if (display_parse_login == 1) + if (login_conf.display_parse_login) { if (packet_id == 0x64) { @@ -2895,10 +2502,10 @@ void parse_login(Session *s) if (result == -1) { GmLevel gm_level = isGM(account.account_id); - if (!(gm_level.satisfies(min_level_to_connect))) + if (!(gm_level.satisfies(login_conf.min_level_to_connect))) { LOGIN_LOG("Connection refused: the minimum GM level for connection is %d (account: %s, GM level: %d, ip: %s).\n"_fmt, - min_level_to_connect, account.userid, + login_conf.min_level_to_connect, account.userid, gm_level, ip); Packet_Fixed<0x0081> fixed_81; fixed_81.error_code = 1; // 01 = Server closed @@ -2928,9 +2535,9 @@ void parse_login(Session *s) */ // if (version_2 & VERSION_2_UPDATEHOST) { - if (update_host) + if (login_conf.update_host) { - send_packet_repeatonly<0x0063, 4, 1>(s, update_host); + send_packet_repeatonly<0x0063, 4, 1>(s, login_conf.update_host); } } @@ -2943,7 +2550,7 @@ void parse_login(Session *s) { Packet_Repeat<0x0069> info; if (lan_ip_check(ip)) - info.ip = lan_char_ip; + info.ip = login_lan_conf.lan_char_ip; else info.ip = server[i].ip; info.port = server[i].port; @@ -3043,11 +2650,11 @@ void parse_login(Session *s) ServerName server_name = stringish<ServerName>(fixed.server_name.to_print()); LOGIN_LOG("Connection request of the char-server '%s' @ %s:%d (ip: %s)\n"_fmt, server_name, fixed.ip, fixed.port, ip); - if (account.userid == userid && account.passwd == passwd) + if (account.userid == login_conf.userid && account.passwd == login_conf.passwd) { // If this is the main server, and we don't already have a main server if (!server_session[0] - && server_name == main_server) + && server_name == login_conf.main_server) { account.account_id = wrap<AccountId>(0_u32); goto x2710_okay; @@ -3082,7 +2689,7 @@ void parse_login(Session *s) //maintenance = RFIFOW(fd, 82); //is_new = RFIFOW(fd, 84); server_session[unwrap<AccountId>(account.account_id)] = s; - if (anti_freeze_enable) + if (login_conf.anti_freeze_enable) server_freezeflag[unwrap<AccountId>(account.account_id)] = 5; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed Packet_Fixed<0x2711> fixed_11; @@ -3124,7 +2731,7 @@ void parse_login(Session *s) Packet_Fixed<0x7531> fixed_31; Version version = CURRENT_LOGIN_SERVER_VERSION; - version.flags = new_account ? 1 : 0; + version.flags = login_conf.new_account ? 1 : 0; fixed_31.version = version; send_fpacket<0x7531, 10>(s, fixed_31); break; @@ -3163,8 +2770,8 @@ void parse_login(Session *s) // non encrypted password AccountPass password = stringish<AccountPass>(fixed.account_pass.to_print()); // If remote administration is enabled and password sent by client matches password read from login server configuration file - if ((admin_state == 1) - && (password == admin_pass)) + if (login_conf.admin_state + && password == login_conf.admin_pass) { LOGIN_LOG("'ladmin'-login: Connection in administration mode accepted (non encrypted password: %s, ip: %s)\n"_fmt, password, ip); @@ -3172,7 +2779,7 @@ void parse_login(Session *s) fixed_19.error = 0; s->set_parsers(SessionParsers{.func_parse= parse_admin, .func_delete= delete_admin}); } - else if (admin_state != 1) + else if (!login_conf.admin_state) LOGIN_LOG("'ladmin'-login: Connection in administration mode REFUSED - remote administration is disabled (non encrypted password: %s, ip: %s)\n"_fmt, password, ip); else @@ -3194,22 +2801,19 @@ void parse_login(Session *s) default: { - if (save_unknown_packets) { - io::AppendFile logfp(login_log_unknown_packets_filename); - if (logfp.is_open()) { timestamp_milliseconds_buffer timestr; stamp_time(timestr); - FPRINTF(logfp, + FPRINTF(stderr, "%s: receiving of an unknown packet -> disconnection\n"_fmt, timestr); - FPRINTF(logfp, + FPRINTF(stderr, "parse_login: connection #%d (ip: %s), packet: 0x%x (with being read: %zu).\n"_fmt, s, ip, packet_id, packet_avail(s)); - FPRINTF(logfp, "Detail (in hex):\n"_fmt); - packet_dump(logfp, s); + FPRINTF(stderr, "Detail (in hex):\n"_fmt); + packet_dump(s); } } LOGIN_LOG("End of connection, unknown packet (ip: %s)\n"_fmt, ip); @@ -3222,67 +2826,19 @@ void parse_login(Session *s) s->set_eof(); } -//---------------------------------- -// Reading Lan Support configuration -//---------------------------------- -static -bool login_lan_config(XString w1, ZString w2) -{ - struct hostent *h = nullptr; - - { - if (w1 == "lan_char_ip"_s) - { - // Read Char-Server Lan IP Address - h = gethostbyname(w2.c_str()); - if (h != nullptr) - { - lan_char_ip = IP4Address({ - static_cast<uint8_t>(h->h_addr[0]), - static_cast<uint8_t>(h->h_addr[1]), - static_cast<uint8_t>(h->h_addr[2]), - static_cast<uint8_t>(h->h_addr[3]), - }); - } - else - { - PRINTF("Bad IP value: %s\n"_fmt, w2); - return false; - } - PRINTF("LAN IP of char-server: %s.\n"_fmt, lan_char_ip); - } - else if (w1 == "subnet"_s /*backward compatibility*/ - || w1 == "lan_subnet"_s) - { - if (!extract(w2, &lan_subnet)) - { - PRINTF("Bad IP mask: %s\n"_fmt, w2); - return false; - } - PRINTF("Sub-network of the char-server: %s.\n"_fmt, - lan_subnet); - } - else - { - return false; - } - } - return true; -} - static bool lan_check() { // log the LAN configuration LOGIN_LOG("The LAN configuration of the server is set:\n"_fmt); - LOGIN_LOG("- with LAN IP of char-server: %s.\n"_fmt, lan_char_ip); + LOGIN_LOG("- with LAN IP of char-server: %s.\n"_fmt, login_lan_conf.lan_char_ip); LOGIN_LOG("- with the sub-network of the char-server: %s.\n"_fmt, - lan_subnet); + login_lan_conf.lan_subnet); // sub-network check of the char-server { PRINTF("LAN test of LAN IP of the char-server: "_fmt); - if (!lan_ip_check(lan_char_ip)) + if (!lan_ip_check(login_lan_conf.lan_char_ip)) { PRINTF(SGR_BOLD SGR_RED "***ERROR: LAN IP of the char-server doesn't belong to the specified Sub-network"_fmt SGR_RESET "\n"); LOGIN_LOG("***ERROR: LAN IP of the char-server doesn't belong to the specified Sub-network.\n"_fmt); @@ -3293,229 +2849,6 @@ bool lan_check() return true; } -//----------------------------------- -// Reading general configuration file -//----------------------------------- -static -bool login_config(XString w1, ZString w2) -{ - { - if (w1 == "admin_state"_s) - { - admin_state = config_switch(w2); - } - else if (w1 == "admin_pass"_s) - { - admin_pass = stringish<AccountPass>(w2); - } - else if (w1 == "ladminallowip"_s) - { - if (w2 == "clear"_s) - { - access_ladmin.clear(); - } - else - { - // a.b.c.d/0.0.0.0 (canonically, 0.0.0.0/0) covers all - if (w2 == "all"_s) - { - // reset all previous values - access_ladmin.clear(); - // set to all - access_ladmin.push_back(IP4Mask()); - } - else if (w2 - && !(access_ladmin.size() == 1 - && access_ladmin.front().mask() == IP4Address())) - { - // don't add IP if already 'all' - IP4Mask n; - if (!extract(w2, &n)) - { - PRINTF("Bad IP mask: %s\n"_fmt, w2); - return false; - } - access_ladmin.push_back(n); - } - } - } - else if (w1 == "gm_pass"_s) - { - gm_pass = w2; - } - else if (w1 == "level_new_gm"_s) - { - level_new_gm = GmLevel::from(static_cast<uint32_t>(atoi(w2.c_str()))); - } - else if (w1 == "new_account"_s) - { - new_account = config_switch(w2); - } - else if (w1 == "login_port"_s) - { - login_port = atoi(w2.c_str()); - } - else if (w1 == "account_filename"_s) - { - account_filename = w2; - } - else if (w1 == "gm_account_filename"_s) - { - gm_account_filename = w2; - } - else if (w1 == "gm_account_filename_check_timer"_s) - { - gm_account_filename_check_timer = std::chrono::seconds(atoi(w2.c_str())); - } - else if (w1 == "login_log_filename"_s) - { - login_log_filename = w2; - } - else if (w1 == "login_log_unknown_packets_filename"_s) - { - login_log_unknown_packets_filename = w2; - } - else if (w1 == "save_unknown_packets"_s) - { - save_unknown_packets = config_switch(w2); - } - else if (w1 == "display_parse_login"_s) - { - display_parse_login = config_switch(w2); // 0: no, 1: yes - } - else if (w1 == "display_parse_admin"_s) - { - display_parse_admin = config_switch(w2); // 0: no, 1: yes - } - else if (w1 == "display_parse_fromchar"_s) - { - display_parse_fromchar = config_switch(w2); // 0: no, 1: yes (without packet 0x2714), 2: all packets - } - else if (w1 == "min_level_to_connect"_s) - { - min_level_to_connect = GmLevel::from(static_cast<uint32_t>(atoi(w2.c_str()))); - } - else if (w1 == "add_to_unlimited_account"_s) - { - add_to_unlimited_account = config_switch(w2); - } - else if (w1 == "start_limited_time"_s) - { - start_limited_time = atoi(w2.c_str()); - } - else if (w1 == "check_ip_flag"_s) - { - check_ip_flag = config_switch(w2); - } - else if (w1 == "order"_s) - { - if (w2 == "deny,allow"_s || w2 == "deny, allow"_s) - access_order = ACO::DENY_ALLOW; - else if (w2 == "allow,deny"_s || w2 == "allow, deny"_s) - access_order = ACO::ALLOW_DENY; - else if (w2 == "mutual-failture"_s || w2 == "mutual-failure"_s) - access_order = ACO::MUTUAL_FAILURE; - else - { - PRINTF("Bad order: %s\n"_fmt, w2); - return false; - } - } - else if (w1 == "allow"_s) - { - if (w2 == "clear"_s) - { - access_allow.clear(); - } - else - { - if (w2 == "all"_s) - { - // reset all previous values - access_allow.clear(); - // set to all - access_allow.push_back(IP4Mask()); - } - else if (w2 - && !(access_allow.size() == 1 - && access_allow.front().mask() == IP4Address())) - { - // don't add IP if already 'all' - IP4Mask n; - if (!extract(w2, &n)) - { - PRINTF("Bad IP mask: %s\n"_fmt, w2); - return false; - } - access_allow.push_back(n); - } - } - } - else if (w1 == "deny"_s) - { - if (w2 == "clear"_s) - { - access_deny.clear(); - } - else - { - if (w2 == "all"_s) - { - // reset all previous values - access_deny.clear(); - // set to all - access_deny.push_back(IP4Mask()); - } - else if (w2 - && !(access_deny.size() == 1 - && access_deny.front().mask() == IP4Address())) - { - // don't add IP if already 'all' - IP4Mask n; - if (!extract(w2, &n)) - { - PRINTF("Bad IP mask: %s\n"_fmt, w2); - return false; - } - access_deny.push_back(n); - } - } - } - else if (w1 == "anti_freeze_enable"_s) - { - anti_freeze_enable = config_switch(w2); - } - else if (w1 == "anti_freeze_interval"_s) - { - anti_freeze_interval = std::max( - std::chrono::seconds(atoi(w2.c_str())), - 5_s); - } - else if (w1 == "update_host"_s) - { - update_host = w2; - } - else if (w1 == "main_server"_s) - { - main_server = stringish<ServerName>(w2); - } - else if (w1 == "userid"_s) - { - userid = stringish<AccountName>(w2); - } - else if (w1 == "passwd"_s) - { - passwd = stringish<AccountPass>(w2); - } - else - { - return false; - } - } - - return true; -} - //------------------------------------- // Displaying of configuration warnings //------------------------------------- @@ -3523,135 +2856,60 @@ static bool display_conf_warnings(void) { bool rv = true; - if (admin_state != 0 && admin_state != 1) - { - PRINTF("***WARNING: Invalid value for admin_state parameter -> set to 0 (no remote admin).\n"_fmt); - admin_state = 0; - rv = false; - } - if (admin_state == 1) + if (login_conf.admin_state) { - if (!admin_pass) + if (!login_conf.admin_pass) { PRINTF("***WARNING: Administrator password is void (admin_pass).\n"_fmt); rv = false; } - else if (admin_pass == stringish<AccountPass>("admin"_s)) + else if (login_conf.admin_pass == stringish<AccountPass>("admin"_s)) { PRINTF("***WARNING: You are using the default administrator password (admin_pass).\n"_fmt); PRINTF(" We highly recommend that you change it.\n"_fmt); } } - if (!gm_pass) + if (!login_conf.gm_pass) { PRINTF("***WARNING: 'To GM become' password is void (gm_pass).\n"_fmt); PRINTF(" We highly recommend that you set one password.\n"_fmt); rv = false; } - else if (gm_pass == "gm"_s) + else if (login_conf.gm_pass == "gm"_s) { PRINTF("***WARNING: You are using the default GM password (gm_pass).\n"_fmt); PRINTF(" We highly recommend that you change it.\n"_fmt); } - if (new_account != 0 && new_account != 1) - { - PRINTF("***WARNING: Invalid value for new_account parameter -> set to 0 (no new account).\n"_fmt); - new_account = 0; - rv = false; - } - - if (login_port < 1024 || login_port > 65535) - { - PRINTF("***WARNING: Invalid value for login_port parameter -> set to 6900 (default).\n"_fmt); - login_port = 6900; - rv = false; - } - - if (gm_account_filename_check_timer.count() < 0) + if (login_conf.gm_account_filename_check_timer.count() < 0) { PRINTF("***WARNING: Invalid value for gm_account_filename_check_timer parameter.\n"_fmt); PRINTF(" -> set to 15 sec (default).\n"_fmt); - gm_account_filename_check_timer = 15_s; + login_conf.gm_account_filename_check_timer = 15_s; rv = false; } - else if (gm_account_filename_check_timer == 1_s) + else if (login_conf.gm_account_filename_check_timer == 1_s) { PRINTF("***WARNING: Invalid value for gm_account_filename_check_timer parameter.\n"_fmt); PRINTF(" -> set to 2 sec (minimum value).\n"_fmt); - gm_account_filename_check_timer = 2_s; - rv = false; - } - - if (save_unknown_packets != 0 && save_unknown_packets != 1) - { - PRINTF("WARNING: Invalid value for save_unknown_packets parameter -> set to 0-no save.\n"_fmt); - save_unknown_packets = 0; - rv = false; - } - - if (display_parse_login != 0 && display_parse_login != 1) - { // 0: no, 1: yes - PRINTF("***WARNING: Invalid value for display_parse_login parameter\n"_fmt); - PRINTF(" -> set to 0 (no display).\n"_fmt); - display_parse_login = 0; - rv = false; - } - - if (display_parse_admin != 0 && display_parse_admin != 1) - { // 0: no, 1: yes - PRINTF("***WARNING: Invalid value for display_parse_admin parameter\n"_fmt); - PRINTF(" -> set to 0 (no display).\n"_fmt); - display_parse_admin = 0; - rv = false; - } - - if (display_parse_fromchar < 0 || display_parse_fromchar > 2) - { // 0: no, 1: yes (without packet 0x2714), 2: all packets - PRINTF("***WARNING: Invalid value for display_parse_fromchar parameter\n"_fmt); - PRINTF(" -> set to 0 (no display).\n"_fmt); - display_parse_fromchar = 0; - rv = false; - } - - if (add_to_unlimited_account != 0 && add_to_unlimited_account != 1) - { // 0: no, 1: yes - PRINTF("***WARNING: Invalid value for add_to_unlimited_account parameter\n"_fmt); - PRINTF(" -> set to 0 (impossible to add a time to an unlimited account).\n"_fmt); - add_to_unlimited_account = 0; - rv = false; - } - - if (start_limited_time < -1) - { // -1: create unlimited account, 0 or more: additionnal sec from now to create limited time - PRINTF("***WARNING: Invalid value for start_limited_time parameter\n"_fmt); - PRINTF(" -> set to -1 (new accounts are created with unlimited time).\n"_fmt); - start_limited_time = -1; + login_conf.gm_account_filename_check_timer = 2_s; rv = false; } - if (check_ip_flag != 0 && check_ip_flag != 1) - { // 0: no, 1: yes - PRINTF("***WARNING: Invalid value for check_ip_flag parameter\n"_fmt); - PRINTF(" -> set to 1 (check players ip between login-server & char-server).\n"_fmt); - check_ip_flag = 1; - rv = false; - } - - if (access_order == ACO::DENY_ALLOW) + if (login_conf.order == ACO::DENY_ALLOW) { - if (access_deny.size() == 1 && access_deny.front().mask() == IP4Address()) + if (login_conf.deny.size() == 1 && login_conf.deny.front().mask() == IP4Address()) { PRINTF("***WARNING: The IP security order is 'deny,allow' (allow if not deny).\n"_fmt); PRINTF(" And you refuse ALL IP.\n"_fmt); rv = false; } } - else if (access_order == ACO::ALLOW_DENY) + else if (login_conf.order == ACO::ALLOW_DENY) { - if (access_allow.empty()) + if (login_conf.allow.empty()) { PRINTF("***WARNING: The IP security order is 'allow,deny' (deny if not allow).\n"_fmt); PRINTF(" But, NO IP IS AUTHORISED!\n"_fmt); @@ -3661,14 +2919,14 @@ bool display_conf_warnings(void) else { // ACO::MUTUAL_FAILURE - if (access_allow.empty()) + if (login_conf.allow.empty()) { PRINTF("***WARNING: The IP security order is 'mutual-failture'\n"_fmt); PRINTF(" (allow if in the allow list and not in the deny list).\n"_fmt); PRINTF(" But, NO IP IS AUTHORISED!\n"_fmt); rv = false; } - else if (access_deny.size() == 1 && access_deny.front().mask() == IP4Address()) + else if (login_conf.deny.size() == 1 && login_conf.deny.front().mask() == IP4Address()) { PRINTF("***WARNING: The IP security order is mutual-failture\n"_fmt); PRINTF(" (allow if in the allow list and not in the deny list).\n"_fmt); @@ -3692,195 +2950,188 @@ void save_config_in_log(void) // save configuration in log file LOGIN_LOG("The configuration of the server is set:\n"_fmt); - if (admin_state != 1) + if (!login_conf.admin_state) LOGIN_LOG("- with no remote administration.\n"_fmt); - else if (!admin_pass) + else if (!login_conf.admin_pass) LOGIN_LOG("- with a remote administration with a VOID password.\n"_fmt); - else if (admin_pass == stringish<AccountPass>("admin"_s)) + else if (login_conf.admin_pass == stringish<AccountPass>("admin"_s)) LOGIN_LOG("- with a remote administration with the DEFAULT password.\n"_fmt); else LOGIN_LOG("- with a remote administration with the password of %zu character(s).\n"_fmt, - admin_pass.size()); - if (access_ladmin.empty() - || (access_ladmin.size() == 1 && access_ladmin.front().mask() == IP4Address())) + login_conf.admin_pass.size()); + if (login_conf.ladminallowip.empty() + || (login_conf.ladminallowip.size() == 1 && login_conf.ladminallowip.front().mask() == IP4Address())) { LOGIN_LOG("- to accept any IP for remote administration\n"_fmt); } else { LOGIN_LOG("- to accept following IP for remote administration:\n"_fmt); - for (const IP4Mask& ae : access_ladmin) + for (const IP4Mask& ae : login_conf.ladminallowip) LOGIN_LOG(" %s\n"_fmt, ae); } - if (!gm_pass) + if (!login_conf.gm_pass) LOGIN_LOG("- with a VOID 'To GM become' password (gm_pass).\n"_fmt); - else if (gm_pass == "gm"_s) + else if (login_conf.gm_pass == "gm"_s) LOGIN_LOG("- with the DEFAULT 'To GM become' password (gm_pass).\n"_fmt); else LOGIN_LOG("- with a 'To GM become' password (gm_pass) of %zu character(s).\n"_fmt, - gm_pass.size()); - if (!level_new_gm) + login_conf.gm_pass.size()); + if (!login_conf.level_new_gm) LOGIN_LOG("- to refuse any creation of GM with @gm.\n"_fmt); else LOGIN_LOG("- to create GM with level '%d' when @gm is used.\n"_fmt, - level_new_gm); + login_conf.level_new_gm); - if (new_account == 1) + if (login_conf.new_account) LOGIN_LOG("- to ALLOW new users (with _F/_M).\n"_fmt); else LOGIN_LOG("- to NOT ALLOW new users (with _F/_M).\n"_fmt); - LOGIN_LOG("- with port: %d.\n"_fmt, login_port); + LOGIN_LOG("- with port: %d.\n"_fmt, login_conf.login_port); LOGIN_LOG("- with the accounts file name: '%s'.\n"_fmt, - account_filename); + login_conf.account_filename); LOGIN_LOG("- with the GM accounts file name: '%s'.\n"_fmt, - gm_account_filename); - if (gm_account_filename_check_timer == interval_t::zero()) + login_conf.gm_account_filename); + if (login_conf.gm_account_filename_check_timer == interval_t::zero()) LOGIN_LOG("- to NOT check GM accounts file modifications.\n"_fmt); else LOGIN_LOG("- to check GM accounts file modifications every %lld seconds.\n"_fmt, - maybe_cast<long long>(gm_account_filename_check_timer.count())); + maybe_cast<long long>(login_conf.gm_account_filename_check_timer.count())); // not necessary to log the 'login_log_filename', we are inside :) - LOGIN_LOG("- with the unknown packets file name: '%s'.\n"_fmt, - login_log_unknown_packets_filename); - if (save_unknown_packets) - LOGIN_LOG("- to SAVE all unkown packets.\n"_fmt); - else - LOGIN_LOG("- to SAVE only unkown packets sending by a char-server or a remote administration.\n"_fmt); - if (display_parse_login) + if (login_conf.display_parse_login) LOGIN_LOG("- to display normal parse packets on console.\n"_fmt); else LOGIN_LOG("- to NOT display normal parse packets on console.\n"_fmt); - if (display_parse_admin) + if (login_conf.display_parse_admin) LOGIN_LOG("- to display administration parse packets on console.\n"_fmt); else LOGIN_LOG("- to NOT display administration parse packets on console.\n"_fmt); - if (display_parse_fromchar) + if (login_conf.display_parse_fromchar) LOGIN_LOG("- to display char-server parse packets on console.\n"_fmt); else LOGIN_LOG("- to NOT display char-server parse packets on console.\n"_fmt); - if (!min_level_to_connect) // 0: all players, 1-99 at least gm level x + if (!login_conf.min_level_to_connect) // 0: all players, 1-99 at least gm level x LOGIN_LOG("- with no minimum level for connection.\n"_fmt); else LOGIN_LOG("- to accept only GM with level %d or more.\n"_fmt, - min_level_to_connect); - - if (add_to_unlimited_account) - LOGIN_LOG("- to authorize adjustment (with timeadd ladmin) on an unlimited account.\n"_fmt); - else - LOGIN_LOG("- to refuse adjustment (with timeadd ladmin) on an unlimited account. You must use timeset (ladmin command) before.\n"_fmt); + login_conf.min_level_to_connect); - if (start_limited_time < 0) - LOGIN_LOG("- to create new accounts with an unlimited time.\n"_fmt); - else if (start_limited_time == 0) - LOGIN_LOG("- to create new accounts with a limited time: time of creation.\n"_fmt); - else - LOGIN_LOG("- to create new accounts with a limited time: time of creation + %d second(s).\n"_fmt, - start_limited_time); - - if (check_ip_flag) - LOGIN_LOG("- with control of players IP between login-server and char-server.\n"_fmt); - else - LOGIN_LOG("- to not check players IP between login-server and char-server.\n"_fmt); - - if (access_order == ACO::DENY_ALLOW) + if (login_conf.order == ACO::DENY_ALLOW) { - if (access_deny.empty()) + if (login_conf.deny.empty()) { LOGIN_LOG("- with the IP security order: 'deny,allow' (allow if not deny). You refuse no IP.\n"_fmt); } - else if (access_deny.size() == 1 && access_deny.front().mask() == IP4Address()) + else if (login_conf.deny.size() == 1 && login_conf.deny.front().mask() == IP4Address()) { LOGIN_LOG("- with the IP security order: 'deny,allow' (allow if not deny). You refuse ALL IP.\n"_fmt); } else { LOGIN_LOG("- with the IP security order: 'deny,allow' (allow if not deny). Refused IP are:\n"_fmt); - for (IP4Mask ae : access_deny) + for (IP4Mask ae : login_conf.deny) LOGIN_LOG(" %s\n"_fmt, ae); } } - else if (access_order == ACO::ALLOW_DENY) + else if (login_conf.order == ACO::ALLOW_DENY) { - if (access_allow.empty()) + if (login_conf.allow.empty()) { LOGIN_LOG("- with the IP security order: 'allow,deny' (deny if not allow). But, NO IP IS AUTHORISED!\n"_fmt); } - else if (access_allow.size() == 1 && access_allow.front().mask() == IP4Address()) + else if (login_conf.allow.size() == 1 && login_conf.allow.front().mask() == IP4Address()) { LOGIN_LOG("- with the IP security order: 'allow,deny' (deny if not allow). You authorise ALL IP.\n"_fmt); } else { LOGIN_LOG("- with the IP security order: 'allow,deny' (deny if not allow). Authorised IP are:\n"_fmt); - for (IP4Mask ae : access_allow) + for (IP4Mask ae : login_conf.allow) LOGIN_LOG(" %s\n"_fmt, ae); } } else { // ACO_MUTUAL_FAILTURE LOGIN_LOG("- with the IP security order: 'mutual-failture' (allow if in the allow list and not in the deny list).\n"_fmt); - if (access_allow.empty()) + if (login_conf.allow.empty()) { LOGIN_LOG(" But, NO IP IS AUTHORISED!\n"_fmt); } - else if (access_deny.size() == 1 && access_deny.front().mask() == IP4Address()) + else if (login_conf.deny.size() == 1 && login_conf.deny.front().mask() == IP4Address()) { LOGIN_LOG(" But, you refuse ALL IP!\n"_fmt); } else { - if (access_allow.size() == 1 && access_allow.front().mask() == IP4Address()) + if (login_conf.allow.size() == 1 && login_conf.allow.front().mask() == IP4Address()) { LOGIN_LOG(" You authorise ALL IP.\n"_fmt); } else { LOGIN_LOG(" Authorised IP are:\n"_fmt); - for (IP4Mask ae : access_allow) + for (IP4Mask ae : login_conf.allow) LOGIN_LOG(" %s\n"_fmt, ae); } LOGIN_LOG(" Refused IP are:\n"_fmt); - for (IP4Mask ae : access_deny) + for (IP4Mask ae : login_conf.deny) LOGIN_LOG(" %s\n"_fmt, ae); } } } +static +bool login_config(io::Spanned<XString> key, io::Spanned<ZString> value) +{ + return parse_login_conf(login_conf, key, value); +} + +static +bool login_lan_config(io::Spanned<XString> key, io::Spanned<ZString> value) +{ + return parse_login_lan_conf(login_lan_conf, key, value); +} + +static +bool login_confs(io::Spanned<XString> key, io::Spanned<ZString> value) +{ + if (key.data == "login_conf"_s) + { + return load_config_file(value.data, login_config); + } + if (key.data == "login_lan_conf"_s) + { + return load_config_file(value.data, login_lan_config); + } + key.span.error("Unknown meta-key for login server"_s); + return false; +} +} // namespace login + //-------------------------------------- // Function called at exit of the server //-------------------------------------- void term_func(void) { - mmo_auth_sync(); + login::mmo_auth_sync(); - auth_data.clear(); - gm_account_db.clear(); - for (int i = 0; i < MAX_SERVERS; i++) + login::auth_data.clear(); + login::gm_account_db.clear(); + for (int i = 0; i < login::MAX_SERVERS; i++) { - Session *s = server_session[i]; + Session *s = login::server_session[i]; if (s) delete_session(s); } - delete_session(login_session); + delete_session(login::login_session); LOGIN_LOG("----End of login-server (normal end with closing of all files).\n"_fmt); } -static -bool login_confs(XString key, ZString value) -{ - unsigned sum = 0; - sum += login_config(key, value); - sum += login_lan_config(key, value); - if (sum >= 2) - abort(); - return sum; -} - //------------------------------ // Main function of login-server //------------------------------ @@ -3913,59 +3164,60 @@ int do_init(Slice<ZString> argv) else { loaded_config_yet = true; - runflag &= load_config_file(argvi, login_confs); + runflag &= load_config_file(argvi, login::login_confs); } } if (!loaded_config_yet) - runflag &= load_config_file("conf/tmwa-login.conf"_s, login_confs); + runflag &= load_config_file("conf/tmwa-login.conf"_s, login::login_confs); // not in login_config_read, because we can use 'import' option, and display same message twice or more // (why is that bad?) - runflag &= display_conf_warnings(); + runflag &= login::display_conf_warnings(); // not before, because log file name can be changed // (that doesn't stop the char-server though) - save_config_in_log(); - runflag &= lan_check(); + login::save_config_in_log(); + runflag &= login::lan_check(); - for (int i = 0; i < AUTH_FIFO_SIZE; i++) - auth_fifo[i].delflag = 1; - for (int i = 0; i < MAX_SERVERS; i++) - server_session[i] = nullptr; + for (int i = 0; i < login::AUTH_FIFO_SIZE; i++) + login::auth_fifo[i].delflag = 1; + for (int i = 0; i < login::MAX_SERVERS; i++) + login::server_session[i] = nullptr; - read_gm_account(); - mmo_auth_init(); + login::read_gm_account(); + login::mmo_auth_init(); // set_termfunc (mmo_auth_sync); - login_session = make_listen_port(login_port, SessionParsers{.func_parse= parse_login, .func_delete= delete_login}); + login::login_session = make_listen_port(login::login_conf.login_port, SessionParsers{.func_parse= login::parse_login, .func_delete= login::delete_login}); Timer(gettick() + 5_min, - check_auth_sync, + login::check_auth_sync, 5_min ).detach(); - if (anti_freeze_enable > 0) + if (login::login_conf.anti_freeze_enable > 0) { Timer(gettick() + 1_s, - char_anti_freeze_system, - anti_freeze_interval + login::char_anti_freeze_system, + login::login_conf.anti_freeze_interval ).detach(); } // add timer to check GM accounts file modification - std::chrono::seconds j = gm_account_filename_check_timer; + std::chrono::seconds j = login::login_conf.gm_account_filename_check_timer; if (j == interval_t::zero()) j = 1_min; Timer(gettick() + j, - check_GM_file, + login::check_GM_file, j).detach(); LOGIN_LOG("The login-server is ready (Server is listening on the port %d).\n"_fmt, - login_port); + login::login_conf.login_port); PRINTF("The login-server is " SGR_BOLD SGR_GREEN "ready" SGR_RESET " (Server is listening on the port %d).\n\n"_fmt, - login_port); + login::login_conf.login_port); return 0; } +// namespace login ends before term_func and do_init } // namespace tmwa diff --git a/src/login/login.hpp b/src/login/login.hpp index 92f3c76..ae99558 100644 --- a/src/login/login.hpp +++ b/src/login/login.hpp @@ -18,11 +18,69 @@ // 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 "login.t.hpp" + #include "fwd.hpp" -#include "login.t.hpp" +#include "../strings/vstring.hpp" + +#include "../compat/time_t.hpp" + +#include "../generic/array.hpp" + +#include "../net/ip.hpp" +#include "../net/timestamp-utils.hpp" + +#include "../mmo/consts.hpp" +#include "../mmo/enums.hpp" +#include "../mmo/ids.hpp" +#include "../mmo/strs.hpp" + +#include "../proto2/net-GlobalReg.hpp" + +#include "../high/mmo.hpp" namespace tmwa { +namespace login +{ +constexpr AccountId START_ACCOUNT_NUM = wrap<AccountId>(2000000); +constexpr AccountId END_ACCOUNT_NUM = wrap<AccountId>(100000000); + +struct AuthData +{ + AccountId account_id; + SEX sex; + AccountName userid; + AccountCrypt pass; + timestamp_milliseconds_buffer lastlogin; + int logincount; + int state; // packet 0x006a value + 1 (0: compte OK) + AccountEmail email; // e-mail (by default: a@a.com) + timestamp_seconds_buffer error_message; // Message of error code #6 = Your are Prohibited to log in until %s (packet 0x006a) + TimeT ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + IP4Address last_ip; // save of last IP of connection + VString<254> memo; // a memo field + int account_reg2_num; + Array<GlobalReg, ACCOUNT_REG2_NUM> account_reg2; +}; + +struct mmo_char_server +{ + ServerName name; + IP4Address ip; + uint16_t port; + uint16_t users; +}; + +struct AuthFifo +{ + AccountId account_id; + int login_id1, login_id2; + IP4Address ip; + SEX sex; + int delflag; +}; +} // namespace login } // namespace tmwa diff --git a/src/login/login.t.hpp b/src/login/login.t.hpp index f2c775a..9be0620 100644 --- a/src/login/login.t.hpp +++ b/src/login/login.t.hpp @@ -1,9 +1,10 @@ #pragma once -// login.t.hpp - externally useful types from login +// login.t.hpp - Types for the login server // // Copyright © ????-2004 Athena Dev Teams // Copyright © 2004-2011 The Mana World Development Team // Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © 2014 MadCamel // // This file is part of The Mana World (Athena server) // @@ -22,23 +23,18 @@ #include "fwd.hpp" -#include <cstdint> - -#include "../generic/enum.hpp" - namespace tmwa { -namespace e +namespace login { -enum class VERSION_2 : uint8_t +enum class ACO { - /// client supports updatehost - UPDATEHOST = 0x01, - /// send servers in forward order - SERVERORDER = 0x02, + DENY_ALLOW, + ALLOW_DENY, + MUTUAL_FAILURE, }; -ENUM_BITWISE_OPERATORS(VERSION_2) -} -using e::VERSION_2; +} // namespace login +// out of namespace because ADL is dumb +bool extract(XString str, login::ACO *aco); } // namespace tmwa diff --git a/src/login/main.cpp b/src/login/main.cpp index 48a471a..4495bda 100644 --- a/src/login/main.cpp +++ b/src/login/main.cpp @@ -17,7 +17,7 @@ // 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 "../mmo/core.hpp" +#include "../high/core.hpp" #include "login.hpp" diff --git a/src/main-gdb-head.py b/src/main-gdb-head.py index 2dac471..a465c97 100644 --- a/src/main-gdb-head.py +++ b/src/main-gdb-head.py @@ -29,22 +29,55 @@ def get_basic_type(type_): type_ = type_.strip_typedefs() return type_.unqualified() +def info_symbol(addr): + ''' returns (symbol, offset, section, lib or None) or None? + ''' + info = gdb.execute('info symbol %d' % addr, to_string=True) + try: + sym_and_off, sec_and_lib = info.split(' in section ') + except ValueError: + return None + try: + sym, off = sym_and_off.split(' + ') + except ValueError: + sym = sym_and_off + off = 0 + else: + off = int(off, 10) + try: + sec, lib = sec_and_lib.split(' of ') + except ValueError: + sec = sec_and_lib + lib = None + return (sym, off, sec, lib) + def finish(): - global finish, initial_globals, FastPrinters, EnumPrinter + global finish, initial_globals, FastPrinters, EnumPrinter, PointerPrinter final_globals = {id(v):v for v in globals().values()} diff = set(final_globals.keys()) - set(initial_globals.keys()) \ - - {'finish', 'initial_globals', 'FastPrinters'} + - { + 'finish', + 'initial_globals', + 'FastPrinters', + 'EnumPrinter', + 'PointerPrinter', + } fp = FastPrinters() ep = EnumPrinter + ptrp = PointerPrinter # After this, don't access any more globals in this function. - del finish, initial_globals, FastPrinters, EnumPrinter + del finish, initial_globals, FastPrinters, EnumPrinter, PointerPrinter for i in diff: v = final_globals[i] if hasattr(v, 'children') or hasattr(v, 'to_string'): fp.add_printer(v) + # TODO see if there's a way to detect the top-level printers too + # the problem is that some of them collide and the order *is* + # important, but sets and dicts don't preserve order. + # Particularly, 'PointerPrinter' must come after 'FastPrinters'. obj = gdb.current_objfile() if obj is None: @@ -53,9 +86,12 @@ def finish(): else: filename = obj.filename obj.pretty_printers.append(fp) + n = len(obj.pretty_printers) obj.pretty_printers.append(ep) - print('Added %d+1 custom printers for %s' - % (len(fp.printers), filename)) + obj.pretty_printers.append(ptrp) + n = len(obj.pretty_printers) - n + print('Added %d+%d custom printers for %s' + % (len(fp.printers), n, filename)) class EnumPrinter(object): __slots__ = ('_value') @@ -82,6 +118,49 @@ class EnumPrinter(object): scope = get_basic_type(v.type).tag return '%s::%s' % (scope, name) +class PointerPrinter(object): + __slots__ = ('_value') + name = 'any-symbol-pointer' + enabled = True + + def __new__(cls, v): + type = get_basic_type(v.type) + if type.code != gdb.TYPE_CODE_PTR: + return None + return object.__new__(cls) + + def __init__(self, v): + self._value = v + + def to_string(self): + v = self._value + uptr = gdb.lookup_type('uintptr_t') + addr = int(v.cast(uptr)) + if not addr: + s = 'nullptr' + else: + try: + sym, off, sec, lib = info_symbol(addr) + except TypeError: + sp = gdb.parse_and_eval('$sp') + sp = int(sp.cast(uptr)) + LOTS = 8 * 1024 * 1024 + diff = addr - sp + if +diff >= 0 and +diff <= LOTS: + a = '<$sp+0x%x>' % +diff + elif -diff >= 0 and -diff <= LOTS: + a = '<$sp-0x%x>' % -diff + else: + a = '<heap 0x%x>' % addr + s = '(%s)%s' % (v.type, a) + else: + if off: + s = '<%s+%d>' % off + else: + s = '<%s>' % sym + return s + + class FastPrinters(object): ''' printer dispatch the way gdb *should* have done it ''' diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp index 0d70b36..7739966 100644 --- a/src/map/atcommand.cpp +++ b/src/map/atcommand.cpp @@ -24,8 +24,6 @@ #include <algorithm> -#include "../conf/version.hpp" - #include "../compat/nullpo.hpp" #include "../compat/fun.hpp" @@ -39,31 +37,40 @@ #include "../generic/random.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" +#include "../io/extract.hpp" #include "../io/read.hpp" #include "../io/write.hpp" #include "../net/socket.hpp" #include "../net/timer.hpp" +#include "../net/timestamp-utils.hpp" #include "../mmo/config_parse.hpp" -#include "../mmo/core.hpp" -#include "../mmo/extract.hpp" +#include "../mmo/cxxstdio_enums.hpp" #include "../mmo/extract_enums.hpp" #include "../mmo/human_time_diff.hpp" #include "../mmo/ids.hpp" -#include "../mmo/mmo.hpp" -#include "../mmo/utils.hpp" #include "../mmo/version.hpp" +#include "../high/core.hpp" +#include "../high/extract_mmo.hpp" +#include "../high/mmo.hpp" +#include "../high/utils.hpp" + +#include "../ast/npc.hpp" + #include "battle.hpp" +#include "battle_conf.hpp" #include "chrif.hpp" #include "clif.hpp" +#include "globals.hpp" #include "intif.hpp" #include "itemdb.hpp" #include "map.hpp" +#include "map_conf.hpp" #include "mob.hpp" #include "npc.hpp" +#include "npc-parse.hpp" #include "party.hpp" #include "pc.hpp" #include "skill.hpp" @@ -76,6 +83,8 @@ namespace tmwa { +namespace map +{ enum class ATCE { OKAY, @@ -105,7 +114,7 @@ Map<XString, AtCommandInfo> atcommand_info; static -AtCommandInfo *atcommand(XString cmd); +Option<Borrowed<AtCommandInfo>> atcommand(XString cmd); // These @commands are used within other @commands. static @@ -203,9 +212,7 @@ void log_atcommand(dumb_ptr<map_session_data> sd, ZString cmd) return; timestamp_seconds_buffer tmpstr; stamp_time(tmpstr); - MapName map = (sd->bl_m - ? sd->bl_m->name_ - : stringish<MapName>("undefined.gat"_s)); + MapName map = (sd->bl_m->name_); FPRINTF(*fp, "[%s] %s(%d,%d) %s(%d) : %s\n"_fmt, tmpstr, map, sd->bl_x, sd->bl_y, @@ -213,11 +220,9 @@ void log_atcommand(dumb_ptr<map_session_data> sd, ZString cmd) cmd); } -AString gm_log; - io::AppendFile *get_gm_log() { - if (!gm_log) + if (!map_conf.gm_log) return nullptr; struct tm ctime = TimeT::now(); @@ -233,7 +238,7 @@ io::AppendFile *get_gm_log() last_logfile_nr = logfile_nr; AString fullname = STRPRINTF("%s.%04d-%02d"_fmt, - gm_log, year, month); + map_conf.gm_log, year, month); if (gm_logfile) gm_logfile.reset(); @@ -243,7 +248,7 @@ io::AppendFile *get_gm_log() if (!gm_logfile) { perror("GM log file"); - gm_log = AString(); + map_conf.gm_log = AString(); } return gm_logfile.get(); } @@ -260,8 +265,6 @@ bool is_atcommand(Session *s, dumb_ptr<map_session_data> sd, ZString arg; asplit(message, &command, &arg); - AtCommandInfo *info = atcommand(command); - if (!gmlvl) gmlvl = pc_isGM(sd); if (battle_config.atcommand_gm_only != 0 && !gmlvl) @@ -271,14 +274,16 @@ bool is_atcommand(Session *s, dumb_ptr<map_session_data> sd, clif_displaymessage(s, output); return true; } - if (!info) + + Option<P<AtCommandInfo>> info_ = atcommand(command); + P<AtCommandInfo> info = TRY_UNWRAP(info_, { AString output = STRPRINTF("GM command not found: %s"_fmt, AString(command)); clif_displaymessage(s, output); return true; // don't show in chat - } + }); if (!(gmlvl.satisfies(info->level))) { AString output = STRPRINTF("GM command is level %d, but you are level %d: %s"_fmt, @@ -320,15 +325,15 @@ bool is_atcommand(Session *s, dumb_ptr<map_session_data> sd, } } -AtCommandInfo *atcommand(XString cmd) +Option<Borrowed<AtCommandInfo>> atcommand(XString cmd) { if (cmd.startswith('@')) { XString command = cmd.xslice_t(1); - AtCommandInfo *it = atcommand_info.search(command); + Option<P<AtCommandInfo>> it = atcommand_info.search(command); return it; } - return nullptr; + return None; } static @@ -344,51 +349,42 @@ void atkillmonster_sub(dumb_ptr<block_list> bl, int flag) } static -AtCommandInfo *get_atcommandinfo_byname(XString name) +Option<Borrowed<AtCommandInfo>> get_atcommandinfo_byname(XString name) { return atcommand_info.search(name); } -bool atcommand_config_read(ZString cfgName) +static +bool atcommand_config(io::Spanned<XString> w1, io::Spanned<ZString> w2) { - io::ReadFile in(cfgName); - if (!in.is_open()) - { - PRINTF("At commands configuration file not found: %s\n"_fmt, cfgName); - return false; - } - bool rv = true; - AString line; - while (in.getline(line)) { - if (is_comment(line)) - continue; - XString w1; - ZString w2; - if (!config_split(line, &w1, &w2)) + Option<P<AtCommandInfo>> p_ = get_atcommandinfo_byname(w1.data); + OMATCH_BEGIN (p_) { - PRINTF("Bad config line: %s\n"_fmt, line); - rv = false; - continue; - } - AtCommandInfo *p = get_atcommandinfo_byname(w1); - if (p != nullptr) - { - p->level = GmLevel::from(static_cast<uint32_t>(atoi(w2.c_str()))); - } - else if (w1 == "import"_s) - rv &= atcommand_config_read(w2); - else - { - PRINTF("%s: bad line: %s\n"_fmt, cfgName, line); - rv = false; + OMATCH_CASE_SOME (p) + { + p->level = GmLevel::from(static_cast<uint32_t>(atoi(w2.data.c_str()))); + } + OMATCH_CASE_NONE () + { + { + w1.span.error("Unknown @command for permission level config."_s); + rv = false; + } + } } + OMATCH_END (); } return rv; } +bool atcommand_config_read(ZString cfgName) +{ + return load_config_file(cfgName, atcommand_config); +} + /// @ command processing functions static @@ -421,9 +417,7 @@ ATCE atcommand_help(Session *s, dumb_ptr<map_session_data>, if (message.startswith('@')) { ZString cmd = message.xslice_t(1); - const AtCommandInfo *info = atcommand_info.search(cmd); - if (!info) - return ATCE::EXIST; + P<AtCommandInfo> info = TRY_UNWRAP(atcommand_info.search(cmd), return ATCE::EXIST); clif_displaymessage(s, STRPRINTF("Usage: @%s %s"_fmt, cmd, info->args)); clif_displaymessage(s, info->help); return ATCE::OKAY; @@ -534,16 +528,16 @@ ATCE atcommand_charwarp(Session *s, dumb_ptr<map_session_data> sd, // you can rura+ only lower or same GM level if (x > 0 && x < 800 && y > 0 && y < 800) { - map_local *m = map_mapname2mapid(map_name); - if (m != nullptr && m->flag.get(MapFlag::NOWARPTO) - && !pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level)))) + Option<P<map_local>> m = map_mapname2mapid(map_name); + if (m.map([](P<map_local> m_){ return m_->flag.get(MapFlag::NOWARPTO); }).copy_or(false) + && !pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level)) { clif_displaymessage(s, "You are not authorised to warp someone to this map."_s); return ATCE::PERM; } - if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARP) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + if (pl_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 this player from its actual map."_s); @@ -603,16 +597,16 @@ ATCE atcommand_warp(Session *s, dumb_ptr<map_session_data> sd, if (x > 0 && x < 800 && y > 0 && y < 800) { - map_local *m = map_mapname2mapid(map_name); - if (m != nullptr && m->flag.get(MapFlag::NOWARPTO) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + Option<P<map_local>> m = map_mapname2mapid(map_name); + if (m.map([](P<map_local> m_){ return m_->flag.get(MapFlag::NOWARPTO); }).copy_or(false) + && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level))) { clif_displaymessage(s, "You are not authorised to warp you to this map."_s); return ATCE::PERM; } - if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARP) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + 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); @@ -645,7 +639,7 @@ ATCE atcommand_where(Session *s, dumb_ptr<map_session_data> sd, dumb_ptr<map_session_data> pl_sd = character.to__actual() ? map_nick2sd(character) : sd; if (pl_sd != nullptr && !((battle_config.hide_GM_session - || bool(pl_sd->status.option & Option::HIDE)) + || bool(pl_sd->status.option & Opt0::HIDE)) && !(pc_isGM(sd).detects(pc_isGM(pl_sd))))) { // you can look only lower or same level @@ -679,15 +673,15 @@ ATCE atcommand_goto(Session *s, dumb_ptr<map_session_data> sd, dumb_ptr<map_session_data> pl_sd = map_nick2sd(character); if (pl_sd != nullptr) { - if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARPTO) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + if (pl_sd->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 player."_s); return ATCE::PERM; } - if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARP) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + 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); @@ -707,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) { @@ -720,15 +777,15 @@ ATCE atcommand_jump(Session *s, dumb_ptr<map_session_data> sd, y = random_::in(1, 399); if (x > 0 && x < 800 && y > 0 && y < 800) { - if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARPTO) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + if (sd->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 your actual map."_s); return ATCE::PERM; } - if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARP) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + 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); @@ -768,7 +825,7 @@ ATCE atcommand_who(Session *s, dumb_ptr<map_session_data> sd, GmLevel pl_gm_level = pc_isGM(pl_sd); if (! ((battle_config.hide_GM_session - || bool(pl_sd->status.option & Option::HIDE)) + || bool(pl_sd->status.option & Opt0::HIDE)) && !(gm_level.detects(pl_gm_level)))) { // you can look only lower or same level @@ -812,7 +869,6 @@ ATCE atcommand_whogroup(Session *s, dumb_ptr<map_session_data> sd, ZString message) { int count; - PartyPair p; VString<23> match_text = message; match_text = match_text.to_lower(); @@ -830,7 +886,7 @@ ATCE atcommand_whogroup(Session *s, dumb_ptr<map_session_data> sd, GmLevel pl_gm_level = pc_isGM(pl_sd); if (! ((battle_config.hide_GM_session - || bool(pl_sd->status.option & Option::HIDE)) + || bool(pl_sd->status.option & Opt0::HIDE)) && (!(gm_level.detects(pl_gm_level))))) { // you can look only lower or same level @@ -838,8 +894,8 @@ ATCE atcommand_whogroup(Session *s, dumb_ptr<map_session_data> sd, if (player_name.contains_seq(match_text)) { // search with no case sensitive - p = party_search(pl_sd->status.party_id); - PartyName temp0 = p ? p->name : stringish<PartyName>("None"_s); + Option<PartyPair> p_ = party_search(pl_sd->status.party_id); + PartyName temp0 = p_.pmd_pget(&PartyMost::name).move_or(stringish<PartyName>("None"_s)); AString output; if (pl_gm_level) output = STRPRINTF( @@ -870,15 +926,14 @@ ATCE atcommand_whomap(Session *s, dumb_ptr<map_session_data> sd, ZString message) { int count; - map_local *map_id; - { + Borrowed<map_local> map_id = + ({ MapName map_name; extract(message, &map_name); - map_id = map_mapname2mapid(map_name); - if (map_id == nullptr) - map_id = sd->bl_m; - } + + map_mapname2mapid(map_name).copy_or(sd->bl_m); + }); count = 0; GmLevel gm_level = pc_isGM(sd); @@ -893,7 +948,7 @@ ATCE atcommand_whomap(Session *s, dumb_ptr<map_session_data> sd, GmLevel pl_gm_level = pc_isGM(pl_sd); if (! ((battle_config.hide_GM_session - || bool(pl_sd->status.option & Option::HIDE)) + || bool(pl_sd->status.option & Opt0::HIDE)) && (!(gm_level.detects(pl_gm_level))))) { // you can look only lower or same level @@ -929,16 +984,14 @@ ATCE atcommand_whomapgroup(Session *s, dumb_ptr<map_session_data> sd, ZString message) { int count; - PartyPair p; - map_local *map_id; - { + P<map_local> map_id = + ({ MapName map_name; extract(message, &map_name); - map_id = map_mapname2mapid(map_name); - if (map_id == nullptr) - map_id = sd->bl_m; - } + + map_mapname2mapid(map_name).copy_or(sd->bl_m); + }); count = 0; GmLevel gm_level = pc_isGM(sd); @@ -953,14 +1006,14 @@ ATCE atcommand_whomapgroup(Session *s, dumb_ptr<map_session_data> sd, GmLevel pl_gm_level = pc_isGM(pl_sd); if (! ((battle_config.hide_GM_session - || bool(pl_sd->status.option & Option::HIDE)) + || bool(pl_sd->status.option & Opt0::HIDE)) && (!(gm_level.detects(pl_gm_level))))) { // you can look only lower or same level if (pl_sd->bl_m == map_id) { - p = party_search(pl_sd->status.party_id); - PartyName temp0 = p ? p->name : stringish<PartyName>("None"_s); + Option<PartyPair> p_ = party_search(pl_sd->status.party_id); + 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, @@ -994,7 +1047,6 @@ ATCE atcommand_whogm(Session *s, dumb_ptr<map_session_data> sd, ZString message) { int count; - PartyPair p; VString<23> match_text = message; match_text = match_text.to_lower(); @@ -1014,7 +1066,7 @@ ATCE atcommand_whogm(Session *s, dumb_ptr<map_session_data> sd, { if (! ((battle_config.hide_GM_session - || bool(pl_sd->status.option & Option::HIDE)) + || bool(pl_sd->status.option & Opt0::HIDE)) && (!(gm_level.detects(pl_gm_level))))) { // you can look only lower or same level @@ -1034,8 +1086,8 @@ ATCE atcommand_whogm(Session *s, dumb_ptr<map_session_data> sd, "Novice/Human"_s, pl_sd->status.job_level); clif_displaymessage(s, output); - p = party_search(pl_sd->status.party_id); - PartyName temp0 = p ? p->name : stringish<PartyName>("None"_s); + 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); @@ -1076,16 +1128,16 @@ static ATCE atcommand_load(Session *s, dumb_ptr<map_session_data> sd, ZString) { - map_local *m = map_mapname2mapid(sd->status.save_point.map_); - if (m != nullptr && m->flag.get(MapFlag::NOWARPTO) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + Option<P<map_local>> m = map_mapname2mapid(sd->status.save_point.map_); + if (m.map([](P<map_local> m_){ return m_->flag.get(MapFlag::NOWARPTO); }).copy_or(false) + && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level))) { clif_displaymessage(s, "You are not authorised to warp you to your save map."_s); return ATCE::PERM; } - if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARP) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + 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); @@ -1148,16 +1200,13 @@ static ATCE atcommand_storage(Session *s, dumb_ptr<map_session_data> sd, ZString) { - Storage *stor; - if (sd->state.storage_open) { clif_displaymessage(s, "msg_table[250]"_s); return ATCE::EXIST; } - if ((stor = account2storage2(sd->status_key.account_id)) != nullptr - && stor->storage_status == 1) + if (account2storage2(sd->status_key.account_id).pmd_pget(&Storage::storage_status).copy_or(0) == 1) { clif_displaymessage(s, "msg_table[250]"_s); return ATCE::EXIST; @@ -1174,7 +1223,7 @@ ATCE atcommand_option(Session *s, dumb_ptr<map_session_data> sd, { Opt1 param1 = Opt1::ZERO; Opt2 param2 = Opt2::ZERO; - Option param3 = Option::ZERO; + Opt0 param3 = Opt0::ZERO; if (!extract(message, record<',', 1>(¶m1, ¶m2, ¶m3))) return ATCE::USAGE; @@ -1194,14 +1243,14 @@ static ATCE atcommand_hide(Session *s, dumb_ptr<map_session_data> sd, ZString) { - if (bool(sd->status.option & Option::HIDE)) + if (bool(sd->status.option & Opt0::HIDE)) { - sd->status.option &= ~Option::HIDE; + sd->status.option &= ~Opt0::HIDE; clif_displaymessage(s, "Invisible: Off."_s); } else { - sd->status.option |= Option::HIDE; + sd->status.option |= Opt0::HIDE; clif_displaymessage(s, "Invisible: On."_s); } clif_changeoption(sd); @@ -1259,8 +1308,8 @@ ATCE atcommand_alive(Session *s, dumb_ptr<map_session_data> sd, sd->status.hp = sd->status.max_hp; sd->status.sp = sd->status.max_sp; pc_setstand(sd); - if (static_cast<interval_t>(battle_config.player_invincible_time) > interval_t::zero()) - pc_setinvincibletimer(sd, static_cast<interval_t>(battle_config.player_invincible_time)); + if (battle_config.player_invincible_time > interval_t::zero()) + pc_setinvincibletimer(sd, battle_config.player_invincible_time); clif_updatestatus(sd, SP::HP); clif_updatestatus(sd, SP::SP); clif_resurrection(sd, 1); @@ -1312,7 +1361,7 @@ ATCE atcommand_heal(Session *s, dumb_ptr<map_session_data> sd, if (hp < 0) // display like damage - clif_damage(sd, sd, gettick(), interval_t::zero(), interval_t::zero(), -hp, 0, DamageType::RETURNED, 0); + clif_damage(sd, sd, gettick(), interval_t::zero(), interval_t::zero(), -hp, 0, DamageType::RETURNED); if (hp != 0 || sp != 0) { @@ -1332,13 +1381,27 @@ ATCE atcommand_heal(Session *s, dumb_ptr<map_session_data> sd, } static +Option<P<struct item_data>> extract_item_opt(XString item_name) +{ + Option<P<struct item_data>> item_data = itemdb_searchname(item_name); + if (item_data.is_some()) + return item_data; + + ItemNameId item_id; + if (extract(item_name, &item_id)) + { + item_data = itemdb_exists(item_id); + return item_data; + } + return None; +} + +static ATCE atcommand_item(Session *s, dumb_ptr<map_session_data> sd, ZString message) { XString item_name; int number = 0; - ItemNameId item_id; - struct item_data *item_data = nullptr; int get_count, i; if (!extract(message, record<' ', 1>(&item_name, &number))) @@ -1351,14 +1414,10 @@ ATCE atcommand_item(Session *s, dumb_ptr<map_session_data> sd, if (number <= 0) number = 1; - if ((item_data = itemdb_searchname(item_name)) != nullptr) - item_id = item_data->nameid; - else if (extract(item_name, &item_id) && (item_data = itemdb_exists(item_id)) != nullptr) - item_id = item_data->nameid; - else - return ATCE::EXIST; + P<struct item_data> item_data = TRY_UNWRAP(extract_item_opt(item_name), return ATCE::EXIST); + ItemNameId item_id = item_data->nameid; + assert (item_id); - if (item_id) { get_count = number; if (item_data->type == ItemType::WEAPON @@ -1379,11 +1438,6 @@ ATCE atcommand_item(Session *s, dumb_ptr<map_session_data> sd, } clif_displaymessage(s, "Item created."_s); } - else - { - clif_displaymessage(s, "Invalid item ID or name."_s); - return ATCE::EXIST; - } return ATCE::OKAY; } @@ -1537,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) { @@ -1595,6 +1630,25 @@ ATCE atcommand_pvpoff(Session *s, dumb_ptr<map_session_data> sd, } static +ATCE atcommand_exprate(Session *s, dumb_ptr<map_session_data>, + ZString message) +{ + int rate; + + if (!extract(message, &rate) || !rate) + { + clif_displaymessage(s, + "Please, enter a rate adjustement (usage: @exprate <percent>)."_s); + return ATCE::USAGE; + } + battle_config.base_exp_rate = rate; + battle_config.job_exp_rate = rate; + AString output = STRPRINTF("All Xp at %d percent"_fmt, rate); + clif_displaymessage(s, output); + return ATCE::OKAY; +} + +static ATCE atcommand_pvpon(Session *s, dumb_ptr<map_session_data> sd, ZString) { @@ -1621,7 +1675,6 @@ ATCE atcommand_pvpon(Session *s, dumb_ptr<map_session_data> sd, pl_sd->pvp_timer = Timer(gettick() + 200_ms, std::bind(pc_calc_pvprank_timer, ph::_1, ph::_2, pl_sd->bl_id)); pl_sd->pvp_rank = 0; - pl_sd->pvp_lastusers = 0; pl_sd->pvp_point = 5; } } @@ -1809,14 +1862,13 @@ static void atcommand_killmonster_sub(Session *s, dumb_ptr<map_session_data> sd, ZString message, const int drop) { - map_local *map_id; - { + P<map_local> map_id = + ({ MapName map_name; extract(message, &map_name); - map_id = map_mapname2mapid(map_name); - if (map_id == nullptr) - map_id = sd->bl_m; - } + + map_mapname2mapid(map_name).copy_or(sd->bl_m); + }); map_foreachinarea(std::bind(atkillmonster_sub, ph::_1, drop), map_id, @@ -2080,15 +2132,15 @@ ATCE atcommand_recall(Session *s, dumb_ptr<map_session_data> sd, if (pc_isGM(sd).overwhelms(pc_isGM(pl_sd))) { // you can recall only lower or same level - if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARPTO) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + if (sd->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 somenone to your actual map."_s); return ATCE::PERM; } - if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARP) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + if (pl_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 this player from its actual map."_s); @@ -2127,8 +2179,8 @@ ATCE atcommand_revive(Session *s, dumb_ptr<map_session_data> sd, { pl_sd->status.hp = pl_sd->status.max_hp; pc_setstand(pl_sd); - if (static_cast<interval_t>(battle_config.player_invincible_time) > interval_t::zero()) - pc_setinvincibletimer(sd, static_cast<interval_t>(battle_config.player_invincible_time)); + if (battle_config.player_invincible_time > interval_t::zero()) + pc_setinvincibletimer(sd, battle_config.player_invincible_time); clif_updatestatus(pl_sd, SP::HP); clif_updatestatus(pl_sd, SP::SP); clif_resurrection(pl_sd, 1); @@ -2257,7 +2309,7 @@ ATCE atcommand_character_option(Session *s, dumb_ptr<map_session_data> sd, { Opt1 opt1; Opt2 opt2; - Option opt3; + Opt0 opt3; CharName character; if (!asplit(message, &opt1, &opt2, &opt3, &character)) return ATCE::USAGE; @@ -2403,16 +2455,15 @@ ATCE atcommand_character_save(Session *s, dumb_ptr<map_session_data> sd, if (pc_isGM(sd).overwhelms(pc_isGM(pl_sd))) { // you can change save point only to lower or same gm level - map_local *m = map_mapname2mapid(map_name); - if (m == nullptr) + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(map_name), { clif_displaymessage(s, "Map not found."_s); return ATCE::EXIST; - } - else + }); + { - if (m != nullptr && m->flag.get(MapFlag::NOWARPTO) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + if (m->flag.get(MapFlag::NOWARPTO) + && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level))) { clif_displaymessage(s, "You are not authorised to set this map as a save map."_s); @@ -2976,7 +3027,6 @@ ATCE atcommand_idsearch(Session *s, dumb_ptr<map_session_data>, { ItemName item_name; int match; - struct item_data *item; if (!extract(message, &item_name) || !item_name) return ATCE::USAGE; @@ -2986,8 +3036,8 @@ ATCE atcommand_idsearch(Session *s, dumb_ptr<map_session_data>, match = 0; for (ItemNameId i = wrap<ItemNameId>(0); i < wrap<ItemNameId>(-1); i = next(i)) { - if ((item = itemdb_exists(i)) != nullptr - && item->jname.contains_seq(item_name)) + P<struct item_data> item = TRY_UNWRAP(itemdb_exists(i), continue); + if (item->jname.contains_seq(item_name)) { match++; output = STRPRINTF("%s: %d"_fmt, item->jname, item->nameid); @@ -3350,8 +3400,8 @@ ATCE atcommand_recallall(Session *s, dumb_ptr<map_session_data> sd, { int count; - if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARPTO) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + if (sd->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 somenone to your actual map."_s); @@ -3371,8 +3421,8 @@ ATCE atcommand_recallall(Session *s, dumb_ptr<map_session_data> sd, && pc_isGM(sd).overwhelms(pc_isGM(pl_sd))) { // you can recall only lower or same level - if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARP) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + if (pl_sd->bl_m->flag.get(MapFlag::NOWARP) + && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level))) count++; else pc_setpos(pl_sd, sd->mapname_, sd->bl_x, sd->bl_y, BeingRemoveWhy::QUIT); @@ -3396,57 +3446,62 @@ ATCE atcommand_partyrecall(Session *s, dumb_ptr<map_session_data> sd, ZString message) { PartyName party_name; - PartyPair p; int count; if (!extract(message, &party_name) || !party_name) return ATCE::USAGE; - if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARPTO) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + if (sd->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 somenone to your actual map."_s); return ATCE::PERM; } - if ((p = party_searchname(party_name)) || - // name first to avoid error when name begin with a number - (p = party_search(wrap<PartyId>(static_cast<uint32_t>(atoi(message.c_str())))))) + // name first to avoid error when name begin with a number + Option<PartyPair> p_ = party_searchname(party_name); + if (p_.is_none()) + p_ = party_search(wrap<PartyId>(static_cast<uint32_t>(atoi(message.c_str())))); + OMATCH_BEGIN (p_) { - count = 0; - for (io::FD i : iter_fds()) + OMATCH_CASE_SOME (p) { - Session *s2 = get_session(i); - if (!s2) - continue; - dumb_ptr<map_session_data> pl_sd = dumb_ptr<map_session_data>(static_cast<map_session_data *>(s2->session_data.get())); - if (pl_sd && pl_sd->state.auth - && sd->status_key.account_id != pl_sd->status_key.account_id - && pl_sd->status.party_id == p.party_id) + count = 0; + for (io::FD i : iter_fds()) { - if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARP) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) - count++; - else - pc_setpos(pl_sd, sd->mapname_, sd->bl_x, sd->bl_y, BeingRemoveWhy::QUIT); + Session *s2 = get_session(i); + if (!s2) + continue; + dumb_ptr<map_session_data> pl_sd = dumb_ptr<map_session_data>(static_cast<map_session_data *>(s2->session_data.get())); + if (pl_sd && pl_sd->state.auth + && sd->status_key.account_id != pl_sd->status_key.account_id + && pl_sd->status.party_id == p.party_id) + { + if (pl_sd->bl_m->flag.get(MapFlag::NOWARP) + && !(pc_isGM(sd).satisfies(battle_config.any_warp_GM_min_level))) + count++; + else + pc_setpos(pl_sd, sd->mapname_, sd->bl_x, sd->bl_y, BeingRemoveWhy::QUIT); + } + } + AString output = STRPRINTF("All online characters of the %s party are near you."_fmt, p->name); + clif_displaymessage(s, output); + if (count) + { + output = STRPRINTF( + "Because you are not authorised to warp from some maps, %d player(s) have not been recalled."_fmt, + count); + clif_displaymessage(s, output); } } - AString output = STRPRINTF("All online characters of the %s party are near you."_fmt, p->name); - clif_displaymessage(s, output); - if (count) + OMATCH_CASE_NONE () { - output = STRPRINTF( - "Because you are not authorised to warp from some maps, %d player(s) have not been recalled."_fmt, - count); - clif_displaymessage(s, output); + clif_displaymessage(s, "Incorrect name or ID, or no one from the party is online."_s); + return ATCE::EXIST; } } - else - { - clif_displaymessage(s, "Incorrect name or ID, or no one from the party is online."_s); - return ATCE::EXIST; - } + OMATCH_END (); return ATCE::OKAY; } @@ -3468,9 +3523,7 @@ ATCE atcommand_mapinfo(Session *s, dumb_ptr<map_session_data> sd, if (!map_name) map_name = sd->mapname_; - map_local *m_id = map_mapname2mapid(map_name); - if (m_id != nullptr) - return ATCE::EXIST; + P<map_local> m_id = TRY_UNWRAP(map_mapname2mapid(map_name), return ATCE::EXIST); clif_displaymessage(s, "------ Map Info ------"_s); AString output = STRPRINTF("Map Name: %s"_fmt, map_name); @@ -3592,29 +3645,34 @@ ATCE atcommand_partyspy(Session *s, dumb_ptr<map_session_data> sd, if (!extract(message, &party_name)) return ATCE::USAGE; - PartyPair p; - if ((p = party_searchname(party_name)) || - // name first to avoid error when name begin with a number - (p = party_search(wrap<PartyId>(static_cast<uint32_t>(atoi(message.c_str())))))) + // name first to avoid error when name begin with a number + Option<PartyPair> p_ = party_searchname(party_name); + if (p_.is_none()) + p_ = party_search(wrap<PartyId>(static_cast<uint32_t>(atoi(message.c_str())))); + OMATCH_BEGIN (p_) { - if (sd->partyspy == p.party_id) + OMATCH_CASE_SOME (p) { - sd->partyspy = PartyId(); - AString output = STRPRINTF("No longer spying on the %s party."_fmt, p->name); - clif_displaymessage(s, output); + if (sd->partyspy == p.party_id) + { + sd->partyspy = PartyId(); + AString output = STRPRINTF("No longer spying on the %s party."_fmt, p->name); + clif_displaymessage(s, output); + } + else + { + sd->partyspy = p.party_id; + AString output = STRPRINTF("Spying on the %s party."_fmt, p->name); + clif_displaymessage(s, output); + } } - else + OMATCH_CASE_NONE () { - sd->partyspy = p.party_id; - AString output = STRPRINTF("Spying on the %s party."_fmt, p->name); - clif_displaymessage(s, output); + clif_displaymessage(s, "Incorrect name or ID, or no one from the party is online."_s); + return ATCE::EXIST; } } - else - { - clif_displaymessage(s, "Incorrect name or ID, or no one from the party is online."_s); - return ATCE::EXIST; - } + OMATCH_END (); return ATCE::OKAY; } @@ -3684,19 +3742,15 @@ ATCE atcommand_chardelitem(Session *s, dumb_ptr<map_session_data> sd, CharName character; XString item_name; int i, number = 0; - ItemNameId item_id; int count; - struct item_data *item_data; if (!asplit(message, &item_name, &number, &character) || number < 1) return ATCE::USAGE; - if ((item_data = itemdb_searchname(item_name)) != nullptr) - item_id = item_data->nameid; - else if (extract(item_name, &item_id) && (item_data = itemdb_exists(item_id)) != nullptr) - item_id = item_data->nameid; + P<struct item_data> item_data = TRY_UNWRAP(extract_item_opt(item_name), return ATCE::EXIST); + ItemNameId item_id = item_data->nameid; + assert (item_id); - if (item_id) { dumb_ptr<map_session_data> pl_sd = map_nick2sd(character); if (pl_sd != nullptr) @@ -3744,11 +3798,6 @@ ATCE atcommand_chardelitem(Session *s, dumb_ptr<map_session_data> sd, return ATCE::EXIST; } } - else - { - clif_displaymessage(s, "Invalid item ID or name."_s); - return ATCE::RANGE; - } return ATCE::OKAY; } @@ -3855,7 +3904,6 @@ static ATCE atcommand_character_item_list(Session *s, dumb_ptr<map_session_data> sd, ZString message) { - struct item_data *item_data = nullptr; int count, counter; CharName character; @@ -3872,10 +3920,10 @@ ATCE atcommand_character_item_list(Session *s, dumb_ptr<map_session_data> sd, count = 0; for (IOff0 i : IOff0::iter()) { - if (pl_sd->status.inventory[i].nameid - && (item_data = - itemdb_search(pl_sd->status.inventory[i].nameid)) != - nullptr) + if (!pl_sd->status.inventory[i].nameid) + continue; + P<struct item_data> item_data = TRY_UNWRAP(itemdb_exists(pl_sd->status.inventory[i].nameid), continue); + { counter = counter + pl_sd->status.inventory[i].amount; count++; @@ -3966,8 +4014,6 @@ static ATCE atcommand_character_storage_list(Session *s, dumb_ptr<map_session_data> sd, ZString message) { - Storage *stor; - struct item_data *item_data = nullptr; int count, counter; CharName character; @@ -3980,50 +4026,56 @@ ATCE atcommand_character_storage_list(Session *s, dumb_ptr<map_session_data> sd, if (pc_isGM(sd).overwhelms(pc_isGM(pl_sd))) { // you can look items only lower or same level - if ((stor = account2storage2(pl_sd->status_key.account_id)) != nullptr) + Option<P<Storage>> stor_ = account2storage2(pl_sd->status_key.account_id); + OMATCH_BEGIN (stor_) { - counter = 0; - count = 0; - for (SOff0 i : SOff0::iter()) + OMATCH_CASE_SOME (stor) { - if (stor->storage_[i].nameid - && (item_data = - itemdb_search(stor->storage_[i].nameid)) != nullptr) + counter = 0; + count = 0; + for (SOff0 i : SOff0::iter()) { - counter = counter + stor->storage_[i].amount; - count++; - if (count == 1) + if (!stor->storage_[i].nameid) + continue; + P<struct item_data> item_data = TRY_UNWRAP(itemdb_exists(stor->storage_[i].nameid), continue); + { - AString output = STRPRINTF( - "------ Storage items list of '%s' ------"_fmt, - pl_sd->status_key.name); + counter = counter + stor->storage_[i].amount; + count++; + if (count == 1) + { + AString output = STRPRINTF( + "------ Storage items list of '%s' ------"_fmt, + pl_sd->status_key.name); + clif_displaymessage(s, output); + } + AString output; + if (true) + output = STRPRINTF("%d %s (%s, id: %d)"_fmt, + stor->storage_[i].amount, + item_data->name, item_data->jname, + stor->storage_[i].nameid); clif_displaymessage(s, output); } - AString output; - if (true) - output = STRPRINTF("%d %s (%s, id: %d)"_fmt, - stor->storage_[i].amount, - item_data->name, item_data->jname, - stor->storage_[i].nameid); + } + if (count == 0) + clif_displaymessage(s, + "No item found in the storage of this player."_s); + else + { + AString output = STRPRINTF( + "%d item(s) found in %d kind(s) of items."_fmt, + counter, count); clif_displaymessage(s, output); } } - if (count == 0) - clif_displaymessage(s, - "No item found in the storage of this player."_s); - else + OMATCH_CASE_NONE () { - AString output = STRPRINTF( - "%d item(s) found in %d kind(s) of items."_fmt, - counter, count); - clif_displaymessage(s, output); + clif_displaymessage(s, "This player has no storage."_s); + return ATCE::OKAY; } } - else - { - clif_displaymessage(s, "This player has no storage."_s); - return ATCE::OKAY; - } + OMATCH_END (); } else { @@ -4041,81 +4093,40 @@ ATCE atcommand_character_storage_list(Session *s, dumb_ptr<map_session_data> sd, } static -ATCE atcommand_killer(Session *s, dumb_ptr<map_session_data> sd, +ATCE atcommand_pvp(Session *s, dumb_ptr<map_session_data> sd, ZString) { - sd->special_state.killer = !sd->special_state.killer; - - if (sd->special_state.killer) - clif_displaymessage(s, "You be a killa..."_s); - else - clif_displaymessage(s, "You gonna be own3d..."_s); - - return ATCE::OKAY; -} - -static -ATCE atcommand_charkiller(Session *s, dumb_ptr<map_session_data>, - ZString message) -{ - CharName character; - - if (!asplit(message, &character)) - return ATCE::USAGE; - - dumb_ptr<map_session_data> pl_sd = map_nick2sd(character); - if (pl_sd == nullptr) - return ATCE::EXIST; - - pl_sd->special_state.killer = !pl_sd->special_state.killer; + int chan = sd->state.pvpchannel; + if (sd->pvp_timer || (chan > 1)) + return ATCE::OKAY; - if (pl_sd->special_state.killer) - { - clif_displaymessage(s, "The player is now a killer"_s); - clif_displaymessage(pl_sd->sess, "You are now a killer"_s); - } - else - { - clif_displaymessage(s, "The player is no longer a killer"_s); - clif_displaymessage(pl_sd->sess, "You are no longer a killer"_s); + if (chan < 1) { + sd->state.pvpchannel = 1; + clif_displaymessage(s, "##3PvP : ##BOn"_s); + } else { + sd->state.pvpchannel = 0; + clif_displaymessage(s, "##3PvP : ##BOff"_s); } + pc_setpvptimer(sd, battle_config.player_pvp_time); return ATCE::OKAY; } static -ATCE atcommand_killable(Session *s, dumb_ptr<map_session_data> sd, - ZString) -{ - sd->special_state.killable = !sd->special_state.killable; - - if (sd->special_state.killable) - clif_displaymessage(s, "You gonna be own3d..."_s); - else - clif_displaymessage(s, "You be a killa..."_s); - - return ATCE::OKAY; -} - -static -ATCE atcommand_charkillable(Session *s, dumb_ptr<map_session_data>, +ATCE atcommand_charpvp(Session *, dumb_ptr<map_session_data>, ZString message) { CharName character; + int channel; - if (!asplit(message, &character)) + if (!extract(message, record<' '>(&character, &channel))) return ATCE::USAGE; dumb_ptr<map_session_data> pl_sd = map_nick2sd(character); if (pl_sd == nullptr) return ATCE::EXIST; - pl_sd->special_state.killable = !pl_sd->special_state.killable; - - if (pl_sd->special_state.killable) - clif_displaymessage(s, "The player is now killable"_s); - else - clif_displaymessage(s, "The player is no longer killable"_s); + pl_sd->state.pvpchannel = channel; return ATCE::OKAY; } @@ -4155,13 +4166,21 @@ ATCE atcommand_addwarp(Session *s, dumb_ptr<map_session_data> sd, if (!extract(message, record<' '>(&mapname, &x, &y))) return ATCE::USAGE; - AString w1 = STRPRINTF("%s,%d,%d"_fmt, sd->mapname_, sd->bl_x, sd->bl_y); AString w3 = STRPRINTF("%s%d%d%d%d"_fmt, mapname, sd->bl_x, sd->bl_y, x, y); - AString w4 = STRPRINTF("1,1,%s.gat,%d,%d"_fmt, mapname, x, y); - NpcName w3name = stringish<NpcName>(w3); - int ret = npc_parse_warp(w1, "warp"_s, w3name, w4); - if (ret) + + ast::npc::Warp warp; + warp.m.data = sd->mapname_; + warp.x.data = sd->bl_x; + warp.y.data = sd->bl_y; + warp.name.data = w3name; + warp.xs.data = 1+2; + warp.ys.data = 1+2; + warp.to_m.data = mapname; + warp.to_x.data = x; + warp.to_y.data = y; + + if (!npc_load_warp(warp)) // warp failed return ATCE::RANGE; @@ -4433,14 +4452,15 @@ ATCE atcommand_adjcmdlvl(Session *s, dumb_ptr<map_session_data>, return ATCE::USAGE; } - AtCommandInfo *it = atcommand_info.search(cmd); + Option<P<AtCommandInfo>> it_ = atcommand_info.search(cmd); { - if (it) + OMATCH_BEGIN_SOME (it, it_) { it->level = newlev; clif_displaymessage(s, "@command level changed."_s); return ATCE::OKAY; } + OMATCH_END (); } clif_displaymessage(s, "@command not found."_s); @@ -4665,15 +4685,15 @@ ATCE atcommand_jump_iterate(Session *s, dumb_ptr<map_session_data> sd, pl_sd = get_start(); } - if (pl_sd->bl_m && pl_sd->bl_m->flag.get(MapFlag::NOWARPTO) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + if (pl_sd->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 player."_s); return ATCE::PERM; } - if (sd->bl_m && sd->bl_m->flag.get(MapFlag::NOWARP) - && !(pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.any_warp_GM_min_level))))) + 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); @@ -4744,11 +4764,11 @@ ATCE atcommand_skillpool_info(Session *s, dumb_ptr<map_session_data>, clif_displaymessage(s, buf); } - buf = STRPRINTF("Learned skills out of %d for %s:"_fmt, - skill_pool_skills_size, character); + buf = STRPRINTF("Learned skills out of %zu for %s:"_fmt, + skill_pool_skills.size(), character); clif_displaymessage(s, buf); - for (i = 0; i < skill_pool_skills_size; ++i) + for (i = 0; i < skill_pool_skills.size(); ++i) { const RString& name = skill_name(skill_pool_skills[i]); int lvl = pl_sd->status.skill[skill_pool_skills[i]].lv; @@ -4919,14 +4939,10 @@ static ATCE atcommand_source(Session *s, dumb_ptr<map_session_data>, ZString) { - clif_displaymessage(s, - "This server code consists of Free Software under GPL3&AGPL3"_s); - clif_displaymessage(s, - "This is commit " VERSION_HASH ", also known as " VERSION_FULL ""_s); - clif_displaymessage(s, - "The version is " VERSION_STRING ""_s); - clif_displaymessage(s, - "For source, see " VENDOR_SOURCE ""_s); + clif_displaymessage(s, VERSION_INFO_HEADER); + clif_displaymessage(s, VERSION_INFO_COMMIT); + clif_displaymessage(s, VERSION_INFO_NUMBER); + clif_displaymessage(s, VERSION_INFO_URL); return ATCE::OKAY; } @@ -4954,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}}, @@ -5023,12 +5042,12 @@ 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}}, + {"exprate"_s, {"<percent>"_s, + 60, atcommand_exprate, + "Set base job/exp rate"_s}}, {"pvpon"_s, {""_s, 60, atcommand_pvpon, "Disable PvP on your map"_s}}, @@ -5242,21 +5261,15 @@ Map<XString, AtCommandInfo> atcommand_info = {"addwarp"_s, {"<mapname> <x> <y>"_s, 80, atcommand_addwarp, "Create a new permanent warp"_s}}, - {"killer"_s, {""_s, - 60, atcommand_killer, - "Toggle whether you are a killer"_s}}, - {"charkiller"_s, {"<charname>"_s, - 60, atcommand_charkiller, - "Toggle whether a player is a killer"_s}}, + {"pvp"_s, {""_s, + 0, atcommand_pvp, + "Toggle your pvp flag"_s}}, {"npcmove"_s, {"<x> <y> <npc-name>"_s, 80, atcommand_npcmove, "Force an NPC to move on the map"_s}}, - {"killable"_s, {""_s, - 60, atcommand_killable, - "Toggle whether you are killable"_s}}, - {"charkillable"_s, {"<charname>"_s, - 60, atcommand_charkillable, - "Toggle whether a player is killable"_s}}, + {"charpvp"_s, {"<charname> <channel>"_s, + 40, atcommand_charpvp, + "Set the pvp channel of another player"_s}}, {"chareffect"_s, {"<type> <target>"_s, 40, atcommand_chareffect, "Apply effect type with arg 0 to a player"_s}}, @@ -5357,4 +5370,5 @@ Map<XString, AtCommandInfo> atcommand_info = 0, atcommand_source, "Legal information about source code (must be a level 0 command!)"_s}}, }; +} // namespace map } // namespace tmwa diff --git a/src/map/atcommand.hpp b/src/map/atcommand.hpp index 4bf5277..4c0e421 100644 --- a/src/map/atcommand.hpp +++ b/src/map/atcommand.hpp @@ -22,17 +22,11 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" - -#include "../generic/fwd.hpp" - -#include "../net/fwd.hpp" - -#include "../mmo/fwd.hpp" - namespace tmwa { +namespace map +{ bool is_atcommand(Session *s, dumb_ptr<map_session_data> sd, ZString message, GmLevel gmlvl); @@ -40,8 +34,6 @@ bool atcommand_config_read(ZString cfgName); void log_atcommand(dumb_ptr<map_session_data> sd, ZString cmd); -// only used by map.cpp -extern AString gm_log; - void atcommand_config_write(ZString cfgName); +} // namespace map } // namespace tmwa diff --git a/src/map/battle.cpp b/src/map/battle.cpp index 856408c..5b63acc 100644 --- a/src/map/battle.cpp +++ b/src/map/battle.cpp @@ -32,12 +32,17 @@ #include "../generic/random.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" #include "../io/read.hpp" +#include "../io/span.hpp" #include "../mmo/config_parse.hpp" +#include "../mmo/cxxstdio_enums.hpp" +#include "../high/utils.hpp" + +#include "battle_conf.hpp" #include "clif.hpp" +#include "globals.hpp" #include "itemdb.hpp" #include "map.hpp" #include "mob.hpp" @@ -50,13 +55,8 @@ namespace tmwa { -static Battle_Config init_battle_config(); - -DIAG_PUSH(); -DIAG_I(shadow); -struct Battle_Config battle_config = init_battle_config(); -DIAG_POP(); - +namespace map +{ /*========================================== * 自分をロックしている対象の数を返す(汎用) * 戻りは整数で0以上 @@ -482,21 +482,6 @@ int battle_get_atk(dumb_ptr<block_list> bl) } /*========================================== - * 対象の左手Atkを返す(汎用) - * 戻りは整数で0以上 - *------------------------------------------ - */ -static -int battle_get_atk_(dumb_ptr<block_list> bl) -{ - nullpo_retz(bl); - if (bl->bl_type == BL::PC) - return bl->is_player()->watk_; - else - return 0; -} - -/*========================================== * 対象のAtk2を返す(汎用) * 戻りは整数で0以上 *------------------------------------------ @@ -520,21 +505,6 @@ int battle_get_atk2(dumb_ptr<block_list> bl) } /*========================================== - * 対象の左手Atk2を返す(汎用) - * 戻りは整数で0以上 - *------------------------------------------ - */ -static -int battle_get_atk_2(dumb_ptr<block_list> bl) -{ - nullpo_retz(bl); - if (bl->bl_type == BL::PC) - return bl->is_player()->watk_2; - else - return 0; -} - -/*========================================== * 対象のMAtk1を返す(汎用) * 戻りは整数で0以上 *------------------------------------------ @@ -756,7 +726,7 @@ interval_t battle_get_adelay(dumb_ptr<block_list> bl) if (aspd_rate != 100) adelay = adelay * aspd_rate / 100; - return std::max(adelay, static_cast<interval_t>(battle_config.monster_max_aspd) * 2); + return std::max(adelay, battle_config.monster_max_aspd * 2); } } @@ -771,7 +741,7 @@ interval_t battle_get_amotion(dumb_ptr<block_list> bl) interval_t amotion = 2_s; int aspd_rate = 100; if (bl->bl_type == BL::MOB) - amotion = static_cast<interval_t>(get_mob_db(bl->is_mob()->mob_class).amotion); + amotion = get_mob_db(bl->is_mob()->mob_class).amotion; if (sc_data) { @@ -783,7 +753,7 @@ interval_t battle_get_amotion(dumb_ptr<block_list> bl) if (aspd_rate != 100) amotion = amotion * aspd_rate / 100; - return std::max(amotion, static_cast<interval_t>(battle_config.monster_max_aspd)); + return std::max(amotion, battle_config.monster_max_aspd); } } @@ -792,7 +762,7 @@ interval_t battle_get_dmotion(dumb_ptr<block_list> bl) nullpo_retr(interval_t::zero(), bl); if (bl->bl_type == BL::MOB) { - return static_cast<interval_t>(get_mob_db(bl->is_mob()->mob_class).dmotion); + return get_mob_db(bl->is_mob()->mob_class).dmotion; } else if (bl->bl_type == BL::PC) { @@ -884,16 +854,6 @@ eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> battle_ return nullptr; } -short *battle_get_sc_count(dumb_ptr<block_list> bl) -{ - nullpo_retr(nullptr, bl); - if (bl->bl_type == BL::MOB) - return &bl->is_mob()->sc_count; - else if (bl->bl_type == BL::PC) - return &bl->is_player()->sc_count; - return nullptr; -} - Opt1 *battle_get_opt1(dumb_ptr<block_list> bl) { nullpo_retn(bl); @@ -930,7 +890,7 @@ Opt3 *battle_get_opt3(dumb_ptr<block_list> bl) return nullptr; } -Option *battle_get_option(dumb_ptr<block_list> bl) +Opt0 *battle_get_option(dumb_ptr<block_list> bl) { nullpo_retn(bl); if (bl->bl_type == BL::MOB) @@ -1023,17 +983,6 @@ int battle_stopattack(dumb_ptr<block_list> bl) return 0; } -// 移動停止 -int battle_stopwalking(dumb_ptr<block_list> bl, int type) -{ - nullpo_retz(bl); - if (bl->bl_type == BL::MOB) - return mob_stop_walking(bl->is_mob(), type); - else if (bl->bl_type == BL::PC) - return pc_stop_walking(bl->is_player(), type); - return 0; -} - /*========================================== * ダメージ最終計算 *------------------------------------------ @@ -1082,7 +1031,7 @@ struct Damage battle_calc_mob_weapon_attack(dumb_ptr<block_list> src, int def2 = battle_get_def2(target); int t_vit = battle_get_vit(target); struct Damage wd {}; - int damage, damage2 = 0; + int damage; DamageType type; int div_; BF flag; @@ -1112,7 +1061,7 @@ struct Damage battle_calc_mob_weapon_attack(dumb_ptr<block_list> src, || battle_config.vit_penaly_type > 0) target_count += battle_counttargeted(target, src, - ATK(battle_config.agi_penaly_count_lv)); // FIXME + battle_config.agi_penaly_count_lv); if (battle_config.agi_penaly_type > 0) { if (target_count >= battle_config.agi_penaly_count) @@ -1198,7 +1147,7 @@ struct Damage battle_calc_mob_weapon_attack(dumb_ptr<block_list> src, int t_def; target_count = 1 + battle_counttargeted(target, src, - ATK(battle_config.vit_penaly_count_lv)); // FIXME + battle_config.vit_penaly_count_lv); if (battle_config.vit_penaly_type > 0) { if (target_count >= battle_config.vit_penaly_count) @@ -1273,7 +1222,7 @@ struct Damage battle_calc_mob_weapon_attack(dumb_ptr<block_list> src, if (type == DamageType::NORMAL && !random_::chance({hitrate, 100})) { - damage = damage2 = 0; + damage = 0; dmg_lv = ATK::FLEE; } else @@ -1312,7 +1261,6 @@ struct Damage battle_calc_mob_weapon_attack(dumb_ptr<block_list> src, skill_num, skill_lv, flag); wd.damage = damage; - wd.damage2 = 0; wd.type = type; wd.div_ = div_; wd.amotion = battle_get_amotion(src); @@ -1357,14 +1305,13 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, int def2 = battle_get_def2(target); int t_vit = battle_get_vit(target); struct Damage wd {}; - int damage, damage2; + int damage; DamageType type; int div_; BF flag; ATK dmg_lv = ATK::ZERO; eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data, t_sc_data; - int atkmax_ = 0, atkmin_ = 0; //二刀流用 - int watk, watk_; + int watk; bool da = false; int ac_flag = 0; int target_distance; @@ -1392,7 +1339,7 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, flee = battle_get_flee(target); if (battle_config.agi_penaly_type > 0 || battle_config.vit_penaly_type > 0) //AGI、VITペナルティ設定が有効 target_count += battle_counttargeted(target, src, - ATK(battle_config.agi_penaly_count_lv)); //対象の数を算出 + battle_config.agi_penaly_count_lv); //対象の数を算出 if (battle_config.agi_penaly_type > 0) { if (target_count >= battle_config.agi_penaly_count) @@ -1428,13 +1375,12 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, dex = battle_get_dex(src); //DEX watk = battle_get_atk(src); //ATK - watk_ = battle_get_atk_(src); //ATK左手 type = DamageType::NORMAL; div_ = 1; // single attack { - damage = damage2 = battle_get_baseatk(sd); //damega,damega2初登場、base_atkの取得 + damage = battle_get_baseatk(sd); //damega,damega2初登場、base_atkの取得 } if (sd->attackrange > 2) { // [fate] ranged weapon? @@ -1443,22 +1389,21 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, damage * (256 + ((range_damage_bonus * target_distance) / sd->attackrange)) >> 8; - damage2 = - damage2 * (256 + - ((range_damage_bonus * target_distance) / - sd->attackrange)) >> 8; } - atkmin = atkmin_ = dex; //最低ATKはDEXで初期化? + atkmin = dex; //最低ATKはDEXで初期化? sd->state.arrow_atk = 0; //arrow_atk初期化 IOff0 widx = sd->equip_index_maybe[EQUIP::WEAPON]; - IOff0 sidx = sd->equip_index_maybe[EQUIP::SHIELD]; - if (widx.ok() && sd->inventory_data[widx]) - atkmin = atkmin * (80 + sd->inventory_data[widx]->wlv * 20) / 100; - if (sidx.ok() && sd->inventory_data[sidx]) - atkmin_ = atkmin_ * (80 + sd->inventory_data[sidx]->wlv * 20) / 100; + if (widx.ok()) + { + OMATCH_BEGIN_SOME (sdidw, sd->inventory_data[widx]) + { + atkmin = atkmin * (80 + sdidw->wlv * 20) / 100; + } + OMATCH_END (); + } if (sd->status.weapon == ItemLook::BOW) { //武器が弓矢の場合 atkmin = watk * ((atkmin < watk) ? atkmin : watk) / 100; //弓用最低ATK計算 @@ -1468,13 +1413,10 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, { atkmax = watk; - atkmax_ = watk_; } if (atkmin > atkmax && !(sd->state.arrow_atk)) atkmin = atkmax; //弓は最低が上回る場合あり - if (atkmin_ > atkmax_) - atkmin_ = atkmax_; if (sd->double_rate > 0 && skill_num == SkillID::ZERO && skill_lv >= 0) da = random_::chance({sd->double_rate, 100}); @@ -1486,9 +1428,6 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, if (sd->state.arrow_atk) cri += sd->arrow_cri; - if (sd->status.weapon == ItemLook::_16) - // カタールの場合、クリティカルを倍に - cri <<= 1; cri -= battle_get_luk(target) * 3; if (ac_flag) cri = 1000; @@ -1503,11 +1442,9 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, && random_::chance({cri, 1000})) { damage += atkmax; - damage2 += atkmax_; if (sd->atk_rate != 100) { damage = (damage * sd->atk_rate) / 100; - damage2 = (damage2 * sd->atk_rate) / 100; } if (sd->state.arrow_atk) damage += sd->arrow_atk; @@ -1521,14 +1458,9 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, damage += random_::in(atkmin, atkmax); else damage += atkmin; - if (atkmax_ > atkmin_) - damage2 += random_::in(atkmin_, atkmax_); - else - damage2 += atkmin_; if (sd->atk_rate != 100) { damage = (damage * sd->atk_rate) / 100; - damage2 = (damage2 * sd->atk_rate) / 100; } if (sd->state.arrow_atk) @@ -1551,7 +1483,7 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, int t_def; target_count = 1 + battle_counttargeted(target, src, - ATK(battle_config.vit_penaly_count_lv)); // FIXME + battle_config.vit_penaly_count_lv); if (battle_config.vit_penaly_type > 0) { if (target_count >= battle_config.vit_penaly_count) @@ -1614,28 +1546,17 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, damage -= random_::in(0, vitbonusmax); } } - { - { - damage2 = damage2 * (100 - def1) / 100; - damage2 -= t_def; - if (vitbonusmax > 0) - damage2 -= random_::in(0, vitbonusmax); - } - } } } } // 精錬ダメージの追加 { //DEF, VIT無視 damage += battle_get_atk2(src); - damage2 += battle_get_atk_2(src); } // 0未満だった場合1に補正 if (damage < 1) damage = 1; - if (damage2 < 1) - damage2 = 1; // スキル修正2(修練系) // 修練ダメージ(右手のみ) ソニックブロー時は別処理(1撃に付き1/8適応) @@ -1652,7 +1573,7 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, hitrate = (hitrate < 5) ? 5 : hitrate; if (type == DamageType::NORMAL && !random_::chance({hitrate, 100})) { - damage = damage2 = 0; + damage = 0; dmg_lv = ATK::FLEE; } else @@ -1662,37 +1583,6 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, if (damage < 0) damage = 0; - if (damage2 < 0) - damage2 = 0; - - // >二刀流の左右ダメージ計算誰かやってくれぇぇぇぇえええ! - // >map_session_data に左手ダメージ(atk,atk2)追加して - // >pc_calcstatus()でやるべきかな? - // map_session_data に左手武器(atk,atk2,ele,star,atkmods)追加して - // pc_calcstatus()でデータを入力しています - - //左手のみ武器装備 - if (sd->weapontype1 == ItemLook::NONE - && sd->weapontype2 != ItemLook::NONE) - { - damage = damage2; - damage2 = 0; - } - // 右手、左手修練の適用 - if (sd->status.weapon >= ItemLook::SINGLE_HANDED_COUNT) - { // 二刀流か? - int dmg = damage, dmg2 = damage2; - // 右手修練(60% 〜 100%) 右手全般 - damage = damage * 50 / 100; - if (dmg > 0 && damage < 1) - damage = 1; - // 左手修練(40% 〜 80%) 左手全般 - damage2 = damage2 * 30 / 100; - if (dmg2 > 0 && damage2 < 1) - damage2 = 1; - } - else //二刀流でなければ左手ダメージは0 - damage2 = 0; // 右手,短剣のみ if (da) @@ -1702,19 +1592,11 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, type = DamageType::DOUBLED; } - if (sd->status.weapon == ItemLook::_16) - { - // カタール追撃ダメージ - damage2 = damage * 1 / 100; - if (damage > 0 && damage2 < 1) - damage2 = 1; - } - // 完全回避の判定 if (skill_num == SkillID::ZERO && skill_lv >= 0 && tsd != nullptr && div_ < 255 && random_::chance({battle_get_flee2(target), 1000})) { - damage = damage2 = 0; + damage = 0; type = DamageType::FLEE2; dmg_lv = ATK::LUCKY; } @@ -1725,7 +1607,7 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, if (skill_num == SkillID::ZERO && skill_lv >= 0 && tmd != nullptr && div_ < 255 && random_::chance({battle_get_flee2(target), 1000})) { - damage = damage2 = 0; + damage = 0; type = DamageType::FLEE2; dmg_lv = ATK::LUCKY; } @@ -1736,35 +1618,18 @@ struct Damage battle_calc_pc_weapon_attack(dumb_ptr<block_list> src, { if (damage > 0) damage = 1; - if (damage2 > 0) - damage2 = 1; } - if (damage > 0 || damage2 > 0) + if (damage > 0) { - if (damage2 < 1) // ダメージ最終修正 + { damage = battle_calc_damage(src, target, damage, div_, skill_num, skill_lv, flag); - else if (damage < 1) // 右手がミス? - damage2 = - battle_calc_damage(src, target, damage2, div_, skill_num, - skill_lv, flag); - else - { // 両 手/カタールの場合はちょっと計算ややこしい - int d1 = damage + damage2, d2 = damage2; - damage = - battle_calc_damage(src, target, damage + damage2, div_, - skill_num, skill_lv, flag); - damage2 = (d2 * 100 / d1) * damage / 100; - if (damage > 1 && damage2 < 1) - damage2 = 1; - damage -= damage2; } } wd.damage = damage; - wd.damage2 = damage2; wd.type = type; wd.div_ = div_; wd.amotion = battle_get_amotion(src); @@ -1872,7 +1737,6 @@ struct Damage battle_calc_magic_attack(dumb_ptr<block_list> bl, md.div_ = div_; md.amotion = battle_get_amotion(bl); md.dmotion = battle_get_dmotion(target); - md.damage2 = 0; md.type = DamageType::NORMAL; md.flag = aflag; @@ -1937,7 +1801,6 @@ struct Damage battle_calc_misc_attack(dumb_ptr<block_list> bl, md.div_ = div_; md.amotion = battle_get_amotion(bl); md.dmotion = battle_get_dmotion(target); - md.damage2 = 0; md.type = DamageType::NORMAL; md.flag = aflag; return md; @@ -2048,13 +1911,7 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target, { clif_damage(src, target, tick, wd.amotion, wd.dmotion, - wd.damage, wd.div_, wd.type, wd.damage2); - if (sd - && (sd->status.weapon == ItemLook::_16 - || sd->status.weapon >= ItemLook::SINGLE_HANDED_COUNT) - && wd.damage2 == 0) - clif_damage(src, target, tick + 10_ms, - wd.amotion, wd.dmotion, 0, 1, DamageType::NORMAL, 0); + wd.damage, wd.div_, wd.type); } MapBlockLock lock; @@ -2063,9 +1920,17 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target, { IOff0 weapon_index = sd->equip_index_maybe[EQUIP::WEAPON]; ItemNameId weapon; - if (weapon_index.ok() && sd->inventory_data[weapon_index] - && bool(sd->status.inventory[weapon_index].equip & EPOS::WEAPON)) - weapon = sd->inventory_data[weapon_index]->nameid; + if (weapon_index.ok()) + { + OMATCH_BEGIN_SOME (sdidw, sd->inventory_data[weapon_index]) + { + if (bool(sd->status.inventory[weapon_index].equip & EPOS::WEAPON)) + { + weapon = sdidw->nameid; + } + } + OMATCH_END (); + } MAP_LOG("PC%d %s:%d,%d WPNDMG %s%d %d FOR %d WPN %d"_fmt, sd->status_key.char_id, src->bl_m->name_, src->bl_x, src->bl_y, @@ -2074,7 +1939,7 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target, ? unwrap<CharId>(target->is_player()->status_key.char_id) : unwrap<BlockId>(target->bl_id), battle_get_class(target), - wd.damage + wd.damage2, weapon); + wd.damage, weapon); } if (target->bl_type == BL::PC) @@ -2087,16 +1952,16 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target, ? unwrap<CharId>(src->is_player()->status_key.char_id) : unwrap<BlockId>(src->bl_id), battle_get_class(src), - wd.damage + wd.damage2); + wd.damage); } - battle_damage(src, target, (wd.damage + wd.damage2), 0); + battle_damage(src, target, (wd.damage), 0); if (target->bl_prev != nullptr && (target->bl_type != BL::PC || (target->bl_type == BL::PC && !pc_isdead(target->is_player())))) { - if (wd.damage > 0 || wd.damage2 > 0) + if (wd.damage > 0) { skill_additional_effect(src, target, SkillID::ZERO, 0); } @@ -2105,7 +1970,7 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target, { if (bool(wd.flag & BF::WEAPON) && src != target - && (wd.damage > 0 || wd.damage2 > 0)) + && (wd.damage > 0)) { int hp = 0, sp = 0; if (sd->hp_drain_rate && wd.damage > 0 @@ -2113,21 +1978,11 @@ ATK battle_weapon_attack(dumb_ptr<block_list> src, dumb_ptr<block_list> target, { hp += (wd.damage * sd->hp_drain_per) / 100; } - if (sd->hp_drain_rate_ && wd.damage2 > 0 - && random_::chance({sd->hp_drain_rate_, 100})) - { - hp += (wd.damage2 * sd->hp_drain_per_) / 100; - } if (sd->sp_drain_rate && wd.damage > 0 && random_::chance({sd->sp_drain_rate, 100})) { sp += (wd.damage * sd->sp_drain_per) / 100; } - if (sd->sp_drain_rate_ && wd.damage2 > 0 - && random_::chance({sd->sp_drain_rate_, 100})) - { - sp += (wd.damage2 * sd->sp_drain_per_) / 100; - } if (hp || sp) pc_heal(sd, hp, sp); } @@ -2310,380 +2165,5 @@ int battle_check_range(dumb_ptr<block_list> src, dumb_ptr<block_list> bl, return (path_search(&wpd, src->bl_m, src->bl_x + dx, src->bl_y + dy, bl->bl_x - dx, bl->bl_y - dy, 0x10001) != -1) ? 1 : 0; } - -Battle_Config init_battle_config() -{ - DIAG_PUSH(); - DIAG_I(shadow); - Battle_Config battle_config; - DIAG_POP(); - { - battle_config.warp_point_debug = 0; - battle_config.enemy_critical = 0; - battle_config.enemy_critical_rate = 100; - battle_config.enemy_str = 1; - battle_config.enemy_perfect_flee = 0; - battle_config.casting_rate = 100; - battle_config.delay_rate = 100; - battle_config.delay_dependon_dex = 0; - battle_config.skill_delay_attack_enable = 0; - battle_config.monster_skill_add_range = 0; - battle_config.player_damage_delay = 1; - battle_config.flooritem_lifetime = std::chrono::duration_cast<std::chrono::milliseconds>(LIFETIME_FLOORITEM).count(); - battle_config.item_auto_get = 0; - battle_config.drop_pickup_safety_zone = 20; - battle_config.item_first_get_time = 3000; - battle_config.item_second_get_time = 1000; - battle_config.item_third_get_time = 1000; - - battle_config.base_exp_rate = 100; - battle_config.job_exp_rate = 100; - battle_config.death_penalty_type = 0; - battle_config.death_penalty_base = 0; - battle_config.death_penalty_job = 0; - battle_config.restart_hp_rate = 0; - battle_config.restart_sp_rate = 0; - battle_config.monster_hp_rate = 100; - battle_config.monster_max_aspd = 199; - battle_config.atcommand_gm_only = 0; - battle_config.gm_all_equipment = 0; - battle_config.monster_active_enable = 1; - battle_config.mob_skill_use = 1; - battle_config.mob_count_rate = 100; - battle_config.basic_skill_check = 1; - battle_config.player_invincible_time = 5000; - battle_config.skill_min_damage = 0; - battle_config.natural_healhp_interval = 6000; - battle_config.natural_healsp_interval = 8000; - battle_config.natural_heal_skill_interval = 10000; - battle_config.natural_heal_weight_rate = 50; - battle_config.itemheal_regeneration_factor = 1; - battle_config.arrow_decrement = 1; - battle_config.max_aspd = 199; - battle_config.max_hp = 32500; - battle_config.max_sp = 32500; - battle_config.max_lv = 99; // [MouseJstr] - battle_config.max_parameter = 99; - battle_config.monster_skill_log = 0; - battle_config.battle_log = 0; - battle_config.save_log = 0; - battle_config.error_log = 1; - battle_config.etc_log = 1; - battle_config.save_clothcolor = 0; - battle_config.undead_detect_type = 0; - battle_config.agi_penaly_type = 0; - battle_config.agi_penaly_count = 3; - battle_config.agi_penaly_num = 0; - battle_config.agi_penaly_count_lv = static_cast<int>(ATK::FLEE); // FIXME - battle_config.vit_penaly_type = 0; - battle_config.vit_penaly_count = 3; - battle_config.vit_penaly_num = 0; - battle_config.vit_penaly_count_lv = static_cast<int>(ATK::DEF); // FIXME - battle_config.mob_changetarget_byskill = 0; - battle_config.player_attack_direction_change = 1; - battle_config.monster_attack_direction_change = 1; - battle_config.display_delay_skill_fail = 1; - battle_config.dead_branch_active = 0; - battle_config.show_steal_in_same_party = 0; - battle_config.hide_GM_session = 0; - battle_config.invite_request_check = 1; - battle_config.disp_experience = 0; - battle_config.prevent_logout = 1; // Added by RoVeRT - battle_config.maximum_level = 255; // Added by Valaris - battle_config.drops_by_luk = 0; // [Valaris] - battle_config.pk_mode = 0; // [Valaris] - battle_config.multi_level_up = 0; // [Valaris] - battle_config.hack_info_GM_level = 60; // added by [Yor] (default: 60, GM level) - battle_config.any_warp_GM_min_level = 20; // added by [Yor] - battle_config.min_hair_style = 0; - battle_config.max_hair_style = 20; - battle_config.min_hair_color = 0; - battle_config.max_hair_color = 9; - battle_config.min_cloth_color = 0; - battle_config.max_cloth_color = 4; - - battle_config.castrate_dex_scale = 150; - - battle_config.area_size = 14; - - battle_config.chat_lame_penalty = 2; - battle_config.chat_spam_threshold = 10; - battle_config.chat_spam_flood = 10; - battle_config.chat_spam_ban = 1; - battle_config.chat_spam_warn = 8; - battle_config.chat_maxline = 255; - - battle_config.packet_spam_threshold = 2; - battle_config.packet_spam_flood = 30; - battle_config.packet_spam_kick = 1; - - battle_config.mask_ip_gms = 1; - - battle_config.mob_splash_radius = -1; - } - return battle_config; -} - -bool battle_config_read(ZString cfgName) -{ - bool rv = true; - io::ReadFile in(cfgName); - if (!in.is_open()) - { - PRINTF("file not found: %s\n"_fmt, cfgName); - return false; - } - - AString line; - while (in.getline(line)) - { -#define BATTLE_CONFIG_VAR(name) {#name##_s, &battle_config.name} - const struct - { - LString str; - int *val; - } data[] = - { - BATTLE_CONFIG_VAR(warp_point_debug), - BATTLE_CONFIG_VAR(enemy_critical), - BATTLE_CONFIG_VAR(enemy_critical_rate), - BATTLE_CONFIG_VAR(enemy_str), - BATTLE_CONFIG_VAR(enemy_perfect_flee), - BATTLE_CONFIG_VAR(casting_rate), - BATTLE_CONFIG_VAR(delay_rate), - BATTLE_CONFIG_VAR(delay_dependon_dex), - BATTLE_CONFIG_VAR(skill_delay_attack_enable), - BATTLE_CONFIG_VAR(monster_skill_add_range), - BATTLE_CONFIG_VAR(player_damage_delay), - BATTLE_CONFIG_VAR(flooritem_lifetime), - BATTLE_CONFIG_VAR(item_auto_get), - BATTLE_CONFIG_VAR(drop_pickup_safety_zone), - BATTLE_CONFIG_VAR(item_first_get_time), - BATTLE_CONFIG_VAR(item_second_get_time), - BATTLE_CONFIG_VAR(item_third_get_time), - BATTLE_CONFIG_VAR(base_exp_rate), - BATTLE_CONFIG_VAR(job_exp_rate), - BATTLE_CONFIG_VAR(death_penalty_type), - BATTLE_CONFIG_VAR(death_penalty_base), - BATTLE_CONFIG_VAR(death_penalty_job), - BATTLE_CONFIG_VAR(restart_hp_rate), - BATTLE_CONFIG_VAR(restart_sp_rate), - BATTLE_CONFIG_VAR(monster_hp_rate), - BATTLE_CONFIG_VAR(monster_max_aspd), - BATTLE_CONFIG_VAR(atcommand_gm_only), - BATTLE_CONFIG_VAR(atcommand_spawn_quantity_limit), - BATTLE_CONFIG_VAR(gm_all_equipment), - BATTLE_CONFIG_VAR(monster_active_enable), - BATTLE_CONFIG_VAR(mob_skill_use), - BATTLE_CONFIG_VAR(mob_count_rate), - BATTLE_CONFIG_VAR(basic_skill_check), - BATTLE_CONFIG_VAR(player_invincible_time), - BATTLE_CONFIG_VAR(skill_min_damage), - BATTLE_CONFIG_VAR(natural_healhp_interval), - BATTLE_CONFIG_VAR(natural_healsp_interval), - BATTLE_CONFIG_VAR(natural_heal_skill_interval), - BATTLE_CONFIG_VAR(natural_heal_weight_rate), - BATTLE_CONFIG_VAR(itemheal_regeneration_factor), - BATTLE_CONFIG_VAR(arrow_decrement), - BATTLE_CONFIG_VAR(max_aspd), - BATTLE_CONFIG_VAR(max_hp), - BATTLE_CONFIG_VAR(max_sp), - BATTLE_CONFIG_VAR(max_lv), - BATTLE_CONFIG_VAR(max_parameter), - BATTLE_CONFIG_VAR(monster_skill_log), - BATTLE_CONFIG_VAR(battle_log), - BATTLE_CONFIG_VAR(save_log), - BATTLE_CONFIG_VAR(error_log), - BATTLE_CONFIG_VAR(etc_log), - BATTLE_CONFIG_VAR(save_clothcolor), - BATTLE_CONFIG_VAR(undead_detect_type), - BATTLE_CONFIG_VAR(agi_penaly_type), - BATTLE_CONFIG_VAR(agi_penaly_count), - BATTLE_CONFIG_VAR(agi_penaly_num), - BATTLE_CONFIG_VAR(agi_penaly_count_lv), - BATTLE_CONFIG_VAR(vit_penaly_type), - BATTLE_CONFIG_VAR(vit_penaly_count), - BATTLE_CONFIG_VAR(vit_penaly_num), - BATTLE_CONFIG_VAR(vit_penaly_count_lv), - BATTLE_CONFIG_VAR(mob_changetarget_byskill), - BATTLE_CONFIG_VAR(player_attack_direction_change), - BATTLE_CONFIG_VAR(monster_attack_direction_change), - BATTLE_CONFIG_VAR(display_delay_skill_fail), - BATTLE_CONFIG_VAR(dead_branch_active), - BATTLE_CONFIG_VAR(show_steal_in_same_party), - BATTLE_CONFIG_VAR(hide_GM_session), - BATTLE_CONFIG_VAR(invite_request_check), - BATTLE_CONFIG_VAR(disp_experience), - BATTLE_CONFIG_VAR(prevent_logout), // Added by RoVeRT - BATTLE_CONFIG_VAR(alchemist_summon_reward), // [Valaris] - BATTLE_CONFIG_VAR(maximum_level), // [Valaris] - BATTLE_CONFIG_VAR(drops_by_luk), // [Valaris] - BATTLE_CONFIG_VAR(monsters_ignore_gm), // [Valaris] - BATTLE_CONFIG_VAR(pk_mode), // [Valaris] - BATTLE_CONFIG_VAR(multi_level_up), // [Valaris] - BATTLE_CONFIG_VAR(hack_info_GM_level), // added by [Yor] - BATTLE_CONFIG_VAR(any_warp_GM_min_level), // added by [Yor] - BATTLE_CONFIG_VAR(min_hair_style), // added by [MouseJstr] - BATTLE_CONFIG_VAR(max_hair_style), // added by [MouseJstr] - BATTLE_CONFIG_VAR(min_hair_color), // added by [MouseJstr] - BATTLE_CONFIG_VAR(max_hair_color), // added by [MouseJstr] - BATTLE_CONFIG_VAR(min_cloth_color), // added by [MouseJstr] - BATTLE_CONFIG_VAR(max_cloth_color), // added by [MouseJstr] - BATTLE_CONFIG_VAR(castrate_dex_scale), // added by [MouseJstr] - BATTLE_CONFIG_VAR(area_size), // added by [MouseJstr] - BATTLE_CONFIG_VAR(chat_lame_penalty), - BATTLE_CONFIG_VAR(chat_spam_threshold), - BATTLE_CONFIG_VAR(chat_spam_flood), - BATTLE_CONFIG_VAR(chat_spam_ban), - BATTLE_CONFIG_VAR(chat_spam_warn), - BATTLE_CONFIG_VAR(chat_maxline), - BATTLE_CONFIG_VAR(packet_spam_threshold), - BATTLE_CONFIG_VAR(packet_spam_flood), - BATTLE_CONFIG_VAR(packet_spam_kick), - BATTLE_CONFIG_VAR(mask_ip_gms), - BATTLE_CONFIG_VAR(mob_splash_radius), - }; - - if (is_comment(line)) - continue; - XString w1; - ZString w2; - if (!config_split(line, &w1, &w2)) - { - PRINTF("Bad config line: %s\n"_fmt, line); - rv = false; - continue; - } - - if (w1 == "import"_s) - { - battle_config_read(w2); - continue; - } - - for (auto datum : data) - if (w1 == datum.str) - { - *datum.val = config_switch(w2); - goto continue_outer; - } - - PRINTF("WARNING: unknown battle conf key: %s\n"_fmt, AString(w1)); - rv = false; - - continue_outer: - ; - } - - return rv; -} - -void battle_config_check() -{ - { - if (static_cast<interval_t>(battle_config.flooritem_lifetime) < 1_s) - battle_config.flooritem_lifetime = std::chrono::duration_cast<std::chrono::milliseconds>(LIFETIME_FLOORITEM).count(); - if (battle_config.restart_hp_rate < 0) - battle_config.restart_hp_rate = 0; - else if (battle_config.restart_hp_rate > 100) - battle_config.restart_hp_rate = 100; - if (battle_config.restart_sp_rate < 0) - battle_config.restart_sp_rate = 0; - else if (battle_config.restart_sp_rate > 100) - battle_config.restart_sp_rate = 100; - if (battle_config.natural_healhp_interval < NATURAL_HEAL_INTERVAL.count()) - battle_config.natural_healhp_interval = NATURAL_HEAL_INTERVAL.count(); - if (battle_config.natural_healsp_interval < NATURAL_HEAL_INTERVAL.count()) - battle_config.natural_healsp_interval = NATURAL_HEAL_INTERVAL.count(); - if (battle_config.natural_heal_skill_interval < NATURAL_HEAL_INTERVAL.count()) - battle_config.natural_heal_skill_interval = NATURAL_HEAL_INTERVAL.count(); - if (battle_config.natural_heal_weight_rate < 50) - battle_config.natural_heal_weight_rate = 50; - if (battle_config.natural_heal_weight_rate > 101) - battle_config.natural_heal_weight_rate = 101; - battle_config.monster_max_aspd = - 2000 - battle_config.monster_max_aspd * 10; - if (battle_config.monster_max_aspd < 10) - battle_config.monster_max_aspd = 10; - if (battle_config.monster_max_aspd > 1000) - battle_config.monster_max_aspd = 1000; - battle_config.max_aspd = 2000 - battle_config.max_aspd * 10; - if (battle_config.max_aspd < 10) - battle_config.max_aspd = 10; - if (battle_config.max_aspd > 1000) - battle_config.max_aspd = 1000; - if (battle_config.max_hp > 1000000) - battle_config.max_hp = 1000000; - if (battle_config.max_hp < 100) - battle_config.max_hp = 100; - if (battle_config.max_sp > 1000000) - battle_config.max_sp = 1000000; - if (battle_config.max_sp < 100) - battle_config.max_sp = 100; - if (battle_config.max_parameter < 10) - battle_config.max_parameter = 10; - if (battle_config.max_parameter > 10000) - battle_config.max_parameter = 10000; - - if (battle_config.agi_penaly_count < 2) - battle_config.agi_penaly_count = 2; - if (battle_config.vit_penaly_count < 2) - battle_config.vit_penaly_count = 2; - - if (battle_config.hack_info_GM_level < 0) // added by [Yor] - battle_config.hack_info_GM_level = 0; - else if (battle_config.hack_info_GM_level > 100) - battle_config.hack_info_GM_level = 100; - - if (battle_config.any_warp_GM_min_level < 0) // added by [Yor] - battle_config.any_warp_GM_min_level = 0; - else if (battle_config.any_warp_GM_min_level > 100) - battle_config.any_warp_GM_min_level = 100; - - if (battle_config.chat_spam_ban < 0) - battle_config.chat_spam_ban = 0; - else if (battle_config.chat_spam_ban > 32767) - battle_config.chat_spam_ban = 32767; - - if (battle_config.chat_spam_flood < 0) - battle_config.chat_spam_flood = 0; - else if (battle_config.chat_spam_flood > 32767) - battle_config.chat_spam_flood = 32767; - - if (battle_config.chat_spam_warn < 0) - battle_config.chat_spam_warn = 0; - else if (battle_config.chat_spam_warn > 32767) - battle_config.chat_spam_warn = 32767; - - if (battle_config.chat_spam_threshold < 0) - battle_config.chat_spam_threshold = 0; - else if (battle_config.chat_spam_threshold > 32767) - battle_config.chat_spam_threshold = 32767; - - if (battle_config.chat_maxline < 1) - battle_config.chat_maxline = 1; - else if (battle_config.chat_maxline > 512) - battle_config.chat_maxline = 512; - - if (battle_config.packet_spam_threshold < 0) - battle_config.packet_spam_threshold = 0; - else if (battle_config.packet_spam_threshold > 32767) - battle_config.packet_spam_threshold = 32767; - - if (battle_config.packet_spam_flood < 0) - battle_config.packet_spam_flood = 0; - else if (battle_config.packet_spam_flood > 32767) - battle_config.packet_spam_flood = 32767; - - if (battle_config.packet_spam_kick < 0) - battle_config.packet_spam_kick = 0; - else if (battle_config.packet_spam_kick > 1) - battle_config.packet_spam_kick = 1; - - if (battle_config.mask_ip_gms < 0) - battle_config.mask_ip_gms = 0; - else if (battle_config.mask_ip_gms > 1) - battle_config.mask_ip_gms = 1; - } -} +} // namespace map } // namespace tmwa diff --git a/src/map/battle.hpp b/src/map/battle.hpp index 97a4a86..1a13420 100644 --- a/src/map/battle.hpp +++ b/src/map/battle.hpp @@ -20,27 +20,25 @@ // 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 "battle.t.hpp" -#include "../strings/fwd.hpp" - -#include "../generic/fwd.hpp" +#include "fwd.hpp" #include "../net/timer.t.hpp" -#include "clif.t.hpp" +#include "../mmo/clif.t.hpp" #include "map.t.hpp" -#include "skill.t.hpp" +#include "../mmo/skill.t.hpp" namespace tmwa { +namespace map +{ // ダメージ struct Damage { - int damage, damage2; + int damage; DamageType type; int div_; interval_t amotion, dmotion; @@ -62,7 +60,6 @@ int battle_heal(dumb_ptr<block_list> bl, dumb_ptr<block_list> target, int hp, // 攻撃や移動を止める int battle_stopattack(dumb_ptr<block_list> bl); -int battle_stopwalking(dumb_ptr<block_list> bl, int type); // 通常攻撃処理まとめ ATK battle_weapon_attack(dumb_ptr<block_list> bl, dumb_ptr<block_list> target, @@ -101,128 +98,15 @@ MobMode battle_get_mode(dumb_ptr<block_list> bl); int battle_get_stat(SP stat_id, dumb_ptr<block_list> bl); eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> battle_get_sc_data(dumb_ptr<block_list> bl); -short *battle_get_sc_count(dumb_ptr<block_list> bl); Opt1 *battle_get_opt1(dumb_ptr<block_list> bl); Opt2 *battle_get_opt2(dumb_ptr<block_list> bl); Opt3 *battle_get_opt3(dumb_ptr<block_list> bl); -Option *battle_get_option(dumb_ptr<block_list> bl); +Opt0 *battle_get_option(dumb_ptr<block_list> bl); bool battle_check_undead(Race race, Element element); int battle_check_target(dumb_ptr<block_list> src, dumb_ptr<block_list> target, BCT flag); int battle_check_range(dumb_ptr<block_list> src, dumb_ptr<block_list> bl, int range); - -extern struct Battle_Config -{ - int warp_point_debug; - int enemy_critical; - int enemy_critical_rate; - int enemy_str; - int enemy_perfect_flee; - int casting_rate, delay_rate, delay_dependon_dex; - int skill_delay_attack_enable; - int monster_skill_add_range; - int player_damage_delay; - int flooritem_lifetime; - int item_auto_get; - int item_first_get_time; - int item_second_get_time; - int item_third_get_time; - int base_exp_rate, job_exp_rate; - int death_penalty_type; - int death_penalty_base, death_penalty_job; - int restart_hp_rate; - int restart_sp_rate; - int monster_hp_rate; - int monster_max_aspd; - int atcommand_gm_only; - int atcommand_spawn_quantity_limit; - int gm_all_equipment; - int monster_active_enable; - int mob_skill_use; - int mob_count_rate; - int basic_skill_check; - int player_invincible_time; - int skill_min_damage; - int natural_healhp_interval; - int natural_healsp_interval; - int natural_heal_skill_interval; - int natural_heal_weight_rate; - int arrow_decrement; - int max_aspd; - int max_hp; - int max_sp; - int max_lv; - int max_parameter; - int monster_skill_log; - int battle_log; - int save_log; - int error_log; - int etc_log; - int save_clothcolor; - int undead_detect_type; - int agi_penaly_type; - int agi_penaly_count; - int agi_penaly_num; - int vit_penaly_type; - int vit_penaly_count; - int vit_penaly_num; - int mob_changetarget_byskill; - int player_attack_direction_change; - int monster_attack_direction_change; - int display_delay_skill_fail; - int dead_branch_active; - int show_steal_in_same_party; - - int prevent_logout; - - int alchemist_summon_reward; - int maximum_level; - int drops_by_luk; - int monsters_ignore_gm; - int multi_level_up; - int pk_mode; - - int agi_penaly_count_lv; - int vit_penaly_count_lv; - - int hide_GM_session; - int invite_request_check; - int disp_experience; - - int hack_info_GM_level; - int any_warp_GM_min_level; - - int min_hair_style; - int max_hair_style; - int min_hair_color; - int max_hair_color; - int min_cloth_color; - int max_cloth_color; - - int castrate_dex_scale; - int area_size; - - int chat_lame_penalty; - int chat_spam_threshold; - int chat_spam_flood; - int chat_spam_ban; - int chat_spam_warn; - int chat_maxline; - - int packet_spam_threshold; - int packet_spam_flood; - int packet_spam_kick; - - int mask_ip_gms; - - int drop_pickup_safety_zone; - int itemheal_regeneration_factor; - - int mob_splash_radius; -} battle_config; - -bool battle_config_read(ZString cfgName); -void battle_config_check(); +} // namespace map } // namespace tmwa diff --git a/src/map/battle.t.hpp b/src/map/battle.t.hpp index 53c34ff..4759b68 100644 --- a/src/map/battle.t.hpp +++ b/src/map/battle.t.hpp @@ -29,6 +29,8 @@ namespace tmwa { +namespace map +{ namespace e { enum class BF : uint16_t @@ -241,4 +243,5 @@ earray<Races, Race, Race::COUNT> race_shift //= Races::boss, Races::other, }}; +} // namespace map } // namespace tmwa diff --git a/src/map/chrif.cpp b/src/map/chrif.cpp index 0748f43..2606911 100644 --- a/src/map/chrif.cpp +++ b/src/map/chrif.cpp @@ -29,21 +29,25 @@ #include "../io/cxxstdio.hpp" #include "../net/ip.hpp" -#include "../net/packets.hpp" #include "../net/socket.hpp" #include "../net/timer.hpp" +#include "../net/timestamp-utils.hpp" #include "../proto2/char-map.hpp" #include "../mmo/human_time_diff.hpp" -#include "../mmo/mmo.hpp" -#include "../mmo/utils.hpp" +#include "../high/mmo.hpp" + +#include "../wire/packets.hpp" #include "battle.hpp" +#include "battle_conf.hpp" #include "clif.hpp" +#include "globals.hpp" #include "intif.hpp" #include "itemdb.hpp" #include "map.hpp" +#include "map_conf.hpp" #include "npc.hpp" #include "pc.hpp" #include "storage.hpp" @@ -53,60 +57,8 @@ namespace tmwa { -Session *char_session; -static -IP4Address char_ip; -static -int char_port = 6121; -static -AccountName userid; -static -AccountPass passwd; -static -int chrif_state; - -// 設定ファイル読み込み関係 -/*========================================== - * - *------------------------------------------ - */ -void chrif_setuserid(AccountName id) -{ - userid = id; -} - -/*========================================== - * - *------------------------------------------ - */ -void chrif_setpasswd(AccountPass pwd) -{ - passwd = pwd; -} - -AccountPass chrif_getpasswd(void) -{ - return passwd; -} - -/*========================================== - * - *------------------------------------------ - */ -void chrif_setip(IP4Address ip) +namespace map { - char_ip = ip; -} - -/*========================================== - * - *------------------------------------------ - */ -void chrif_setport(int port) -{ - char_port = port; -} - /*========================================== * *------------------------------------------ @@ -151,11 +103,11 @@ static int chrif_connect(Session *s) { Packet_Fixed<0x2af8> fixed_f8; - fixed_f8.account_name = userid; - fixed_f8.account_pass = passwd; + fixed_f8.account_name = map_conf.userid; + fixed_f8.account_pass = map_conf.passwd; fixed_f8.unused = 0; - fixed_f8.ip = clif_getip(); - fixed_f8.port = clif_getport(); + fixed_f8.ip = map_conf.map_ip; + fixed_f8.port = map_conf.map_port; send_fpacket<0x2af8, 60>(s, fixed_f8); return 0; @@ -266,7 +218,7 @@ int chrif_changemapserverack(Session *, const Packet_Fixed<0x2b06>& fixed) if (fixed.error == 1) { if (battle_config.error_log) - PRINTF("map server change failed.\n"_fmt); + PRINTF("Changing the map server failed.\n"_fmt); pc_authfail(sd->status_key.account_id); return 0; } @@ -289,7 +241,7 @@ int chrif_connectack(Session *s, const Packet_Fixed<0x2af9>& fixed) { if (fixed.code) { - PRINTF("Connected to char-server failed %d.\n"_fmt, fixed.code); + PRINTF("Connecting to char-server failed %d.\n"_fmt, fixed.code); exit(1); } PRINTF("Connected to char-server (connection #%d).\n"_fmt, s); @@ -297,11 +249,6 @@ int chrif_connectack(Session *s, const Packet_Fixed<0x2af9>& fixed) chrif_sendmap(s); - PRINTF("chrif: OnCharIfInit event done. (%d events)\n"_fmt, - npc_event_doall(stringish<ScriptLabel>("OnCharIfInit"_s))); - PRINTF("chrif: OnInterIfInit event done. (%d events)\n"_fmt, - npc_event_doall(stringish<ScriptLabel>("OnInterIfInit"_s))); - return 0; } @@ -314,13 +261,11 @@ int chrif_sendmapack(Session *, Packet_Fixed<0x2afb> fixed) { if (fixed.unknown) //impossible { - PRINTF("chrif : send map list to char server failed %d\n"_fmt, + PRINTF("chrif: sending the map list to char-server failed %d\n"_fmt, fixed.unknown); exit(1); } - wisp_server_name = fixed.whisper_name; - chrif_state = 2; return 0; @@ -395,23 +340,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 *------------------------------------------ */ @@ -455,7 +383,7 @@ void chrif_char_ask_name(AccountId id, CharName character_name, short operation_ fixed_0e.operation = operation_type; // type of operation if (operation_type == 2) fixed_0e.ban_add = modif; - PRINTF("chrif : sended 0x2b0e\n"_fmt); + PRINTF("chrif: sent 0x2b0e\n"_fmt); send_fpacket<0x2b0e, 44>(char_session, fixed_0e); } @@ -469,7 +397,7 @@ void chrif_char_ask_name(AccountId id, CharName character_name, short operation_ * 4: unban * 5: changesex * type of answer: - * 0: login-server resquest done + * 0: login-server request done * 1: player not found * 2: gm level too low * 3: login-server offline @@ -486,7 +414,7 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed) { AString output; if (fixed.error == 1) // player not found - output = STRPRINTF("The player '%s' doesn't exist."_fmt, + output = STRPRINTF("The player, '%s,' doesn't exist."_fmt, player_name); else { @@ -495,20 +423,20 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed) case 1: // block switch (fixed.error) { - case 0: // login-server resquest done + case 0: // login-server request done output = STRPRINTF( - "Login-server has been asked to block the player '%s'."_fmt, + "Login-server has been asked to block '%s'."_fmt, player_name); break; //case 1: // player not found case 2: // gm level too low output = STRPRINTF( - "Your GM level don't authorise you to block the player '%s'."_fmt, + "Your GM level doesn't authorize you to block the player '%s'."_fmt, player_name); break; case 3: // login-server offline output = STRPRINTF( - "Login-server is offline. Impossible to block the the player '%s'."_fmt, + "Login-server is offline, so it's impossible to block '%s'."_fmt, player_name); break; } @@ -516,20 +444,20 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed) case 2: // ban switch (fixed.error) { - case 0: // login-server resquest done + case 0: // login-server request done output = STRPRINTF( - "Login-server has been asked to ban the player '%s'."_fmt, + "Login-server has been asked to ban '%s'."_fmt, player_name); break; //case 1: // player not found case 2: // gm level too low output = STRPRINTF( - "Your GM level don't authorise you to ban the player '%s'."_fmt, + "Your GM level doesn't authorize you to ban '%s'."_fmt, player_name); break; case 3: // login-server offline output = STRPRINTF( - "Login-server is offline. Impossible to ban the the player '%s'."_fmt, + "Login-server is offline, so it's impossible to ban '%s'."_fmt, player_name); break; } @@ -537,20 +465,20 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed) case 3: // unblock switch (fixed.error) { - case 0: // login-server resquest done + case 0: // login-server request done output = STRPRINTF( - "Login-server has been asked to unblock the player '%s'."_fmt, + "Login-server has been asked to unblock '%s'."_fmt, player_name); break; //case 1: // player not found case 2: // gm level too low output = STRPRINTF( - "Your GM level don't authorise you to unblock the player '%s'."_fmt, + "Your GM level doesn't authorize you to unblock '%s'."_fmt, player_name); break; case 3: // login-server offline output = STRPRINTF( - "Login-server is offline. Impossible to unblock the the player '%s'."_fmt, + "Login-server is offline, so it's impossible to unblock '%s'."_fmt, player_name); break; } @@ -558,20 +486,20 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed) case 4: // unban switch (fixed.error) { - case 0: // login-server resquest done + case 0: // login-server request done output = STRPRINTF( - "Login-server has been asked to unban the player '%s'."_fmt, + "Login-server has been asked to unban '%s'."_fmt, player_name); break; //case 1: // player not found case 2: // gm level too low output = STRPRINTF( - "Your GM level don't authorise you to unban the player '%s'."_fmt, + "Your GM level doesn't authorize you to unban '%s'."_fmt, player_name); break; case 3: // login-server offline output = STRPRINTF( - "Login-server is offline. Impossible to unban the the player '%s'."_fmt, + "Login-server is offline, so it's impossible to unban '%s'."_fmt, player_name); break; } @@ -579,20 +507,20 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed) case 5: // changesex switch (fixed.error) { - case 0: // login-server resquest done + case 0: // login-server request done output = STRPRINTF( - "Login-server has been asked to change the sex of the player '%s'."_fmt, + "Login-server has been asked to change the sex of '%s'."_fmt, player_name); break; //case 1: // player not found case 2: // gm level too low output = STRPRINTF( - "Your GM level don't authorise you to change the sex of the player '%s'."_fmt, + "Your GM level doesn't authorize you to change the sex of '%s'."_fmt, player_name); break; case 3: // login-server offline output = STRPRINTF( - "Login-server is offline. Impossible to change the sex of the the player '%s'."_fmt, + "Login-server is offline, so it's impossible to change the sex of '%s'."_fmt, player_name); break; } @@ -603,36 +531,12 @@ int chrif_char_ask_name_answer(Session *, const Packet_Fixed<0x2b0f>& fixed) clif_displaymessage(sd->sess, output); } else - PRINTF("chrif_char_ask_name_answer failed - player not online.\n"_fmt); + PRINTF("chrif_char_ask_name_answer failed because the player is not online.\n"_fmt); return 0; } /*========================================== - * 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) *------------------------------------------ */ @@ -659,15 +563,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 @@ -750,18 +653,13 @@ int chrif_divorce(CharId char_id, CharId partner_id) if (sd && sd->status.partner_id == partner_id) { sd->status.partner_id = CharId(); - - if (sd->npc_flags.divorce) - { - sd->npc_flags.divorce = 0; - map_scriptcont(sd, sd->npc_id); - } } sd = map_nick2sd(map_charid2nick(partner_id)); - nullpo_retz(sd); - if (sd->status.partner_id == char_id) + if (sd && sd->status.partner_id == char_id) + { sd->status.partner_id = CharId(); + } return 0; } @@ -801,14 +699,14 @@ int chrif_accountdeletion(Session *, const Packet_Fixed<0x2b13>& fixed) { sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters clif_displaymessage(sd->sess, - "Your account has been deleted (disconnection)..."_s); + "Your account has been deleted. You will now be disconnected..."_s); clif_setwaitclose(sd->sess); // forced to disconnect for the change } } else { if (sd != nullptr) - PRINTF("chrif_accountdeletion failed - player not online.\n"_fmt); + PRINTF("chrif_accountdeletion failed because the player is not online.\n"_fmt); } return 0; @@ -838,11 +736,11 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed) { // status or final date of a banishment case 1: // 0 = Unregistered ID clif_displaymessage(sd->sess, - "Your account has 'Unregistered'."_s); + "Your account has an unregistered ID."_s); break; case 2: // 1 = Incorrect Password clif_displaymessage(sd->sess, - "Your account has an 'Incorrect Password'..."_s); + "Your password is incorrect."_s); break; case 3: // 2 = This ID is expired clif_displaymessage(sd->sess, @@ -850,7 +748,7 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed) break; case 4: // 3 = Rejected from Server clif_displaymessage(sd->sess, - "Your account has been rejected from server."_s); + "Your account has been rejected by the server."_s); break; case 5: // 4 = You have been blocked by the GM Team clif_displaymessage(sd->sess, @@ -858,19 +756,19 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed) break; case 6: // 5 = Your Game's EXE file is not the latest version clif_displaymessage(sd->sess, - "Your Game's EXE file is not the latest version."_s); + "You need to update your client."_s); break; case 7: // 6 = Your are Prohibited to log in until %s clif_displaymessage(sd->sess, - "Your account has been prohibited to log in."_s); + "Your account has been prohibited from logging in."_s); break; case 8: // 7 = Server is jammed due to over populated clif_displaymessage(sd->sess, - "Server is jammed due to over populated."_s); + "The server is overpopulated."_s); break; case 9: // 8 = No MSG (actually, all states after 9 except 99 are No MSG, use only this) clif_displaymessage(sd->sess, - "Your account has not more authorised."_s); + "Your account must be authorized."_s); break; case 100: // 99 = This ID has been totally erased clif_displaymessage(sd->sess, @@ -878,7 +776,7 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed) break; default: clif_displaymessage(sd->sess, - "Your account has not more authorised."_s); + "Your account must be authorized."_s); break; } } @@ -897,7 +795,7 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed) else { if (sd != nullptr) - PRINTF("chrif_accountban failed - player not online.\n"_fmt); + PRINTF("chrif_accountban failed because the player is not online.\n"_fmt); } return 0; @@ -910,137 +808,17 @@ int chrif_accountban(Session *, const Packet_Fixed<0x2b14>& fixed) static int chrif_recvgmaccounts(Session *s, const std::vector<Packet_Repeat<0x2b15>>& repeat) { - PRINTF("From login-server: receiving of %d GM accounts information.\n"_fmt, + PRINTF("Receiving information on %d GM accounts from login-server.\n"_fmt, pc_read_gm_account(s, repeat)); return 0; } -/*========================================== - * Request to reload GM accounts and their levels: send to char-server by [Yor] - *------------------------------------------ - */ -int chrif_reloadGMdb(void) -{ - if (!char_session) - return -1; - - Packet_Fixed<0x2af7> fixed_f7; - send_fpacket<0x2af7, 2>(char_session, fixed_f7); - - return 0; -} - -/*======================================== - * Map item IDs - *---------------------------------------- - */ - -static -void ladmin_itemfrob_fix_item(ItemNameId source, ItemNameId dest, Item *item) -{ - if (item && item->nameid == source) - { - item->nameid = dest; - item->equip = EPOS::ZERO; - } -} - -static -void ladmin_itemfrob_c2(dumb_ptr<block_list> bl, ItemNameId source_id, ItemNameId dest_id) -{ -#define IFIX(v) if (v == source_id) {v = dest_id; } -#define FIX(item) ladmin_itemfrob_fix_item(source_id, dest_id, &item) - - if (!bl) - return; - - switch (bl->bl_type) - { - case BL::PC: - { - dumb_ptr<map_session_data> pc = bl->is_player(); - Storage *stor = account2storage2(pc->status_key.account_id); - - for (IOff0 j : IOff0::iter()) - IFIX(pc->status.inventory[j].nameid); - // cart is no longer supported - // IFIX(pc->status.weapon); - IFIX(pc->status.shield); - IFIX(pc->status.head_top); - IFIX(pc->status.head_mid); - IFIX(pc->status.head_bottom); - - if (stor) - { - for (SOff0 j : SOff0::iter()) - FIX(stor->storage_[j]); - } - - for (IOff0 j : IOff0::iter()) - { - struct item_data *item = pc->inventory_data[j]; - if (item && item->nameid == source_id) - { - item->nameid = dest_id; - if (bool(item->equip)) - pc_unequipitem(pc, j, CalcStatus::NOW); - item->nameid = dest_id; - } - } - - break; - } - - case BL::MOB: - { - dumb_ptr<mob_data> mob = bl->is_mob(); - for (Item& itm : mob->lootitemv) - FIX(itm); - break; - } - - case BL::ITEM: - { - dumb_ptr<flooritem_data> item = bl->is_item(); - FIX(item->item_data); - break; - } - } -#undef FIX -#undef IFIX -} - -static -void ladmin_itemfrob_c(dumb_ptr<block_list> bl, ItemNameId source_id, ItemNameId dest_id) -{ - ladmin_itemfrob_c2(bl, source_id, dest_id); -} - -static -void ladmin_itemfrob(Session *, const Packet_Fixed<0x2afa>& fixed) -{ - ItemNameId source_id = fixed.source_item_id; - ItemNameId dest_id = fixed.dest_item_id; - dumb_ptr<block_list> bl = map_get_first_session(); - - // flooritems - map_foreachobject(std::bind(ladmin_itemfrob_c, ph::_1, source_id, dest_id), - BL::NUL /* any object */); - - // player characters (and, hopefully, mobs) - while (bl->bl_next) - { - ladmin_itemfrob_c2(bl, source_id, dest_id); - bl = bl->bl_next; - } -} - static void chrif_delete(Session *s) { assert (s == char_session); - PRINTF("Map-server can't connect to char-server (connection #%d).\n"_fmt, + PRINTF("map-server can't connect to char-server (connection #%d).\n"_fmt, s); char_session = nullptr; } @@ -1070,16 +848,6 @@ void chrif_parse(Session *s) chrif_connectack(s, fixed); break; } - case 0x2afa: - { - Packet_Fixed<0x2afa> fixed; - rv = recv_fpacket<0x2afa, 10>(s, fixed); - if (rv != RecvResult::Complete) - break; - - ladmin_itemfrob(s, fixed); - break; - } case 0x2afb: { Packet_Fixed<0x2afb> fixed; @@ -1099,12 +867,11 @@ void chrif_parse(Session *s) AccountId id = payload.account_id; int login_id2 = payload.login_id2; - TimeT connect_until_time = payload.connect_until; short tmw_version = payload.packet_tmw_version; CharKey st_key = payload.char_key; CharData st_data = payload.char_data; pc_authok(id, login_id2, - connect_until_time, tmw_version, + tmw_version, &st_key, &st_data); break; } @@ -1159,16 +926,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; @@ -1250,7 +1007,7 @@ void chrif_parse(Session *s) return; if (battle_config.error_log) - PRINTF("chrif_parse : unknown packet %d %d\n"_fmt, s, + PRINTF("chrif_parse: unknown packet %d %d\n"_fmt, s, packet_id); s->set_eof(); return; @@ -1283,7 +1040,7 @@ void send_users_tochar(TimerData *, tick_t) if (sd && sd->state.auth && !((battle_config.hide_GM_session || sd->state.shroud_active - || bool(sd->status.option & Option::HIDE)) && pc_isGM(sd))) + || bool(sd->status.option & Opt0::HIDE)) && pc_isGM(sd))) { Packet_Repeat<0x2aff> info; info.char_id = sd->status_key.char_id; @@ -1304,9 +1061,9 @@ void check_connect_char_server(TimerData *, tick_t) { if (!char_session) { - PRINTF("Attempt to connect to char-server...\n"_fmt); + PRINTF("Attempting to connect to char-server...\n"_fmt); chrif_state = 0; - char_session = make_connection(char_ip, char_port, + char_session = make_connection(map_conf.char_ip, map_conf.char_port, SessionParsers{.func_parse= chrif_parse, .func_delete= chrif_delete}); if (!char_session) return; @@ -1331,4 +1088,5 @@ void do_init_chrif(void) 5_s ).detach(); } +} // namespace map } // namespace tmwa diff --git a/src/map/chrif.hpp b/src/map/chrif.hpp index 4711bc5..655103d 100644 --- a/src/map/chrif.hpp +++ b/src/map/chrif.hpp @@ -22,24 +22,11 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" - -#include "../generic/fwd.hpp" - -#include "../net/fwd.hpp" - -#include "../mmo/fwd.hpp" - namespace tmwa { -void chrif_setuserid(AccountName); -void chrif_setpasswd(AccountPass); -AccountPass chrif_getpasswd(void); - -void chrif_setip(IP4Address); -void chrif_setport(int); - +namespace map +{ int chrif_isconnect(void); int chrif_authreq(dumb_ptr<map_session_data>); @@ -55,12 +42,8 @@ void chrif_changeemail(AccountId id, AccountEmail actual_email, AccountEmail new void chrif_char_ask_name(AccountId id, CharName character_name, short operation_type, HumanTimeDiff modif); int chrif_saveaccountreg2(dumb_ptr<map_session_data> sd); -int chrif_reloadGMdb(void); int chrif_send_divorce(CharId char_id); void do_init_chrif(void); - -// only used by intif.cpp -// and clif.cpp for the new on_delete stuff ... -extern Session *char_session; +} // namespace map } // namespace tmwa diff --git a/src/map/clif.cpp b/src/map/clif.cpp index b47bf28..243ffaf 100644 --- a/src/map/clif.cpp +++ b/src/map/clif.cpp @@ -36,30 +36,36 @@ #include "../strings/xstring.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" +#include "../io/extract.hpp" #include "../io/write.hpp" #include "../net/ip.hpp" -#include "../net/packets.hpp" #include "../net/socket.hpp" #include "../net/timer.hpp" +#include "../net/timestamp-utils.hpp" #include "../proto2/any-user.hpp" #include "../proto2/char-map.hpp" #include "../proto2/map-user.hpp" -#include "../mmo/md5more.hpp" -#include "../mmo/utils.hpp" +#include "../mmo/cxxstdio_enums.hpp" #include "../mmo/version.hpp" +#include "../high/md5more.hpp" + +#include "../wire/packets.hpp" + #include "atcommand.hpp" #include "battle.hpp" +#include "battle_conf.hpp" #include "chrif.hpp" +#include "globals.hpp" #include "intif.hpp" #include "itemdb.hpp" #include "magic.hpp" #include "magic-stmt.hpp" #include "map.hpp" +#include "map_conf.hpp" #include "npc.hpp" #include "party.hpp" #include "pc.hpp" @@ -73,6 +79,8 @@ namespace tmwa { +namespace map +{ constexpr int EMOTE_IGNORED = 0x0e; // functions list. Rate is how many milliseconds are required between @@ -117,11 +125,6 @@ enum class SendWho }; static -IP4Address map_ip; -static -int map_port = 5121; - -static int clif_changelook_towards(dumb_ptr<block_list> bl, LOOK type, int val, dumb_ptr<map_session_data> dstsd); static @@ -149,42 +152,6 @@ void clif_delete(Session *s) /*========================================== - * map鯖のip設定 - *------------------------------------------ - */ -void clif_setip(IP4Address ip) -{ - map_ip = ip; -} - -/*========================================== - * map鯖のport設定 - *------------------------------------------ - */ -void clif_setport(int port) -{ - map_port = port; -} - -/*========================================== - * map鯖のip読み出し - *------------------------------------------ - */ -IP4Address clif_getip(void) -{ - return map_ip; -} - -/*========================================== - * map鯖のport読み出し - *------------------------------------------ - */ -int clif_getport(void) -{ - return map_port; -} - -/*========================================== * *------------------------------------------ */ @@ -231,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, @@ -295,7 +257,6 @@ void clif_send_sub(dumb_ptr<block_list> bl, const Buffer& buf, static int clif_send(const Buffer& buf, dumb_ptr<block_list> bl, SendWho type) { - PartyPair p; int x0 = 0, x1 = 0, y0 = 0, y1 = 0; if (type != SendWho::ALL_CLIENT) @@ -305,7 +266,7 @@ int clif_send(const Buffer& buf, dumb_ptr<block_list> bl, SendWho type) if (bl->bl_type == BL::PC) { dumb_ptr<map_session_data> sd2 = bl->is_player(); - if (bool(sd2->status.option & Option::INVISIBILITY)) + if (bool(sd2->status.option & Opt0::INVISIBILITY)) { // Obscure hidden GMs @@ -384,20 +345,22 @@ int clif_send(const Buffer& buf, dumb_ptr<block_list> bl, SendWho type) case SendWho::PARTY_WOS: // 自分以外の全パーティーメンバに送信 case SendWho::PARTY_SAMEMAP: // 同じマップの全パーティーメンバに送信 case SendWho::PARTY_SAMEMAP_WOS: // 自分以外の同じマップの全パーティーメンバに送信 + { + Option<PartyPair> p_ = None; if (bl->bl_type == BL::PC) { dumb_ptr<map_session_data> sd = bl->is_player(); if (sd->partyspy) { - p = party_search(sd->partyspy); + p_ = party_search(sd->partyspy); } else { if (sd->status.party_id) - p = party_search(sd->status.party_id); + p_ = party_search(sd->status.party_id); } } - if (p) + OMATCH_BEGIN_SOME (p, p_) { for (int i = 0; i < MAX_PARTY; i++) { @@ -436,6 +399,8 @@ int clif_send(const Buffer& buf, dumb_ptr<block_list> bl, SendWho type) } } } + OMATCH_END (); + } break; case SendWho::SELF: { @@ -617,37 +582,6 @@ int clif_clearchar(dumb_ptr<block_list> bl, BeingRemoveWhy type) return 0; } -static -void clif_clearchar_delay_sub(TimerData *, tick_t, - dumb_ptr<block_list> bl, BeingRemoveWhy type) -{ - clif_clearchar(bl, type); - MapBlockLock::freeblock(bl); -} - -int clif_clearchar_delay(tick_t tick, - dumb_ptr<block_list> bl, BeingRemoveWhy type) -{ - dumb_ptr<block_list> tmpbl; - tmpbl.new_(); - - // yikes! - tmpbl->bl_next = bl->bl_next; - tmpbl->bl_prev = bl->bl_prev; - tmpbl->bl_id = bl->bl_id; - tmpbl->bl_m = bl->bl_m; - tmpbl->bl_x = bl->bl_x; - tmpbl->bl_y = bl->bl_y; - tmpbl->bl_type = bl->bl_type; - - Timer(tick, - std::bind(clif_clearchar_delay_sub, ph::_1, ph::_2, - tmpbl, type) - ).detach(); - - return 0; -} - /*========================================== * *------------------------------------------ @@ -684,14 +618,14 @@ void clif_set0078_main_1d8(dumb_ptr<map_session_data> sd, Buffer& buf) fixed_1d8.weapon = sd->attack_spell_look_override; else { - if (widx.ok() && sd->inventory_data[widx]) + if (widx.ok() && sd->inventory_data[widx].is_some()) { fixed_1d8.weapon = sd->status.inventory[widx].nameid; } else fixed_1d8.weapon = ItemNameId(); } - if (sidx.ok() && sidx != widx && sd->inventory_data[sidx]) + if (sidx.ok() && sidx != widx && sd->inventory_data[sidx].is_some()) { fixed_1d8.shield = sd->status.inventory[sidx].nameid; } @@ -738,14 +672,14 @@ void clif_set0078_alt_1d9(dumb_ptr<map_session_data> sd, Buffer& buf) fixed_1d8.weapon = sd->attack_spell_look_override; else { - if (widx.ok() && sd->inventory_data[widx]) + if (widx.ok() && sd->inventory_data[widx].is_some()) { fixed_1d8.weapon = sd->status.inventory[widx].nameid; } else fixed_1d8.weapon = ItemNameId(); } - if (sidx.ok() && sidx != widx && sd->inventory_data[sidx]) + if (sidx.ok() && sidx != widx && sd->inventory_data[sidx].is_some()) { fixed_1d8.shield = sd->status.inventory[sidx].nameid; } @@ -791,13 +725,13 @@ void clif_set007b(dumb_ptr<map_session_data> sd, Buffer& buf) fixed_1da.hair_style = sd->status.hair; IOff0 widx = sd->equip_index_maybe[EQUIP::WEAPON]; IOff0 sidx = sd->equip_index_maybe[EQUIP::SHIELD]; - if (widx.ok() && sd->inventory_data[widx]) + if (widx.ok() && sd->inventory_data[widx].is_some()) { fixed_1da.weapon = sd->status.inventory[widx].nameid; } else fixed_1da.weapon = ItemNameId(); - if (sidx.ok() && sidx != widx && sd->inventory_data[sidx]) + if (sidx.ok() && sidx != widx && sd->inventory_data[sidx].is_some()) { fixed_1da.shield = sd->status.inventory[sidx].nameid; } @@ -969,7 +903,7 @@ int clif_spawnnpc(dumb_ptr<npc_data> nd) { nullpo_retz(nd); - if (nd->npc_class == NEGATIVE_SPECIES || nd->flag & 1 || nd->npc_class == INVISIBLE_CLASS) + if (nd->flag & 1 || nd->npc_class == INVISIBLE_CLASS) return 0; Packet_Fixed<0x007c> fixed_7c; @@ -1002,7 +936,7 @@ int clif_spawn_fake_npc_for_player(dumb_ptr<map_session_data> sd, BlockId fake_n fixed_7c.speed = interval_t(); fixed_7c.opt1 = Opt1::ZERO; fixed_7c.opt2 = Opt2::ZERO; - fixed_7c.option = Option::ZERO; + fixed_7c.option = Opt0::ZERO; fixed_7c.species = FAKE_NPC_CLASS; fixed_7c.pos.x = sd->bl_x; fixed_7c.pos.y = sd->bl_y; @@ -1013,7 +947,7 @@ int clif_spawn_fake_npc_for_player(dumb_ptr<map_session_data> sd, BlockId fake_n fixed_78.speed = interval_t(); fixed_78.opt1 = Opt1::ZERO; fixed_78.opt2 = Opt2::ZERO; - fixed_78.option = Option::ZERO; + fixed_78.option = Opt0::ZERO; fixed_78.species = FAKE_NPC_CLASS; fixed_78.unused_head_bottom_or_species_again = unwrap<Species>(FAKE_NPC_CLASS); fixed_78.pos.x = sd->bl_x; @@ -1224,7 +1158,6 @@ int clif_npcbuysell(dumb_ptr<map_session_data> sd, BlockId id) */ int clif_buylist(dumb_ptr<map_session_data> sd, dumb_ptr<npc_data_shop> nd) { - struct item_data *id; int i, val; nullpo_retz(sd); @@ -1234,7 +1167,7 @@ int clif_buylist(dumb_ptr<map_session_data> sd, dumb_ptr<npc_data_shop> nd) std::vector<Packet_Repeat<0x00c6>> repeat_c6(nd->shop_items.size()); for (i = 0; i < nd->shop_items.size(); i++) { - id = itemdb_search(nd->shop_items[i].nameid); + P<struct item_data> id = itemdb_search(nd->shop_items[i].nameid); val = nd->shop_items[i].value; repeat_c6[i].base_price = val; // base price repeat_c6[i].actual_price = val; // actual price @@ -1258,9 +1191,11 @@ int clif_selllist(dumb_ptr<map_session_data> sd) std::vector<Packet_Repeat<0x00c7>> repeat_c7; for (IOff0 i : IOff0::iter()) { - if (sd->status.inventory[i].nameid && sd->inventory_data[i]) + if (!sd->status.inventory[i].nameid) + continue; + OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[i]) { - int val = sd->inventory_data[i]->value_sell; + int val = sdidi->value_sell; if (val < 0) continue; Packet_Repeat<0x00c7> info; @@ -1269,6 +1204,7 @@ int clif_selllist(dumb_ptr<map_session_data> sd) info.actual_price = val; repeat_c7.push_back(info); } + OMATCH_END (); } send_packet_repeatonly<0x00c7, 4, 10>(s, repeat_c7); @@ -1379,9 +1315,9 @@ int clif_additem(dumb_ptr<map_session_data> sd, IOff0 n, int amount, PickupFail } else { - if (!n.ok() || !sd->status.inventory[n].nameid - || sd->inventory_data[n] == nullptr) + if (!n.ok() || !sd->status.inventory[n].nameid) return 1; + auto sdidn = TRY_UNWRAP(sd->inventory_data[n], return 1); fixed_a0.ioff2 = n.shift(); fixed_a0.amount = amount; @@ -1396,9 +1332,9 @@ int clif_additem(dumb_ptr<map_session_data> sd, IOff0 n, int amount, PickupFail fixed_a0.card3 = 0; } fixed_a0.epos = pc_equippoint(sd, n); - fixed_a0.item_type = (sd->inventory_data[n]->type == ItemType::_7 + fixed_a0.item_type = (sdidn->type == ItemType::_7 ? ItemType::WEAPON - : sd->inventory_data[n]->type); + : sdidn->type); fixed_a0.pickup_fail = fail; } @@ -1435,17 +1371,18 @@ void clif_itemlist(dumb_ptr<map_session_data> sd) std::vector<Packet_Repeat<0x01ee>> repeat_1ee; for (IOff0 i : IOff0::iter()) { - if (!sd->status.inventory[i].nameid - || sd->inventory_data[i] == nullptr - || itemdb_isequip2(sd->inventory_data[i])) + if (!sd->status.inventory[i].nameid) + continue; + auto sdidi = TRY_UNWRAP(sd->inventory_data[i], continue); + if (itemdb_isequip2(sdidi)) continue; Packet_Repeat<0x01ee> info; info.ioff2 = i.shift(); info.name_id = sd->status.inventory[i].nameid; - info.item_type = sd->inventory_data[i]->type; + info.item_type = sdidi->type; info.identify = 1; info.amount = sd->status.inventory[i].amount; - if (sd->inventory_data[i]->equip == EPOS::ARROW) + if (sdidi->equip == EPOS::ARROW) { info.epos = EPOS::ARROW; if (bool(sd->status.inventory[i].equip)) @@ -1479,17 +1416,18 @@ void clif_equiplist(dumb_ptr<map_session_data> sd) std::vector<Packet_Repeat<0x00a4>> repeat_a4; for (IOff0 i : IOff0::iter()) { - if (!sd->status.inventory[i].nameid - || sd->inventory_data[i] == nullptr - || !itemdb_isequip2(sd->inventory_data[i])) + if (!sd->status.inventory[i].nameid) + continue; + P<struct item_data> sdidi = TRY_UNWRAP(sd->inventory_data[i], continue); + if (!itemdb_isequip2(sdidi)) continue; Packet_Repeat<0x00a4> info; info.ioff2 = i.shift(); info.name_id = sd->status.inventory[i].nameid; info.item_type = ( - sd->inventory_data[i]->type == ItemType::_7 + sdidi->type == ItemType::_7 ? ItemType::WEAPON - : sd->inventory_data[i]->type); + : sdidi->type); info.identify = 0; info.epos_pc = pc_equippoint(sd, i); info.epos_inv = sd->status.inventory[i].equip; @@ -1513,10 +1451,9 @@ void clif_equiplist(dumb_ptr<map_session_data> sd) * カプラさんに預けてある消耗品&収集品リスト *------------------------------------------ */ -int clif_storageitemlist(dumb_ptr<map_session_data> sd, Storage *stor) +int clif_storageitemlist(dumb_ptr<map_session_data> sd, Borrowed<Storage> stor) { nullpo_retz(sd); - nullpo_retz(stor); Session *s = sd->sess; std::vector<Packet_Repeat<0x01f0>> repeat_1f0; @@ -1525,9 +1462,7 @@ int clif_storageitemlist(dumb_ptr<map_session_data> sd, Storage *stor) if (!stor->storage_[i].nameid) continue; - struct item_data *id; - id = itemdb_search(stor->storage_[i].nameid); - nullpo_retz(id); + P<struct item_data> id = itemdb_search(stor->storage_[i].nameid); if (itemdb_isequip2(id)) continue; @@ -1555,10 +1490,9 @@ int clif_storageitemlist(dumb_ptr<map_session_data> sd, Storage *stor) * カプラさんに預けてある装備リスト *------------------------------------------ */ -int clif_storageequiplist(dumb_ptr<map_session_data> sd, Storage *stor) +int clif_storageequiplist(dumb_ptr<map_session_data> sd, Borrowed<Storage> stor) { nullpo_retz(sd); - nullpo_retz(stor); Session *s = sd->sess; std::vector<Packet_Repeat<0x00a6>> repeat_a6; @@ -1567,9 +1501,7 @@ int clif_storageequiplist(dumb_ptr<map_session_data> sd, Storage *stor) if (!stor->storage_[i].nameid) continue; - struct item_data *id; - id = itemdb_search(stor->storage_[i].nameid); - nullpo_retz(id); + P<struct item_data> id = itemdb_search(stor->storage_[i].nameid); if (!itemdb_isequip2(id)) continue; Packet_Repeat<0x00a6> info; @@ -1831,7 +1763,7 @@ int clif_changelook_towards(dumb_ptr<block_list> bl, LOOK type, int val, if (bl->bl_type == BL::PC) sd = bl->is_player(); - if (sd && bool(sd->status.option & Option::INVISIBILITY)) + if (sd && bool(sd->status.option & Opt0::INVISIBILITY)) return 0; if (sd @@ -1845,7 +1777,7 @@ int clif_changelook_towards(dumb_ptr<block_list> bl, LOOK type, int val, fixed_1d7.look_type = type; IOff0 idx = sd->equip_index_maybe[equip_point]; - if (idx.ok() && sd->inventory_data[idx]) + if (idx.ok() && sd->inventory_data[idx].is_some()) { fixed_1d7.weapon_or_name_id_or_value = unwrap<ItemNameId>(sd->status.inventory[idx].nameid); } @@ -1862,14 +1794,14 @@ int clif_changelook_towards(dumb_ptr<block_list> bl, LOOK type, int val, fixed_1d7.weapon_or_name_id_or_value = unwrap<ItemNameId>(sd->attack_spell_look_override); else { - if (widx.ok() && sd->inventory_data[widx]) + if (widx.ok() && sd->inventory_data[widx].is_some()) { fixed_1d7.weapon_or_name_id_or_value = unwrap<ItemNameId>(sd->status.inventory[widx].nameid); } else fixed_1d7.weapon_or_name_id_or_value = unwrap<ItemNameId>(ItemNameId()); } - if (sidx.ok() && sidx != widx && sd->inventory_data[sidx]) + if (sidx.ok() && sidx != widx && sd->inventory_data[sidx].is_some()) { fixed_1d7.shield = sd->status.inventory[sidx].nameid; } @@ -2074,7 +2006,7 @@ int clif_changeoption(dumb_ptr<block_list> bl) nullpo_retz(bl); - Option option = *battle_get_option(bl); + Opt0 option = *battle_get_option(bl); sc_data = battle_get_sc_data(bl); Packet_Fixed<0x0119> fixed_119; @@ -2263,10 +2195,9 @@ int clif_tradecompleted(dumb_ptr<map_session_data> sd, int fail) *------------------------------------------ */ int clif_updatestorageamount(dumb_ptr<map_session_data> sd, - Storage *stor) + Borrowed<Storage> stor) { nullpo_retz(sd); - nullpo_retz(stor); Session *s = sd->sess; Packet_Fixed<0x00f2> fixed_f2; @@ -2281,11 +2212,10 @@ int clif_updatestorageamount(dumb_ptr<map_session_data> sd, * カプラ倉庫にアイテムを追加する *------------------------------------------ */ -int clif_storageitemadded(dumb_ptr<map_session_data> sd, Storage *stor, +int clif_storageitemadded(dumb_ptr<map_session_data> sd, Borrowed<Storage> stor, SOff0 index, int amount) { nullpo_retz(sd); - nullpo_retz(stor); Session *s = sd->sess; Packet_Fixed<0x00f4> fixed_f4; @@ -2357,7 +2287,7 @@ static void clif_getareachar_pc(dumb_ptr<map_session_data> sd, dumb_ptr<map_session_data> dstsd) { - if (bool(dstsd->status.option & Option::INVISIBILITY)) + if (bool(dstsd->status.option & Opt0::INVISIBILITY)) return; nullpo_retv(sd); @@ -2392,7 +2322,7 @@ void clif_getareachar_npc(dumb_ptr<map_session_data> sd, dumb_ptr<npc_data> nd) nullpo_retv(sd); nullpo_retv(nd); - if (nd->npc_class == NEGATIVE_SPECIES || nd->flag & 1 || nd->npc_class == INVISIBLE_CLASS) + if (nd->flag & 1 || nd->npc_class == INVISIBLE_CLASS) return; Buffer buf; @@ -2470,7 +2400,7 @@ int clif_fixpcpos(dumb_ptr<map_session_data> sd) */ int clif_damage(dumb_ptr<block_list> src, dumb_ptr<block_list> dst, tick_t tick, interval_t sdelay, interval_t ddelay, int damage, - int div, DamageType type, int damage2) + int div, DamageType type) { eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; @@ -2488,7 +2418,7 @@ int clif_damage(dumb_ptr<block_list> src, dumb_ptr<block_list> dst, fixed_8a.damage = (damage > 0x7fff) ? 0x7fff : damage; fixed_8a.div = div; fixed_8a.damage_type = type; - fixed_8a.damage2 = damage2; + fixed_8a.damage2 = 0; Buffer buf = create_fpacket<0x008a, 29>(fixed_8a); clif_send(buf, src, SendWho::AREA); @@ -2686,43 +2616,6 @@ void clif_mobinsight(dumb_ptr<block_list> bl, dumb_ptr<mob_data> md) } /*========================================== - * - *------------------------------------------ - */ -int clif_skillinfo(dumb_ptr<map_session_data> sd, SkillID skillid, int type, - int range) -{ - nullpo_retz(sd); - - Session *s = sd->sess; - if (!sd->status.skill[skillid].lv) - return 0; - Packet_Fixed<0x0147> fixed_147; - fixed_147.info.skill_id = skillid; - if (type < 0) - fixed_147.info.type_or_inf = skill_get_inf(skillid); - else - fixed_147.info.type_or_inf = type; - fixed_147.info.flags = SkillFlags::ZERO; - fixed_147.info.level = sd->status.skill[skillid].lv; - fixed_147.info.sp = skill_get_sp(skillid, sd->status.skill[skillid].lv); - if (range < 0) - { - range = skill_get_range(skillid, sd->status.skill[skillid].lv); - if (range < 0) - range = battle_get_range(sd) - (range + 1); - fixed_147.info.range = range; - } - else - fixed_147.info.range = range; - fixed_147.info.unused = ""_s; - fixed_147.info.can_raise = sd->status.skill[skillid].lv < skill_get_max_raise(skillid); - send_fpacket<0x0147, 39>(s, fixed_147); - - return 0; -} - -/*========================================== * スキルリストを送信する *------------------------------------------ */ @@ -2971,8 +2864,6 @@ int clif_party_info(PartyPair p, Session *s) int i; dumb_ptr<map_session_data> sd = nullptr; - nullpo_retz(p); - Packet_Head<0x00fb> head_fb; std::vector<Packet_Repeat<0x00fb>> repeat_fb; head_fb.party_name = p->name; @@ -3018,15 +2909,12 @@ int clif_party_info(PartyPair p, Session *s) void clif_party_invite(dumb_ptr<map_session_data> sd, dumb_ptr<map_session_data> tsd) { - PartyPair p; - nullpo_retv(sd); nullpo_retv(tsd); Session *s = tsd->sess; - if (!(p = party_search(sd->status.party_id))) - return; + PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return); Packet_Fixed<0x00fe> fixed_fe; fixed_fe.account_id = sd->status_key.account_id; @@ -3068,8 +2956,6 @@ void clif_party_inviteack(dumb_ptr<map_session_data> sd, CharName nick, int flag */ void clif_party_option(PartyPair p, dumb_ptr<map_session_data> sd, int flag) { - nullpo_retv(p); - if (sd == nullptr && flag == 0) { int i; @@ -3102,8 +2988,6 @@ void clif_party_leaved(PartyPair p, dumb_ptr<map_session_data> sd, { int i; - nullpo_retv(p); - Packet_Fixed<0x0105> fixed_105; fixed_105.account_id = account_id; fixed_105.char_name = name; @@ -3140,8 +3024,6 @@ void clif_party_message(PartyPair p, AccountId account_id, XString mes) dumb_ptr<map_session_data> sd = nullptr; int i; - nullpo_retv(p); - for (i = 0; i < MAX_PARTY; i++) { sd = dumb_ptr<map_session_data>(p->member[i].sd); @@ -3214,21 +3096,6 @@ int clif_movetoattack(dumb_ptr<map_session_data> sd, dumb_ptr<block_list> bl) } /*========================================== - * MVPエフェクト - *------------------------------------------ - */ -int clif_mvp_effect(dumb_ptr<map_session_data> sd) -{ - nullpo_retz(sd); - - Packet_Fixed<0x010c> fixed_10c; - fixed_10c.block_id = sd->bl_id; - Buffer buf = create_fpacket<0x010c, 6>(fixed_10c); - clif_send(buf, sd, SendWho::AREA); - return 0; -} - -/*========================================== * エモーション *------------------------------------------ */ @@ -3243,7 +3110,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) { @@ -3442,9 +3308,9 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd) // 119 // 78 - if (battle_config.player_invincible_time > 0) + if (battle_config.player_invincible_time > interval_t::zero()) { - pc_setinvincibletimer(sd, static_cast<interval_t>(battle_config.player_invincible_time)); + pc_setinvincibletimer(sd, battle_config.player_invincible_time); } map_addblock(sd); // ブロック登録 @@ -3467,7 +3333,6 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd) std::bind(pc_calc_pvprank_timer, ph::_1, ph::_2, sd->bl_id)); sd->pvp_rank = 0; - sd->pvp_lastusers = 0; sd->pvp_point = 5; } } @@ -3642,16 +3507,20 @@ RecvResult clif_parse_GetCharNameRequest(Session *s, dumb_ptr<map_session_data> fixed_95.char_name = ssd->status_key.name; send_fpacket<0x0095, 30>(s, fixed_95); - PartyPair p; - PartyName party_name; int send = 0; - if (ssd->status.party_id && (p = party_search(ssd->status.party_id))) + if (ssd->status.party_id) { - party_name = p->name; - send = 1; + Option<PartyPair> p_ = party_search(ssd->status.party_id); + + OMATCH_BEGIN_SOME (p, p_) + { + party_name = p->name; + send = 1; + } + OMATCH_END (); } if (send) @@ -3665,7 +3534,7 @@ RecvResult clif_parse_GetCharNameRequest(Session *s, dumb_ptr<map_session_data> send_fpacket<0x0195, 102>(s, fixed_195); } - if (pc_isGM(sd).satisfies(GmLevel::from(static_cast<uint32_t>(battle_config.hack_info_GM_level)))) + if (pc_isGM(sd).satisfies(battle_config.hack_info_GM_level)) { IP4Address ip = ssd->get_ip(); Packet_Fixed<0x020c> fixed_20c; @@ -3853,25 +3722,6 @@ RecvResult clif_parse_Emotion(Session *s, dumb_ptr<map_session_data> sd) *------------------------------------------ */ static -RecvResult clif_parse_HowManyConnections(Session *s, dumb_ptr<map_session_data>) -{ - Packet_Fixed<0x00c1> fixed; - RecvResult rv = recv_fpacket<0x00c1, 2>(s, fixed); - if (rv != RecvResult::Complete) - return rv; - - Packet_Fixed<0x00c2> fixed_c2; - fixed_c2.users = map_getusers(); - send_fpacket<0x00c2, 6>(s, fixed_c2); - - return rv; -} - -/*========================================== - * - *------------------------------------------ - */ -static RecvResult clif_parse_ActionRequest(Session *s, dumb_ptr<map_session_data> sd) { Packet_Fixed<0x0089> fixed; @@ -3904,7 +3754,7 @@ RecvResult clif_parse_ActionRequest(Session *s, dumb_ptr<map_session_data> sd) { case DamageType::NORMAL: case DamageType::CONTINUOUS: - if (bool(sd->status.option & Option::HIDE)) + if (bool(sd->status.option & Opt0::HIDE)) return rv; if (!battle_config.skill_delay_attack_enable) { @@ -4046,7 +3896,7 @@ RecvResult clif_parse_Wis(Session *s, dumb_ptr<map_session_data> sd) if (dstsd->sess == s) { ZString mes = "You cannot page yourself."_s; - clif_wis_message(s, wisp_server_name, mes); + clif_wis_message(s, WISP_SERVER_NAME, mes); } else { @@ -4199,15 +4049,16 @@ RecvResult clif_parse_EquipItem(Session *s, dumb_ptr<map_session_data> sd) if (sd->npc_id) return rv; - if (sd->inventory_data[index]) + OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[index]) { EPOS epos = fixed.epos_ignored; - if (sd->inventory_data[index]->type == ItemType::ARROW) + if (sdidi->type == ItemType::ARROW) epos = EPOS::ARROW; // Note: the EPOS argument to pc_equipitem is actually ignored pc_equipitem(sd, index, epos); } + OMATCH_END (); return rv; } @@ -4816,6 +4667,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 @@ -5011,7 +4916,7 @@ func_table clif_parse_func_table[0x0220] = {0, 5, nullptr, }, // 0x00be {1000, 3, clif_parse_Emotion, }, // 0x00bf {0, 7, nullptr, }, // 0x00c0 - {0, 2, clif_parse_HowManyConnections, }, // 0x00c1 + {0, 2, nullptr, }, // 0x00c1 {0, 6, nullptr, }, // 0x00c2 {0, 8, nullptr, }, // 0x00c3 {0, 6, nullptr, }, // 0x00c4 @@ -5431,12 +5336,10 @@ uint16_t clif_check_packet_flood(Session *s, int cmd) // They are flooding if (tick < sd->flood_rates[cmd] + rate) { - TimeT now = TimeT::now(); - // If it's a nasty flood we log and possibly kick - if (now > sd->packet_flood_reset_due) + if (tick > sd->packet_flood_reset_due) { - sd->packet_flood_reset_due = static_cast<time_t>(now) + battle_config.packet_spam_threshold; + sd->packet_flood_reset_due = tick + battle_config.packet_spam_threshold; sd->packet_flood_in = 0; } @@ -5638,7 +5541,6 @@ unknown_packet: PRINTF("\nclif_parse: session #%d, packet 0x%x, lenght %zu\n"_fmt, s, packet_id, packet_avail(s)); { - ZString packet_txt = "save/packet.txt"_s; if (sd && sd->state.auth) { PRINTF("Unknown packet: Account ID %d, character ID %d, player name %s.\n"_fmt, @@ -5650,35 +5552,27 @@ unknown_packet: else PRINTF("Unknown packet (unknown)\n"_fmt); - io::AppendFile fp(packet_txt); - if (!fp.is_open()) - { - PRINTF("clif.c: cant write [%s] !!! data is lost !!!\n"_fmt, - packet_txt); - return; - } - else { timestamp_seconds_buffer now; stamp_time(now); if (sd && sd->state.auth) { - FPRINTF(fp, + FPRINTF(stderr, "%s\nPlayer with account ID %d (character ID %d, player name %s) sent wrong packet:\n"_fmt, now, sd->status_key.account_id, sd->status_key.char_id, sd->status_key.name); } else if (sd) // not authentified! (refused by char-server or disconnect before to be authentified) - FPRINTF(fp, + FPRINTF(stderr, "%s\nUnauthenticated player with account ID %d sent wrong packet:\n"_fmt, now, sd->bl_id); else - FPRINTF(fp, + FPRINTF(stderr, "%s\nUnknown connection sent wrong packet:\n"_fmt, now); - packet_dump(fp, s); + packet_dump(s); } } } @@ -5687,6 +5581,7 @@ unknown_packet: void do_init_clif(void) { - make_listen_port(map_port, SessionParsers{.func_parse= clif_parse, .func_delete= clif_delete}); + make_listen_port(map_conf.map_port, SessionParsers{.func_parse= clif_parse, .func_delete= clif_delete}); } +} // namespace map } // namespace tmwa diff --git a/src/map/clif.hpp b/src/map/clif.hpp index adb4889..5117ff3 100644 --- a/src/map/clif.hpp +++ b/src/map/clif.hpp @@ -20,34 +20,26 @@ // 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/clif.t.hpp" -#include "clif.t.hpp" +#include "fwd.hpp" #include <functional> -#include "../strings/fwd.hpp" - -#include "../generic/fwd.hpp" +#include "../high/mmo.hpp" #include "../net/timer.t.hpp" -#include "../mmo/fwd.hpp" -#include "../mmo/mmo.hpp" - #include "battle.t.hpp" #include "map.t.hpp" #include "pc.t.hpp" -#include "skill.t.hpp" +#include "../mmo/skill.t.hpp" namespace tmwa { -void clif_setip(IP4Address); -void clif_setport(int); - -IP4Address clif_getip(void); -int clif_getport(void); +namespace map +{ int clif_countusers(void); void clif_setwaitclose(Session *); @@ -57,7 +49,6 @@ int clif_charselectok(BlockId); int clif_dropflooritem(dumb_ptr<flooritem_data>); int clif_clearflooritem(dumb_ptr<flooritem_data>, Session *); int clif_clearchar(dumb_ptr<block_list>, BeingRemoveWhy); // area or fd -int clif_clearchar_delay(tick_t, dumb_ptr<block_list>, BeingRemoveWhy); void clif_clearchar_id(BlockId, BeingRemoveWhy, Session *); int clif_spawnpc(dumb_ptr<map_session_data>); //area int clif_spawnnpc(dumb_ptr<npc_data>); // area @@ -87,11 +78,11 @@ void clif_delitem(dumb_ptr<map_session_data>, IOff0, int); //self int clif_updatestatus(dumb_ptr<map_session_data>, SP); //self int clif_damage(dumb_ptr<block_list>, dumb_ptr<block_list>, tick_t, interval_t, interval_t, - int, int, DamageType, int); // area + int, int, DamageType); // area inline int clif_takeitem(dumb_ptr<block_list> src, dumb_ptr<block_list> dst) { - return clif_damage(src, dst, tick_t(), interval_t::zero(), interval_t::zero(), 0, 0, DamageType::TAKEITEM, 0); + return clif_damage(src, dst, tick_t(), interval_t::zero(), interval_t::zero(), 0, 0, DamageType::TAKEITEM); } int clif_changelook(dumb_ptr<block_list>, LOOK, int); // area void clif_changelook_accessories(dumb_ptr<block_list> bl, dumb_ptr<map_session_data> dst); // area or target; list gloves, boots etc. @@ -105,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 @@ -119,12 +111,12 @@ int clif_tradecancelled(dumb_ptr<map_session_data> sd); int clif_tradecompleted(dumb_ptr<map_session_data> sd, int fail); // storage -int clif_storageitemlist(dumb_ptr<map_session_data> sd, Storage *stor); +int clif_storageitemlist(dumb_ptr<map_session_data> sd, Borrowed<Storage> stor); int clif_storageequiplist(dumb_ptr<map_session_data> sd, - Storage *stor); + Borrowed<Storage> stor); int clif_updatestorageamount(dumb_ptr<map_session_data> sd, - Storage *stor); -int clif_storageitemadded(dumb_ptr<map_session_data> sd, Storage *stor, + Borrowed<Storage> stor); +int clif_storageitemadded(dumb_ptr<map_session_data> sd, Borrowed<Storage> stor, SOff0 index, int amount); int clif_storageitemremoved(dumb_ptr<map_session_data> sd, SOff0 index, int amount); @@ -136,8 +128,6 @@ void clif_pcoutsight(dumb_ptr<block_list>, dumb_ptr<map_session_data>); void clif_mobinsight(dumb_ptr<block_list>, dumb_ptr<mob_data>); void clif_moboutsight(dumb_ptr<block_list>, dumb_ptr<mob_data>); -int clif_skillinfo(dumb_ptr<map_session_data> sd, SkillID skillid, int type, - int range); void clif_skillinfoblock(dumb_ptr<map_session_data> sd); int clif_skillup(dumb_ptr<map_session_data> sd, SkillID skill_num); @@ -157,8 +147,6 @@ void clif_wis_end(Session *s, int flag); void clif_itemlist(dumb_ptr<map_session_data> sd); void clif_equiplist(dumb_ptr<map_session_data> sd); -int clif_mvp_effect(dumb_ptr<map_session_data> sd); - int clif_movetoattack(dumb_ptr<map_session_data> sd, dumb_ptr<block_list> bl); // party @@ -187,6 +175,10 @@ 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 } // namespace tmwa diff --git a/src/generic/intern-pool.cpp b/src/map/consts.hpp index f45b098..a68d8e3 100644 --- a/src/generic/intern-pool.cpp +++ b/src/map/consts.hpp @@ -1,5 +1,5 @@ -#include "intern-pool.hpp" -// intern-pool.cpp - Cached integer/string lookups. +#pragma once +// consts.hpp - Constants for tmwa-map. // // Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> // @@ -18,9 +18,17 @@ // 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 "../poison.hpp" +#include "fwd.hpp" + +#include "../ints/udl.hpp" + +#include "../mmo/ids.hpp" namespace tmwa { +namespace map +{ +constexpr BlockId MAX_FLOORITEM = wrap<BlockId>(500000_u32); +} // namespace map } // namespace tmwa diff --git a/src/map/fwd.hpp b/src/map/fwd.hpp index 79bbbcd..911d566 100644 --- a/src/map/fwd.hpp +++ b/src/map/fwd.hpp @@ -20,11 +20,38 @@ #include "../sanity.hpp" +#include <cstdint> + +#include "../ints/fwd.hpp" // rank 1 +#include "../range/fwd.hpp" // rank 1 +#include "../strings/fwd.hpp" // rank 1 +#include "../compat/fwd.hpp" // rank 2 +#include "../generic/fwd.hpp" // rank 3 +#include "../io/fwd.hpp" // rank 4 +#include "../net/fwd.hpp" // rank 5 +#include "../sexpr/fwd.hpp" // rank 5 +#include "../mmo/fwd.hpp" // rank 6 +#include "../proto2/fwd.hpp" // rank 8 +#include "../high/fwd.hpp" // rank 9 +#include "../wire/fwd.hpp" // rank 9 +#include "../ast/fwd.hpp" // rank 10 +// map/fwd.hpp is rank ∞ because it is an executable + namespace tmwa { +namespace map +{ // meh, add more when I feel like it -class BlockId; +struct BattleConf; +struct MapConf; + +struct charid2nick; +struct map_abstract; +struct mob_db_; +struct skill_db_; +struct event_data; + struct block_list; struct map_session_data; struct npc_data; @@ -36,9 +63,13 @@ class npc_data_script; class npc_data_shop; class npc_data_warp; class npc_data_message; -struct NpcEvent; struct item_data; +struct quest_data; + +struct ScriptState; +struct str_data_t; +class SIR; namespace magic { @@ -55,5 +86,7 @@ struct env_t; struct magic_conf_t; struct component_t; struct effect_set_t; +struct proc_t; } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/globals.cpp b/src/map/globals.cpp new file mode 100644 index 0000000..dce3906 --- /dev/null +++ b/src/map/globals.cpp @@ -0,0 +1,149 @@ +#include "globals.hpp" +// globals.cpp - Evil global variables for tmwa-map. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "../generic/intern-pool.hpp" + +#include "../io/write.hpp" + +#include "../proto2/net-Storage.hpp" + +#include "battle_conf.hpp" +#include "itemdb.hpp" +#include "quest.hpp" +#include "magic-interpreter.hpp" +#include "map_conf.hpp" +#include "mob.hpp" +#include "npc-internal.hpp" +#include "script-parse-internal.hpp" +#include "skill.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ + namespace map + { + BattleConf battle_config; + MapConf map_conf; + + // only used by intif.cpp + // and clif.cpp for the new on_delete stuff ... + Session *char_session; + 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 + magic_conf_t magic_conf; + env_t magic_default_env = { &magic_conf, nullptr }; + namespace magic_v2 + { + std::map<RString, proc_t> procs; + std::map<RString, val_t> const_defm; + } // namespace magic_v2 + } // namespace magic + + DMap<BlockId, dumb_ptr<block_list>> id_db; + UPMap<MapName, map_abstract> maps_db; + DMap<CharName, dumb_ptr<map_session_data>> nick_db; + Map<CharId, charid2nick> charid_db; + int world_user_count = 0; + Array<dumb_ptr<block_list>, unwrap<BlockId>(MAX_FLOORITEM)> object; + BlockId first_free_object_id = BlockId(); + int save_settings = 0xFFFF; + int block_free_lock = 0; + std::vector<dumb_ptr<block_list>> block_free; + /// This is a dummy entry that is shared by all the linked lists, + /// so that any entry can unlink itself without worrying about + /// whether it was the the head of the list. + block_list bl_head; + std::unique_ptr<io::AppendFile> map_logfile; + long map_logfile_index; + mob_db_ mob_db[2001]; + std::list<AString> npc_srcs; + int npc_warp, npc_shop, npc_script, npc_mob; + BlockId npc_id = START_NPC_NUM; + Map<NpcEvent, struct event_data> ev_db; + DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name; + // used for clock-based event triggers + // only tm_min, tm_hour, and tm_mday are used + tm ev_tm_b = + { + .tm_sec= 0, + .tm_min= -1, + .tm_hour= -1, + .tm_mday= -1, + .tm_mon= 0, + .tm_year= 0, + .tm_wday= 0, + .tm_yday= 0, + .tm_isdst= 0, + }; + Map<PartyId, PartyMost> party_db; + std::map<AccountId, GmLevel> gm_accountm; + tick_t natural_heal_tick, natural_heal_prev_tick; + interval_t natural_heal_diff_tick; + int last_save_fd; + bool save_flag; + Map<AccountId, Storage> storage_db; + + Map<RString, str_data_t> str_datam; + str_data_t LABEL_NEXTLINE_; + Map<ScriptLabel, int> scriptlabel_db; + std::set<ScriptLabel> probable_labels; + UPMap<RString, const ScriptBuffer> userfunc_db; + int parse_cmd_if = 0; + Option<Borrowed<str_data_t>> parse_cmdp = None; + InternPool variable_names; + // TODO: replace this whole mess with some sort of input stream that works + // a line at a time. + ZString startptr; + int startline; + int script_errors = 0; + DMap<SIR, int> mapreg_db; + Map<SIR, RString> mapregstr_db; + int mapreg_dirty = -1; + + std::vector<SkillID> skill_pool_skills; + earray<skill_db_, SkillID, SkillID::MAX_SKILL_DB> skill_db; + // these variables are set in the 'else' branches, + // and used in the (recursive) 'if' branch + // TODO kill it, kill it with fire. + BlockId skill_area_temp_id; + int skill_area_temp_hp; + + // Some other globals are not moved here, because they are + // large and initialized in-place and then *mostly* unmodified. + // + // src/map/atcommand.cpp: + // Map<XString, AtCommandInfo> atcommand_info; + // src/map/script-fun.cpp: + // BuiltinFunction builtin_functions[]; + // src/map/clif.cpp: + // func_table clif_parse_func_table[0x0220]; + // src/map/magic-expr.cpp: + // std::map<ZString, fun_t> functions; + // src/map/magic-stmt.cpp: + // std::map<ZString, op_t> operations; + } // namespace map +} // namespace tmwa diff --git a/src/map/globals.hpp b/src/map/globals.hpp new file mode 100644 index 0000000..b457b4e --- /dev/null +++ b/src/map/globals.hpp @@ -0,0 +1,109 @@ +#pragma once +// globals.hpp - Evil global variables for tmwa-map. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include <ctime> + +#include <list> +#include <map> +#include <memory> +#include <set> +#include <vector> + +#include "../ints/wrap.hpp" + +#include "../net/timer.t.hpp" + +#include "../mmo/skill.t.hpp" + +#include "consts.hpp" +#include "script-buffer.hpp" + + +namespace tmwa +{ + namespace map + { + extern BattleConf battle_config; + extern MapConf map_conf; + extern Session *char_session; + 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 + extern magic_conf_t magic_conf; + extern env_t magic_default_env; + namespace magic_v2 + { + extern std::map<RString, proc_t> procs; + extern std::map<RString, val_t> const_defm; + } // namespace magic_v2 + } // namespace magic + extern DMap<BlockId, dumb_ptr<block_list>> id_db; + extern UPMap<MapName, map_abstract> maps_db; + extern DMap<CharName, dumb_ptr<map_session_data>> nick_db; + extern Map<CharId, charid2nick> charid_db; + extern int world_user_count; + extern Array<dumb_ptr<block_list>, unwrap<BlockId>(MAX_FLOORITEM)> object; + extern BlockId first_free_object_id; + extern int save_settings; + extern int block_free_lock; + extern std::vector<dumb_ptr<block_list>> block_free; + extern block_list bl_head; + extern std::unique_ptr<io::AppendFile> map_logfile; + extern long map_logfile_index; + extern mob_db_ mob_db[2001]; + extern std::list<AString> npc_srcs; + extern int npc_warp, npc_shop, npc_script, npc_mob; + extern BlockId npc_id; + extern Map<NpcEvent, event_data> ev_db; + extern DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name; + extern tm ev_tm_b; + extern Map<PartyId, PartyMost> party_db; + extern std::map<AccountId, GmLevel> gm_accountm; + extern tick_t natural_heal_tick, natural_heal_prev_tick; + extern interval_t natural_heal_diff_tick; + extern int last_save_fd; + extern bool save_flag; + extern Map<AccountId, Storage> storage_db; + extern Map<RString, str_data_t> str_datam; + extern str_data_t LABEL_NEXTLINE_; + extern Map<ScriptLabel, int> scriptlabel_db; + extern std::set<ScriptLabel> probable_labels; + extern UPMap<RString, const ScriptBuffer> userfunc_db; + extern int parse_cmd_if; + extern Option<Borrowed<str_data_t>> parse_cmdp; + extern InternPool variable_names; + extern ZString startptr; + extern int startline; + extern int script_errors; + extern DMap<SIR, int> mapreg_db; + extern Map<SIR, RString> mapregstr_db; + extern int mapreg_dirty; + extern std::vector<SkillID> skill_pool_skills; + extern earray<skill_db_, SkillID, SkillID::MAX_SKILL_DB> skill_db; + extern BlockId skill_area_temp_id; + extern int skill_area_temp_hp; + } // namespace map +} // namespace tmwa diff --git a/src/map/grfio.cpp b/src/map/grfio.cpp index 4a1656b..3475108 100644 --- a/src/map/grfio.cpp +++ b/src/map/grfio.cpp @@ -33,19 +33,21 @@ #include "../strings/zstring.hpp" #include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" #include "../io/read.hpp" -#include "../mmo/extract.hpp" -#include "../mmo/mmo.hpp" +#include "../high/extract_mmo.hpp" +#include "../high/mmo.hpp" + +#include "globals.hpp" #include "../poison.hpp" namespace tmwa { -static -std::map<MapName, RString> resnametable; - +namespace map +{ bool load_resnametable(ZString filename) { io::ReadFile in(filename); @@ -106,4 +108,5 @@ std::vector<uint8_t> grfio_reads(MapName rname) close(fd); return buffer; } +} // namespace map } // namespace tmwa diff --git a/src/map/grfio.hpp b/src/map/grfio.hpp index d2ab058..25e27ef 100644 --- a/src/map/grfio.hpp +++ b/src/map/grfio.hpp @@ -26,17 +26,16 @@ #include <vector> -#include "../strings/fwd.hpp" - -#include "../mmo/fwd.hpp" - namespace tmwa { +namespace map +{ bool load_resnametable(ZString filename); /// Load a resource into memory, subject to data/resnametable.txt. /// Normally, resourcename is xxx-y.gat and the file is xxx-y.wlk. /// Currently there is exactly one .wlk per .gat, but multiples are fine. std::vector<uint8_t> grfio_reads(MapName resourcename); +} // namespace map } // namespace tmwa diff --git a/src/map/intif.cpp b/src/map/intif.cpp index 314db24..a5709ef 100644 --- a/src/map/intif.cpp +++ b/src/map/intif.cpp @@ -29,16 +29,19 @@ #include "../io/cxxstdio.hpp" -#include "../net/packets.hpp" #include "../net/socket.hpp" -#include "../mmo/mmo.hpp" +#include "../high/mmo.hpp" #include "../proto2/char-map.hpp" +#include "../wire/packets.hpp" + #include "battle.hpp" +#include "battle_conf.hpp" #include "chrif.hpp" #include "clif.hpp" +#include "globals.hpp" #include "map.hpp" #include "party.hpp" #include "pc.hpp" @@ -49,6 +52,8 @@ namespace tmwa { +namespace map +{ //----------------------------------------------------------------- // inter serverへの送信 @@ -156,9 +161,8 @@ void intif_request_storage(AccountId account_id) } // 倉庫データ送信 -void intif_send_storage(Storage *stor) +void intif_send_storage(Borrowed<Storage> stor) { - nullpo_retv(stor); if (!char_session) return; @@ -386,7 +390,6 @@ int intif_parse_AccountReg(Session *, const Packet_Head<0x3804>& head, const std static int intif_parse_LoadStorage(Session *, const Packet_Payload<0x3810>& payload) { - Storage *stor; dumb_ptr<map_session_data> sd; sd = map_id2sd(account_to_block(payload.account_id)); @@ -397,7 +400,7 @@ int intif_parse_LoadStorage(Session *, const Packet_Payload<0x3810>& payload) payload.account_id); return 1; } - stor = account2storage(payload.account_id); + P<Storage> stor = account2storage(payload.account_id); if (stor->storage_status == 1) { // Already open.. lets ignore this update if (battle_config.error_log) @@ -463,9 +466,7 @@ void intif_parse_PartyInfo(Session *, const Packet_Head<0x3821>& head, bool has_ PartyId pi = head.party_id; PartyMost pm = option.party_most; - PartyPair pp; - pp.party_id = pi; - pp.party_most = ± + PartyPair pp{pi, borrow(pm)}; party_recv_info(pp); } @@ -697,4 +698,5 @@ RecvResult intif_parse(Session *s, uint16_t packet_id) } return rv; } +} // namespace map } // namespace tmwa diff --git a/src/map/intif.hpp b/src/map/intif.hpp index 5be61a9..ac68040 100644 --- a/src/map/intif.hpp +++ b/src/map/intif.hpp @@ -22,17 +22,11 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" - -#include "../generic/fwd.hpp" - -#include "../net/fwd.hpp" - -#include "../mmo/fwd.hpp" - namespace tmwa { +namespace map +{ RecvResult intif_parse(Session *, uint16_t packet_id); void intif_GMmessage(XString mes); @@ -44,7 +38,7 @@ void intif_saveaccountreg(dumb_ptr<map_session_data> sd); void intif_request_accountreg(dumb_ptr<map_session_data> sd); void intif_request_storage(AccountId account_id); -void intif_send_storage(Storage *stor); +void intif_send_storage(Borrowed<Storage> stor); void intif_create_party(dumb_ptr<map_session_data> sd, PartyName name); void intif_request_partyinfo(PartyId party_id); @@ -55,4 +49,5 @@ 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); void intif_party_checkconflict(PartyId party_id, AccountId account_id, CharName nick); +} // namespace map } // namespace tmwa diff --git a/src/map/itemdb.cpp b/src/map/itemdb.cpp index 50cc5c4..7dd725e 100644 --- a/src/map/itemdb.cpp +++ b/src/map/itemdb.cpp @@ -29,20 +29,24 @@ #include "../generic/db.hpp" #include "../io/cxxstdio.hpp" -#include "../io/read.hpp" +#include "../io/extract.hpp" +#include "../io/line.hpp" #include "../mmo/config_parse.hpp" -#include "../mmo/extract.hpp" #include "../mmo/extract_enums.hpp" +#include "../ast/item.hpp" + +#include "globals.hpp" +#include "script-parse.hpp" + #include "../poison.hpp" namespace tmwa { -static -Map<ItemNameId, struct item_data> item_db; - +namespace map +{ // Function declarations /*========================================== @@ -51,24 +55,24 @@ Map<ItemNameId, struct item_data> item_db; */ // name = item alias, so we should find items aliases first. if not found then look for "jname" (full name) static -void itemdb_searchname_sub(struct item_data *item, ItemName str, struct item_data **dst) +void itemdb_searchname_sub(Borrowed<struct item_data> item, ItemName str, Borrowed<Option<Borrowed<struct item_data>>> dst) { if (item->name == str) - *dst = item; + *dst = Some(item); } /*========================================== * 名前で検索 *------------------------------------------ */ -struct item_data *itemdb_searchname(XString str_) +Option<Borrowed<struct item_data>> itemdb_searchname(XString str_) { ItemName str = stringish<ItemName>(str_); if (XString(str) != str_) - return nullptr; - struct item_data *item = nullptr; + return None; + Option<P<struct item_data>> item = None; for (auto& pair : item_db) - itemdb_searchname_sub(&pair.second, str, &item); + itemdb_searchname_sub(borrow(pair.second), str, borrow(item)); return item; } @@ -76,7 +80,7 @@ struct item_data *itemdb_searchname(XString str_) * DBの存在確認 *------------------------------------------ */ -struct item_data *itemdb_exists(ItemNameId nameid) +Option<Borrowed<struct item_data>> itemdb_exists(ItemNameId nameid) { return item_db.search(nameid); } @@ -85,13 +89,16 @@ struct item_data *itemdb_exists(ItemNameId nameid) * DBの検索 *------------------------------------------ */ -struct item_data *itemdb_search(ItemNameId nameid) +Borrowed<struct item_data> itemdb_search(ItemNameId nameid) { - struct item_data *id = item_db.search(nameid); - if (id) + Option<P<struct item_data>> id_ = item_db.search(nameid); + OMATCH_BEGIN_SOME (id, id_) + { return id; + } + OMATCH_END (); - id = item_db.init(nameid); + P<struct item_data> id = item_db.init(nameid); id->nameid = nameid; id->value_buy = 10; @@ -123,10 +130,8 @@ int itemdb_isequip(ItemNameId nameid) * *------------------------------------------ */ -int itemdb_isequip2(struct item_data *data) +bool itemdb_isequip2(Borrowed<struct item_data> data) { - if (!data) - return false; ItemType type = data->type; return !(type == ItemType::USE || type == ItemType::_2 @@ -149,99 +154,64 @@ int itemdb_isequip3(ItemNameId nameid) bool itemdb_readdb(ZString filename) { - bool rv = true; - - int ln = 0, lines = 0; + io::LineCharReader in(filename); + if (!in.is_open()) { - io::ReadFile in(filename); - - if (!in.is_open()) - { - PRINTF("can't read %s\n"_fmt, filename); - return false; - } + PRINTF("can't read %s\n"_fmt, filename); + return false; + } - lines = 0; + int ln = 0; - AString line; - while (in.getline(line)) + while (true) + { + auto res = TRY_UNWRAP(ast::item::parse_item(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::item::ItemOrComment ioc = TRY_UNWRAP(std::move(res.get_success()), return false); + + MATCH_BEGIN (ioc) { - lines++; - if (is_comment(line)) - continue; - // a line is 17 normal fields followed by 2 {} fields - // the fields are separated by ", *", but there may be , - // in the {}. - - auto it = std::find(line.begin(), line.end(), '{'); - XString main_part = line.xislice_h(it).rstrip(); - // According to the code, tail_part may be empty. See later. - ZString tail_part = line.xislice_t(it); - - XString unused_slot_count; - item_data idv {}; - if (!extract( - main_part, record<','>( - &idv.nameid, - lstripping(&idv.name), - lstripping(&idv.jname), - lstripping(&idv.type), - lstripping(&idv.value_buy), - lstripping(&idv.value_sell), - lstripping(&idv.weight), - lstripping(&idv.atk), - lstripping(&idv.def), - lstripping(&idv.range), - lstripping(&idv.magic_bonus), - lstripping(&unused_slot_count), - lstripping(&idv.sex), - lstripping(&idv.equip), - lstripping(&idv.wlv), - lstripping(&idv.elv), - lstripping(&idv.look) - ) - ) - ) - { - PRINTF("%s:%d: error: bad item line: %s\n"_fmt, - filename, lines, line); - rv = false; - continue; - } - - ln++; - - struct item_data *id = itemdb_search(idv.nameid); - *id = std::move(idv); - if (id->value_buy == 0 && id->value_sell == 0) + MATCH_CASE (const ast::item::Comment&, c) { + (void)c; } - else if (id->value_buy == 0) + MATCH_CASE (const ast::item::Item&, item) { - id->value_buy = id->value_sell * 2; + ln++; + + item_data idv {}; + idv.nameid = item.id.data; + idv.name = item.name.data; + idv.jname = item.jname.data; + idv.type = item.type.data; + idv.value_buy = item.buy_price.data ?: item.sell_price.data * 2; + idv.value_sell = item.sell_price.data ?: item.buy_price.data / 2; + idv.weight = item.weight.data; + idv.atk = item.atk.data; + idv.def = item.def.data; + idv.range = item.range.data; + idv.magic_bonus = item.magic_bonus.data; + idv.sex = item.gender.data; + idv.equip = item.loc.data; + idv.wlv = item.wlv.data; + idv.elv = item.elv.data; + idv.look = item.view.data; + + idv.use_script = compile_script(STRPRINTF("use script %d"_fmt, idv.nameid), item.use_script, true); + idv.equip_script = compile_script(STRPRINTF("equip script %d"_fmt, idv.nameid), item.equip_script, true); + + Borrowed<struct item_data> id = itemdb_search(idv.nameid); + *id = std::move(idv); } - else if (id->value_sell == 0) - { - id->value_sell = id->value_buy / 2; - } - - id->use_script = nullptr; - id->equip_script = nullptr; - - if (!tail_part) - continue; - id->use_script = parse_script(tail_part, lines, true); - - tail_part = tail_part.xislice_t(std::find(tail_part.begin() + 1, tail_part.end(), '{')); - if (!tail_part) - continue; - id->equip_script = parse_script(tail_part, lines, true); } - PRINTF("read %s done (count=%d)\n"_fmt, filename, ln); + MATCH_END (); } - - return rv; } /*========================================== @@ -265,4 +235,5 @@ void do_final_itemdb(void) itemdb_final(&pair.second); item_db.clear(); } +} // namespace map } // namespace tmwa diff --git a/src/map/itemdb.hpp b/src/map/itemdb.hpp index 25b4dad..5e19c0b 100644 --- a/src/map/itemdb.hpp +++ b/src/map/itemdb.hpp @@ -23,14 +23,16 @@ #include "fwd.hpp" #include "../mmo/ids.hpp" -#include "../mmo/mmo.hpp" +#include "../high/mmo.hpp" #include "map.t.hpp" -#include "script.hpp" +#include "script-buffer.hpp" namespace tmwa { +namespace map +{ struct item_data { ItemNameId nameid; @@ -59,11 +61,11 @@ struct random_item_data }; inline -struct item_data *itemdb_searchname(ItemName) = delete; -struct item_data *itemdb_searchname(XString name); +Option<Borrowed<struct item_data>> itemdb_searchname(ItemName) = delete; +Option<Borrowed<struct item_data>> itemdb_searchname(XString name); // TODO this function should die -struct item_data *itemdb_search(ItemNameId nameid); -struct item_data *itemdb_exists(ItemNameId nameid); +Borrowed<struct item_data> itemdb_search(ItemNameId nameid); +Option<Borrowed<struct item_data>> itemdb_exists(ItemNameId nameid); inline ItemType itemdb_type(ItemNameId n) @@ -97,11 +99,12 @@ int itemdb_value_sell(ItemNameId n) } int itemdb_isequip(ItemNameId); -int itemdb_isequip2(struct item_data *); +bool itemdb_isequip2(Borrowed<struct item_data>); int itemdb_isequip3(ItemNameId); void itemdb_reload(void); void do_final_itemdb(void); bool itemdb_readdb(ZString filename); +} // namespace map } // namespace tmwa diff --git a/src/map/magic-expr-eval.hpp b/src/map/magic-expr-eval.hpp index 4529c04..e8ed4aa 100644 --- a/src/map/magic-expr-eval.hpp +++ b/src/map/magic-expr-eval.hpp @@ -28,6 +28,8 @@ namespace tmwa { +namespace map +{ namespace magic { // TODO soon kill this unlike I killed VAR @@ -48,4 +50,5 @@ namespace magic #define ARG_MAY_BE_AREA(x) (args[x].is<ValArea>() || args[x].is<ValArea>()) } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic-expr.cpp b/src/map/magic-expr.cpp index 674c850..197727e 100644 --- a/src/map/magic-expr.cpp +++ b/src/map/magic-expr.cpp @@ -33,7 +33,8 @@ #include "../generic/random.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" + +#include "../mmo/cxxstdio_enums.hpp" #include "battle.hpp" #include "itemdb.hpp" @@ -42,12 +43,15 @@ #include "magic-interpreter-base.hpp" #include "npc.hpp" #include "pc.hpp" +#include "script-call.hpp" #include "../poison.hpp" namespace tmwa { +namespace map +{ namespace magic { static @@ -56,14 +60,15 @@ void free_area(dumb_ptr<area_t> area) if (!area) return; - MATCH (*area) + MATCH_BEGIN (*area) { - CASE (const AreaUnion&, a) + MATCH_CASE (const AreaUnion&, a) { free_area(a.a_union[0]); free_area(a.a_union[1]); } } + MATCH_END (); area.delete_(); } @@ -71,109 +76,111 @@ void free_area(dumb_ptr<area_t> area) static dumb_ptr<area_t> dup_area(dumb_ptr<area_t> area) { - MATCH (*area) + MATCH_BEGIN (*area) { - CASE (const location_t&, loc) + MATCH_CASE (const location_t&, loc) { return dumb_ptr<area_t>::make(loc); } - CASE (const AreaUnion&, a) + MATCH_CASE (const AreaUnion&, a) { AreaUnion u; u.a_union[0] = dup_area(a.a_union[0]); u.a_union[1] = dup_area(a.a_union[1]); return dumb_ptr<area_t>::make(u); } - CASE (const AreaRect&, rect) + MATCH_CASE (const AreaRect&, rect) { return dumb_ptr<area_t>::make(rect); } - CASE (const AreaBar&, bar) + MATCH_CASE (const AreaBar&, bar) { return dumb_ptr<area_t>::make(bar); } } + MATCH_END (); abort(); } void magic_copy_var(val_t *dest, const val_t *src) { - MATCH (*src) + MATCH_BEGIN (*src) { - // mumble mumble not a public API ... - default: + MATCH_DEFAULT () { abort(); } - CASE (const ValUndef&, s) + MATCH_CASE (const ValUndef&, s) { *dest = s; } - CASE (const ValInt&, s) + MATCH_CASE (const ValInt&, s) { *dest = s; } - CASE (const ValDir&, s) + MATCH_CASE (const ValDir&, s) { *dest = s; } - CASE (const ValString&, s) + MATCH_CASE (const ValString&, s) { *dest = ValString{s.v_string}; } - CASE (const ValEntityInt&, s) + MATCH_CASE (const ValEntityInt&, s) { *dest = s; } - CASE (const ValEntityPtr&, s) + MATCH_CASE (const ValEntityPtr&, s) { *dest = s; } - CASE (const ValLocation&, s) + MATCH_CASE (const ValLocation&, s) { *dest = s; } - CASE (const ValArea&, s) + MATCH_CASE (const ValArea&, s) { *dest = ValArea{dup_area(s.v_area)}; } - CASE (const ValSpell&, s) + MATCH_CASE (const ValSpell&, s) { *dest = s; } - CASE (const ValInvocationInt&, s) + MATCH_CASE (const ValInvocationInt&, s) { *dest = s; } - CASE (const ValInvocationPtr&, s) + MATCH_CASE (const ValInvocationPtr&, s) { *dest = s; } - CASE (const ValFail&, s) + MATCH_CASE (const ValFail&, s) { *dest = s; } - CASE (const ValNegative1&, s) + MATCH_CASE (const ValNegative1&, s) { *dest = s; } } + MATCH_END (); } void magic_clear_var(val_t *v) { - MATCH (*v) + MATCH_BEGIN (*v) { - CASE (ValString&, s) + MATCH_CASE (ValString&, s) { (void)s; } - CASE (const ValArea&, a) + MATCH_CASE (const ValArea&, a) { free_area(a.v_area); } } + MATCH_END (); } static @@ -212,63 +219,64 @@ void stringify(val_t *v) }}; AString buf; - MATCH (*v) + MATCH_BEGIN (*v) { - default: + MATCH_DEFAULT () { abort(); } - CASE (const ValUndef&, x) + MATCH_CASE (const ValUndef&, x) { (void)x; buf = "UNDEF"_s; } - CASE (const ValInt&, x) + MATCH_CASE (const ValInt&, x) { buf = STRPRINTF("%i"_fmt, x.v_int); } - CASE (const ValString&, x) + MATCH_CASE (const ValString&, x) { (void)x; return; } - CASE (const ValDir&, x) + MATCH_CASE (const ValDir&, x) { buf = dirs[x.v_dir]; } - CASE (const ValEntityPtr&, x) + MATCH_CASE (const ValEntityPtr&, x) { buf = show_entity(x.v_entity); } - CASE (const ValLocation&, x) + MATCH_CASE (const ValLocation&, x) { buf = STRPRINTF("<\"%s\", %d, %d>"_fmt, x.v_location.m->name_, x.v_location.x, x.v_location.y); } - CASE (const ValArea&, x) + MATCH_CASE (const ValArea&, x) { buf = "%area"_s; free_area(x.v_area); } - CASE (const ValSpell&, x) + MATCH_CASE (const ValSpell&, x) { buf = x.v_spell->name; } - CASE (const ValInvocationInt&, x) + MATCH_CASE (const ValInvocationInt&, x) { dumb_ptr<invocation> invocation_ = map_id2bl(x.v_iid)->is_spell(); buf = invocation_->spell->name; } - CASE (const ValInvocationPtr&, x) + MATCH_CASE (const ValInvocationPtr&, x) { dumb_ptr<invocation> invocation_ = x.v_invocation; buf = invocation_->spell->name; } } + MATCH_END (); *v = ValString{buf}; } @@ -310,14 +318,15 @@ void make_location(val_t *v) { if (ValArea *a = v->get_if<ValArea>()) { - MATCH (*a->v_area) + MATCH_BEGIN (*a->v_area) { - CASE (const location_t&, location) + MATCH_CASE (const location_t&, location) { free_area(a->v_area); *v = ValLocation{location}; } } + MATCH_END (); } } @@ -582,39 +591,41 @@ int fun_if_then_else(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) return 0; } -void magic_area_rect(map_local **m, int *x, int *y, int *width, int *height, +Borrowed<map_local> magic_area_rect(int *x, int *y, int *width, int *height, area_t& area_) { - MATCH (area_) + MATCH_BEGIN (area_) { - CASE (const AreaUnion&, a) + MATCH_CASE (const AreaUnion&, a) { (void)a; abort(); } - CASE (const location_t&, a_loc) + MATCH_CASE (const location_t&, a_loc) { - *m = a_loc.m; + P<map_local> m = a_loc.m; *x = a_loc.x; *y = a_loc.y; *width = 1; *height = 1; + return m; } - CASE (const AreaRect&, a_rect) + MATCH_CASE (const AreaRect&, a_rect) { - *m = a_rect.loc.m; + P<map_local> m = a_rect.loc.m; *x = a_rect.loc.x; *y = a_rect.loc.y; *width = a_rect.width; *height = a_rect.height; + return m; } - CASE (const AreaBar&, a_bar) + MATCH_CASE (const AreaBar&, a_bar) { int tx = a_bar.loc.x; int ty = a_bar.loc.y; int twidth = a_bar.width; int tdepth = a_bar.width; - *m = a_bar.loc.m; + P<map_local> m = a_bar.loc.m; switch (a_bar.dir) { @@ -653,53 +664,54 @@ void magic_area_rect(map_local **m, int *x, int *y, int *width, int *height, *y = ty; *width = *height = 1; } + return m; } } + MATCH_END (); + abort(); } -int magic_location_in_area(map_local *m, int x, int y, dumb_ptr<area_t> area) +int magic_location_in_area(Borrowed<map_local> m, int x, int y, dumb_ptr<area_t> area) { - MATCH (*area) + MATCH_BEGIN (*area) { - CASE (const AreaUnion&, a) + MATCH_CASE (const AreaUnion&, a) { return magic_location_in_area(m, x, y, a.a_union[0]) || magic_location_in_area(m, x, y, a.a_union[1]); } - CASE (const location_t&, a_loc) + MATCH_CASE (const location_t&, a_loc) { (void)a_loc; // TODO this can be simplified - map_local *am; int ax, ay, awidth, aheight; - magic_area_rect(&am, &ax, &ay, &awidth, &aheight, *area); + P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area); return (am == m && (x >= ax) && (y >= ay) && (x < ax + awidth) && (y < ay + aheight)); } - CASE (const AreaRect&, a_rect) + MATCH_CASE (const AreaRect&, a_rect) { (void)a_rect; // TODO this is too complicated - map_local *am; int ax, ay, awidth, aheight; - magic_area_rect(&am, &ax, &ay, &awidth, &aheight, *area); + P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area); return (am == m && (x >= ax) && (y >= ay) && (x < ax + awidth) && (y < ay + aheight)); } - CASE (const AreaBar&, a_bar) + MATCH_CASE (const AreaBar&, a_bar) { (void)a_bar; // TODO this is wrong - map_local *am; int ax, ay, awidth, aheight; - magic_area_rect(&am, &ax, &ay, &awidth, &aheight, *area); + P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area); return (am == m && (x >= ax) && (y >= ay) && (x < ax + awidth) && (y < ay + aheight)); } } + MATCH_END (); abort(); } @@ -872,18 +884,17 @@ int fun_hash_entity(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) // ret -1: not a string, ret 1: no such item, ret 0: OK int magic_find_item(Slice<val_t> args, int index, Item *item_, int *stackable) { - struct item_data *item_data; + Option<P<struct item_data>> item_data_ = None; int must_add_sequentially; if (args[index].is<ValInt>()) - item_data = itemdb_exists(wrap<ItemNameId>(static_cast<uint16_t>(ARGINT(index)))); + item_data_ = itemdb_exists(wrap<ItemNameId>(static_cast<uint16_t>(ARGINT(index)))); else if (args[index].is<ValString>()) - item_data = itemdb_searchname(ARGSTR(index)); + item_data_ = itemdb_searchname(ARGSTR(index)); else return -1; - if (!item_data) - return 1; + P<struct item_data> item_data = TRY_UNWRAP(item_data_, return 1); // Very elegant. must_add_sequentially = ( @@ -1082,22 +1093,21 @@ int fun_line_of_sight(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) void magic_random_location(location_t *dest, dumb_ptr<area_t> area) { - MATCH (*area) + MATCH_BEGIN (*area) { - CASE (const AreaUnion&, a) + MATCH_CASE (const AreaUnion&, a) { if (random_::chance({a.a_union[0]->size, area->size})) magic_random_location(dest, a.a_union[0]); else magic_random_location(dest, a.a_union[1]); } - CASE (const location_t&, a_loc) + MATCH_CASE (const location_t&, a_loc) { (void)a_loc; // TODO this can be simplified - map_local *m; int x, y, w, h; - magic_area_rect(&m, &x, &y, &w, &h, *area); + P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area); if (w <= 1) w = 1; @@ -1113,13 +1123,12 @@ void magic_random_location(location_t *dest, dumb_ptr<area_t> area) dest->x = pair.first; dest->y = pair.second; } - CASE (const AreaRect&, a_rect) + MATCH_CASE (const AreaRect&, a_rect) { (void)a_rect; // TODO this can be simplified - map_local *m; int x, y, w, h; - magic_area_rect(&m, &x, &y, &w, &h, *area); + P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area); if (w <= 1) w = 1; @@ -1135,13 +1144,12 @@ void magic_random_location(location_t *dest, dumb_ptr<area_t> area) dest->x = pair.first; dest->y = pair.second; } - CASE (const AreaBar&, a_bar) + MATCH_CASE (const AreaBar&, a_bar) { (void)a_bar; // TODO this is wrong - map_local *m; int x, y, w, h; - magic_area_rect(&m, &x, &y, &w, &h, *area); + P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area); if (w <= 1) w = 1; @@ -1158,6 +1166,7 @@ void magic_random_location(location_t *dest, dumb_ptr<area_t> area) dest->y = pair.second; } } + MATCH_END (); } static @@ -1228,7 +1237,7 @@ int fun_running_status_update(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) static int fun_status_option(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) { - *result = ValInt{(bool((ARGPC(0))->status.option & static_cast<Option>(ARGINT(1))))}; + *result = ValInt{(bool((ARGPC(0))->status.option & static_cast<Opt0>(ARGINT(1))))}; return 0; } @@ -1511,10 +1520,8 @@ int eval_location(dumb_ptr<env_t> env, location_t *dest, const e_location_t *exp && x.is<ValInt>() && y.is<ValInt>()) { MapName name = VString<15>(ZString(m.get_if<ValString>()->v_string)); - map_local *map_id = map_mapname2mapid(name); magic_clear_var(&m); - if (!map_id) - return 1; + P<map_local> map_id = TRY_UNWRAP(map_mapname2mapid(name), return 1); dest->m = map_id; dest->x = x.get_if<ValInt>()->v_int; dest->y = y.get_if<ValInt>()->v_int; @@ -1532,9 +1539,9 @@ int eval_location(dumb_ptr<env_t> env, location_t *dest, const e_location_t *exp static dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_) { - MATCH (expr_) + MATCH_BEGIN (expr_) { - CASE (const e_location_t&, a_loc) + MATCH_CASE (const e_location_t&, a_loc) { location_t loc; if (eval_location(env, &loc, &a_loc)) @@ -1546,7 +1553,7 @@ dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_) return dumb_ptr<area_t>::make(loc); } } - CASE (const ExprAreaUnion&, a) + MATCH_CASE (const ExprAreaUnion&, a) { AreaUnion u; bool fail = false; @@ -1568,7 +1575,7 @@ dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_) } return dumb_ptr<area_t>::make(u); } - CASE (const ExprAreaRect&, a_rect) + MATCH_CASE (const ExprAreaRect&, a_rect) { val_t width, height; magic_eval(env, &width, a_rect.width); @@ -1594,7 +1601,7 @@ dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_) return nullptr; } } - CASE (const ExprAreaBar&, a_bar) + MATCH_CASE (const ExprAreaBar&, a_bar) { val_t width, depth, dir; magic_eval(env, &width, a_bar.width); @@ -1626,6 +1633,7 @@ dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_) } } } + MATCH_END (); abort(); } @@ -1744,14 +1752,14 @@ int magic_signature_check(ZString opname, ZString funname, ZString signature, void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr) { - MATCH (*expr) + MATCH_BEGIN (*expr) { - CASE (const val_t&, e_val) + MATCH_CASE (const val_t&, e_val) { magic_copy_var(dest, &e_val); } - CASE (const e_location_t&, e_location) + MATCH_CASE (const e_location_t&, e_location) { location_t loc; if (eval_location(env, &loc, &e_location)) @@ -1759,14 +1767,14 @@ void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr) else *dest = ValLocation{loc}; } - CASE (const e_area_t&, e_area) + MATCH_CASE (const e_area_t&, e_area) { if (dumb_ptr<area_t> area = eval_area(env, e_area)) *dest = ValArea{area}; else *dest = ValFail(); } - CASE (const ExprFunApp&, e_funapp) + MATCH_CASE (const ExprFunApp&, e_funapp) { val_t arguments[MAX_ARGS]; int args_nr = e_funapp.args_nr; @@ -1800,12 +1808,12 @@ void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr) for (i = 0; i < args_nr; ++i) magic_clear_var(&arguments[i]); } - CASE (const ExprId&, e) + MATCH_CASE (const ExprId&, e) { val_t& v = env->VAR(e.e_id); magic_copy_var(dest, &v); } - CASE (const ExprField&, e_field) + MATCH_CASE (const ExprField&, e_field) { val_t v; int id = e_field.id; @@ -1833,6 +1841,7 @@ void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr) } } } + MATCH_END (); } int magic_eval_int(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr) @@ -1861,4 +1870,5 @@ AString magic_eval_str(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr) return result.get_if<ValString>()->v_string; } } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic-expr.hpp b/src/map/magic-expr.hpp index 294e665..055f37b 100644 --- a/src/map/magic-expr.hpp +++ b/src/map/magic-expr.hpp @@ -21,20 +21,16 @@ #include "fwd.hpp" -#include "../generic/fwd.hpp" - -#include "../range/fwd.hpp" - #include "../strings/zstring.hpp" #include "../strings/literal.hpp" -#include "../mmo/fwd.hpp" - #include "magic-interpreter.t.hpp" namespace tmwa { +namespace map +{ namespace magic { /* @@ -97,14 +93,15 @@ int magic_find_item(Slice<val_t> args, int index, Item *item, int *stackable); default: break; \ } -int magic_location_in_area(map_local *m, int x, int y, dumb_ptr<area_t> area); +int magic_location_in_area(Borrowed<map_local> m, int x, int y, dumb_ptr<area_t> area); /* Helper definitions for dealing with functions and operations */ int magic_signature_check(ZString opname, ZString funname, ZString signature, Slice<val_t> args, int line, int column); -void magic_area_rect(map_local **m, int *x, int *y, int *width, int *height, +Borrowed<map_local> magic_area_rect(int *x, int *y, int *width, int *height, area_t& area); } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic-expr.py b/src/map/magic-expr.py index 0d9db55..f53ddc8 100644 --- a/src/map/magic-expr.py +++ b/src/map/magic-expr.py @@ -1,7 +1,7 @@ class fun_t(object): __slots__ = ('_value') - name = 'tmwa::magic::fun_t' + name = 'tmwa::map::magic::fun_t' depth = 1 enabled = True @@ -31,8 +31,8 @@ class fun_t(object): ''' tests = [ - ('static_cast<tmwa::magic::fun_t *>(nullptr)', + ('static_cast<tmwa::map::magic::fun_t *>(nullptr)', '(fun_t *) nullptr'), - ('new tmwa::magic::fun_t{"name"_s, "sig"_s, \'\\0\', nullptr}', - 'regex:\(fun_t \*\) = \{->name = "name", ->signature = "sig", ->ret_ty = 0 \'\\\\000\', ->fun = (0x)?0}'), + ('new tmwa::map::magic::fun_t{"name"_s, "sig"_s, \'\\0\', nullptr}', + '(fun_t *) = {->name = "name", ->signature = "sig", ->ret_ty = 0 \'\\000\', ->fun = nullptr}'), ] diff --git a/src/map/magic-interpreter-base.cpp b/src/map/magic-interpreter-base.cpp index be9a61a..c2be363 100644 --- a/src/map/magic-interpreter-base.cpp +++ b/src/map/magic-interpreter-base.cpp @@ -25,10 +25,12 @@ #include "../strings/xstring.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" + +#include "../mmo/cxxstdio_enums.hpp" #include "../net/timer.hpp" +#include "globals.hpp" #include "magic.hpp" #include "magic-expr.hpp" #include "magic-interpreter.hpp" @@ -39,6 +41,8 @@ namespace tmwa { +namespace map +{ namespace magic { static @@ -78,9 +82,6 @@ void set_spell(val_t *v, dumb_ptr<spell_t> x) *v = ValSpell{x}; } -magic_conf_t magic_conf; /* Global magic conf */ -env_t magic_default_env = { &magic_conf, nullptr }; - AString magic_find_invocation(XString spellname) { auto it = magic_conf.spells_by_name.find(spellname); @@ -313,22 +314,22 @@ const effect_set_t *spellguard_check_sub(spellguard_check_t *check, if (guard == nullptr) return nullptr; - MATCH (*guard) + MATCH_BEGIN (*guard) { - CASE (const GuardCondition&, s) + MATCH_CASE (const GuardCondition&, s) { if (!magic_eval_int(env, s.s_condition)) return nullptr; } - CASE (const GuardComponents&, s) + MATCH_CASE (const GuardComponents&, s) { copy_components(&check->components, s.s_components); } - CASE (const GuardCatalysts&, s) + MATCH_CASE (const GuardCatalysts&, s) { copy_components(&check->catalysts, s.s_catalysts); } - CASE (const GuardChoice&, s) + MATCH_CASE (const GuardChoice&, s) { spellguard_check_t altcheck = *check; const effect_set_t *retval; @@ -350,15 +351,15 @@ const effect_set_t *spellguard_check_sub(spellguard_check_t *check, return spellguard_check_sub(check, s.s_alt, caster, env, near_miss); } - CASE (const GuardMana&, s) + MATCH_CASE (const GuardMana&, s) { check->mana += magic_eval_int(env, s.s_mana); } - CASE (const GuardCastTime&, s) + MATCH_CASE (const GuardCastTime&, s) { check->casttime += static_cast<interval_t>(magic_eval_int(env, s.s_casttime)); } - CASE (const effect_set_t&, s_effect) + MATCH_CASE (const effect_set_t&, s_effect) { if (spellguard_can_satisfy(check, caster, env, near_miss)) return &s_effect; @@ -366,6 +367,7 @@ const effect_set_t *spellguard_check_sub(spellguard_check_t *check, return nullptr; } } + MATCH_END (); return spellguard_check_sub(check, guard->next, caster, env, near_miss); } @@ -547,4 +549,5 @@ int spell_unbind(dumb_ptr<map_session_data> subject, dumb_ptr<invocation> invoca return 1; } } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic-interpreter-base.hpp b/src/map/magic-interpreter-base.hpp index 4bb41a0..7c00db0 100644 --- a/src/map/magic-interpreter-base.hpp +++ b/src/map/magic-interpreter-base.hpp @@ -21,20 +21,13 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" - -#include "../generic/fwd.hpp" - -#include "../mmo/fwd.hpp" - namespace tmwa { +namespace map +{ namespace magic { -extern magic_conf_t magic_conf; /* Global magic conf */ -extern env_t magic_default_env; /* Fake default environment */ - /** * Adds a component selection to a component holder (which may initially be nullptr) */ @@ -87,4 +80,5 @@ dumb_ptr<spell_t> magic_find_spell(XString invocation); void spell_update_location(dumb_ptr<invocation> invocation); } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic-interpreter.hpp b/src/map/magic-interpreter.hpp index 3bb600c..cbd92a9 100644 --- a/src/map/magic-interpreter.hpp +++ b/src/map/magic-interpreter.hpp @@ -19,39 +19,43 @@ // 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 "magic-interpreter.t.hpp" +#include "fwd.hpp" + #include <cassert> #include <memory> -#include "../strings/fwd.hpp" #include "../strings/rstring.hpp" -#include "../generic/fwd.hpp" - #include "../sexpr/variant.hpp" #include "../net/timer.t.hpp" #include "../mmo/ids.hpp" -#include "../mmo/utils.hpp" #include "map.hpp" -#include "script.hpp" -#include "skill.t.hpp" +#include "script-buffer.hpp" +#include "../mmo/skill.t.hpp" namespace tmwa { +namespace map +{ namespace magic { struct location_t { - map_local *m; + Borrowed<map_local> m; int x, y; + + // This constructor exists solely to work around the design constraints + // of sexpr::Variant<>. See comments in variant.tcc for future plans. + __attribute__((deprecated)) + location_t() noexcept : m(borrow(undefined_gat)), x(), y() {} + location_t(Borrowed<map_local> m_, int x_, int y_) : m(m_), x(x_), y(y_) {} }; struct AreaUnion @@ -622,4 +626,5 @@ struct proc_t {} }; } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic-interpreter.py b/src/map/magic-interpreter.py index cf17b1c..520ab37 100644 --- a/src/map/magic-interpreter.py +++ b/src/map/magic-interpreter.py @@ -1,6 +1,6 @@ class AreaUnion(object): __slots__ = ('_value') - name = 'tmwa::magic::AreaUnion' + name = 'tmwa::map::magic::AreaUnion' enabled = True def __init__(self, value): @@ -27,23 +27,23 @@ class area_t(object): using tmwa::operator "" _s; inline - tmwa::map_local *fake_map_local_x_dup_for_area_t(tmwa::ZString name) + tmwa::Borrowed<tmwa::map::map_local> fake_map_local_x_dup_for_area_t(tmwa::ZString name) { - auto *p = new tmwa::map_local{}; + auto *p = new tmwa::map::map_local{}; p->name_ = tmwa::stringish<tmwa::MapName>(name); - return p; + return tmwa::borrow(*p); } ''' tests = [ - ('tmwa::magic::area_t(tmwa::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456})', - '{<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}'), - ('tmwa::magic::area_t(tmwa::magic::AreaUnion{{tmwa::dumb_ptr<tmwa::magic::area_t>::make(tmwa::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}), tmwa::dumb_ptr<tmwa::magic::area_t>::make(tmwa::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 321, 654})}})', - '{<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::AreaUnion) = {{<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}, {<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 321, y = 654}}, size = 1}}}, size = 2}'), - ('tmwa::magic::area_t(tmwa::magic::AreaRect{tmwa::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}, 789, 102})', - '{<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::AreaRect) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}, width = 789, height = 102}}, size = 80478}'), - ('tmwa::magic::area_t(tmwa::magic::AreaBar{tmwa::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 42, 43}, 123, 456, tmwa::DIR::NW})', - '{<tmwa::sexpr::Variant<tmwa::magic::location_t, tmwa::magic::AreaUnion, tmwa::magic::AreaRect, tmwa::magic::AreaBar>> = {(tmwa::magic::AreaBar) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 43}, width = 123, depth = 456, dir = tmwa::DIR::NW}}, size = 112632}'), + ('tmwa::map::magic::area_t(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}'), + ('tmwa::map::magic::area_t(tmwa::map::magic::AreaUnion{{tmwa::dumb_ptr<tmwa::map::magic::area_t>::make(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}), tmwa::dumb_ptr<tmwa::map::magic::area_t>::make(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 321, 654})}})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaUnion) = {{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}, {<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 321, y = 654}}, size = 1}}}, size = 2}'), + ('tmwa::map::magic::area_t(tmwa::map::magic::AreaRect{tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}, 789, 102})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaRect) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}, width = 789, height = 102}}, size = 80478}'), + ('tmwa::map::magic::area_t(tmwa::map::magic::AreaBar{tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 42, 43}, 123, 456, tmwa::DIR::NW})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaBar) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 43}, width = 123, depth = 456, dir = tmwa::DIR::NW}}, size = 112632}'), ] @@ -55,47 +55,47 @@ class val_t(object): using tmwa::operator "" _s; inline - tmwa::map_local *fake_map_local_x_dup_for_val_t(tmwa::ZString name) + tmwa::Borrowed<tmwa::map::map_local> fake_map_local_x_dup_for_val_t(tmwa::ZString name) { - auto *p = new tmwa::map_local{}; + auto *p = new tmwa::map::map_local{}; p->name_ = tmwa::stringish<tmwa::MapName>(name); - return p; + return tmwa::borrow(*p); } ''' tests = [ - ('tmwa::magic::val_t(tmwa::magic::ValUndef{})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValUndef) = {<No data fields>}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValInt{42})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValInt) = {v_int = 42}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValDir{tmwa::DIR::NW})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValDir) = {v_dir = tmwa::DIR::NW}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValString{"Hello"_s})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValString) = {v_string = "Hello"}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValEntityInt{tmwa::wrap<tmwa::BlockId>(123)})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValEntityInt) = {v_eid = 123}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValEntityPtr{tmwa::dumb_ptr<tmwa::block_list>()})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValEntityPtr) = {v_entity = 0x0}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValLocation{tmwa::magic::location_t{fake_map_local_x_dup_for_val_t("map"_s), 42, 123}})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValLocation) = {v_location = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 123}}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValArea{tmwa::dumb_ptr<tmwa::magic::area_t>()})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValArea) = {v_area = 0x0}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValSpell{tmwa::dumb_ptr<tmwa::magic::spell_t>()})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValSpell) = {v_spell = 0x0}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValInvocationInt{tmwa::wrap<tmwa::BlockId>(123)})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValInvocationInt) = {v_iid = 123}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValInvocationPtr{})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValInvocationPtr) = {v_invocation = 0x0}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValFail{})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValFail) = {<No data fields>}}, <No data fields>}'), - ('tmwa::magic::val_t(tmwa::magic::ValNegative1{})', - '{<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValNegative1) = {<No data fields>}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValUndef{})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValUndef) = {<No data fields>}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValInt{42})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInt) = {v_int = 42}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValDir{tmwa::DIR::NW})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValDir) = {v_dir = tmwa::DIR::NW}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValString{"Hello"_s})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValString) = {v_string = "Hello"}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValEntityInt{tmwa::wrap<tmwa::BlockId>(123)})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValEntityInt) = {v_eid = 123}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValEntityPtr{tmwa::dumb_ptr<tmwa::map::block_list>()})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValEntityPtr) = {v_entity = 0x0}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValLocation{tmwa::map::magic::location_t{fake_map_local_x_dup_for_val_t("map"_s), 42, 123}})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValLocation) = {v_location = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 123}}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValArea{tmwa::dumb_ptr<tmwa::map::magic::area_t>()})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValArea) = {v_area = 0x0}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValSpell{tmwa::dumb_ptr<tmwa::map::magic::spell_t>()})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValSpell) = {v_spell = 0x0}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValInvocationInt{tmwa::wrap<tmwa::BlockId>(123)})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInvocationInt) = {v_iid = 123}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValInvocationPtr{})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInvocationPtr) = {v_invocation = 0x0}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValFail{})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValFail) = {<No data fields>}}, <No data fields>}'), + ('tmwa::map::magic::val_t(tmwa::map::magic::ValNegative1{})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValNegative1) = {<No data fields>}}, <No data fields>}'), ] class ExprAreaUnion(object): __slots__ = ('_value') - name = 'tmwa::magic::ExprAreaUnion' + name = 'tmwa::map::magic::ExprAreaUnion' enabled = True def __init__(self, value): @@ -119,14 +119,14 @@ class e_area_t(object): enabled = True tests = [ - ('tmwa::magic::e_area_t(tmwa::magic::e_location_t())', - '{<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'), - ('tmwa::magic::e_area_t(tmwa::magic::ExprAreaUnion{{tmwa::dumb_ptr<tmwa::magic::e_area_t>::make(tmwa::magic::e_location_t()), tmwa::dumb_ptr<tmwa::magic::e_area_t>::make(tmwa::magic::e_location_t())}})', - '{<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::ExprAreaUnion) = {{<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}, {<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}}, <No data fields>}'), - ('tmwa::magic::e_area_t(tmwa::magic::ExprAreaRect{tmwa::magic::e_location_t(), tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::expr_t>()})', - '{<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::ExprAreaRect) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, height = 0x0}}, <No data fields>}'), - ('tmwa::magic::e_area_t(tmwa::magic::ExprAreaBar{tmwa::magic::e_location_t(), tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::expr_t>()})', - '{<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::ExprAreaBar) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, depth = 0x0, dir = 0x0}}, <No data fields>}'), + ('tmwa::map::magic::e_area_t(tmwa::map::magic::e_location_t())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'), + ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaUnion{{tmwa::dumb_ptr<tmwa::map::magic::e_area_t>::make(tmwa::map::magic::e_location_t()), tmwa::dumb_ptr<tmwa::map::magic::e_area_t>::make(tmwa::map::magic::e_location_t())}})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaUnion) = {{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}, {<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}}, <No data fields>}'), + ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaRect{tmwa::map::magic::e_location_t(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>()})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaRect) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, height = 0x0}}, <No data fields>}'), + ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaBar{tmwa::map::magic::e_location_t(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>()})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaBar) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, depth = 0x0, dir = 0x0}}, <No data fields>}'), ] @@ -135,18 +135,18 @@ class expr_t(object): enabled = True tests = [ - ('tmwa::magic::expr_t(tmwa::magic::val_t(tmwa::magic::ValUndef()))', - '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::val_t) = {<tmwa::sexpr::Variant<tmwa::magic::ValUndef, tmwa::magic::ValInt, tmwa::magic::ValDir, tmwa::magic::ValString, tmwa::magic::ValEntityInt, tmwa::magic::ValEntityPtr, tmwa::magic::ValLocation, tmwa::magic::ValArea, tmwa::magic::ValSpell, tmwa::magic::ValInvocationInt, tmwa::magic::ValInvocationPtr, tmwa::magic::ValFail, tmwa::magic::ValNegative1>> = {(tmwa::magic::ValUndef) = {<No data fields>}}, <No data fields>}}, <No data fields>}'), - ('tmwa::magic::expr_t(tmwa::magic::e_location_t())', - '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'), - ('tmwa::magic::expr_t(tmwa::magic::e_area_t(tmwa::magic::e_location_t()))', - '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::e_area_t) = {<tmwa::sexpr::Variant<tmwa::magic::e_location_t, tmwa::magic::ExprAreaUnion, tmwa::magic::ExprAreaRect, tmwa::magic::ExprAreaBar>> = {(tmwa::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}, <No data fields>}'), - ('tmwa::magic::expr_t(tmwa::magic::ExprFunApp())', - '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::ExprFunApp) = {funp = (fun_t *) nullptr, line_nr = 0, column = 0, args_nr = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, <No data fields>}'), - ('tmwa::magic::expr_t(tmwa::magic::ExprId{123})', - '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::ExprId) = {e_id = 123}}, <No data fields>}'), - ('tmwa::magic::expr_t(tmwa::magic::ExprField{tmwa::dumb_ptr<tmwa::magic::expr_t>(), 42})', - '{<tmwa::sexpr::Variant<tmwa::magic::val_t, tmwa::magic::e_location_t, tmwa::magic::e_area_t, tmwa::magic::ExprFunApp, tmwa::magic::ExprId, tmwa::magic::ExprField>> = {(tmwa::magic::ExprField) = {expr = 0x0, id = 42}}, <No data fields>}'), + ('tmwa::map::magic::expr_t(tmwa::map::magic::val_t(tmwa::map::magic::ValUndef()))', + '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::val_t) = {<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValUndef) = {<No data fields>}}, <No data fields>}}, <No data fields>}'), + ('tmwa::map::magic::expr_t(tmwa::map::magic::e_location_t())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'), + ('tmwa::map::magic::expr_t(tmwa::map::magic::e_area_t(tmwa::map::magic::e_location_t()))', + '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::e_area_t) = {<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}, <No data fields>}'), + ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprFunApp())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprFunApp) = {funp = (fun_t *) nullptr, line_nr = 0, column = 0, args_nr = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, <No data fields>}'), + ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprId{123})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprId) = {e_id = 123}}, <No data fields>}'), + ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprField{tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), 42})', + '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprField) = {expr = 0x0, id = 42}}, <No data fields>}'), ] @@ -154,30 +154,30 @@ class effect_t(object): enabled = True tests = [ - ('tmwa::magic::effect_t(tmwa::magic::EffectSkip{}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectSkip) = {<No data fields>}}, next = 0x0}'), - ('tmwa::magic::effect_t(tmwa::magic::EffectAbort{}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectAbort) = {<No data fields>}}, next = 0x0}'), - ('tmwa::magic::effect_t(tmwa::magic::EffectAssign{42, tmwa::dumb_ptr<tmwa::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectAssign) = {id = 42, expr = 0x0}}, next = 0x0}'), - ('tmwa::magic::effect_t(tmwa::magic::EffectForEach{123, tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>(), tmwa::magic::FOREACH_FILTER::PC}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectForEach) = {id = 123, area = 0x0, body = 0x0, filter = tmwa::magic::FOREACH_FILTER::PC}}, next = 0x0}'), - ('tmwa::magic::effect_t(tmwa::magic::EffectFor{42, tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectFor) = {id = 42, start = 0x0, stop = 0x0, body = 0x0}}, next = 0x0}'), - ('tmwa::magic::effect_t(tmwa::magic::EffectIf{tmwa::dumb_ptr<tmwa::magic::expr_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectIf) = {cond = 0x0, true_branch = 0x0, false_branch = 0x0}}, next = 0x0}'), - ('tmwa::magic::effect_t(tmwa::magic::EffectSleep{tmwa::dumb_ptr<tmwa::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectSleep) = {e_sleep = 0x0}}, next = 0x0}'), - ('tmwa::magic::effect_t(tmwa::magic::EffectScript{tmwa::dumb_ptr<const tmwa::ScriptBuffer>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectScript) = {e_script = 0x0}}, next = 0x0}'), - ('tmwa::magic::effect_t(tmwa::magic::EffectBreak{}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectBreak) = {<No data fields>}}, next = 0x0}'), - ('tmwa::magic::effect_t(tmwa::magic::EffectOp(), tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectOp) = {opp = (op_t *) nullptr, args_nr = 0, line_nr = 0, column = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, next = 0x0}'), - ('tmwa::magic::effect_t(tmwa::magic::EffectEnd{}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectEnd) = {<No data fields>}}, next = 0x0}'), - ('tmwa::magic::effect_t(tmwa::magic::EffectCall{nullptr, tmwa::dumb_ptr<std::vector<tmwa::dumb_ptr<tmwa::magic::expr_t>>>(), tmwa::dumb_ptr<tmwa::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::EffectSkip, tmwa::magic::EffectAbort, tmwa::magic::EffectAssign, tmwa::magic::EffectForEach, tmwa::magic::EffectFor, tmwa::magic::EffectIf, tmwa::magic::EffectSleep, tmwa::magic::EffectScript, tmwa::magic::EffectBreak, tmwa::magic::EffectOp, tmwa::magic::EffectEnd, tmwa::magic::EffectCall>> = {(tmwa::magic::EffectCall) = {formalv = 0x0, actualvp = 0x0, body = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectSkip{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectSkip) = {<No data fields>}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectAbort{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectAbort) = {<No data fields>}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectAssign{42, tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectAssign) = {id = 42, expr = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectForEach{123, tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::map::magic::FOREACH_FILTER::PC}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectForEach) = {id = 123, area = 0x0, body = 0x0, filter = tmwa::map::magic::FOREACH_FILTER::PC}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectFor{42, tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectFor) = {id = 42, start = 0x0, stop = 0x0, body = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectIf{tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectIf) = {cond = 0x0, true_branch = 0x0, false_branch = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectSleep{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectSleep) = {e_sleep = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectScript{tmwa::dumb_ptr<const tmwa::map::ScriptBuffer>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectScript) = {e_script = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectBreak{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectBreak) = {<No data fields>}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectOp(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectOp) = {opp = (op_t *) nullptr, args_nr = 0, line_nr = 0, column = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectEnd{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectEnd) = {<No data fields>}}, next = 0x0}'), + ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectCall{nullptr, tmwa::dumb_ptr<std::vector<tmwa::dumb_ptr<tmwa::map::magic::expr_t>>>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectCall) = {formalv = nullptr, actualvp = 0x0, body = 0x0}}, next = 0x0}'), ] @@ -185,20 +185,20 @@ class spellguard_t(object): enabled = True tests = [ - ('tmwa::magic::spellguard_t(tmwa::magic::GuardCondition{tmwa::dumb_ptr<tmwa::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardCondition) = {s_condition = 0x0}}, next = 0x0}'), - ('tmwa::magic::spellguard_t(tmwa::magic::GuardMana{tmwa::dumb_ptr<tmwa::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardMana) = {s_mana = 0x0}}, next = 0x0}'), - ('tmwa::magic::spellguard_t(tmwa::magic::GuardCastTime{tmwa::dumb_ptr<tmwa::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardCastTime) = {s_casttime = 0x0}}, next = 0x0}'), - ('tmwa::magic::spellguard_t(tmwa::magic::GuardComponents{tmwa::dumb_ptr<tmwa::magic::component_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardComponents) = {s_components = 0x0}}, next = 0x0}'), - ('tmwa::magic::spellguard_t(tmwa::magic::GuardCatalysts{tmwa::dumb_ptr<tmwa::magic::component_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardCatalysts) = {s_catalysts = 0x0}}, next = 0x0}'), - ('tmwa::magic::spellguard_t(tmwa::magic::GuardChoice{tmwa::dumb_ptr<tmwa::magic::spellguard_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::GuardChoice) = {s_alt = 0x0}}, next = 0x0}'), - ('tmwa::magic::spellguard_t(tmwa::magic::effect_set_t{tmwa::dumb_ptr<tmwa::magic::effect_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>(), tmwa::dumb_ptr<tmwa::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::GuardCondition, tmwa::magic::GuardMana, tmwa::magic::GuardCastTime, tmwa::magic::GuardComponents, tmwa::magic::GuardCatalysts, tmwa::magic::GuardChoice, tmwa::magic::effect_set_t>> = {(tmwa::magic::effect_set_t) = {effect = 0x0, at_trigger = 0x0, at_end = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCondition{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCondition) = {s_condition = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardMana{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardMana) = {s_mana = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCastTime{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCastTime) = {s_casttime = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardComponents{tmwa::dumb_ptr<tmwa::map::magic::component_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardComponents) = {s_components = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCatalysts{tmwa::dumb_ptr<tmwa::map::magic::component_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCatalysts) = {s_catalysts = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardChoice{tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardChoice) = {s_alt = 0x0}}, next = 0x0}'), + ('tmwa::map::magic::spellguard_t(tmwa::map::magic::effect_set_t{tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::effect_set_t) = {effect = 0x0, at_trigger = 0x0, at_end = 0x0}}, next = 0x0}'), ] @@ -206,10 +206,10 @@ class cont_activation_record_t(object): enabled = True tests = [ - ('tmwa::magic::cont_activation_record_t(tmwa::magic::CarForEach{42, true, tmwa::dumb_ptr<tmwa::magic::effect_t>(), tmwa::dumb_ptr<std::vector<tmwa::BlockId>>(), 123}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::CarForEach, tmwa::magic::CarFor, tmwa::magic::CarProc>> = {(tmwa::magic::CarForEach) = {id = 42, ty_is_spell_not_entity = true, body = 0x0, entities_vp = 0x0, index = 123}}, return_location = 0x0}'), - ('tmwa::magic::cont_activation_record_t(tmwa::magic::CarFor{42, tmwa::dumb_ptr<tmwa::magic::effect_t>(), 123, 456}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::CarForEach, tmwa::magic::CarFor, tmwa::magic::CarProc>> = {(tmwa::magic::CarFor) = {id = 42, body = 0x0, current = 123, stop = 456}}, return_location = 0x0}'), - ('tmwa::magic::cont_activation_record_t(tmwa::magic::CarProc{123, nullptr, tmwa::dumb_ptr<tmwa::magic::val_t[]>()}, tmwa::dumb_ptr<tmwa::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::magic::CarForEach, tmwa::magic::CarFor, tmwa::magic::CarProc>> = {(tmwa::magic::CarProc) = {args_nr = 123, formalap = 0x0, old_actualpa = 0x0 = {sz = 0}}}, return_location = 0x0}'), + ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarForEach{42, true, tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<std::vector<tmwa::BlockId>>(), 123}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarForEach) = {id = 42, ty_is_spell_not_entity = true, body = 0x0, entities_vp = 0x0, index = 123}}, return_location = 0x0}'), + ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarFor{42, tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), 123, 456}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarFor) = {id = 42, body = 0x0, current = 123, stop = 456}}, return_location = 0x0}'), + ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarProc{123, nullptr, tmwa::dumb_ptr<tmwa::map::magic::val_t[]>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', + '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarProc) = {args_nr = 123, formalap = nullptr, old_actualpa = 0x0 = {sz = 0}}}, return_location = 0x0}'), ] diff --git a/src/map/magic-interpreter.t.hpp b/src/map/magic-interpreter.t.hpp index ab151fc..e302354 100644 --- a/src/map/magic-interpreter.t.hpp +++ b/src/map/magic-interpreter.t.hpp @@ -26,6 +26,8 @@ namespace tmwa { +namespace map +{ namespace magic { enum class SPELLARG : uint8_t @@ -79,4 +81,5 @@ ENUM_BITWISE_OPERATORS(INVOCATION_FLAG) } using e::INVOCATION_FLAG; } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic-stmt.cpp b/src/map/magic-stmt.cpp index 2a657fa..4d8330a 100644 --- a/src/map/magic-stmt.cpp +++ b/src/map/magic-stmt.cpp @@ -29,7 +29,8 @@ #include "../generic/random2.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" + +#include "../mmo/cxxstdio_enums.hpp" #include "../net/timer.hpp" @@ -42,7 +43,9 @@ #include "magic-interpreter-base.hpp" #include "mob.hpp" #include "npc.hpp" +#include "npc-parse.hpp" #include "pc.hpp" +#include "script-call.hpp" #include "skill.hpp" #include "../poison.hpp" @@ -50,6 +53,8 @@ namespace tmwa { +namespace map +{ namespace magic { /* used for local spell effects */ @@ -58,17 +63,18 @@ constexpr Species INVISIBLE_NPC = wrap<Species>(127); static void clear_activation_record(cont_activation_record_t *ar) { - MATCH (*ar) + MATCH_BEGIN (*ar) { - CASE (CarForEach&, c_foreach) + MATCH_CASE (CarForEach&, c_foreach) { c_foreach.entities_vp.delete_(); } - CASE (CarProc&, c_proc) + MATCH_CASE (CarProc&, c_proc) { c_proc.old_actualpa.delete_(); } } + MATCH_END (); } static @@ -218,7 +224,7 @@ BlockId trigger_spell(BlockId subject, BlockId spell) } static -void entity_warp(dumb_ptr<block_list> target, map_local *destm, int destx, int desty); +void entity_warp(dumb_ptr<block_list> target, Borrowed<map_local> destm, int destx, int desty); static void char_update(dumb_ptr<map_session_data> character) @@ -261,7 +267,7 @@ void timer_callback_effect_npc_delete(TimerData *, tick_t, BlockId npc_id) } static -dumb_ptr<npc_data> local_spell_effect(map_local *m, int x, int y, int effect, +dumb_ptr<npc_data> local_spell_effect(Borrowed<map_local> m, int x, int y, int effect, interval_t tdelay) { /* 1 minute should be enough for all interesting spell effects, I hope */ @@ -421,7 +427,7 @@ int op_messenger_npc(dumb_ptr<env_t>, Slice<val_t> args) } static -void entity_warp(dumb_ptr<block_list> target, map_local *destm, int destx, int desty) +void entity_warp(dumb_ptr<block_list> target, Borrowed<map_local> destm, int destx, int desty) { if (target->bl_type == BL::PC || target->bl_type == BL::MOB) { @@ -767,8 +773,8 @@ int op_injure(dumb_ptr<env_t> env, Slice<val_t> args) if (target->bl_type == BL::PC && !target->bl_m->flag.get(MapFlag::PVP) - && !target->is_player()->special_state.killable - && (caster->bl_type != BL::PC || !caster->is_player()->special_state.killer)) + && (caster->bl_type == BL::PC) + && ((caster->is_player()->state.pvpchannel > 1) && (target->is_player()->state.pvpchannel != caster->is_player()->state.pvpchannel))) return 0; /* Cannot damage other players outside of pvp */ if (target != caster) @@ -786,7 +792,7 @@ int op_injure(dumb_ptr<env_t> env, Slice<val_t> args) // display damage first, because dealing damage may deallocate the target. clif_damage(caster, target, gettick(), interval_t::zero(), interval_t::zero(), - damage_caused, 0, DamageType::NORMAL, 0); + damage_caused, 0, DamageType::NORMAL); if (caster->bl_type == BL::PC) { @@ -995,9 +1001,9 @@ dumb_ptr<effect_t> return_to_stack(dumb_ptr<invocation> invocation_) { cont_activation_record_t *ar = &invocation_->stack.back(); - MATCH (*ar) + MATCH_BEGIN (*ar) { - CASE (const CarProc&, c_proc) + MATCH_CASE (const CarProc&, c_proc) { dumb_ptr<effect_t> ret = ar->return_location; for (int i = 0; i < c_proc.args_nr; i++) @@ -1014,7 +1020,7 @@ dumb_ptr<effect_t> return_to_stack(dumb_ptr<invocation> invocation_) return ret; } - CASE (CarForEach&, c_foreach) + MATCH_CASE (CarForEach&, c_foreach) { BlockId entity_id; val_t *var = &invocation_->env->varu[c_foreach.id]; @@ -1045,7 +1051,7 @@ dumb_ptr<effect_t> return_to_stack(dumb_ptr<invocation> invocation_) return c_foreach.body; } - CASE (CarFor&, c_for) + MATCH_CASE (CarFor&, c_for) { if (c_for.current > c_for.stop) { @@ -1062,6 +1068,7 @@ dumb_ptr<effect_t> return_to_stack(dumb_ptr<invocation> invocation_) return c_for.body; } } + MATCH_END (); abort(); } } @@ -1133,46 +1140,43 @@ void find_entities_in_area(area_t& area_, std::vector<BlockId> *entities_vp, FOREACH_FILTER filter) { - MATCH (area_) + MATCH_BEGIN (area_) { - CASE (const AreaUnion&, a) + MATCH_CASE (const AreaUnion&, a) { find_entities_in_area(*a.a_union[0], entities_vp, filter); find_entities_in_area(*a.a_union[1], entities_vp, filter); } - CASE (const location_t&, a_loc) + MATCH_CASE (const location_t&, a_loc) { (void)a_loc; // TODO this can be simplified - map_local *m; int x, y, width, height; - magic_area_rect(&m, &x, &y, &width, &height, area_); + Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_); map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter), m, x, y, x + width, y + height, BL::NUL /* filter elsewhere */); } - CASE (const AreaRect&, a_rect) + MATCH_CASE (const AreaRect&, a_rect) { (void)a_rect; // TODO this can be simplified - map_local *m; int x, y, width, height; - magic_area_rect(&m, &x, &y, &width, &height, area_); + Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_); map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter), m, x, y, x + width, y + height, BL::NUL /* filter elsewhere */); } - CASE (const AreaBar&, a_bar) + MATCH_CASE (const AreaBar&, a_bar) { (void)a_bar; // TODO this is wrong - map_local *m; int x, y, width, height; - magic_area_rect(&m, &x, &y, &width, &height, area_); + Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_); map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter), m, x, y, @@ -1180,6 +1184,7 @@ void find_entities_in_area(area_t& area_, BL::NUL /* filter elsewhere */); } } + MATCH_END (); } static @@ -1312,13 +1317,13 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete) dumb_ptr<effect_t> next = e->next; int i; - MATCH (*e) + MATCH_BEGIN (*e) { - CASE (const EffectSkip&, e_) + MATCH_CASE (const EffectSkip&, e_) { (void)e_; } - CASE (const EffectAbort&, e_) + MATCH_CASE (const EffectAbort&, e_) { (void)e_; invocation_->flags |= INVOCATION_FLAG::ABORTED; @@ -1326,34 +1331,34 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete) clear_stack(invocation_); next = nullptr; } - CASE (const EffectEnd&, e_) + MATCH_CASE (const EffectEnd&, e_) { (void)e_; clear_stack(invocation_); next = nullptr; } - CASE (const EffectAssign&, e_assign) + MATCH_CASE (const EffectAssign&, e_assign) { magic_eval(invocation_->env, &invocation_->env->varu[e_assign.id], e_assign.expr); } - CASE (const EffectForEach&, e_foreach) + MATCH_CASE (const EffectForEach&, e_foreach) { next = run_foreach(invocation_, &e_foreach, next); } - CASE (const EffectFor&, e_for) + MATCH_CASE (const EffectFor&, e_for) { next = run_for (invocation_, &e_for, next); } - CASE (const EffectIf&, e_if) + MATCH_CASE (const EffectIf&, e_if) { if (magic_eval_int(invocation_->env, e_if.cond)) next = e_if.true_branch; else next = e_if.false_branch; } - CASE (const EffectSleep&, e_) + MATCH_CASE (const EffectSleep&, e_) { interval_t sleeptime = static_cast<interval_t>( magic_eval_int(invocation_->env, e_.e_sleep)); @@ -1361,7 +1366,7 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete) if (sleeptime > interval_t::zero()) return sleeptime; } - CASE (const EffectScript&, e_) + MATCH_CASE (const EffectScript&, e_) { dumb_ptr<map_session_data> caster = map_id_is_player(invocation_->caster); if (caster) @@ -1391,7 +1396,7 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete) // dealing with an NPC int newpos = run_script_l( - ScriptPointer(&*e_.e_script, invocation_->script_pos), + ScriptPointer(borrow(*e_.e_script), invocation_->script_pos), message_recipient, invocation_->bl_id, arg); /* Returns the new script position, or -1 once the script is finished */ @@ -1408,12 +1413,12 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete) } REFRESH_INVOCATION; // Script may have killed the caster } - CASE (const EffectBreak&, e_) + MATCH_CASE (const EffectBreak&, e_) { (void)e_; next = return_to_stack(invocation_); } - CASE (const EffectOp&, e_op) + MATCH_CASE (const EffectOp&, e_op) { op_t *op = e_op.opp; val_t args[MAX_ARGS]; @@ -1432,11 +1437,12 @@ interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete) REFRESH_INVOCATION; // Effect may have killed the caster } - CASE (const EffectCall&, e_call) + MATCH_CASE (const EffectCall&, e_call) { next = run_call(invocation_, &e_call, next); } } + MATCH_END (); break_match: if (!next) @@ -1535,4 +1541,5 @@ int spell_attack(BlockId caster_id, BlockId target_id) return 1; } } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic-stmt.hpp b/src/map/magic-stmt.hpp index 0385858..3b04fe3 100644 --- a/src/map/magic-stmt.hpp +++ b/src/map/magic-stmt.hpp @@ -21,17 +21,15 @@ #include "fwd.hpp" -#include "../range/fwd.hpp" - #include "../strings/zstring.hpp" -#include "../generic/fwd.hpp" - -#include "skill.t.hpp" +#include "../mmo/skill.t.hpp" namespace tmwa { +namespace map +{ namespace magic { struct op_t @@ -91,4 +89,5 @@ int spell_attack(BlockId caster, BlockId target); void spell_free_invocation(dumb_ptr<invocation> invocation); } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic-stmt.py b/src/map/magic-stmt.py index 14289ef..7cc43d0 100644 --- a/src/map/magic-stmt.py +++ b/src/map/magic-stmt.py @@ -1,7 +1,7 @@ class op_t(object): __slots__ = ('_value') - name = 'tmwa::magic::op_t' + name = 'tmwa::map::magic::op_t' depth = 1 enabled = True @@ -30,8 +30,8 @@ class op_t(object): ''' tests = [ - ('static_cast<tmwa::magic::op_t *>(nullptr)', + ('static_cast<tmwa::map::magic::op_t *>(nullptr)', '(op_t *) nullptr'), - ('new tmwa::magic::op_t{"name"_s, "sig"_s, nullptr}', - 'regex:\(op_t \*\) = \{->name = "name", ->signature = "sig", ->op = (0x)?0}'), + ('new tmwa::map::magic::op_t{"name"_s, "sig"_s, nullptr}', + '(op_t *) = {->name = "name", ->signature = "sig", ->op = nullptr}'), ] diff --git a/src/map/magic-v2.cpp b/src/map/magic-v2.cpp index 5b375b2..52b1b8f 100644 --- a/src/map/magic-v2.cpp +++ b/src/map/magic-v2.cpp @@ -24,6 +24,8 @@ #include <map> #include <set> +#include "../range/slice.hpp" + #include "../strings/rstring.hpp" #include "../strings/literal.hpp" @@ -34,27 +36,28 @@ #include "../sexpr/parser.hpp" +#include "../ast/script.hpp" + +#include "globals.hpp" #include "itemdb.hpp" #include "magic-expr.hpp" #include "magic-interpreter.hpp" #include "magic-interpreter-base.hpp" #include "magic-stmt.hpp" +#include "script-parse.hpp" #include "../poison.hpp" namespace tmwa { +namespace map +{ namespace magic { namespace magic_v2 { static - std::map<RString, proc_t> procs; - static - std::map<RString, val_t> const_defm; - - static size_t intern_id(ZString id_name) { // TODO use InternPool @@ -134,14 +137,15 @@ namespace magic_v2 /* For FOR and FOREACH, we use special stack handlers and thus don't have to set * the continuation. It's only IF that we need to handle in this fashion. */ - MATCH (*src) + MATCH_BEGIN (*src) { - CASE (EffectIf&, e_if) + MATCH_CASE (EffectIf&, e_if) { set_effect_continuation(e_if.true_branch, continuation); set_effect_continuation(e_if.false_branch, continuation); } } + MATCH_END (); if (src->next) set_effect_continuation(src->next, continuation); @@ -172,13 +176,14 @@ namespace magic_v2 } /* If the premise is a disjunction, b is the continuation of _all_ branches */ - MATCH (*a) + MATCH_BEGIN (*a) { - CASE(const GuardChoice&, s) + MATCH_CASE (const GuardChoice&, s) { spellguard_implication(s.s_alt, b); } } + MATCH_END (); if (a->next) spellguard_implication(a->next, b); @@ -575,9 +580,9 @@ namespace magic_v2 { count = 1; - item_data *item = itemdb_searchname(s._str); - if (!item) - return fail(s, "no such item"_s); + Borrowed<item_data> item = TRY_UNWRAP(itemdb_searchname(s._str), + return fail(s, "no such item"_s) + ); id = item->nameid; return true; } @@ -591,9 +596,9 @@ namespace magic_v2 if (s._list[1]._type != sexpr::STRING) return fail(s._list[1], "item pair second not name"_s); - item_data *item = itemdb_searchname(s._list[1]._str); - if (!item) - return fail(s, "no such item"_s); + Borrowed<item_data> item = TRY_UNWRAP(itemdb_searchname(s._list[1]._str), + return fail(s, "no such item"_s) + ); id = item->nameid; return true; } @@ -782,7 +787,20 @@ namespace magic_v2 if (s._list[1]._type != sexpr::STRING) return fail(s._list[1], "not string"_s); ZString body = s._list[1]._str; - std::unique_ptr<const ScriptBuffer> script = parse_script(body, s._list[1]._span.begin.line, true); + auto begin = s._list[1]._span.begin; + io::LineCharReader lr(io::from_string, begin.filename, body, begin.line, begin.column); + ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.implicit_end = true; + opt.no_event = true; + auto code_res = ast::script::parse_script_body(lr, opt); + if (code_res.get_failure()) + { + PRINTF("%s\n"_fmt, code_res.get_failure()); + } + auto code = TRY_UNWRAP(code_res.get_success(), + return fail(s._list[1], "script does not compile"_s)); + std::unique_ptr<const ScriptBuffer> script = compile_script(STRPRINTF("script magic %s:%d"_fmt, begin.filename, begin.line), code, true); if (!script) return fail(s._list[1], "script does not compile"_s); EffectScript e; @@ -1273,4 +1291,5 @@ bool load_magic_file_v2(ZString filename) return rv; } } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic-v2.hpp b/src/map/magic-v2.hpp index 9ad44a9..fac2773 100644 --- a/src/map/magic-v2.hpp +++ b/src/map/magic-v2.hpp @@ -25,10 +25,13 @@ namespace tmwa { +namespace map +{ namespace magic { bool magic_init0(); // must be called after itemdb initialization bool load_magic_file_v2(ZString filename); } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic.cpp b/src/map/magic.cpp index a0238b5..418312a 100644 --- a/src/map/magic.cpp +++ b/src/map/magic.cpp @@ -28,6 +28,7 @@ #include "../io/cxxstdio.hpp" +#include "globals.hpp" #include "magic-expr.hpp" #include "magic-interpreter.hpp" #include "magic-interpreter-base.hpp" @@ -40,6 +41,8 @@ namespace tmwa { +namespace map +{ namespace magic { /// Return a pair of strings, {spellname, parameter} @@ -71,7 +74,7 @@ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation) { if (pc_isdead(caster)) return 0; - if (bool(caster->status.option & Option::HIDE)) + if (bool(caster->status.option & Opt0::HIDE)) return 0; // No spellcasting while hidden int power = caster->matk1; @@ -123,4 +126,5 @@ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation) return 0; /* Not a spell */ } } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/magic.hpp b/src/map/magic.hpp index a420872..70d40dc 100644 --- a/src/map/magic.hpp +++ b/src/map/magic.hpp @@ -21,16 +21,14 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" - -#include "../generic/fwd.hpp" - #include "map.t.hpp" -#include "skill.t.hpp" +#include "../mmo/skill.t.hpp" namespace tmwa { +namespace map +{ namespace magic { /** @@ -46,4 +44,5 @@ namespace magic */ int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation); } // namespace magic +} // namespace map } // namespace tmwa diff --git a/src/map/main.cpp b/src/map/main.cpp index c16f642..f19272d 100644 --- a/src/map/main.cpp +++ b/src/map/main.cpp @@ -17,7 +17,7 @@ // 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 "../mmo/core.hpp" +#include "../high/core.hpp" #include "map.hpp" @@ -26,6 +26,9 @@ namespace tmwa { +namespace map +{ +} // namespace map } // namespace tmwa int main(int argc, char **argv) diff --git a/src/map/map.cpp b/src/map/map.cpp index 4a25029..c1d760a 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -45,34 +45,41 @@ #include "../generic/random2.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" +#include "../io/extract.hpp" #include "../io/read.hpp" +#include "../io/span.hpp" #include "../io/tty.hpp" #include "../io/write.hpp" #include "../net/socket.hpp" #include "../net/timer.hpp" +#include "../net/timestamp-utils.hpp" #include "../mmo/config_parse.hpp" -#include "../mmo/core.hpp" -#include "../mmo/extract.hpp" -#include "../mmo/utils.hpp" +#include "../mmo/cxxstdio_enums.hpp" #include "../mmo/version.hpp" +#include "../high/core.hpp" + #include "atcommand.hpp" #include "battle.hpp" +#include "battle_conf.hpp" #include "chrif.hpp" #include "clif.hpp" +#include "globals.hpp" #include "grfio.hpp" #include "itemdb.hpp" #include "magic-interpreter.hpp" // for is_spell inline body #include "magic-stmt.hpp" #include "magic-v2.hpp" +#include "map_conf.hpp" #include "mob.hpp" +#include "quest.hpp" #include "npc.hpp" +#include "npc-parse.hpp" #include "party.hpp" #include "pc.hpp" -#include "script.hpp" +#include "script-startup.hpp" #include "skill.hpp" #include "storage.hpp" #include "trade.hpp" @@ -82,53 +89,16 @@ namespace tmwa { -DMap<BlockId, dumb_ptr<block_list>> id_db; - -UPMap<MapName, map_abstract> maps_db; - -static -DMap<CharName, dumb_ptr<map_session_data>> nick_db; - -struct charid2nick -{ - CharName nick; - int req_id; -}; - -static -Map<CharId, struct charid2nick> charid_db; - -static -int users = 0; -static -Array<dumb_ptr<block_list>, unwrap<BlockId>(MAX_FLOORITEM)> object; -static -BlockId first_free_object_id = BlockId(); - -interval_t autosave_time = DEFAULT_AUTOSAVE_INTERVAL; -int save_settings = 0xFFFF; - -AString motd_txt = "conf/motd.txt"_s; - -CharName wisp_server_name = stringish<CharName>("Server"_s); // can be modified in char-server configuration file - -static -void map_delmap(MapName mapname); - void SessionDeleter::operator()(SessionData *sd) { - really_delete1 static_cast<map_session_data *>(sd); + really_delete1 static_cast<map::map_session_data *>(sd); } -VString<49> convert_for_printf(NpcEvent ev) +namespace map { - return STRNPRINTF(50, "%s::%s"_fmt, ev.npc, ev.label); -} -bool extract(XString str, NpcEvent *ev) -{ - XString mid; - return extract(str, record<':'>(&ev->npc, &mid, &ev->label)) && !mid; -} +const CharName WISP_SERVER_NAME = stringish<CharName>("Server"_s); + +map_local undefined_gat = [](){ map_local rv {}; rv.name_ = stringish<MapName>("undefined.gat"_s); return rv; }(); /*========================================== * 全map鯖総計での接続数設定 @@ -137,7 +107,7 @@ bool extract(XString str, NpcEvent *ev) */ void map_setusers(int n) { - users = n; + world_user_count = n; } /*========================================== @@ -146,14 +116,9 @@ void map_setusers(int n) */ int map_getusers(void) { - return users; + return world_user_count; } -static -int block_free_lock = 0; -static -std::vector<dumb_ptr<block_list>> block_free; - void MapBlockLock::freeblock(dumb_ptr<block_list> bl) { if (block_free_lock == 0) @@ -178,12 +143,6 @@ MapBlockLock::~MapBlockLock() } } -/// This is a dummy entry that is shared by all the linked lists, -/// so that any entry can unlink itself without worrying about -/// whether it was the the head of the list. -static -struct block_list bl_head; - /*========================================== * map[]のblock_listに追加 * mobは数が多いので別リスト @@ -202,10 +161,10 @@ int map_addblock(dumb_ptr<block_list> bl) return 0; } - map_local *m = bl->bl_m; + P<map_local> m = bl->bl_m; int x = bl->bl_x; int y = bl->bl_y; - if (!m || + if (m == borrow(undefined_gat) || x < 0 || x >= m->xs || y < 0 || y >= m->ys) return 1; @@ -283,7 +242,7 @@ int map_delblock(dumb_ptr<block_list> bl) * セル上のPCとMOBの数を数える (グランドクロス用) *------------------------------------------ */ -int map_count_oncell(map_local *m, int x, int y) +int map_count_oncell(Borrowed<map_local> m, int x, int y) { int bx, by; dumb_ptr<block_list> bl = nullptr; @@ -318,14 +277,17 @@ int map_count_oncell(map_local *m, int x, int y) *------------------------------------------ */ void map_foreachinarea(std::function<void(dumb_ptr<block_list>)> func, - map_local *m, + Borrowed<map_local> m, int x0, int y0, int x1, int y1, BL type) { std::vector<dumb_ptr<block_list>> bl_list; - if (!m) - return; + // there are some broadcasts during startup + // disable then + if (m == borrow(undefined_gat)) + abort(); + if (x0 < 0) x0 = 0; if (y0 < 0) @@ -381,7 +343,7 @@ void map_foreachinarea(std::function<void(dumb_ptr<block_list>)> func, *------------------------------------------ */ void map_foreachinmovearea(std::function<void(dumb_ptr<block_list>)> func, - map_local *m, + Borrowed<map_local> m, int x0, int y0, int x1, int y1, int dx, int dy, BL type) @@ -501,7 +463,7 @@ void map_foreachinmovearea(std::function<void(dumb_ptr<block_list>)> func, // area radius - may be more useful in some instances) // void map_foreachincell(std::function<void(dumb_ptr<block_list>)> func, - map_local *m, + Borrowed<map_local> m, int x, int y, BL type) { @@ -674,7 +636,7 @@ void map_clearflooritem_timer(TimerData *tid, tick_t, BlockId id) map_delobject(fitem->bl_id, BL::ITEM); } -std::pair<uint16_t, uint16_t> map_randfreecell(map_local *m, +std::pair<uint16_t, uint16_t> map_randfreecell(Borrowed<map_local> m, uint16_t x, uint16_t y, uint16_t w, uint16_t h) { for (int itr : random_::iterator(w * h)) @@ -689,7 +651,7 @@ std::pair<uint16_t, uint16_t> map_randfreecell(map_local *m, /// Return a randomly selected passable cell within a given range. static -std::pair<uint16_t, uint16_t> map_searchrandfreecell(map_local *m, int x, int y, int range) +std::pair<uint16_t, uint16_t> map_searchrandfreecell(Borrowed<map_local> m, int x, int y, int range) { int whole_range = 2 * range + 1; return map_randfreecell(m, x - range, y - range, whole_range, whole_range); @@ -702,7 +664,7 @@ std::pair<uint16_t, uint16_t> map_searchrandfreecell(map_local *m, int x, int y, *------------------------------------------ */ BlockId map_addflooritem_any(Item *item_data, int amount, - map_local *m, int x, int y, + Borrowed<map_local> m, int x, int y, dumb_ptr<map_session_data> *owners, interval_t *owner_protection, interval_t lifetime, int dispersal) { @@ -767,7 +729,7 @@ BlockId map_addflooritem_any(Item *item_data, int amount, } BlockId map_addflooritem(Item *item_data, int amount, - map_local *m, int x, int y, + Borrowed<map_local> m, int x, int y, dumb_ptr<map_session_data> first_sd, dumb_ptr<map_session_data> second_sd, dumb_ptr<map_session_data> third_sd) @@ -776,14 +738,14 @@ BlockId map_addflooritem(Item *item_data, int amount, interval_t owner_protection[3]; { - owner_protection[0] = static_cast<interval_t>(battle_config.item_first_get_time); - owner_protection[1] = owner_protection[0] + static_cast<interval_t>(battle_config.item_second_get_time); - owner_protection[2] = owner_protection[1] + static_cast<interval_t>(battle_config.item_third_get_time); + owner_protection[0] = battle_config.item_first_get_time; + owner_protection[1] = owner_protection[0] + battle_config.item_second_get_time; + owner_protection[2] = owner_protection[1] + battle_config.item_third_get_time; } return map_addflooritem_any(item_data, amount, m, x, y, owners, owner_protection, - static_cast<interval_t>(battle_config.flooritem_lifetime), 1); + battle_config.flooritem_lifetime, 1); } /*========================================== @@ -792,9 +754,7 @@ BlockId map_addflooritem(Item *item_data, int amount, */ void map_addchariddb(CharId charid, CharName name) { - struct charid2nick *p = charid_db.search(charid); - if (p == nullptr) - p = charid_db.init(charid); + P<struct charid2nick> p = charid_db.init(charid); p->nick = name; p->req_id = 0; @@ -929,13 +889,17 @@ dumb_ptr<map_session_data> map_id2sd(BlockId id) */ CharName map_charid2nick(CharId id) { - struct charid2nick *p = charid_db.search(id); + Option<P<struct charid2nick>> p_ = charid_db.search(id); - if (p == nullptr) - return CharName(); - if (p->req_id != 0) - return CharName(); - return p->nick; + return p_.cmap( + [](P<struct charid2nick> p) + { + return p->req_id == 0; + }, + [](P<struct charid2nick> p) + { + return p->nick; + }).move_or(CharName()); } /*========================================*/ @@ -1047,11 +1011,9 @@ dumb_ptr<block_list> map_id2bl(BlockId id) * map.npcへ追加 (warp等の領域持ちのみ) *------------------------------------------ */ -int map_addnpc(map_local *m, dumb_ptr<npc_data> nd) +int map_addnpc(Borrowed<map_local> m, dumb_ptr<npc_data> nd) { int i; - if (!m) - return -1; for (i = 0; i < m->npc_num && i < MAX_NPC_PER_MAP; i++) if (m->npc[i] == nullptr) break; @@ -1109,27 +1071,39 @@ void map_removenpc(void) * map名からmap番号へ変換 *------------------------------------------ */ -map_local *map_mapname2mapid(MapName name) +Option<Borrowed<map_local>> map_mapname2mapid(MapName name) { - map_abstract *md = maps_db.get(name); - if (md == nullptr || md->gat == nullptr) - return nullptr; - return static_cast<map_local *>(md); + Option<P<map_abstract>> md_ = maps_db.get(name); + return md_.cmap( + [](P<map_abstract> md) + { + return bool(md->gat); + }, + [](P<map_abstract> md) + { + return md.downcast_to<map_local>(); + }); } /*========================================== * 他鯖map名からip,port変換 *------------------------------------------ */ -int map_mapname2ipport(MapName name, IP4Address *ip, int *port) +int map_mapname2ipport(MapName name, Borrowed<IP4Address> ip, Borrowed<int> port) { - map_abstract *md = maps_db.get(name); - if (md == nullptr || md->gat) - return -1; - map_remote *mdos = static_cast<map_remote *>(md); - *ip = mdos->ip; - *port = mdos->port; - return 0; + Option<P<map_abstract>> md_ = maps_db.get(name); + return md_.cmap( + [](P<map_abstract> md) + { + return !md->gat; + }, + [ip, port](P<map_abstract> md) + { + auto mdos = md.downcast_to<map_remote>(); + *ip = mdos->ip; + *port = mdos->port; + return 0; + }).copy_or(-1); } /// Check compatibility of directions. @@ -1208,7 +1182,7 @@ DIR map_calc_dir(dumb_ptr<block_list> src, int x, int y) * (m,x,y)の状態を調べる *------------------------------------------ */ -MapCell map_getcell(map_local *m, int x, int y) +MapCell map_getcell(Borrowed<map_local> m, int x, int y) { if (x < 0 || x >= m->xs - 1 || y < 0 || y >= m->ys - 1) return MapCell::UNWALKABLE; @@ -1219,7 +1193,7 @@ MapCell map_getcell(map_local *m, int x, int y) * (m,x,y)の状態をtにする *------------------------------------------ */ -void map_setcell(map_local *m, int x, int y, MapCell t) +void map_setcell(Borrowed<map_local> m, int x, int y, MapCell t) { if (x < 0 || x >= m->xs || y < 0 || y >= m->ys) return; @@ -1232,37 +1206,41 @@ void map_setcell(map_local *m, int x, int y, MapCell t) */ int map_setipport(MapName name, IP4Address ip, int port) { - map_abstract *md = maps_db.get(name); - if (md == nullptr) + Option<P<map_abstract>> md_ = maps_db.get(name); + OMATCH_BEGIN (md_) { - // not exist -> add new data - auto mdos = make_unique<map_remote>(); - mdos->name_ = name; - mdos->gat = nullptr; - mdos->ip = ip; - mdos->port = port; - maps_db.put(mdos->name_, std::move(mdos)); - } - else - { - if (md->gat) + OMATCH_CASE_SOME (md) { - // local -> check data - if (ip != clif_getip() || port != clif_getport()) + if (md->gat) + { + // local -> check data + if (ip != map_conf.map_ip || port != map_conf.map_port) + { + PRINTF("from char server : %s -> %s:%d\n"_fmt, + name, ip, port); + return 1; + } + } + else { - PRINTF("from char server : %s -> %s:%d\n"_fmt, - name, ip, port); - return 1; + // update + P<map_remote> mdos = md.downcast_to<map_remote>(); + mdos->ip = ip; + mdos->port = port; } } - else + OMATCH_CASE_NONE () { - // update - map_remote *mdos = static_cast<map_remote *>(md); + // not exist -> add new data + auto mdos = make_unique<map_remote>(); + mdos->name_ = name; + mdos->gat = nullptr; mdos->ip = ip; mdos->port = port; + maps_db.put(mdos->name_, std::move(mdos)); } } + OMATCH_END (); return 0; } @@ -1281,7 +1259,7 @@ bool map_readmap(map_local *m, size_t num, MapName fn) int xs = m->xs = gat_v[0] | gat_v[1] << 8; int ys = m->ys = gat_v[2] | gat_v[3] << 8; - PRINTF("\rLoading Maps [%zu/%zu]: %-30s (%i, %i)"_fmt, + PRINTF("Loading Maps [%zu/%zu]: %-30s (%i, %i)\r"_fmt, num, maps_db.size(), fn, xs, ys); fflush(stdout); @@ -1334,7 +1312,7 @@ bool map_readallmap(void) } } - PRINTF("\rMaps Loaded: %-65zu\n"_fmt, maps_db.size()); + PRINTF("Maps Loaded: %-65zu\n"_fmt, maps_db.size()); if (maps_removed) { PRINTF("Cowardly refusing to keep going after removing %d maps.\n"_fmt, @@ -1349,7 +1327,6 @@ bool map_readallmap(void) * 読み込むmapを追加する *------------------------------------------ */ -static void map_addmap(MapName mapname) { if (mapname == "clear"_s) @@ -1384,18 +1361,11 @@ void map_delmap(MapName mapname) constexpr int LOGFILE_SECONDS_PER_CHUNK_SHIFT = 10; static -std::unique_ptr<io::AppendFile> map_logfile; -static -AString map_logfile_name; -static -long map_logfile_index; - -static void map_close_logfile(void) { if (map_logfile) { - AString filename = STRPRINTF("%s.%ld"_fmt, map_logfile_name, map_logfile_index); + AString filename = STRPRINTF("%s.%ld"_fmt, map_conf.log_file, map_logfile_index); const char *args[] = { "gzip", @@ -1423,22 +1393,23 @@ void map_start_logfile(long index) AString filename_buf = STRPRINTF( "%s.%ld"_fmt, - map_logfile_name, + map_conf.log_file, map_logfile_index); map_logfile = make_unique<io::AppendFile>(filename_buf); if (!map_logfile->is_open()) { map_logfile.reset(); - perror(map_logfile_name.c_str()); + perror(map_conf.log_file.c_str()); } } static -void map_set_logfile(AString filename) +void map_set_logfile() { - struct timeval tv; + if (!map_conf.log_file) + return; - map_logfile_name = std::move(filename); + struct timeval tv; gettimeofday(&tv, nullptr); map_start_logfile(tv.tv_sec >> LOGFILE_SECONDS_PER_CHUNK_SHIFT); @@ -1463,147 +1434,6 @@ void map_log(XString line) log_with_timestamp(*map_logfile, line); } -/*========================================== - * 設定ファイルを読み込む - *------------------------------------------ - */ -static -bool map_config_read(ZString cfgName) -{ - struct hostent *h = nullptr; - - io::ReadFile in(cfgName); - if (!in.is_open()) - { - PRINTF("Map configuration file not found at: %s\n"_fmt, cfgName); - return false; - } - - bool rv = true; - AString line; - while (in.getline(line)) - { - if (is_comment(line)) - continue; - XString w1; - ZString w2; - if (!config_split(line, &w1, &w2)) - { - PRINTF("Bad config line: %s\n"_fmt, line); - rv = false; - continue; - } - if (w1 == "userid"_s) - { - AccountName name = stringish<AccountName>(w2); - chrif_setuserid(name); - } - else if (w1 == "passwd"_s) - { - AccountPass pass = stringish<AccountPass>(w2); - chrif_setpasswd(pass); - } - else if (w1 == "char_ip"_s) - { - h = gethostbyname(w2.c_str()); - IP4Address w2ip; - if (h != nullptr) - { - w2ip = IP4Address({ - static_cast<uint8_t>(h->h_addr[0]), - static_cast<uint8_t>(h->h_addr[1]), - static_cast<uint8_t>(h->h_addr[2]), - static_cast<uint8_t>(h->h_addr[3]), - }); - PRINTF("Character server IP address : %s -> %s\n"_fmt, - w2, w2ip); - } - else - { - PRINTF("Bad IP value: %s\n"_fmt, line); - return false; - } - chrif_setip(w2ip); - } - else if (w1 == "char_port"_s) - { - chrif_setport(atoi(w2.c_str())); - } - else if (w1 == "map_ip"_s) - { - h = gethostbyname(w2.c_str()); - IP4Address w2ip; - if (h != nullptr) - { - w2ip = IP4Address({ - static_cast<uint8_t>(h->h_addr[0]), - static_cast<uint8_t>(h->h_addr[1]), - static_cast<uint8_t>(h->h_addr[2]), - static_cast<uint8_t>(h->h_addr[3]), - }); - PRINTF("Map server IP address : %s -> %s\n"_fmt, - w2, w2ip); - } - else - { - PRINTF("Bad IP value: %s\n"_fmt, line); - return false; - } - clif_setip(w2ip); - } - else if (w1 == "map_port"_s) - { - clif_setport(atoi(w2.c_str())); - } - else if (w1 == "map"_s) - { - MapName name = VString<15>(w2); - map_addmap(name); - } - else if (w1 == "delmap"_s) - { - MapName name = VString<15>(w2); - map_delmap(name); - } - else if (w1 == "npc"_s) - { - npc_addsrcfile(w2); - } - else if (w1 == "delnpc"_s) - { - npc_delsrcfile(w2); - } - else if (w1 == "autosave_time"_s) - { - autosave_time = std::chrono::seconds(atoi(w2.c_str())); - if (autosave_time <= interval_t::zero()) - autosave_time = DEFAULT_AUTOSAVE_INTERVAL; - } - else if (w1 == "motd_txt"_s) - { - motd_txt = w2; - } - else if (w1 == "mapreg_txt"_s) - { - mapreg_txt = w2; - } - else if (w1 == "gm_log"_s) - { - gm_log = std::move(w2); - } - else if (w1 == "log_file"_s) - { - map_set_logfile(w2); - } - else if (w1 == "import"_s) - { - rv &= map_config_read(w2); - } - } - - return rv; -} - static void cleanup_sub(dumb_ptr<block_list> bl) { @@ -1629,17 +1459,86 @@ void cleanup_sub(dumb_ptr<block_list> bl) } } +int compare_item(Item *a, Item *b) +{ + return (a->nameid == b->nameid); +} + +static +bool map_config(io::Spanned<XString> key, io::Spanned<ZString> value) +{ + return parse_map_conf(map_conf, key, value); +} + +static +bool battle_config_(io::Spanned<XString> key, io::Spanned<ZString> value) +{ + return parse_battle_conf(battle_config, key, value); +} + +static +bool map_confs(io::Spanned<XString> key, io::Spanned<ZString> value) +{ + if (key.data == "map_conf"_s) + return load_config_file(value.data, map_config); + if (key.data == "battle_conf"_s) + return load_config_file(value.data, battle_config_); + if (key.data == "atcommand_conf"_s) + return atcommand_config_read(value.data); + + if (key.data == "item_db"_s) + 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) + return skill_readdb(value.data); + if (key.data == "magic_conf"_s) + return magic::load_magic_file_v2(value.data); + + if (key.data == "resnametable"_s) + return load_resnametable(value.data); + if (key.data == "const_db"_s) + return read_constdb(value.data); + key.span.error("Unknown meta-key for map server"_s); + return false; +} + +int map_scriptcont(dumb_ptr<map_session_data> sd, BlockId id) +{ + dumb_ptr<block_list> bl = map_id2bl(id); + + if (!bl) + return 0; + + switch (bl->bl_type) + { + case BL::NPC: + return npc_scriptcont(sd, id); + case BL::SPELL: + magic::spell_execute_script(bl->is_spell()); + break; + } + + return 0; +} +} // namespace map + /*========================================== * map鯖終了時処理 *------------------------------------------ */ void term_func(void) { + using namespace tmwa::map; for (auto& mit : maps_db) { if (!mit.second->gat) continue; - map_local *map_id = static_cast<map_local *>(mit.second.get()); + P<map_local> map_id = borrow(*mit.second).downcast_to<map_local>(); map_foreachinarea(cleanup_sub, map_id, @@ -1662,46 +1561,14 @@ void term_func(void) map_close_logfile(); } -int compare_item(Item *a, Item *b) -{ - return (a->nameid == b->nameid); -} - -static -bool map_confs(XString key, ZString value) -{ - if (key == "map_conf"_s) - return map_config_read(value); - if (key == "battle_conf"_s) - return battle_config_read(value); - if (key == "atcommand_conf"_s) - return atcommand_config_read(value); - - if (key == "item_db"_s) - return itemdb_readdb(value); - if (key == "mob_db"_s) - return mob_readdb(value); - if (key == "mob_skill_db"_s) - return mob_readskilldb(value); - if (key == "skill_db"_s) - return skill_readdb(value); - if (key == "magic_conf"_s) - return magic::load_magic_file_v2(value); - - if (key == "resnametable"_s) - return load_resnametable(value); - if (key == "const_db"_s) - return read_constdb(value); - PRINTF("unknown map conf key: %s\n"_fmt, AString(key)); - return false; -} - /*====================================================== * Map-Server Init and Command-line Arguments [Valaris] *------------------------------------------------------ */ int do_init(Slice<ZString> argv) { + using namespace tmwa::map; + ZString argv0 = argv.pop_front(); runflag &= magic::magic_init0(); @@ -1749,7 +1616,8 @@ int do_init(Slice<ZString> argv) if (!loaded_config_yet) runflag &= load_config_file("conf/tmwa-map.conf"_s, map_confs); - battle_config_check(); + map_set_logfile(); + runflag &= map_readallmap(); do_init_chrif(); @@ -1767,26 +1635,7 @@ int do_init(Slice<ZString> argv) PRINTF("The server is running in " SGR_BOLD SGR_RED "PK Mode" SGR_RESET "\n"_fmt); PRINTF("The map-server is " SGR_BOLD SGR_GREEN "ready" SGR_RESET " (Server is listening on the port %d).\n\n"_fmt, - clif_getport()); - - return 0; -} - -int map_scriptcont(dumb_ptr<map_session_data> sd, BlockId id) -{ - dumb_ptr<block_list> bl = map_id2bl(id); - - if (!bl) - return 0; - - switch (bl->bl_type) - { - case BL::NPC: - return npc_scriptcont(sd, id); - case BL::SPELL: - magic::spell_execute_script(bl->is_spell()); - break; - } + map_conf.map_port); return 0; } diff --git a/src/map/map.hpp b/src/map/map.hpp index d88ff54..f57dcee 100644 --- a/src/map/map.hpp +++ b/src/map/map.hpp @@ -20,17 +20,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. -#include "fwd.hpp" - #include "map.t.hpp" +#include "fwd.hpp" + #include <chrono> #include <functional> #include <list> #include "../ints/udl.hpp" -#include "../strings/fwd.hpp" #include "../strings/rstring.hpp" #include "../strings/astring.hpp" #include "../strings/vstring.hpp" @@ -42,18 +41,19 @@ #include "../net/socket.hpp" #include "../net/timer.t.hpp" -#include "../mmo/utils.hpp" - #include "battle.t.hpp" -#include "clif.t.hpp" +#include "../mmo/clif.t.hpp" #include "mapflag.hpp" #include "mob.t.hpp" -#include "script.hpp" // change to script.t.hpp -#include "skill.t.hpp" +#include "script-buffer.hpp" +#include "script-persist.hpp" +#include "../mmo/skill.t.hpp" namespace tmwa { +namespace map +{ constexpr int MAX_NPC_PER_MAP = 512; constexpr int BLOCK_SIZE = 8; #define AREA_SIZE battle_config.area_size @@ -61,47 +61,19 @@ constexpr std::chrono::seconds LIFETIME_FLOORITEM = 1_min; constexpr int MAX_SKILL_LEVEL = 100; constexpr int MAX_EVENTTIMER = 32; constexpr interval_t NATURAL_HEAL_INTERVAL = 500_ms; -constexpr BlockId MAX_FLOORITEM = wrap<BlockId>(500000_u32); constexpr int MAX_LEVEL = 255; constexpr int MAX_WALKPATH = 48; constexpr int MAX_DROP_PER_MAP = 48; -constexpr interval_t DEFAULT_AUTOSAVE_INTERVAL = 1_min; - -// formerly VString<49>, as name::label -struct NpcEvent -{ - NpcName npc; - ScriptLabel label; +constexpr std::chrono::seconds DEFAULT_AUTOSAVE_INTERVAL = 1_min; - explicit operator bool() - { - return npc || label; - } - bool operator !() - { - return !bool(*this); - } - - friend bool operator == (const NpcEvent& l, const NpcEvent& r) - { - return l.npc == r.npc && l.label == r.label; - } - - friend bool operator < (const NpcEvent& l, const NpcEvent& r) - { - return l.npc < r.npc || (l.npc == r.npc && l.label < r.label); - } - - friend VString<49> convert_for_printf(NpcEvent ev); -}; -bool extract(XString str, NpcEvent *ev); +extern map_local undefined_gat; struct block_list { dumb_ptr<block_list> bl_next, bl_prev; BlockId bl_id; - map_local *bl_m; + Borrowed<map_local> bl_m = borrow(undefined_gat); short bl_x, bl_y; BL bl_type; @@ -156,7 +128,7 @@ struct map_session_data : block_list, SessionData unsigned dead_sit:2; unsigned skillcastcancel:1; unsigned waitingdisconnect:1; - unsigned lr_flag:2; + unsigned lr_flag_is_arrow_2:1; unsigned connect_new:1; unsigned arrow_atk:1; BF attack_type;//:3; @@ -170,11 +142,10 @@ struct map_session_data : block_list, SessionData unsigned shroud_disappears_on_pickup:1; unsigned shroud_disappears_on_talk:1; unsigned seen_motd:1; + unsigned pvpchannel; } state; struct { - unsigned killer:1; - unsigned killable:1; unsigned unbreakable_weapon:1; unsigned unbreakable_armor:1; unsigned deaf:1; @@ -185,7 +156,19 @@ struct map_session_data : block_list, SessionData unsigned char tmw_version; // tmw client version CharKey status_key; CharData status; - GenericArray<struct item_data *, InventoryIndexing<IOff0, MAX_INVENTORY>> inventory_data; + GenericArray<Option<Borrowed<struct item_data>>, InventoryIndexing<IOff0, MAX_INVENTORY>> inventory_data = + {{ + None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, + None, None, None, None, None, None, None, None, None, None, + }}; // explicit is better than implicit earray<IOff0, EQUIP, EQUIP::COUNT> equip_index_maybe; int weight, max_weight; MapName mapname_; @@ -205,13 +188,12 @@ struct map_session_data : block_list, SessionData int npc_amount; // I have no idea exactly what these are doing ... // but one should probably be replaced with a ScriptPointer ??? - const ScriptBuffer *npc_script, *npc_scriptroot; + Option<Borrowed<const ScriptBuffer>> npc_script = None, npc_scriptroot = None; std::vector<struct script_data> npc_stackbuf; RString npc_str; struct { unsigned storage:1; - unsigned divorce:1; } npc_flags; Timer attacktimer; @@ -235,7 +217,7 @@ struct map_session_data : block_list, SessionData short spellpower_bonus_target, spellpower_bonus_current; // [Fate] Spellpower boni. _current is the active one. //_current slowly approximates _target, and _target is determined by equipment. - short attackrange, attackrange_; + short attackrange; // [Fate] Used for gradual healing; amount of enqueued regeneration struct quick_regeneration quick_regeneration_hp, quick_regeneration_sp; @@ -249,18 +231,17 @@ struct map_session_data : block_list, SessionData interval_t hp_sub, sp_sub; interval_t inchealhptick, inchealsptick; - ItemLook weapontype1, weapontype2; + ItemLook weapontype1; earray<int, ATTR, ATTR::COUNT> paramb, paramc, parame, paramcard; int hit, flee, flee2; interval_t aspd, amotion, dmotion; int watk, watk2; int def, def2, mdef, mdef2, critical, matk1, matk2; int hprate, sprate, dsprate; - int watk_, watk_2; int base_atk, atk_rate; int arrow_atk; int arrow_cri, arrow_hit, arrow_range; - int nhealhp, nhealsp, nshealhp, nshealsp, nsshealhp, nsshealsp; + int nhealhp, nhealsp; int aspd_rate, speed_rate, hprecov_rate, sprecov_rate, critical_def, double_rate; int matk_rate; @@ -269,9 +250,6 @@ struct map_session_data : block_list, SessionData mdef_rate, mdef2_rate; int double_add_rate, speed_add_rate, aspd_add_rate, perfect_hit_add; short hp_drain_rate, hp_drain_per, sp_drain_rate, sp_drain_per; - short hp_drain_rate_, hp_drain_per_, sp_drain_rate_, sp_drain_per_; - short break_weapon_rate, break_armor_rate; - short add_steal_rate; int die_counter; @@ -283,7 +261,6 @@ struct map_session_data : block_list, SessionData Map<SIR, RString> regstrm; earray<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; - short sc_count; AccountId trade_partner; Array<IOff2, TRADE_MAX> deal_item_index; @@ -298,11 +275,8 @@ struct map_session_data : block_list, SessionData PartyId partyspy; // [Syrus22] - int catch_target_class; - int pvp_point, pvp_rank; Timer pvp_timer; - int pvp_lastusers; std::list<NpcEvent> eventqueuel; Array<Timer, MAX_EVENTTIMER> eventtimer; @@ -312,14 +286,14 @@ struct map_session_data : block_list, SessionData unsigned in_progress:1; } auto_ban_info; - TimeT chat_reset_due; - TimeT chat_repeat_reset_due; + tick_t chat_reset_due; + tick_t chat_repeat_reset_due; int chat_lines_in; int chat_total_repeats; RString chat_lastmsg; tick_t flood_rates[0x220]; - TimeT packet_flood_reset_due; + tick_t packet_flood_reset_due; int packet_flood_in; IP4Address get_ip() @@ -355,7 +329,7 @@ struct npc_data : block_list Opt1 opt1; Opt2 opt2; Opt3 opt3; - Option option; + Opt0 option; short flag; std::list<RString> eventqueuel; @@ -383,6 +357,7 @@ public: std::unique_ptr<const ScriptBuffer> script; // Diameter. short xs, ys; + bool event_needs_map; // Whether the timer advances if not beyond end. bool timer_active; @@ -439,7 +414,7 @@ struct mob_data : block_list MobMode mode; struct { - map_local *m; + Borrowed<map_local> m = borrow(undefined_gat); short x0, y0, xs, ys; interval_t delay1, delay2; } spawn; @@ -478,11 +453,10 @@ struct mob_data : block_list std::vector<Item> lootitemv; earray<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; - short sc_count; Opt1 opt1; Opt2 opt2; Opt3 opt3; - Option option; + Opt0 option; short min_chase; Timer deletetimer; @@ -514,10 +488,10 @@ struct map_abstract // gat is nullptr for map_remote and non-nullptr for map_local std::unique_ptr<MapCell[]> gat; + map_abstract() = default; + map_abstract(map_abstract&&) = default; virtual ~map_abstract() {} }; -extern -UPMap<MapName, map_abstract> maps_db; struct map_local : map_abstract { @@ -538,7 +512,7 @@ struct map_remote : map_abstract }; inline -MapCell read_gatp(map_local *m, int x, int y) +MapCell read_gatp(Borrowed<map_local> m, int x, int y) { assert (0 <= x && x < m->xs); assert (0 <= y && y < m->ys); @@ -554,12 +528,7 @@ struct flooritem_data : block_list Item item_data; }; -extern interval_t autosave_time; -extern int save_settings; - -extern AString motd_txt; - -extern CharName wisp_server_name; +extern const CharName WISP_SERVER_NAME; // 鯖全体情報 void map_setusers(int); @@ -580,21 +549,21 @@ public: int map_addblock(dumb_ptr<block_list>); int map_delblock(dumb_ptr<block_list>); void map_foreachinarea(std::function<void(dumb_ptr<block_list>)>, - map_local *, + Borrowed<map_local>, int, int, int, int, BL); // -- moonsoul (added map_foreachincell) void map_foreachincell(std::function<void(dumb_ptr<block_list>)>, - map_local *, + Borrowed<map_local>, int, int, BL); void map_foreachinmovearea(std::function<void(dumb_ptr<block_list>)>, - map_local *, + Borrowed<map_local>, int, int, int, int, int, int, BL); //block関連に追加 -int map_count_oncell(map_local *m, int x, int y); +int map_count_oncell(Borrowed<map_local> m, int x, int y); // 一時的object関連 BlockId map_addobject(dumb_ptr<block_list>); void map_delobject(BlockId, BL type); @@ -604,7 +573,7 @@ void map_foreachobject(std::function<void(dumb_ptr<block_list>)>, // void map_quit(dumb_ptr<map_session_data>); // npc -int map_addnpc(map_local *, dumb_ptr<npc_data>); +int map_addnpc(Borrowed<map_local>, dumb_ptr<npc_data>); void map_log(XString line); #define MAP_LOG(format, ...) \ @@ -612,7 +581,7 @@ void map_log(XString line); #define MAP_LOG_PC(sd, fmt, ...) \ MAP_LOG("PC%d %s:%d,%d " fmt, \ - sd->status_key.char_id, (sd->bl_m ? sd->bl_m->name_ : stringish<MapName>("undefined.gat"_s)), sd->bl_x, sd->bl_y, ## __VA_ARGS__) + sd->status_key.char_id, (sd->bl_m->name_), sd->bl_x, sd->bl_y, ## __VA_ARGS__) // 床アイテム関連 void map_clearflooritem_timer(TimerData *, tick_t, BlockId); @@ -622,17 +591,15 @@ void map_clearflooritem(BlockId id) map_clearflooritem_timer(nullptr, tick_t(), id); } BlockId map_addflooritem_any(Item *, int amount, - map_local *m, int x, int y, + Borrowed<map_local> m, int x, int y, dumb_ptr<map_session_data> *owners, interval_t *owner_protection, interval_t lifetime, int dispersal); BlockId map_addflooritem(Item *, int, - map_local *, int, int, + Borrowed<map_local>, int, int, dumb_ptr<map_session_data>, dumb_ptr<map_session_data>, dumb_ptr<map_session_data>); // キャラid=>キャラ名 変換関連 -extern -DMap<BlockId, dumb_ptr<block_list>> id_db; void map_addchariddb(CharId charid, CharName name); CharName map_charid2nick(CharId); @@ -671,8 +638,8 @@ dumb_ptr<magic::invocation> map_id_is_spell(BlockId id) } -map_local *map_mapname2mapid(MapName); -int map_mapname2ipport(MapName, IP4Address *, int *); +Option<Borrowed<map_local>> map_mapname2mapid(MapName); +int map_mapname2ipport(MapName, Borrowed<IP4Address>, Borrowed<int>); int map_setipport(MapName name, IP4Address ip, int port); void map_addiddb(dumb_ptr<block_list>); void map_deliddb(dumb_ptr<block_list> bl); @@ -689,14 +656,14 @@ dumb_ptr<map_session_data> map_get_prev_session( dumb_ptr<map_session_data> current); // gat関連 -MapCell map_getcell(map_local *, int, int); -void map_setcell(map_local *, int, int, MapCell); +MapCell map_getcell(Borrowed<map_local>, int, int); +void map_setcell(Borrowed<map_local>, int, int, MapCell); // その他 bool map_check_dir(DIR s_dir, DIR t_dir); DIR map_calc_dir(dumb_ptr<block_list> src, int x, int y); -std::pair<uint16_t, uint16_t> map_randfreecell(map_local *m, +std::pair<uint16_t, uint16_t> map_randfreecell(Borrowed<map_local> m, uint16_t x, uint16_t y, uint16_t w, uint16_t h); inline dumb_ptr<map_session_data> block_list::as_player() { return dumb_ptr<map_session_data>(static_cast<map_session_data *>(this)) ; } @@ -722,4 +689,14 @@ inline dumb_ptr<npc_data_script> npc_data::is_script() { return npc_subtype == N inline dumb_ptr<npc_data_shop> npc_data::is_shop() { return npc_subtype == NpcSubtype::SHOP ? as_shop() : nullptr ; } inline dumb_ptr<npc_data_warp> npc_data::is_warp() { return npc_subtype == NpcSubtype::WARP ? as_warp() : nullptr ; } inline dumb_ptr<npc_data_message> npc_data::is_message() { return npc_subtype == NpcSubtype::MESSAGE ? as_message() : nullptr ; } + +void map_addmap(MapName mapname); +void map_delmap(MapName mapname); + +struct charid2nick +{ + CharName nick; + int req_id; +}; +} // namespace map } // namespace tmwa diff --git a/src/map/map.py b/src/map/map.py index cb29d53..c7adf56 100644 --- a/src/map/map.py +++ b/src/map/map.py @@ -1,7 +1,7 @@ class map_local(object): __slots__ = ('_value') - name = 'tmwa::map_local' + name = 'tmwa::map::map_local' depth = 1 enabled = True @@ -26,14 +26,14 @@ class map_local(object): yield '->ys', value['ys'] tests = [ - ('static_cast<tmwa::map_local *>(nullptr)', '(map_local *) nullptr'), + ('static_cast<tmwa::map::map_local *>(nullptr)', '(map_local *) nullptr'), ('fake_map_local("map"_s, 42, 404)', '(map_local *) = {->name = "map", ->xs = 42, ->ys = 404}'), ] class map_remote(object): __slots__ = ('_value') - name = 'tmwa::map_remote' + name = 'tmwa::map::map_remote' depth = 1 enabled = True @@ -58,14 +58,14 @@ class map_remote(object): yield '->port', value['port'] tests = [ - ('static_cast<tmwa::map_remote *>(nullptr)', '(map_remote *) nullptr'), + ('static_cast<tmwa::map::map_remote *>(nullptr)', '(map_remote *) nullptr'), ('fake_map_remote("map"_s, tmwa::IP4Address({8, 8, 8, 8}), 6667)', '(map_remote *) = {->name = "map", ->ip = 8.8.8.8, ->port = 6667}'), ] class map_abstract(object): __slots__ = ('_value') - name = 'tmwa::map_abstract' + name = 'tmwa::map::map_abstract' depth = 1 enabled = True @@ -79,19 +79,19 @@ class map_abstract(object): if value is None: return '(map_abstract *) nullptr' gat = value.dereference()['gat'] - gat = gat.address.cast(gdb.lookup_type('tmwa::map_abstract').pointer().pointer()).dereference() + gat = gat.address.cast(gdb.lookup_type('tmwa::map::map_abstract').pointer().pointer()).dereference() if gat: - return value.cast(gdb.lookup_type('tmwa::map_local').pointer()) + return value.cast(gdb.lookup_type('tmwa::map::map_local').pointer()) else: - return value.cast(gdb.lookup_type('tmwa::map_remote').pointer()) + return value.cast(gdb.lookup_type('tmwa::map::map_remote').pointer()) tests = [ - ('static_cast<tmwa::map_abstract *>(nullptr)', '(map_abstract *) nullptr'), + ('static_cast<tmwa::map::map_abstract *>(nullptr)', '(map_abstract *) nullptr'), ] + [ - ('static_cast<tmwa::map_abstract *>(%s); value->gat.reset(new tmwa::MapCell[1])' % expr, expected) + ('static_cast<tmwa::map::map_abstract *>(%s); value->gat.reset(new tmwa::map::MapCell[1])' % expr, expected) for (expr, expected) in map_local.tests[1:] ] + [ - ('static_cast<tmwa::map_abstract *>(%s)' % expr, expected) + ('static_cast<tmwa::map::map_abstract *>(%s)' % expr, expected) for (expr, expected) in map_remote.tests[1:] ] @@ -99,9 +99,9 @@ class map_abstract(object): using tmwa::operator "" _s; inline - tmwa::map_local *fake_map_local(tmwa::ZString name, int xs, int ys) + tmwa::map::map_local *fake_map_local(tmwa::ZString name, int xs, int ys) { - auto *p = new tmwa::map_local{}; + auto *p = new tmwa::map::map_local{}; p->name_ = tmwa::stringish<tmwa::MapName>(name); p->xs = xs; p->ys = ys; @@ -109,17 +109,17 @@ class map_abstract(object): } inline - tmwa::map_remote *fake_map_remote(tmwa::ZString name, tmwa::IP4Address ip, uint16_t port) + tmwa::map::map_remote *fake_map_remote(tmwa::ZString name, tmwa::IP4Address ip, uint16_t port) { - auto *p = new tmwa::map_remote{}; + auto *p = new tmwa::map::map_remote{}; p->name_ = tmwa::stringish<tmwa::MapName>(name); p->ip = ip; p->port = port; return p; } - void fake_delete(tmwa::map_abstract *); - void fake_delete(tmwa::map_abstract *map) + void fake_delete(tmwa::map::map_abstract *); + void fake_delete(tmwa::map::map_abstract *map) { delete map; } diff --git a/src/map/map.t.hpp b/src/map/map.t.hpp index b475f9b..267c049 100644 --- a/src/map/map.t.hpp +++ b/src/map/map.t.hpp @@ -29,11 +29,13 @@ #include "../generic/enum.hpp" #include "../mmo/ids.hpp" -#include "../mmo/mmo.hpp" +#include "../high/mmo.hpp" namespace tmwa { +namespace map +{ enum class BL : uint8_t { NUL, @@ -191,13 +193,9 @@ ENUM_BITWISE_OPERATORS(MapCell) } using e::MapCell; -struct MobName : VString<23> {}; -struct NpcName : VString<23> {}; -struct ScriptLabel : VString<23> {}; -struct ItemName : VString<23> {}; - inline BlockId account_to_block(AccountId a) { return wrap<BlockId>(unwrap<AccountId>(a)); } inline AccountId block_to_account(BlockId b) { return wrap<AccountId>(unwrap<BlockId>(b)); } +} // namespace map } // namespace tmwa diff --git a/src/map/mapflag.cpp b/src/map/mapflag.cpp index be2ae67..9f3c9ab 100644 --- a/src/map/mapflag.cpp +++ b/src/map/mapflag.cpp @@ -18,11 +18,15 @@ // 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 "../strings/xstring.hpp" + #include "../poison.hpp" namespace tmwa { +namespace map +{ // because bitfields, that's why bool MapFlags::get(MapFlag mf) const @@ -38,7 +42,7 @@ void MapFlags::set(MapFlag mf, bool val) flags &=~ static_cast<unsigned>(mf); } -bool extract(XString str, MapFlag *mf) +bool impl_extract(XString str, MapFlag *mf) { const struct { @@ -91,4 +95,5 @@ MapFlag map_flag_from_int(int shift) { return static_cast<MapFlag>(1 << shift); } +} // namespace map } // namespace tmwa diff --git a/src/map/mapflag.hpp b/src/map/mapflag.hpp index 6d046fa..3538c56 100644 --- a/src/map/mapflag.hpp +++ b/src/map/mapflag.hpp @@ -22,11 +22,11 @@ #include <cstdint> -#include "../mmo/extract.hpp" // TODO remove this (requires specializing the *other* half) - namespace tmwa { +namespace map +{ // originally from script.cpp // These are part of the script API, so they can't change ever, // even though they are silly. @@ -77,7 +77,8 @@ public: void set(MapFlag, bool); }; -bool extract(XString str, MapFlag *mf); +bool impl_extract(XString str, MapFlag *mf); MapFlag map_flag_from_int(int shift); +} // namespace map } // namespace tmwa diff --git a/src/map/mapflag.py b/src/map/mapflag.py index fe5b016..b0a2f24 100644 --- a/src/map/mapflag.py +++ b/src/map/mapflag.py @@ -1,6 +1,6 @@ class MapFlags(object): __slots__ = ('_value') - name = 'tmwa::MapFlags' + name = 'tmwa::map::MapFlags' enabled = True def __init__(self, value): @@ -52,12 +52,12 @@ class MapFlags(object): ('RESAVE', 30), ] tests = [ - ('reinterpret_cast<const tmwa::MapFlags&>(static_cast<const unsigned int&>(0x80000000))', 'MapFlags(0x80000000)'), - ('reinterpret_cast<const tmwa::MapFlags&>(static_cast<const unsigned int&>(0xf0000000))', 'MapFlags(TOWN | OUTSIDE | RESAVE | 0x80000000)'), + ('reinterpret_cast<const tmwa::map::MapFlags&>(static_cast<const unsigned int&>(0x80000000))', 'MapFlags(0x80000000)'), + ('reinterpret_cast<const tmwa::map::MapFlags&>(static_cast<const unsigned int&>(0xf0000000))', 'MapFlags(TOWN | OUTSIDE | RESAVE | 0x80000000)'), ] + [ - ('tmwa::MapFlags(); value.set(tmwa::MapFlag::%s, true)' % n, 'MapFlags(%s)' % n) + ('tmwa::map::MapFlags(); value.set(tmwa::map::MapFlag::%s, true)' % n, 'MapFlags(%s)' % n) for (n, _) in junk ] + [ - ('reinterpret_cast<const tmwa::MapFlags&>(static_cast<const unsigned int&>(1 << %d))' % i, 'MapFlags(%s)' % n) + ('reinterpret_cast<const tmwa::map::MapFlags&>(static_cast<const unsigned int&>(1 << %d))' % i, 'MapFlags(%s)' % n) for (n, i) in junk ] diff --git a/src/map/mob.cpp b/src/map/mob.cpp index dd061d0..539b547 100644 --- a/src/map/mob.cpp +++ b/src/map/mob.cpp @@ -36,18 +36,20 @@ #include "../generic/random.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" +#include "../io/extract.hpp" #include "../io/read.hpp" #include "../net/socket.hpp" #include "../net/timer.hpp" #include "../mmo/config_parse.hpp" -#include "../mmo/extract.hpp" +#include "../mmo/cxxstdio_enums.hpp" #include "../mmo/extract_enums.hpp" #include "battle.hpp" +#include "battle_conf.hpp" #include "clif.hpp" +#include "globals.hpp" #include "itemdb.hpp" #include "map.hpp" #include "npc.hpp" @@ -61,6 +63,8 @@ namespace tmwa { +namespace map +{ constexpr interval_t MIN_MOBTHINKTIME = 100_ms; // Move probability in the negligent mode MOB (rate of 1000 minute) @@ -68,8 +72,6 @@ constexpr random_::Fraction MOB_LAZYMOVEPERC {50, 1000}; // Warp probability in the negligent mode MOB (rate of 1000 minute) constexpr random_::Fraction MOB_LAZYWARPPERC {20, 1000}; -static -struct mob_db_ mob_db[2001]; struct mob_db_& get_mob_db(Species s) { return mob_db[unwrap<Species>(s)]; @@ -318,12 +320,12 @@ int mob_gen_exp(mob_db_ *mob) (2 * mob->attrs[ATTR::LUK] * mob->max_hp / mod_def); double attack_factor = (mob->atk1 + mob->atk2 + mob->attrs[ATTR::STR] / 3.0 + mob->attrs[ATTR::DEX] / 2.0 + - mob->attrs[ATTR::LUK]) * (1872.0 / mob->adelay) / 4; + mob->attrs[ATTR::LUK]) * (1872.0 / mob->adelay.count()) / 4; double dodge_factor = pow(mob->lv + mob->attrs[ATTR::AGI] + mob->attrs[ATTR::LUK] / 2.0, 4.0 / 3.0); // TODO s/persuit/pursuit/g sometime when I'm not worried about diffs double persuit_factor = - (3 + mob->range) * bool(mob->mode & MobMode::CAN_MOVE) * 1000 / mob->speed; + (3 + mob->range) * bool(mob->mode & MobMode::CAN_MOVE) * 1000 / mob->speed.count(); double aggression_factor = bool(mob->mode & MobMode::AGGRESSIVE) ? 10.0 / 9.0 @@ -331,8 +333,7 @@ int mob_gen_exp(mob_db_ *mob) int xp = floor(effective_hp * pow(sqrt(attack_factor) + sqrt(dodge_factor) + sqrt(persuit_factor) + 55, 3) - * aggression_factor / 2000000.0 - * static_cast<double>(battle_config.base_exp_rate) / 100.); + * aggression_factor / 2000000.0); if (xp < 1) xp = 1; PRINTF("Exp for mob '%s' generated: %d\n"_fmt, mob->name, xp); @@ -357,10 +358,10 @@ void mob_init(dumb_ptr<mob_data> md) md->stats[mob_stat::LUK] = get_mob_db(mob_class).attrs[ATTR::LUK]; md->stats[mob_stat::ATK1] = get_mob_db(mob_class).atk1; md->stats[mob_stat::ATK2] = get_mob_db(mob_class).atk2; - md->stats[mob_stat::ADELAY] = get_mob_db(mob_class).adelay; + md->stats[mob_stat::ADELAY] = get_mob_db(mob_class).adelay.count(); md->stats[mob_stat::DEF] = get_mob_db(mob_class).def; md->stats[mob_stat::MDEF] = get_mob_db(mob_class).mdef; - md->stats[mob_stat::SPEED] = get_mob_db(mob_class).speed; + md->stats[mob_stat::SPEED] = get_mob_db(mob_class).speed.count(); md->stats[mob_stat::XP_BONUS] = MOB_XP_BONUS_BASE; for (i = 0; i < mutations_nr; i++) @@ -410,15 +411,15 @@ BlockId mob_once_spawn(dumb_ptr<map_session_data> sd, NpcEvent event) { dumb_ptr<mob_data> md = nullptr; - map_local *m; int count; - if (sd && mapname == MOB_THIS_MAP) - m = sd->bl_m; - else - m = map_mapname2mapid(mapname); + P<map_local> m = ( + (sd && mapname == MOB_THIS_MAP) + ? sd->bl_m + : TRY_UNWRAP(map_mapname2mapid(mapname), return BlockId()) + ); - if (m == nullptr || amount <= 0 || mobdb_checkid(mob_class) == Species()) + if (amount <= 0 || mobdb_checkid(mob_class) == Species()) return BlockId(); if (sd) @@ -470,18 +471,18 @@ BlockId mob_once_spawn_area(dumb_ptr<map_session_data> sd, { int x, y, i, max, lx = -1, ly = -1; BlockId id; - map_local *m; - if (mapname == MOB_THIS_MAP) - m = sd->bl_m; - else - m = map_mapname2mapid(mapname); + P<map_local> m = ( + (mapname == MOB_THIS_MAP) + ? sd->bl_m + : TRY_UNWRAP(map_mapname2mapid(mapname), return BlockId()) + ); max = (y1 - y0 + 1) * (x1 - x0 + 1) * 3; if (max > 1000) max = 1000; - if (m == nullptr || amount <= 0 || (mobdb_checkid(mob_class) == Species())) // A summon is stopped if a value is unusual + if (amount <= 0 || (mobdb_checkid(mob_class) == Species())) // A summon is stopped if a value is unusual return BlockId(); for (i = 0; i < amount; i++) @@ -1148,7 +1149,7 @@ int mob_spawn(BlockId id) mob_init(md); if (!md->stats[mob_stat::SPEED]) - md->stats[mob_stat::SPEED] = get_mob_db(md->mob_class).speed; + md->stats[mob_stat::SPEED] = get_mob_db(md->mob_class).speed.count(); md->def_ele = get_mob_db(md->mob_class).element; md->master_id = BlockId(); md->master_dist = 0; @@ -1178,11 +1179,10 @@ int mob_spawn(BlockId id) assert (!md->sc_data[i].timer); md->sc_data[i].val1 = 0; } - md->sc_count = 0; md->opt1 = Opt1::ZERO; md->opt2 = Opt2::ZERO; md->opt3 = Opt3::ZERO; - md->option = Option::ZERO; + md->option = Opt0::ZERO; md->hp = battle_get_max_hp(md); if (md->hp <= 0) @@ -1342,7 +1342,7 @@ int mob_target(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl, int dist) nullpo_retz(bl); sc_data = battle_get_sc_data(bl); - Option *option = battle_get_option(bl); + Opt0 *option = battle_get_option(bl); Race race = get_mob_db(md->mob_class).race; if (md->mode == MobMode::ZERO) @@ -1572,7 +1572,7 @@ int mob_ai_sub_hard_slavemob(dumb_ptr<mob_data> md, tick_t tick) // Since it is in the map on which the master is not, teleport is carried out and it pursues. if (mmd->bl_m != md->bl_m) { - mob_warp(md, mmd->bl_m, mmd->bl_x, mmd->bl_y, BeingRemoveWhy::WARPED); + mob_warp(md, Some(mmd->bl_m), mmd->bl_x, mmd->bl_y, BeingRemoveWhy::WARPED); md->state.master_check = 1; return 0; } @@ -1584,7 +1584,7 @@ int mob_ai_sub_hard_slavemob(dumb_ptr<mob_data> md, tick_t tick) // Since the master was in near immediately before, teleport is carried out and it pursues. if (old_dist < 10 && md->master_dist > 18) { - mob_warp(md, nullptr, mmd->bl_x, mmd->bl_y, BeingRemoveWhy::WARPED); + mob_warp(md, None, mmd->bl_x, mmd->bl_y, BeingRemoveWhy::WARPED); md->state.master_check = 1; return 0; } @@ -2177,7 +2177,7 @@ void mob_ai_lazy(TimerData *, tick_t tick) */ struct delay_item_drop { - map_local *m; + Borrowed<map_local> m = borrow(undefined_gat); int x, y; ItemNameId nameid; int amount; @@ -2186,7 +2186,7 @@ struct delay_item_drop struct delay_item_drop2 { - map_local *m; + Borrowed<map_local> m = borrow(undefined_gat); int x, y; Item item_data; dumb_ptr<map_session_data> first_sd, second_sd, third_sd; @@ -2541,7 +2541,6 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage, int base_exp, job_exp, flag = 1; double per; - PartyPair p; // [Fate] The above is the old formula. We do a more involved computation below. // [o11c] Look in git history for old code, you idiot! @@ -2592,16 +2591,17 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage, ); if (it == ptv.end()) { - p = party_search(pid); - if (p && p->exp != 0) + Option<PartyPair> p_ = party_search(pid); + OMATCH_BEGIN_SOME (p, p_) { - DmgLogParty pn {}; - pn.p = p; - pn.base_exp = base_exp; - pn.job_exp = job_exp; - ptv.push_back(pn); - flag = 0; + if (p->exp != 0) + { + DmgLogParty pn{p, base_exp, job_exp}; + ptv.push_back(pn); + flag = 0; + } } + OMATCH_END (); } else { @@ -2763,7 +2763,7 @@ int mob_warpslave(dumb_ptr<mob_data> md, int x, int y) * mobワープ *------------------------------------------ */ -int mob_warp(dumb_ptr<mob_data> md, map_local *m, int x, int y, BeingRemoveWhy type) +int mob_warp(dumb_ptr<mob_data> md, Option<Borrowed<map_local>> m_, int x, int y, BeingRemoveWhy type) { int i = 0, xs = 0, ys = 0, bx = x, by = y; @@ -2772,8 +2772,7 @@ int mob_warp(dumb_ptr<mob_data> md, map_local *m, int x, int y, BeingRemoveWhy t if (md->bl_prev == nullptr) return 0; - if (m == nullptr) - m = md->bl_m; + P<map_local> m = m_.copy_or(md->bl_m); if (type != BeingRemoveWhy::NEGATIVE1) { @@ -2892,10 +2891,10 @@ int mob_summonslave(dumb_ptr<mob_data> md2, int *value_, int amount, int flag) bx = md2->bl_x; by = md2->bl_y; - map_local *m = md2->bl_m; + P<map_local> m = md2->bl_m; Species values[5]; - for (count = 0; count < 5 && values[count] != Species(); ++count) + for (count = 0; count < 5 && value_[count]; ++count) values[count] = wrap<Species>(value_[count]); if (count < 1) return 0; @@ -3417,10 +3416,10 @@ int mob_makedummymobdb(Species mob_class) get_mob_db(mob_class).race = Race::formless; get_mob_db(mob_class).element = LevelElement{0, Element::neutral}; get_mob_db(mob_class).mode = MobMode::ZERO; - get_mob_db(mob_class).speed = 300; - get_mob_db(mob_class).adelay = 1000; - get_mob_db(mob_class).amotion = 500; - get_mob_db(mob_class).dmotion = 500; + get_mob_db(mob_class).speed = 300_ms; + get_mob_db(mob_class).adelay = 1000_ms; + get_mob_db(mob_class).amotion = 500_ms; + get_mob_db(mob_class).dmotion = 500_ms; for (i = 0; i < 8; i++) { get_mob_db(mob_class).dropitem[i].nameid = ItemNameId(); @@ -3430,7 +3429,7 @@ int mob_makedummymobdb(Species mob_class) } static -bool extract(XString str, LevelElement *le) +bool impl_extract(XString str, LevelElement *le) { int tmp; if (extract(str, &tmp)) @@ -3531,30 +3530,33 @@ bool mob_readdb(ZString filename) continue; } - // TODO move this lower - get_mob_db(mob_class) = std::move(mdbv); - if (get_mob_db(mob_class).base_exp < 0) - get_mob_db(mob_class).base_exp = 0; - else if (get_mob_db(mob_class).base_exp > 0 - && (get_mob_db(mob_class).base_exp * - battle_config.base_exp_rate / 100 > 1000000000 - || get_mob_db(mob_class).base_exp * - battle_config.base_exp_rate / 100 < 0)) - get_mob_db(mob_class).base_exp = 1000000000; - else - get_mob_db(mob_class).base_exp = get_mob_db(mob_class).base_exp * battle_config.base_exp_rate / 100; - + { + PRINTF("bad mob line: Xp needs to be greater than 0. %s\n"_fmt, line); + rv = false; + continue; + } + if (get_mob_db(mob_class).base_exp > 1000000000) + { + PRINTF("bad mob line: Xp needs to be less than 1000000000. %s\n"_fmt, line); + rv = false; + continue; + } if (get_mob_db(mob_class).job_exp < 0) - get_mob_db(mob_class).job_exp = 0; - else if (get_mob_db(mob_class).job_exp > 0 - && (get_mob_db(mob_class).job_exp * battle_config.job_exp_rate / - 100 > 1000000000 - || get_mob_db(mob_class).job_exp * - battle_config.job_exp_rate / 100 < 0)) - get_mob_db(mob_class).job_exp = 1000000000; - else - get_mob_db(mob_class).job_exp = get_mob_db(mob_class).job_exp * battle_config.job_exp_rate / 100; + { + PRINTF("bad mob line: Job Xp needs to be greater than 0. %s\n"_fmt, line); + rv = false; + continue; + } + if (get_mob_db(mob_class).job_exp > 1000000000) + { + PRINTF("bad mob line: Job Xp needs to be less than 1000000000. %s\n"_fmt, line); + rv = false; + continue; + } + + // TODO move this lower + get_mob_db(mob_class) = std::move(mdbv); for (int i = 0; i < 8; i++) { @@ -3585,7 +3587,7 @@ bool mob_readdb(ZString filename) } static -bool extract(XString str, MobSkillCondition *msc) +bool impl_extract(XString str, MobSkillCondition *msc) { const struct { @@ -3609,7 +3611,7 @@ bool extract(XString str, MobSkillCondition *msc) } static -bool extract(XString str, MobSkillState *mss) +bool impl_extract(XString str, MobSkillState *mss) { const struct { @@ -3632,7 +3634,7 @@ bool extract(XString str, MobSkillState *mss) } static -bool extract(XString str, MobSkillTarget *mst) +bool impl_extract(XString str, MobSkillTarget *mst) { const struct { @@ -3745,4 +3747,5 @@ void do_init_mob2(void) MIN_MOBTHINKTIME * 10 ).detach(); } +} // namespace map } // namespace tmwa diff --git a/src/map/mob.hpp b/src/map/mob.hpp index d0cc07a..6d87228 100644 --- a/src/map/mob.hpp +++ b/src/map/mob.hpp @@ -20,24 +20,25 @@ // 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 "mob.t.hpp" -#include "../generic/fwd.hpp" +#include "fwd.hpp" + #include "../generic/enum.hpp" #include "../generic/random.t.hpp" #include "../net/timer.t.hpp" #include "battle.t.hpp" -#include "clif.t.hpp" +#include "../mmo/clif.t.hpp" #include "map.hpp" -#include "skill.t.hpp" +#include "../mmo/skill.t.hpp" namespace tmwa { +namespace map +{ #define ENGLISH_NAME stringish<MobName>("--en--"_s) #define JAPANESE_NAME stringish<MobName>("--ja--"_s) #define MOB_THIS_MAP stringish<MapName>("this"_s) @@ -72,7 +73,7 @@ struct mob_db_ Race race; LevelElement element; MobMode mode; - int speed, adelay, amotion, dmotion; + interval_t speed, adelay, amotion, dmotion; int mutations_nr, mutation_power; struct { @@ -127,7 +128,7 @@ int mob_deleteslave(dumb_ptr<mob_data> md); int mob_counttargeted(dumb_ptr<mob_data> md, dumb_ptr<block_list> src, ATK target_lv); -int mob_warp(dumb_ptr<mob_data> md, map_local *m, int x, int y, BeingRemoveWhy type); +int mob_warp(dumb_ptr<mob_data> md, Option<Borrowed<map_local>> m, int x, int y, BeingRemoveWhy type); int mobskill_use(dumb_ptr<mob_data> md, tick_t tick, MobSkillCondition event); int mobskill_event(dumb_ptr<mob_data> md, BF flag); @@ -136,4 +137,5 @@ void mobskill_castend_pos(TimerData *tid, tick_t tick, BlockId id); int mob_summonslave(dumb_ptr<mob_data> md2, int *value, int amount, int flag); void mob_reload(void); +} // namespace map } // namespace tmwa diff --git a/src/map/mob.t.hpp b/src/map/mob.t.hpp index 160a8a3..54e7ebe 100644 --- a/src/map/mob.t.hpp +++ b/src/map/mob.t.hpp @@ -27,6 +27,8 @@ namespace tmwa { +namespace map +{ enum class MobSkillTarget { MST_TARGET = 0, @@ -61,4 +63,5 @@ enum class MobSkillState : uint8_t MSS_LOOT, MSS_CHASE, }; +} // namespace map } // namespace tmwa diff --git a/src/compat/iter.cpp b/src/map/npc-internal.hpp index b6d6b63..993263f 100644 --- a/src/compat/iter.cpp +++ b/src/map/npc-internal.hpp @@ -1,7 +1,9 @@ -#include "iter.hpp" -// iter.cpp - tools for dealing with iterators +#pragma once +// npc-internal.hpp - Noncombatants. // -// Copyright © 2012-2014 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> // // This file is part of The Mana World (Athena server) // @@ -18,9 +20,18 @@ // 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 "../poison.hpp" +#include "npc.hpp" +#include "fwd.hpp" namespace tmwa { +namespace map +{ +struct event_data +{ + dumb_ptr<npc_data_script> nd; + int pos; +}; +} // namespace map } // namespace tmwa diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp new file mode 100644 index 0000000..4d9fcbd --- /dev/null +++ b/src/map/npc-parse.cpp @@ -0,0 +1,807 @@ +#include "npc-parse.hpp" +// npc-parse.cpp - Noncombatants. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include <list> + +#include "../compat/nullpo.hpp" + +#include "../strings/astring.hpp" +#include "../strings/xstring.hpp" +#include "../strings/literal.hpp" + +#include "../generic/enum.hpp" + +#include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" +#include "../io/line.hpp" + +#include "../mmo/config_parse.hpp" + +#include "../high/extract_mmo.hpp" + +#include "../ast/npc.hpp" + +#include "battle.hpp" +#include "battle_conf.hpp" +#include "clif.hpp" +#include "globals.hpp" +#include "itemdb.hpp" +#include "map.hpp" +#include "mob.hpp" +#include "npc-internal.hpp" +#include "script-parse.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace map +{ +static +void npc_clearsrcfile(void) +{ + npc_srcs.clear(); +} + +void npc_addsrcfile(AString name) +{ + if (name == "clear"_s) + { + npc_clearsrcfile(); + return; + } + + npc_srcs.push_back(name); +} + +void npc_delsrcfile(XString name) +{ + if (name == "all"_s) + { + npc_clearsrcfile(); + return; + } + + for (auto it = npc_srcs.begin(); it != npc_srcs.end(); ++it) + { + if (*it == name) + { + npc_srcs.erase(it); + return; + } + } +} + +static +void register_npc_name(dumb_ptr<npc_data> nd) +{ + earray<LString, NpcSubtype, NpcSubtype::COUNT> types //= + {{ + "WARP"_s, + "SHOP"_s, + "SCRIPT"_s, + "MESSAGE"_s, + }}; + if (!nd->name) + { + if (nd->npc_subtype == NpcSubtype::MESSAGE) + return; + PRINTF("WARNING: npc with no name:\n%s @ %s,%d,%d\n"_fmt, + types[nd->npc_subtype], + nd->bl_m->name_, nd->bl_x, nd->bl_y); + return; + } + if (dumb_ptr<npc_data> nd_old = npcs_by_name.get(nd->name)) + { + if (nd->npc_subtype != NpcSubtype::WARP + || nd_old->npc_subtype != NpcSubtype::WARP) + { + PRINTF("WARNING: replacing npc with name: %s\n"_fmt, nd->name); + PRINTF("old: %s @ %s,%d,%d\n"_fmt, + types[nd_old->npc_subtype], + nd_old->bl_m->name_, nd_old->bl_x, nd_old->bl_y); + PRINTF("new: %s @ %s,%d,%d\n"_fmt, + types[nd->npc_subtype], + nd->bl_m->name_, nd->bl_x, nd->bl_y); + } + } + // TODO also check #s ? + npcs_by_name.put(nd->name, nd); +} + +// extern for atcommand @addwarp +bool npc_load_warp(ast::npc::Warp& warp) +{ + MapName mapname = warp.m.data; + int x = warp.x.data, y = warp.y.data; + + int xs = warp.xs.data, ys = warp.ys.data; + MapName to_mapname = warp.to_m.data; + int to_x = warp.to_x.data, to_y = warp.to_y.data; + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort()); + + dumb_ptr<npc_data_warp> nd; + nd.new_(); + nd->bl_id = npc_get_new_npc_id(); + nd->n = map_addnpc(m, nd); + + nd->bl_prev = nd->bl_next = nullptr; + nd->bl_m = m; + nd->bl_x = x; + nd->bl_y = y; + nd->dir = DIR::S; + nd->flag = 0; + nd->name = warp.name.data; + + nd->npc_class = WARP_CLASS; + nd->speed = 200_ms; + nd->option = Opt0::ZERO; + nd->opt1 = Opt1::ZERO; + nd->opt2 = Opt2::ZERO; + nd->opt3 = Opt3::ZERO; + nd->warp.name = to_mapname; + nd->warp.x = to_x; + nd->warp.y = to_y; + nd->warp.xs = xs; + nd->warp.ys = ys; + + for (int i = 0; i < ys; i++) + { + for (int j = 0; j < xs; j++) + { + int x_lo = x - xs / 2; + int y_lo = y - ys / 2; + int xc = x_lo + j; + int yc = y_lo + i; + MapCell t = map_getcell(m, xc, yc); + if (bool(t & MapCell::UNWALKABLE)) + continue; + map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); + } + } + + npc_warp++; + nd->bl_type = BL::NPC; + nd->npc_subtype = NpcSubtype::WARP; + map_addblock(nd); + clif_spawnnpc(nd); + register_npc_name(nd); + + return true; +} + +static +bool npc_load_shop(ast::npc::Shop& shop) +{ + MapName mapname = shop.m.data; + int x = shop.x.data, y = shop.y.data; + DIR dir = shop.d.data; + dumb_ptr<npc_data_shop> nd; + Species npc_class = shop.npc_class.data; + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort()); + + nd.new_(); + nd->shop_items.reserve(shop.items.data.size()); + for (auto& it : shop.items.data) + { + nd->shop_items.emplace_back(); + auto& back = nd->shop_items.back(); + P<item_data> id = ((extract(it.data.name.data, &back.nameid) && back.nameid) + ? ({ + P<item_data> id_ = TRY_UNWRAP(itemdb_exists(back.nameid), { it.data.name.span.error("No item with this numerical id"_s); return false; }); + id_; + }) + : ({ + P<item_data> id_ = TRY_UNWRAP(itemdb_searchname(XString(it.data.name.data)), { it.data.name.span.error("No item with this name"_s); return false; }); + back.nameid = id_->nameid; + id_; + })); + + back.value = it.data.value.data; + if (it.data.value_multiply) + { + back.value = id->value_buy * back.value; + } + } + + nd->bl_prev = nd->bl_next = nullptr; + nd->bl_m = m; + nd->bl_x = x; + nd->bl_y = y; + nd->bl_id = npc_get_new_npc_id(); + nd->dir = dir; + nd->flag = 0; + nd->name = shop.name.data; + nd->npc_class = npc_class; + nd->speed = 200_ms; + nd->option = Opt0::ZERO; + nd->opt1 = Opt1::ZERO; + nd->opt2 = Opt2::ZERO; + nd->opt3 = Opt3::ZERO; + + npc_shop++; + nd->bl_type = BL::NPC; + nd->npc_subtype = NpcSubtype::SHOP; + nd->n = map_addnpc(m, nd); + map_addblock(nd); + clif_spawnnpc(nd); + register_npc_name(nd); + + return true; +} + +static +bool npc_load_monster(ast::npc::Monster& monster) +{ + MapName mapname = monster.m.data; + int x = monster.x.data, y = monster.y.data; + int xs = monster.xs.data, ys = monster.ys.data; + + Species mob_class = monster.mob_class.data; + int num = monster.num.data; + interval_t delay1 = monster.delay1.data; + interval_t delay2 = monster.delay2.data; + NpcEvent eventname = monster.event.data; + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort()); + + if (num > 1 && battle_config.mob_count_rate != 100) + { + num = num * battle_config.mob_count_rate / 100; + if (num < 1) + num = 1; + } + + for (int i = 0; i < num; i++) + { + dumb_ptr<mob_data> md; + md.new_(); + + md->bl_prev = nullptr; + md->bl_next = nullptr; + md->bl_m = m; + md->bl_x = x; + md->bl_y = y; + MobName expected = get_mob_db(mob_class).jname; + if (monster.name.data != expected) + { + monster.name.span.warning(STRPRINTF("Visible label/jname should match: %s"_fmt, expected)); + } + if (monster.name.data == ENGLISH_NAME) + md->name = get_mob_db(mob_class).name; + else if (monster.name.data == JAPANESE_NAME) + md->name = get_mob_db(mob_class).jname; + else + md->name = monster.name.data; + + md->n = i; + md->mob_class = mob_class; + md->bl_id = npc_get_new_npc_id(); + md->spawn.m = m; + md->spawn.x0 = x; + md->spawn.y0 = y; + md->spawn.xs = xs; + md->spawn.ys = ys; + md->spawn.delay1 = delay1; + md->spawn.delay2 = delay2; + + really_memzero_this(&md->state); + // md->timer = nullptr; + md->target_id = BlockId(); + md->attacked_id = BlockId(); + + md->lootitemv.clear(); + + md->npc_event = eventname; + + md->bl_type = BL::MOB; + map_addiddb(md); + mob_spawn(md->bl_id); + + npc_mob++; + } + + return true; +} + +static +bool npc_load_mapflag(ast::npc::MapFlag& mapflag) +{ + MapName mapname = mapflag.m.data; + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), abort()); + + MapFlag mf; + if (!extract(mapflag.name.data, &mf)) + { + mapflag.name.span.error("No such mapflag"_s); + return false; + } + + if (mf == MapFlag::NOPVP) + { + if (mapflag.vec_extra.data.size()) + { + mapflag.vec_extra.span.error("No extra argument expected for mapflag 'nopvp'"_s); + return false; + } + m->flag.set(MapFlag::NOPVP, 1); + m->flag.set(MapFlag::PVP, 0); + return true; + } + + MapName savemap; + int savex, savey; + + if (mf == MapFlag::NOSAVE) + { + if (mapflag.vec_extra.data.size() == 3 + && extract(mapflag.vec_extra.data[0].data, &savemap) + && extract(mapflag.vec_extra.data[1].data, &savex) + && extract(mapflag.vec_extra.data[2].data, &savey) + && map_mapname2mapid(savemap).is_some()) + { + m->save.map_ = savemap; + m->save.x = savex; + m->save.y = savey; + } + else + { + mapflag.vec_extra.span.error("Unable to extract nosave savepoint"_s); + return false; + } + } + else if (mf == MapFlag::RESAVE) + { + if (mapflag.vec_extra.data.size() == 3 + && extract(mapflag.vec_extra.data[0].data, &savemap) + && extract(mapflag.vec_extra.data[1].data, &savex) + && extract(mapflag.vec_extra.data[2].data, &savey) + && map_mapname2mapid(savemap).is_some()) + { + m->resave.map_ = savemap; + m->resave.x = savex; + m->resave.y = savey; + } + else + { + mapflag.vec_extra.span.error("Unable to extract resave savepoint"_s); + return false; + } + } + else + { + if (mapflag.vec_extra.data.size()) + { + mapflag.vec_extra.span.error("No extra argument expected for mapflag"_s); + return false; + } + } + m->flag.set(mf, true); + + return true; +} + +static +void npc_convertlabel_db(ScriptLabel lname, int pos, dumb_ptr<npc_data_script> nd) +{ + nullpo_retv(nd); + + struct npc_label_list eln {}; + eln.name = lname; + eln.pos = pos; + nd->scr.label_listv.push_back(std::move(eln)); +} + +static +bool npc_load_script_function(ast::script::ScriptBody& body, ast::npc::ScriptFunction& script_function) +{ + std::unique_ptr<const ScriptBuffer> script = compile_script(STRPRINTF("script function \"%s\""_fmt, script_function.name.data), body, false); + if (script == nullptr) + return false; + + userfunc_db.put(script_function.name.data, std::move(script)); + + return true; +} + +static +bool npc_load_script_none(ast::script::ScriptBody& body, ast::npc::ScriptNone& script_none) +{ + std::unique_ptr<const ScriptBuffer> script = compile_script(STRPRINTF("script npc \"%s\""_fmt, script_none.name.data), body, false); + if (script == nullptr) + return false; + + dumb_ptr<npc_data_script> nd; + nd.new_(); + nd->scr.event_needs_map = false; + + nd->name = script_none.name.data; + + nd->bl_prev = nd->bl_next = nullptr; + nd->bl_m = borrow(undefined_gat); + nd->bl_x = 0; + nd->bl_y = 0; + nd->bl_id = npc_get_new_npc_id(); + nd->dir = DIR::S; + nd->flag = 0; + nd->npc_class = INVISIBLE_CLASS; + nd->speed = 200_ms; + nd->scr.script = std::move(script); + nd->option = Opt0::ZERO; + nd->opt1 = Opt1::ZERO; + nd->opt2 = Opt2::ZERO; + nd->opt3 = Opt3::ZERO; + + npc_script++; + nd->bl_type = BL::NPC; + nd->npc_subtype = NpcSubtype::SCRIPT; + + register_npc_name(nd); + + for (auto& pair : scriptlabel_db) + npc_convertlabel_db(pair.first, pair.second, nd); + + for (npc_label_list& el : 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 : 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)); + } + } + // The counter starts stopped with 0 ticks, which is the first event, + // unless there is none, in which case begin == end. + nd->scr.timer = interval_t::zero(); + nd->scr.next_event = nd->scr.timer_eventv.begin(); + // nd->scr.timerid = nullptr; + + return true; +} + +static +bool npc_load_script_map(ast::script::ScriptBody& body, ast::npc::ScriptMap& script_map) +{ + MapName mapname = script_map.m.data; + int x = script_map.x.data, y = script_map.y.data; + DIR dir = script_map.d.data; + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), + { + script_map.m.span.error("No such map"_s); + return false; + }); + + std::unique_ptr<const ScriptBuffer> script = compile_script(STRPRINTF("script npc \"%s\""_fmt, script_map.name.data), body, false); + if (script == nullptr) + return false; + + + dumb_ptr<npc_data_script> nd; + nd.new_(); + + Species npc_class = script_map.npc_class.data; + int xs = script_map.xs.data, ys = script_map.ys.data; + + { + for (int i = 0; i < ys; i++) + { + for (int j = 0; j < xs; j++) + { + int x_lo = x - xs / 2; + int y_lo = y - ys / 2; + int xc = x_lo + j; + int yc = y_lo + i; + MapCell t = map_getcell(m, xc, yc); + if (bool(t & MapCell::UNWALKABLE)) + continue; + map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); + } + } + + nd->scr.xs = xs; + nd->scr.ys = ys; + nd->scr.event_needs_map = true; + } + + nd->name = script_map.name.data; + + nd->bl_prev = nd->bl_next = nullptr; + nd->bl_m = m; + nd->bl_x = x; + nd->bl_y = y; + nd->bl_id = npc_get_new_npc_id(); + nd->dir = dir; + nd->flag = 0; + nd->npc_class = npc_class; + nd->speed = 200_ms; + nd->scr.script = std::move(script); + nd->option = Opt0::ZERO; + nd->opt1 = Opt1::ZERO; + nd->opt2 = Opt2::ZERO; + nd->opt3 = Opt3::ZERO; + + npc_script++; + nd->bl_type = BL::NPC; + nd->npc_subtype = NpcSubtype::SCRIPT; + + nd->n = map_addnpc(m, nd); + map_addblock(nd); + + clif_spawnnpc(nd); + + register_npc_name(nd); + + for (auto& pair : scriptlabel_db) + npc_convertlabel_db(pair.first, pair.second, nd); + + for (npc_label_list& el : 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 : 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)); + } + } + // The counter starts stopped with 0 ticks, which is the first event, + // unless there is none, in which case begin == end. + nd->scr.timer = interval_t::zero(); + nd->scr.next_event = nd->scr.timer_eventv.begin(); + // nd->scr.timerid = nullptr; + + return true; +} + +static +bool npc_load_script_any(ast::npc::Script *script) +{ + MATCH_BEGIN (*script) + { + MATCH_CASE (ast::npc::ScriptFunction&, script_function) + { + return npc_load_script_function(script->body, script_function); + } + MATCH_CASE (ast::npc::ScriptNone&, script_none) + { + return npc_load_script_none(script->body, script_none); + } + MATCH_CASE (ast::npc::ScriptMap&, script_map) + { + auto& mapname = script_map.m; + Option<P<map_local>> m = map_mapname2mapid(mapname.data); + if (m.is_none()) + { + mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data)); + return false; + } + return npc_load_script_map(script->body, script_map); + } + } + MATCH_END (); + abort(); +} + +dumb_ptr<npc_data> npc_spawn_text(Borrowed<map_local> m, int x, int y, + Species npc_class, NpcName name, AString message) +{ + dumb_ptr<npc_data_message> retval; + retval.new_(); + retval->bl_id = npc_get_new_npc_id(); + retval->bl_x = x; + retval->bl_y = y; + retval->bl_m = m; + retval->bl_type = BL::NPC; + retval->npc_subtype = NpcSubtype::MESSAGE; + + retval->name = name; + if (message) + retval->message = message; + + retval->npc_class = npc_class; + retval->speed = 200_ms; + + clif_spawnnpc(retval); + map_addblock(retval); + map_addiddb(retval); + register_npc_name(retval); + + return retval; +} + +static +bool load_one_npc(io::LineCharReader& fp, bool& done) +{ + auto res = TRY_UNWRAP(ast::npc::parse_top(fp), { done = true; return true; }); + if (res.get_failure()) + PRINTF("%s\n"_fmt, res.get_failure()); + ast::npc::TopLevel tl = TRY_UNWRAP(std::move(res.get_success()), return false); + + MATCH_BEGIN (tl) + { + MATCH_CASE (ast::npc::Comment&, c) + { + (void)c; + return true; + } + MATCH_CASE (ast::npc::Warp&, warp) + { + auto& mapname = warp.m; + Option<P<map_local>> m = map_mapname2mapid(mapname.data); + if (m.is_none()) + { + mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data)); + return false; + } + return npc_load_warp(warp); + } + MATCH_CASE (ast::npc::Shop&, shop) + { + auto& mapname = shop.m; + Option<P<map_local>> m = map_mapname2mapid(mapname.data); + if (m.is_none()) + { + mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data)); + return false; + } + return npc_load_shop(shop); + } + MATCH_CASE (ast::npc::Monster&, monster) + { + auto& mapname = monster.m; + Option<P<map_local>> m = map_mapname2mapid(mapname.data); + if (m.is_none()) + { + mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data)); + return false; + } + return npc_load_monster(monster); + } + MATCH_CASE (ast::npc::MapFlag&, mapflag) + { + auto& mapname = mapflag.m; + Option<P<map_local>> m = map_mapname2mapid(mapname.data); + if (m.is_none()) + { + mapname.span.error(STRPRINTF("Map not found: %s"_fmt, mapname.data)); + return false; + } + return npc_load_mapflag(mapflag); + } + MATCH_CASE (ast::npc::Script&, script) + { + return npc_load_script_any(&script); + } + } + MATCH_END (); + abort(); +} + +static +bool load_npc_file(ZString nsl) +{ + io::LineCharReader fp(nsl); + if (!fp.is_open()) + { + PRINTF("file not found : %s\n"_fmt, nsl); + return false; + } + PRINTF("Loading NPCs [%d]: %-54s\r"_fmt, unwrap<BlockId>(npc_id) - unwrap<BlockId>(START_NPC_NUM), + nsl); + + bool done = false; + while (!done) + { + if (!load_one_npc(fp, done)) + return false; + } + return true; +} + +bool do_init_npc(void) +{ + bool rv = true; + + for (; !npc_srcs.empty(); npc_srcs.pop_front()) + { + AString nsl = npc_srcs.front(); + rv &= load_npc_file(nsl); + } + PRINTF("NPCs Loaded: %d [Warps:%d Shops:%d Scripts:%d Mobs:%d] %20s\n"_fmt, + unwrap<BlockId>(npc_id) - unwrap<BlockId>(START_NPC_NUM), npc_warp, npc_shop, npc_script, npc_mob, ""_s); + + if (script_errors) + { + PRINTF("Cowardly refusing to continue after %d errors\n"_fmt, script_errors); + rv = false; + } + return rv; +} +} // namespace map +} // namespace tmwa diff --git a/src/map/npc-parse.hpp b/src/map/npc-parse.hpp new file mode 100644 index 0000000..9bc3448 --- /dev/null +++ b/src/map/npc-parse.hpp @@ -0,0 +1,44 @@ +#pragma once +// npc-parse.hpp - Noncombatants. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + + +namespace tmwa +{ +namespace map +{ +bool npc_load_warp(ast::npc::Warp& warp); + +/** + * Spawns and installs a talk-only NPC + * + * \param message The message to speak. If message is nullptr, the NPC will not do anything at all. + */ +dumb_ptr<npc_data> npc_spawn_text(Borrowed<map_local> m, int x, int y, + Species class_, NpcName name, AString message); + +void npc_addsrcfile(AString name); +void npc_delsrcfile(XString name); +bool do_init_npc(void); +} // namespace map +} // namespace tmwa diff --git a/src/map/npc.cpp b/src/map/npc.cpp index a6427d6..4296432 100644 --- a/src/map/npc.cpp +++ b/src/map/npc.cpp @@ -1,4 +1,4 @@ -#include "npc.hpp" +#include "npc-internal.hpp" // npc.cpp - Noncombatants. // // Copyright © ????-2004 Athena Dev Teams @@ -38,23 +38,20 @@ #include "../generic/db.hpp" #include "../io/cxxstdio.hpp" -#include "../io/read.hpp" +#include "../io/extract.hpp" #include "../net/timer.hpp" -#include "../mmo/config_parse.hpp" -#include "../mmo/extract.hpp" -#include "../mmo/utils.hpp" - #include "../proto2/map-user.hpp" #include "battle.hpp" +#include "battle_conf.hpp" #include "clif.hpp" +#include "globals.hpp" #include "itemdb.hpp" #include "map.hpp" -#include "mob.hpp" #include "pc.hpp" -#include "script.hpp" +#include "script-call.hpp" #include "skill.hpp" #include "../poison.hpp" @@ -62,14 +59,8 @@ namespace tmwa { -static -std::list<AString> npc_srcs; - -static -BlockId npc_id = START_NPC_NUM; -static -int npc_warp, npc_shop, npc_script, npc_mob; - +namespace map +{ BlockId npc_get_new_npc_id(void) { BlockId rv = npc_id; @@ -77,21 +68,6 @@ BlockId npc_get_new_npc_id(void) return rv; } -struct event_data -{ - dumb_ptr<npc_data_script> nd; - int pos; -}; -static -Map<NpcEvent, struct event_data> ev_db; -static -DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name; - -// used for clock-based event triggers -// only tm_min, tm_hour, and tm_mday are used -static -struct tm ev_tm_b; - /*========================================== * NPCの無効化/有効化 * npc_enable @@ -206,22 +182,6 @@ int npc_delete(dumb_ptr<npc_data> nd) return 0; } -void npc_timer_event(NpcEvent eventname) -{ - struct event_data *ev = ev_db.search(eventname); - dumb_ptr<npc_data_script> nd; -// int xs,ys; - - if ((ev == nullptr || (nd = ev->nd) == nullptr)) - { - PRINTF("npc_event: event not found [%s]\n"_fmt, - eventname); - return; - } - - run_script(ScriptPointer(nd->scr.script.get(), ev->pos), nd->bl_id, nd->bl_id); -} - /*========================================== * 全てのNPCのOn*イベント実行 *------------------------------------------ @@ -236,7 +196,7 @@ void npc_event_doall_sub(NpcEvent key, struct event_data *ev, if (name == p) { - run_script_l(ScriptPointer(ev->nd->scr.script.get(), ev->pos), rid, ev->nd->bl_id, + run_script_l(ScriptPointer(borrow(*ev->nd->scr.script), ev->pos), rid, ev->nd->bl_id, argv); (*c)++; } @@ -259,7 +219,7 @@ void npc_event_do_sub(NpcEvent key, struct event_data *ev, if (name == key) { - run_script_l(ScriptPointer(ev->nd->scr.script.get(), ev->pos), rid, ev->nd->bl_id, + run_script_l(ScriptPointer(borrow(*ev->nd->scr.script), ev->pos), rid, ev->nd->bl_id, argv); (*c)++; } @@ -353,7 +313,7 @@ void npc_timerevent(TimerData *, tick_t tick, BlockId id, interval_t data) id, next)); } - run_script(ScriptPointer(nd->scr.script.get(), te->pos), BlockId(), nd->bl_id); + run_script(ScriptPointer(borrow(*nd->scr.script), te->pos), BlockId(), nd->bl_id); } /// Start (or resume) counting ticks to the next npc_timerevent. @@ -465,39 +425,36 @@ void npc_settimerevent_tick(dumb_ptr<npc_data_script> nd, interval_t newtimer) int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname, int mob_kill) { - struct event_data *ev = ev_db.search(eventname); + Option<P<struct event_data>> ev_ = ev_db.search(eventname); dumb_ptr<npc_data_script> nd; - int xs, ys; if (sd == nullptr) { PRINTF("npc_event nullpo?\n"_fmt); } - if (ev == nullptr && eventname.label == stringish<ScriptLabel>("OnTouch"_s)) + if (ev_.is_none() && eventname.label == stringish<ScriptLabel>("OnTouch"_s)) return 1; - if (ev == nullptr || (nd = ev->nd) == nullptr) + P<struct event_data> ev = TRY_UNWRAP(ev_, { - if (mob_kill) - { - { - return 0; - } - } - else - { - if (battle_config.error_log) - PRINTF("npc_event: event not found [%s]\n"_fmt, - eventname); - return 0; - } + if (!mob_kill && battle_config.error_log) + PRINTF("npc_event: event not found [%s]\n"_fmt, + eventname); + return 0; + }); + if ((nd = ev->nd) == nullptr) + { + if (!mob_kill && battle_config.error_log) + PRINTF("npc_event: event not found [%s]\n"_fmt, + eventname); + return 0; } - xs = nd->scr.xs; - ys = nd->scr.ys; - if (xs >= 0 && ys >= 0) + if (nd->scr.event_needs_map) { + int xs = nd->scr.xs; + int ys = nd->scr.ys; if (nd->bl_m != sd->bl_m) return 1; if (xs > 0 @@ -521,28 +478,7 @@ int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname, sd->npc_id = nd->bl_id; sd->npc_pos = - run_script(ScriptPointer(nd->scr.script.get(), ev->pos), sd->bl_id, nd->bl_id); - return 0; -} - -static -void npc_command_sub(NpcEvent key, struct event_data *ev, NpcName npcname, XString command) -{ - if (ev->nd->name == npcname - && key.label.startswith("OnCommand"_s)) - { - XString temp = key.label.xslice_t(9); - - if (command == temp) - run_script(ScriptPointer(ev->nd->scr.script.get(), ev->pos), BlockId(), ev->nd->bl_id); - } -} - -int npc_command(dumb_ptr<map_session_data>, NpcName npcname, XString command) -{ - for (auto& pair : ev_db) - npc_command_sub(pair.first, &pair.second, npcname, command); - + run_script(ScriptPointer(borrow(*nd->scr.script), ev->pos), sd->bl_id, nd->bl_id); return 0; } @@ -550,7 +486,7 @@ int npc_command(dumb_ptr<map_session_data>, NpcName npcname, XString command) * 接触型のNPC処理 *------------------------------------------ */ -int npc_touch_areanpc(dumb_ptr<map_session_data> sd, map_local *m, int x, int y) +int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int x, int y) { int i, f = 1; int xs, ys; @@ -647,7 +583,7 @@ int npc_checknear(dumb_ptr<map_session_data> sd, BlockId id) if (nd->bl_type != BL::NPC) return 1; - if (nd->npc_class == NEGATIVE_SPECIES) + if (nd->npc_class == INVISIBLE_CLASS) return 0; // エリア判定 @@ -696,7 +632,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(nd->is_script()->scr.script.get(), 0), sd->bl_id, id); + sd->npc_pos = run_script(ScriptPointer(borrow(*nd->is_script()->scr.script), 0), sd->bl_id, id); break; case NpcSubtype::MESSAGE: if (nd->is_message()->message) @@ -737,7 +673,7 @@ int npc_scriptcont(dumb_ptr<map_session_data> sd, BlockId id) return 0; } - sd->npc_pos = run_script(ScriptPointer(nd->is_script()->scr.script.get(), sd->npc_pos), sd->bl_id, id); + sd->npc_pos = run_script(ScriptPointer(borrow(*nd->is_script()->scr.script), sd->npc_pos), sd->bl_id, id); return 0; } @@ -848,8 +784,7 @@ int npc_buylist(dumb_ptr<map_session_data> sd, const uint16_t& item_l_count = item_list[i].count; const ItemNameId& item_l_id = item_list[i].name_id; - struct item_data *item_data; - if ((item_data = itemdb_exists(item_l_id)) != nullptr) + P<struct item_data> item_data = TRY_UNWRAP(itemdb_exists(item_l_id), continue); { int amount = item_l_count; Item item_tmp {}; @@ -918,718 +853,6 @@ int npc_selllist(dumb_ptr<map_session_data> sd, } -// -// 初期化関係 -// - -/*========================================== - * 読み込むnpcファイルのクリア - *------------------------------------------ - */ -static -void npc_clearsrcfile(void) -{ - npc_srcs.clear(); -} - -/*========================================== - * 読み込むnpcファイルの追加 - *------------------------------------------ - */ -void npc_addsrcfile(AString name) -{ - if (name == "clear"_s) - { - npc_clearsrcfile(); - return; - } - - npc_srcs.push_back(name); -} - -/*========================================== - * 読み込むnpcファイルの削除 - *------------------------------------------ - */ -void npc_delsrcfile(XString name) -{ - if (name == "all"_s) - { - npc_clearsrcfile(); - return; - } - - for (auto it = npc_srcs.begin(); it != npc_srcs.end(); ++it) - { - if (*it == name) - { - npc_srcs.erase(it); - return; - } - } -} - -static -void register_npc_name(dumb_ptr<npc_data> nd) -{ - earray<LString, NpcSubtype, NpcSubtype::COUNT> types //= - {{ - "WARP"_s, - "SHOP"_s, - "SCRIPT"_s, - "MESSAGE"_s, - }}; - if (!nd->name) - { - if (nd->npc_subtype == NpcSubtype::MESSAGE) - return; - PRINTF("WARNING: npc with no name:\n%s @ %s,%d,%d\n"_fmt, - types[nd->npc_subtype], - nd->bl_m->name_, nd->bl_x, nd->bl_y); - return; - } - if (dumb_ptr<npc_data> nd_old = npcs_by_name.get(nd->name)) - { - if (nd->npc_subtype != NpcSubtype::WARP - || nd_old->npc_subtype != NpcSubtype::WARP) - { - PRINTF("WARNING: replacing npc with name: %s\n"_fmt, nd->name); - PRINTF("old: %s @ %s,%d,%d\n"_fmt, - types[nd_old->npc_subtype], - nd_old->bl_m->name_, nd_old->bl_x, nd_old->bl_y); - PRINTF("new: %s @ %s,%d,%d\n"_fmt, - types[nd->npc_subtype], - nd->bl_m->name_, nd->bl_x, nd->bl_y); - } - } - // TODO also check #s ? - npcs_by_name.put(nd->name, nd); -} - -/*========================================== - * warp行解析 - *------------------------------------------ - */ -int npc_parse_warp(XString w1, XString, NpcName w3, XString w4) -{ - int x, y, xs, ys, to_x, to_y; - int i, j; - MapName mapname, to_mapname; - dumb_ptr<npc_data_warp> nd; - - if (!extract(w1, record<','>(&mapname, &x, &y)) || - !extract(w4, record<','>(&xs, &ys, &to_mapname, &to_x, &to_y))) - { - PRINTF("bad warp line : %s\n"_fmt, w3); - return 1; - } - - map_local *m = map_mapname2mapid(mapname); - - nd.new_(); - nd->bl_id = npc_get_new_npc_id(); - nd->n = map_addnpc(m, nd); - - nd->bl_prev = nd->bl_next = nullptr; - nd->bl_m = m; - nd->bl_x = x; - nd->bl_y = y; - nd->dir = DIR::S; - nd->flag = 0; - nd->name = w3; - - if (!battle_config.warp_point_debug) - nd->npc_class = WARP_CLASS; - else - nd->npc_class = WARP_DEBUG_CLASS; - nd->speed = 200_ms; - nd->option = Option::ZERO; - nd->opt1 = Opt1::ZERO; - nd->opt2 = Opt2::ZERO; - nd->opt3 = Opt3::ZERO; - nd->warp.name = to_mapname; - xs += 2; - ys += 2; - nd->warp.x = to_x; - nd->warp.y = to_y; - nd->warp.xs = xs; - nd->warp.ys = ys; - - for (i = 0; i < ys; i++) - { - for (j = 0; j < xs; j++) - { - int x_lo = x - xs / 2; - int y_lo = y - ys / 2; - int xc = x_lo + j; - int yc = y_lo + i; - MapCell t = map_getcell(m, xc, yc); - if (bool(t & MapCell::UNWALKABLE)) - continue; - map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); - } - } - - npc_warp++; - nd->bl_type = BL::NPC; - nd->npc_subtype = NpcSubtype::WARP; - map_addblock(nd); - clif_spawnnpc(nd); - register_npc_name(nd); - - return 0; -} - -static -bool extract(XString xs, npc_item_list *itv) -{ - XString name_or_id; - if (!extract(xs, record<':'>(&name_or_id, &itv->value))) - return false; - struct item_data *id = nullptr; - if (extract(name_or_id, &itv->nameid) && itv->nameid) - goto return_true; - - id = itemdb_searchname(name_or_id.rstrip()); - if (id == nullptr) - return false; - itv->nameid = id->nameid; - goto return_true; - -return_true: - if (itv->value < 0) - { - if (id == nullptr) - id = itemdb_search(itv->nameid); - itv->value = id->value_buy * abs(itv->value); - } - return true; -} - -/*========================================== - * shop行解析 - *------------------------------------------ - */ -static -int npc_parse_shop(XString w1, XString, NpcName w3, ZString w4a) -{ - int x, y; - DIR dir; - MapName mapname; - dumb_ptr<npc_data_shop> nd; - ZString::iterator w4comma; - Species npc_class; - - int dir_; // TODO use enum directly in extract - if (!extract(w1, record<','>(&mapname, &x, &y, &dir_)) - || dir_ < 0 || dir_ >= 8 - || (w4comma = std::find(w4a.begin(), w4a.end(), ',')) == w4a.end() - || !extract(w4a.xislice_h(w4comma), &npc_class)) - { - PRINTF("bad shop line : %s\n"_fmt, w3); - return 1; - } - dir = static_cast<DIR>(dir_); - map_local *m = map_mapname2mapid(mapname); - - nd.new_(); - ZString w4b = w4a.xislice_t(w4comma + 1); - - if (!extract(w4b, vrec<','>(&nd->shop_items))) - { - PRINTF("bad shop items : %s\n"_fmt, w3); - PRINTF(" somewhere --> %s\n"_fmt, w4b); - nd->shop_items.clear(); - } - - if (nd->shop_items.empty()) - { - nd.delete_(); - return 1; - } - - nd->bl_prev = nd->bl_next = nullptr; - nd->bl_m = m; - nd->bl_x = x; - nd->bl_y = y; - nd->bl_id = npc_get_new_npc_id(); - nd->dir = dir; - nd->flag = 0; - nd->name = w3; - nd->npc_class = npc_class; - nd->speed = 200_ms; - nd->option = Option::ZERO; - nd->opt1 = Opt1::ZERO; - nd->opt2 = Opt2::ZERO; - nd->opt3 = Opt3::ZERO; - - npc_shop++; - nd->bl_type = BL::NPC; - nd->npc_subtype = NpcSubtype::SHOP; - nd->n = map_addnpc(m, nd); - map_addblock(nd); - clif_spawnnpc(nd); - register_npc_name(nd); - - return 0; -} - -/*========================================== - * NPCのラベルデータコンバート - *------------------------------------------ - */ -static -void npc_convertlabel_db(ScriptLabel lname, int pos, dumb_ptr<npc_data_script> nd) -{ - nullpo_retv(nd); - - struct npc_label_list eln {}; - eln.name = lname; - eln.pos = pos; - nd->scr.label_listv.push_back(std::move(eln)); -} - -/*========================================== - * script行解析 - *------------------------------------------ - */ -static -int npc_parse_script(XString w1, XString w2, NpcName w3, ZString w4, - XString first_line, io::ReadFile& fp, int *lines) -{ - int x, y; - DIR dir = DIR::S; - map_local *m; - int xs = 0, ys = 0; // [Valaris] thanks to fov - Species npc_class; - MapName mapname; - std::unique_ptr<const ScriptBuffer> script = nullptr; - dumb_ptr<npc_data_script> nd; - int evflag = 0; - - if (w1 == "-"_s) - { - x = 0; - y = 0; - m = nullptr; - } - else - { - int dir_; // TODO use enum directly in extract - if (!extract(w1, record<','>(&mapname, &x, &y, &dir_)) - || dir_ < 0 || dir_ >= 8 - || (w2 == "script"_s && !w4.contains(','))) - { - PRINTF("bad script line : %s\n"_fmt, w3); - return 1; - } - dir = static_cast<DIR>(dir_); - m = map_mapname2mapid(mapname); - } - - if (w2 == "script"_s) - { - // may be empty - MString srcbuf; - srcbuf += first_line.xislice_t(std::find(first_line.begin(), first_line.end(), '{')); - // Note: it was a bug that this was missing. I think. - int startline = *lines; - - // while (!srcbuf.rstrip().endswith('}')) - while (true) - { - auto it = std::find_if_not(srcbuf.rbegin(), srcbuf.rend(), [](char c){ return c == ' ' || c == '\n'; }); - if (it != srcbuf.rend() && *it == '}') - break; - - AString line; - if (!fp.getline(line)) - // eof - break; - (*lines)++; - if (!srcbuf) - { - // may be a no-op - srcbuf += line.xislice_t(std::find(line.begin(), line.end(), '{')); - // safe to execute more than once - // But will usually only happen once - startline = *lines; - } - else - srcbuf += line; - srcbuf += '\n'; - } - script = parse_script(AString(srcbuf), startline, false); - if (script == nullptr) - // script parse error? - return 1; - } - else - { - assert(0 && "duplicate() is no longer supported!\n"_s); - return 0; - } - - nd.new_(); - - if (m == nullptr) - { - } - else if (extract(w4, record<','>(&npc_class, &xs, &ys))) - { - if (xs >= 0) - xs = xs * 2 + 1; - if (ys >= 0) - ys = ys * 2 + 1; - - if (npc_class != NEGATIVE_SPECIES) - { - - for (int i = 0; i < ys; i++) - { - for (int j = 0; j < xs; j++) - { - int x_lo = x - xs / 2; - int y_lo = y - ys / 2; - int xc = x_lo + j; - int yc = y_lo + i; - MapCell t = map_getcell(m, xc, yc); - if (bool(t & MapCell::UNWALKABLE)) - continue; - map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); - } - } - } - - nd->scr.xs = xs; - nd->scr.ys = ys; - } - else - { - XString w4x = w4; - if (w4x.endswith(',')) - w4x = w4x.xrslice_h(1); - if (!extract(w4x, &npc_class)) - abort(); - nd->scr.xs = 0; - nd->scr.ys = 0; - } - - if (npc_class == NEGATIVE_SPECIES && m != nullptr) - { - evflag = 1; - } - - if (w3.contains(':')) - { - assert(false && "feature removed"_s); - abort(); - } - - { - nd->name = w3; - } - - nd->bl_prev = nd->bl_next = nullptr; - nd->bl_m = m; - nd->bl_x = x; - nd->bl_y = y; - nd->bl_id = npc_get_new_npc_id(); - nd->dir = dir; - nd->flag = 0; - nd->npc_class = npc_class; - nd->speed = 200_ms; - nd->scr.script = std::move(script); - nd->option = Option::ZERO; - nd->opt1 = Opt1::ZERO; - nd->opt2 = Opt2::ZERO; - nd->opt3 = Opt3::ZERO; - - npc_script++; - nd->bl_type = BL::NPC; - nd->npc_subtype = NpcSubtype::SCRIPT; - if (m != nullptr) - { - nd->n = map_addnpc(m, nd); - map_addblock(nd); - - if (evflag) - { - struct event_data ev {}; - ev.nd = nd; - ev.pos = 0; - NpcEvent npcev; - npcev.npc = nd->name; - npcev.label = ScriptLabel(); - ev_db.insert(npcev, ev); - } - else - clif_spawnnpc(nd); - } - register_npc_name(nd); - - for (auto& pair : scriptlabel_db) - npc_convertlabel_db(pair.first, pair.second, nd); - - for (npc_label_list& el : 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 : 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)); - } - } - // The counter starts stopped with 0 ticks, which is the first event, - // unless there is none, in which case begin == end. - nd->scr.timer = interval_t::zero(); - nd->scr.next_event = nd->scr.timer_eventv.begin(); - // nd->scr.timerid = nullptr; - - return 0; -} - -/*========================================== - * function行解析 - *------------------------------------------ - */ -static -int npc_parse_function(XString, XString, XString w3, ZString, - XString first_line, io::ReadFile& fp, int *lines) -{ - MString srcbuf; - srcbuf += first_line.xislice_t(std::find(first_line.begin(), first_line.end(), '{')); - int startline = *lines; - - while (true) - { - auto it = std::find_if_not(srcbuf.rbegin(), srcbuf.rend(), [](char c){ return c == ' ' || c == '\n'; }); - if (it != srcbuf.rend() && *it == '}') - break; - - AString line; - if (!fp.getline(line)) - break; - (*lines)++; - if (!srcbuf) - { - srcbuf += line.xislice_t(std::find(line.begin(), line.end(), '{')); - startline = *lines; - } - else - srcbuf += line; - srcbuf += '\n'; - } - std::unique_ptr<const ScriptBuffer> script = parse_script(AString(srcbuf), startline, false); - if (script == nullptr) - { - // script parse error? - return 1; - } - - userfunc_db.put(w3, std::move(script)); - - return 0; -} - -/*========================================== - * mob行解析 - *------------------------------------------ - */ -static -int npc_parse_mob(XString w1, XString, MobName w3, ZString w4) -{ - int x, y, xs, ys, num; - Species mob_class; - int i; - MapName mapname; - NpcEvent eventname; - dumb_ptr<mob_data> md; - - xs = ys = 0; - int delay1_ = 0, delay2_ = 0; - if (!extract(w1, record<',', 3>(&mapname, &x, &y, &xs, &ys)) || - !extract(w4, record<',', 2>(&mob_class, &num, &delay1_, &delay2_, &eventname))) - { - PRINTF("bad monster line : %s\n"_fmt, w3); - return 1; - } - interval_t delay1 = std::chrono::milliseconds(delay1_); - interval_t delay2 = std::chrono::milliseconds(delay2_); - - map_local *m = map_mapname2mapid(mapname); - - if (num > 1 && battle_config.mob_count_rate != 100) - { - if ((num = num * battle_config.mob_count_rate / 100) < 1) - num = 1; - } - - for (i = 0; i < num; i++) - { - md.new_(); - - md->bl_prev = nullptr; - md->bl_next = nullptr; - md->bl_m = m; - md->bl_x = x; - md->bl_y = y; - if (w3 == ENGLISH_NAME) - md->name = get_mob_db(mob_class).name; - else if (w3 == JAPANESE_NAME) - md->name = get_mob_db(mob_class).jname; - else - md->name = w3; - - md->n = i; - md->mob_class = mob_class; - md->bl_id = npc_get_new_npc_id(); - md->spawn.m = m; - md->spawn.x0 = x; - md->spawn.y0 = y; - md->spawn.xs = xs; - md->spawn.ys = ys; - md->spawn.delay1 = delay1; - md->spawn.delay2 = delay2; - - really_memzero_this(&md->state); - // md->timer = nullptr; - md->target_id = BlockId(); - md->attacked_id = BlockId(); - - md->lootitemv.clear(); - - md->npc_event = eventname; - - md->bl_type = BL::MOB; - map_addiddb(md); - mob_spawn(md->bl_id); - - npc_mob++; - } - - return 0; -} - -/*========================================== - * マップフラグ行の解析 - *------------------------------------------ - */ -static -int npc_parse_mapflag(XString w1, XString, XString w3, ZString w4) -{ - MapName mapname, savemap; - int savex, savey; - - mapname = stringish<MapName>(w1); - if (!mapname) - return 1; - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return 1; - - MapFlag mf; - if (!extract(w3, &mf)) - return 1; - - if (battle_config.pk_mode && mf == MapFlag::NOPVP) - { - m->flag.set(MapFlag::NOPVP, 1); - m->flag.set(MapFlag::PVP, 0); - return 0; - } - - if (mf == MapFlag::NOSAVE) - { - if (w4 == "SavePoint"_s) - { - m->save.map_ = stringish<MapName>("SavePoint"_s); - m->save.x = -1; - m->save.y = -1; - } - else if (extract(w4, record<','>(&savemap, &savex, &savey))) - { - m->save.map_ = savemap; - m->save.x = savex; - m->save.y = savey; - } - } - if (mf == MapFlag::RESAVE) - { - if (extract(w4, record<','>(&savemap, &savex, &savey))) - { - m->resave.map_ = savemap; - m->resave.x = savex; - m->resave.y = savey; - } - } - m->flag.set(mf, true); - - return 0; -} - -dumb_ptr<npc_data> npc_spawn_text(map_local *m, int x, int y, - Species npc_class, NpcName name, AString message) -{ - dumb_ptr<npc_data_message> retval; - retval.new_(); - retval->bl_id = npc_get_new_npc_id(); - retval->bl_x = x; - retval->bl_y = y; - retval->bl_m = m; - retval->bl_type = BL::NPC; - retval->npc_subtype = NpcSubtype::MESSAGE; - - retval->name = name; - if (message) - retval->message = message; - - retval->npc_class = npc_class; - retval->speed = 200_ms; - - clif_spawnnpc(retval); - map_addblock(retval); - map_addiddb(retval); - register_npc_name(retval); - - return retval; -} - static void npc_free_internal(dumb_ptr<npc_data> nd_) { @@ -1675,116 +898,5 @@ void npc_free(dumb_ptr<npc_data> nd) map_delblock(nd); npc_free_internal(nd); } - -/*========================================== - * npc初期化 - *------------------------------------------ - */ -bool do_init_npc(void) -{ - bool rv = true; - // other fields unused - ev_tm_b.tm_min = -1; - ev_tm_b.tm_hour = -1; - ev_tm_b.tm_mday = -1; - - for (; !npc_srcs.empty(); npc_srcs.pop_front()) - { - AString nsl = npc_srcs.front(); - io::ReadFile fp(nsl); - if (!fp.is_open()) - { - PRINTF("file not found : %s\n"_fmt, nsl); - rv = false; - continue; - } - PRINTF("\rLoading NPCs [%d]: %-54s"_fmt, unwrap<BlockId>(npc_id) - unwrap<BlockId>(START_NPC_NUM), - nsl); - int lines = 0; - AString zline; - while (fp.getline(zline)) - { - XString w1, w2, w3, w4x; - ZString w4z; - lines++; - - if (is_comment(zline)) - continue; - - if (!extract(zline, record<'|', 3>(&w1, &w2, &w3, &w4x)) || !w1 || !w2 || !w3) - { - FPRINTF(stderr, "%s:%d: Broken script line: %s\n"_fmt, nsl, lines, zline); - rv = false; - continue; - } - if (&*w4x.end() == &*zline.end()) - { - w4z = zline.xrslice_t(w4x.size()); - } - assert(bool(w4x) == bool(w4z)); - - if (w1 != "-"_s && w1 != "function"_s) - { - auto comma = std::find(w1.begin(), w1.end(), ','); - MapName mapname = stringish<MapName>(w1.xislice_h(comma)); - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - { - // "mapname" is not assigned to this server - FPRINTF(stderr, "%s:%d: Map not found: %s\n"_fmt, nsl, lines, mapname); - rv = false; - continue; - } - } - if (w2 == "warp"_s) - { - NpcName npcname = stringish<NpcName>(w3); - npc_parse_warp(w1, w2, npcname, w4z); - } - else if (w2 == "shop"_s) - { - NpcName npcname = stringish<NpcName>(w3); - npc_parse_shop(w1, w2, npcname, w4z); - } - else if (w2 == "script"_s) - { - if (w1 == "function"_s) - { - npc_parse_function(w1, w2, w3, w4z, - w4x, fp, &lines); - } - else - { - NpcName npcname = stringish<NpcName>(w3); - npc_parse_script(w1, w2, npcname, w4z, - w4x, fp, &lines); - } - } - else if (w2 == "monster"_s) - { - MobName mobname = stringish<MobName>(w3); - npc_parse_mob(w1, w2, mobname, w4z); - } - else if (w2 == "mapflag"_s) - { - npc_parse_mapflag(w1, w2, w3, w4z); - } - else - { - PRINTF("odd script line: %s\n"_fmt, zline); - script_errors++; - } - } - fflush(stdout); - } - PRINTF("\rNPCs Loaded: %d [Warps:%d Shops:%d Scripts:%d Mobs:%d] %20s\n"_fmt, - unwrap<BlockId>(npc_id) - unwrap<BlockId>(START_NPC_NUM), npc_warp, npc_shop, npc_script, npc_mob, ""_s); - - if (script_errors) - { - PRINTF("Cowardly refusing to continue after %d errors\n"_fmt, script_errors); - rv = false; - } - return rv; -} +} // namespace map } // namespace tmwa diff --git a/src/map/npc.hpp b/src/map/npc.hpp index 33dd378..b587f5f 100644 --- a/src/map/npc.hpp +++ b/src/map/npc.hpp @@ -24,38 +24,33 @@ #include <cstdint> -#include "../strings/fwd.hpp" - -#include "../generic/fwd.hpp" +#include "../range/slice.hpp" #include "../net/timer.t.hpp" -#include "../proto2/fwd.hpp" - #include "map.hpp" +#include "script-call.t.hpp" namespace tmwa { +namespace map +{ constexpr BlockId START_NPC_NUM = wrap<BlockId>(110000000); // TODO make these species, see npc_class in npc_data constexpr Species WARP_CLASS = wrap<Species>(45); constexpr Species FAKE_NPC_CLASS = wrap<Species>(127); -constexpr Species WARP_DEBUG_CLASS = wrap<Species>(722); constexpr Species INVISIBLE_CLASS = wrap<Species>(32767); int npc_event_dequeue(dumb_ptr<map_session_data> sd); int npc_event(dumb_ptr<map_session_data> sd, NpcEvent npcname, int); -void npc_timer_event(NpcEvent eventname); // Added by RoVeRT -int npc_command(dumb_ptr<map_session_data> sd, NpcName npcname, XString command); -int npc_touch_areanpc(dumb_ptr<map_session_data>, map_local *, int, int); +int npc_touch_areanpc(dumb_ptr<map_session_data>, Borrowed<map_local>, int, int); int npc_click(dumb_ptr<map_session_data>, BlockId); int npc_scriptcont(dumb_ptr<map_session_data>, BlockId); int npc_buysellsel(dumb_ptr<map_session_data>, BlockId, int); int npc_buylist(dumb_ptr<map_session_data>, const std::vector<Packet_Repeat<0x00c8>>&); int npc_selllist(dumb_ptr<map_session_data>, const std::vector<Packet_Repeat<0x00c9>>&); -int npc_parse_warp(XString w1, XString, NpcName w3, XString w4); int npc_enable(NpcName name, bool flag); dumb_ptr<npc_data> npc_name2id(NpcName name); @@ -63,21 +58,10 @@ dumb_ptr<npc_data> npc_name2id(NpcName name); BlockId npc_get_new_npc_id(void); /** - * Spawns and installs a talk-only NPC - * - * \param message The message to speak. If message is nullptr, the NPC will not do anything at all. - */ -dumb_ptr<npc_data> npc_spawn_text(map_local *m, int x, int y, - Species class_, NpcName name, AString message); - -/** * Uninstalls and frees an NPC */ void npc_free(dumb_ptr<npc_data> npc); -void npc_addsrcfile(AString); -void npc_delsrcfile(XString); -bool do_init_npc(void); int npc_event_do_oninit(void); int npc_event_doall_l(ScriptLabel name, BlockId rid, Slice<argrec_t> argv); @@ -98,4 +82,5 @@ void npc_timerevent_stop(dumb_ptr<npc_data_script> nd); interval_t npc_gettimerevent_tick(dumb_ptr<npc_data_script> nd); void npc_settimerevent_tick(dumb_ptr<npc_data_script> nd, interval_t newtimer); int npc_delete(dumb_ptr<npc_data> nd); +} // namespace map } // namespace tmwa diff --git a/src/map/party.cpp b/src/map/party.cpp index 8713c60..ccbfd75 100644 --- a/src/map/party.cpp +++ b/src/map/party.cpp @@ -32,10 +32,12 @@ #include "../net/timer.hpp" #include "../mmo/ids.hpp" -#include "../mmo/mmo.hpp" +#include "../high/mmo.hpp" #include "battle.hpp" +#include "battle_conf.hpp" #include "clif.hpp" +#include "globals.hpp" #include "intif.hpp" #include "map.hpp" #include "pc.hpp" @@ -45,13 +47,12 @@ namespace tmwa { +namespace map +{ // 座標やHP送信の間隔 constexpr interval_t PARTY_SEND_XYHP_INVERVAL = 1_s; static -Map<PartyId, PartyMost> party_db; - -static void party_check_conflict(dumb_ptr<map_session_data> sd); static void party_send_xyhp_timer(TimerData *tid, tick_t tick); @@ -66,32 +67,30 @@ void do_init_party(void) } // 検索 -PartyPair party_search(PartyId party_id) +Option<PartyPair> party_search(PartyId party_id) { - PartyPair p; - p.party_most = party_db.search(party_id); - if (p) - p.party_id = party_id; - return p; + Option<P<PartyMost>> party_most_ = party_db.search(party_id); + return party_most_.map([party_id](P<PartyMost> party_most) + { + return PartyPair{party_id, party_most}; + }); } static -void party_searchname_sub(PartyPair p, PartyName str, PartyPair *dst) +void party_searchname_sub(PartyPair p, PartyName str, Borrowed<Option<PartyPair>> dst) { if (p->name == str) - *dst = p; + *dst = Some(p); } // パーティ名検索 -PartyPair party_searchname(PartyName str) +Option<PartyPair> party_searchname(PartyName str) { - PartyPair p; + Option<PartyPair> p = None; for (auto& pair : party_db) { - PartyPair tmp; - tmp.party_id = pair.first; - tmp.party_most = &pair.second; - party_searchname_sub(tmp, str, &p); + PartyPair tmp{pair.first, borrow(pair.second)}; + party_searchname_sub(tmp, str, borrow(p)); } return p; } @@ -129,15 +128,13 @@ void party_created(AccountId account_id, int fail, PartyId party_id, PartyName n { sd->status.party_id = party_id; - PartyPair p = party_search(party_id); - if (p) + if (party_search(party_id).is_some()) { PRINTF("party_created(): ID already exists!\n"_fmt); exit(1); } - p.party_most = party_db.init(party_id); - p.party_id = party_id; + Borrowed<PartyMost> p = party_db.init(party_id); p->name = name; /* The party was created successfully. */ @@ -158,8 +155,6 @@ void party_request_info(PartyId party_id) static int party_check_member(PartyPair p) { - nullpo_retz(p); - for (io::FD i : iter_fds()) { Session *s = get_session(i); @@ -215,24 +210,32 @@ int party_recv_noinfo(PartyId party_id) return 0; } -// 情報所得 -int party_recv_info(const PartyPair sp) +static +PartyPair handle_info(const PartyPair sp) { - int i; - - nullpo_retz(sp); - - PartyPair p = party_search(sp.party_id); - if (!p) + Option<PartyPair> p_ = party_search(sp.party_id); + OMATCH_BEGIN_SOME (p, p_) + { + *p.party_most = *sp.party_most; + return p; + } + OMATCH_END (); { - p.party_most = party_db.init(sp.party_id); + PartyPair p{sp.party_id, party_db.init(sp.party_id)}; // 最初のロードなのでユーザーのチェックを行う *p.party_most = *sp.party_most; party_check_member(p); + return p; } - else - *p.party_most = *sp.party_most; +} + +// 情報所得 +int party_recv_info(const PartyPair sp) +{ + int i; + + PartyPair p = handle_info(sp); for (i = 0; i < MAX_PARTY; i++) { // sdの設定 @@ -261,13 +264,13 @@ int party_recv_info(const PartyPair sp) int party_invite(dumb_ptr<map_session_data> sd, AccountId account_id) { dumb_ptr<map_session_data> tsd = map_id2sd(account_to_block(account_id)); - PartyPair p = party_search(sd->status.party_id); + PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0); int i; int full = 1; /* Indicates whether or not there's room for one more. */ nullpo_retz(sd); - if (!tsd || !p || !tsd->sess) + if (!tsd || !tsd->sess) return 0; if (!battle_config.invite_request_check) @@ -359,7 +362,6 @@ int party_reply_invite(dumb_ptr<map_session_data> sd, AccountId account_id, int int party_member_added(PartyId party_id, AccountId account_id, int flag) { dumb_ptr<map_session_data> sd = map_id2sd(account_to_block(account_id)), sd2; - PartyPair p = party_search(party_id); if (sd == nullptr) { @@ -376,12 +378,12 @@ int party_member_added(PartyId party_id, AccountId account_id, int flag) sd->party_invite = PartyId(); sd->party_invite_account = AccountId(); - if (!p) + PartyPair p = TRY_UNWRAP(party_search(party_id), { PRINTF("party_member_added: party %d not found.\n"_fmt, party_id); intif_party_leave(party_id, account_id); return 0; - } + }); if (flag == 1) { // 失敗 @@ -408,13 +410,11 @@ int party_member_added(PartyId party_id, AccountId account_id, int flag) // パーティ除名要求 int party_removemember(dumb_ptr<map_session_data> sd, AccountId account_id) { - PartyPair p; int i; nullpo_retz(sd); - if (!(p = party_search(sd->status.party_id))) - return 0; + PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0); for (i = 0; i < MAX_PARTY; i++) { // リーダーかどうかチェック @@ -439,13 +439,11 @@ int party_removemember(dumb_ptr<map_session_data> sd, AccountId account_id) // パーティ脱退要求 int party_leave(dumb_ptr<map_session_data> sd) { - PartyPair p; int i; nullpo_retz(sd); - if (!(p = party_search(sd->status.party_id))) - return 0; + PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0); for (i = 0; i < MAX_PARTY; i++) { // 所属しているか @@ -462,8 +460,8 @@ int party_leave(dumb_ptr<map_session_data> sd) int party_member_leaved(PartyId party_id, AccountId account_id, CharName name) { dumb_ptr<map_session_data> sd = map_id2sd(account_to_block(account_id)); - PartyPair p = party_search(party_id); - if (p) + Option<PartyPair> p_ = party_search(party_id); + OMATCH_BEGIN_SOME (p, p_) { int i; for (i = 0; i < MAX_PARTY; i++) @@ -474,6 +472,7 @@ int party_member_leaved(PartyId party_id, AccountId account_id, CharName name) p->member[i].sd = nullptr; } } + OMATCH_END (); if (sd != nullptr && sd->status.party_id == party_id) { sd->status.party_id = PartyId(); @@ -485,10 +484,8 @@ int party_member_leaved(PartyId party_id, AccountId account_id, CharName name) // パーティ解散通知 int party_broken(PartyId party_id) { - PartyPair p; int i; - if (!(p = party_search(party_id))) - return 0; + PartyPair p = TRY_UNWRAP(party_search(party_id), return 0); for (i = 0; i < MAX_PARTY; i++) { @@ -508,12 +505,11 @@ int party_broken(PartyId party_id) // パーティの設定変更要求 int party_changeoption(dumb_ptr<map_session_data> sd, int exp, int item) { - PartyPair p; - nullpo_retz(sd); - if (!sd->status.party_id - || !(p = party_search(sd->status.party_id))) + if (!sd->status.party_id) + return 0; + if (party_search(sd->status.party_id).is_none()) return 0; intif_party_changeoption(sd->status.party_id, sd->status_key.account_id, exp, item); @@ -524,10 +520,8 @@ int party_changeoption(dumb_ptr<map_session_data> sd, int exp, int item) int party_optionchanged(PartyId party_id, AccountId account_id, int exp, int item, int flag) { - PartyPair p; dumb_ptr<map_session_data> sd = map_id2sd(account_to_block(account_id)); - if (!(p = party_search(party_id))) - return 0; + PartyPair p = TRY_UNWRAP(party_search(party_id), return 0); if (!(flag & 0x01)) p->exp = exp; @@ -541,10 +535,8 @@ int party_optionchanged(PartyId party_id, AccountId account_id, int exp, int ite void party_recv_movemap(PartyId party_id, AccountId account_id, MapName mapname, int online, int lv) { - PartyPair p; int i; - if (!(p = party_search(party_id))) - return; + PartyPair p = TRY_UNWRAP(party_search(party_id), return); for (i = 0; i < MAX_PARTY; i++) { PartyMember *m = &p->member[i]; @@ -584,8 +576,6 @@ void party_recv_movemap(PartyId party_id, AccountId account_id, MapName mapname, // パーティメンバの移動 int party_send_movemap(dumb_ptr<map_session_data> sd) { - PartyPair p; - nullpo_retz(sd); if (!sd->status.party_id) @@ -599,7 +589,7 @@ int party_send_movemap(dumb_ptr<map_session_data> sd) party_check_conflict(sd); // あるならパーティ情報送信 - if ((p = party_search(sd->status.party_id))) + PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0); { party_check_member(p); // 所属を確認する if (sd->status.party_id == p.party_id) @@ -616,15 +606,13 @@ int party_send_movemap(dumb_ptr<map_session_data> sd) // パーティメンバのログアウト int party_send_logout(dumb_ptr<map_session_data> sd) { - PartyPair p; - nullpo_retz(sd); if (sd->status.party_id) intif_party_changemap(sd, 0); // sdが無効になるのでパーティ情報から削除 - if ((p = party_search(sd->status.party_id))) + PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return 0); { int i; for (i = 0; i < MAX_PARTY; i++) @@ -646,9 +634,7 @@ void party_send_message(dumb_ptr<map_session_data> sd, XString mes) // パーティメッセージ受信 void party_recv_message(PartyId party_id, AccountId account_id, XString mes) { - PartyPair p; - if (!(p = party_search(party_id))) - return; + PartyPair p = TRY_UNWRAP(party_search(party_id), return); clif_party_message(p, account_id, mes); } @@ -667,8 +653,6 @@ void party_send_xyhp_timer_sub(PartyPair p) { int i; - nullpo_retv(p); - for (i = 0; i < MAX_PARTY; i++) { dumb_ptr<map_session_data> sd = dumb_ptr<map_session_data>(p->member[i].sd); @@ -697,9 +681,7 @@ void party_send_xyhp_timer(TimerData *, tick_t) { for (auto& pair : party_db) { - PartyPair tmp; - tmp.party_id = pair.first; - tmp.party_most = &pair.second; + PartyPair tmp{pair.first, borrow(pair.second)}; party_send_xyhp_timer_sub(tmp); } } @@ -709,8 +691,6 @@ void party_send_xy_clear(PartyPair p) { int i; - nullpo_retv(p); - for (i = 0; i < MAX_PARTY; i++) { dumb_ptr<map_session_data> sd = dumb_ptr<map_session_data>(p->member[i].sd); @@ -739,13 +719,11 @@ void party_send_hp_check(dumb_ptr<block_list> bl, PartyId party_id, int *flag) } // 経験値公平分配 -int party_exp_share(PartyPair p, map_local *mapid, int base_exp, int job_exp) +int party_exp_share(PartyPair p, Borrowed<map_local> mapid, int base_exp, int job_exp) { dumb_ptr<map_session_data> sd; int i, c; - nullpo_retz(p); - for (i = c = 0; i < MAX_PARTY; i++) { sd = dumb_ptr<map_session_data>(p->member[i].sd); @@ -770,7 +748,6 @@ int party_exp_share(PartyPair p, map_local *mapid, int base_exp, int job_exp) void party_foreachsamemap(std::function<void(dumb_ptr<block_list>)> func, dumb_ptr<map_session_data> sd, int type) { - PartyPair p; int i; int x0, y0, x1, y1; dumb_ptr<map_session_data> list[MAX_PARTY]; @@ -778,8 +755,7 @@ void party_foreachsamemap(std::function<void(dumb_ptr<block_list>)> func, nullpo_retv(sd); - if (!(p = party_search(sd->status.party_id))) - return; + PartyPair p = TRY_UNWRAP(party_search(sd->status.party_id), return); x0 = sd->bl_x - AREA_SIZE; y0 = sd->bl_y - AREA_SIZE; @@ -807,4 +783,5 @@ void party_foreachsamemap(std::function<void(dumb_ptr<block_list>)> func, if (list[i]->bl_prev) // 有効かどうかチェック func(list[i]); } +} // namespace map } // namespace tmwa diff --git a/src/map/party.hpp b/src/map/party.hpp index 01a8125..669857e 100644 --- a/src/map/party.hpp +++ b/src/map/party.hpp @@ -24,18 +24,14 @@ #include <functional> -#include "../strings/fwd.hpp" - -#include "../generic/fwd.hpp" - -#include "../mmo/fwd.hpp" - namespace tmwa { +namespace map +{ void do_init_party(void); -PartyPair party_search(PartyId party_id); -PartyPair party_searchname(PartyName str); +Option<PartyPair> party_search(PartyId party_id); +Option<PartyPair> party_searchname(PartyName str); int party_create(dumb_ptr<map_session_data> sd, PartyName name); void party_created(AccountId account_id, int fail, PartyId party_id, PartyName name); @@ -65,8 +61,9 @@ void party_recv_message(PartyId party_id, AccountId account_id, XString mes); void party_send_xy_clear(PartyPair p); void party_send_hp_check(dumb_ptr<block_list> bl, PartyId party_id, int *flag); -int party_exp_share(PartyPair p, map_local *map, int base_exp, int job_exp); +int party_exp_share(PartyPair p, Borrowed<map_local> map, int base_exp, int job_exp); void party_foreachsamemap(std::function<void(dumb_ptr<block_list>)> func, dumb_ptr<map_session_data> sd, int type); +} // namespace map } // namespace tmwa diff --git a/src/map/path.cpp b/src/map/path.cpp index 6950797..52d20ad 100644 --- a/src/map/path.cpp +++ b/src/map/path.cpp @@ -31,7 +31,7 @@ #include "../io/cxxstdio.hpp" -#include "clif.t.hpp" +#include "../mmo/clif.t.hpp" #include "map.hpp" #include "../poison.hpp" @@ -39,6 +39,8 @@ namespace tmwa { +namespace map +{ constexpr int MAX_HEAP = 150; struct tmp_path { @@ -212,10 +214,8 @@ int add_path(int *heap, struct tmp_path *tp, int x, int y, int dist, *------------------------------------------ */ static -bool can_place(struct map_local *m, int x, int y) +bool can_place(Borrowed<struct map_local> m, int x, int y) { - nullpo_retz(m); - return !bool(read_gatp(m, x, y) & MapCell::UNWALKABLE); } @@ -224,10 +224,8 @@ bool can_place(struct map_local *m, int x, int y) *------------------------------------------ */ static -int can_move(struct map_local *m, int x0, int y0, int x1, int y1) +int can_move(Borrowed<struct map_local> m, int x0, int y0, int x1, int y1) { - nullpo_retz(m); - if (x0 - x1 < -1 || x0 - x1 > 1 || y0 - y1 < -1 || y0 - y1 > 1) return 0; if (x1 < 0 || y1 < 0 || x1 >= m->xs || y1 >= m->ys) @@ -247,7 +245,7 @@ int can_move(struct map_local *m, int x0, int y0, int x1, int y1) * path探索 (x0,y0)->(x1,y1) *------------------------------------------ */ -int path_search(struct walkpath_data *wpd, map_local *m, int x0, int y0, int x1, int y1, int flag) +int path_search(struct walkpath_data *wpd, Borrowed<map_local> m, int x0, int y0, int x1, int y1, int flag) { int heap[MAX_HEAP + 1]; int i, rp, x, y; @@ -256,7 +254,7 @@ int path_search(struct walkpath_data *wpd, map_local *m, int x0, int y0, int x1, nullpo_retz(wpd); assert (m->gat); - map_local *md = m; + P<map_local> md = m; if (x1 < 0 || x1 >= md->xs || y1 < 0 || y1 >= md->ys || bool(read_gatp(md, x1, y1) & MapCell::UNWALKABLE)) return -1; @@ -361,4 +359,5 @@ int path_search(struct walkpath_data *wpd, map_local *m, int x0, int y0, int x1, return -1; } } +} // namespace map } // namespace tmwa diff --git a/src/map/path.hpp b/src/map/path.hpp index 3619e2e..f16baaa 100644 --- a/src/map/path.hpp +++ b/src/map/path.hpp @@ -25,5 +25,8 @@ namespace tmwa { -int path_search(struct walkpath_data *, map_local *, int, int, int, int, int); +namespace map +{ +int path_search(struct walkpath_data *, Borrowed<map_local>, int, int, int, int, int); +} // namespace map } // namespace tmwa diff --git a/src/map/pc.cpp b/src/map/pc.cpp index ada5b9f..6fa35b0 100644 --- a/src/map/pc.cpp +++ b/src/map/pc.cpp @@ -37,36 +37,42 @@ #include "../generic/random.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" #include "../io/read.hpp" -#include "../net/timer.hpp" +#include "../mmo/cxxstdio_enums.hpp" -#include "../mmo/utils.hpp" +#include "../net/timer.hpp" +#include "../net/timestamp-utils.hpp" #include "../proto2/char-map.hpp" #include "atcommand.hpp" #include "battle.hpp" +#include "battle_conf.hpp" #include "chrif.hpp" #include "clif.hpp" +#include "globals.hpp" #include "intif.hpp" #include "itemdb.hpp" #include "magic-stmt.hpp" #include "map.hpp" +#include "map_conf.hpp" #include "npc.hpp" #include "party.hpp" #include "path.hpp" -#include "script.hpp" +#include "script-call.hpp" #include "skill.hpp" #include "storage.hpp" #include "trade.hpp" +#include "quest.hpp" #include "../poison.hpp" namespace tmwa { +namespace map +{ // PVP順位計算の間隔 constexpr std::chrono::milliseconds PVP_CALCRANK_INTERVAL = 1_s; @@ -120,7 +126,7 @@ int sp_coefficient_0 = 100; // coefficients for each weapon type // (not all used) static //const -earray<interval_t, ItemLook, ItemLook::SINGLE_HANDED_COUNT> aspd_base_0 //= +earray<interval_t, ItemLook, ItemLook::COUNT> aspd_base_0 //= {{ 650_ms, 700_ms, @@ -254,10 +260,6 @@ earray<EPOS, EQUIP, EQUIP::COUNT> equip_pos //= EPOS::ARROW, }}; -// TODO use DMap<> -static -std::map<AccountId, GmLevel> gm_accountm; - static int pc_checkoverhp(dumb_ptr<map_session_data> sd); static @@ -287,16 +289,12 @@ int pc_iskiller(dumb_ptr<map_session_data> src, { nullpo_retz(src); - if (src->bl_type != BL::PC) + if (src->bl_type != BL::PC || target->bl_type != BL::PC) return 0; - if (src->special_state.killer) + if ((src->state.pvpchannel == 1) && (target->state.pvpchannel == 1) && !src->bl_m->flag.get(MapFlag::NOPVP)) return 1; - - if (target->bl_type != BL::PC) - return 0; - if (target->special_state.killable) + if ((src->state.pvpchannel > 1) && (target->state.pvpchannel == src->state.pvpchannel)) // this one does not respect NOPVP return 1; - return 0; } @@ -319,6 +317,33 @@ int distance(int x0, int y0, int x1, int y1) } static +void pc_pvp_timer(TimerData *, tick_t, BlockId id) +{ + dumb_ptr<map_session_data> sd = map_id2sd(id); + + assert (sd != nullptr); + assert (sd->bl_type == BL::PC); +} + +int pc_setpvptimer(dumb_ptr<map_session_data> sd, interval_t val) +{ + nullpo_retz(sd); + + sd->pvp_timer = Timer(gettick() + val, + std::bind(pc_pvp_timer, ph::_1, ph::_2, + sd->bl_id)); + return 0; +} + +int pc_delpvptimer(dumb_ptr<map_session_data> sd) +{ + nullpo_retz(sd); + + sd->pvp_timer.cancel(); + return 0; +} + +static void pc_invincible_timer(TimerData *, tick_t, BlockId id) { dumb_ptr<map_session_data> sd = map_id2sd(id); @@ -377,7 +402,6 @@ int pc_setrestartvalue(dumb_ptr<map_session_data> sd, int type) clif_updatestatus(sd, SP::SP); sd->heal_xp = 0; // [Fate] Set gainable xp for healing this player to 0 - return 0; } @@ -464,7 +488,7 @@ void pc_makesavestatus(dumb_ptr<map_session_data> sd) // セーブ禁止マップだったので指定位置に移動 if (sd->bl_m->flag.get(MapFlag::NOSAVE)) { - map_local *m = sd->bl_m; + P<map_local> m = sd->bl_m; if (m->save.map_ == "SavePoint"_s) sd->status.last_point = sd->status.save_point; else @@ -505,12 +529,7 @@ EPOS pc_equippoint(dumb_ptr<map_session_data> sd, IOff0 n) { nullpo_retr(EPOS::ZERO, sd); - if (!sd->inventory_data[n]) - return EPOS::ZERO; - - EPOS ep = sd->inventory_data[n]->equip; - - return ep; + return sd->inventory_data[n].pmd_pget(&item_data::equip).copy_or(EPOS::ZERO); } static @@ -521,7 +540,11 @@ int pc_setinventorydata(dumb_ptr<map_session_data> sd) for (IOff0 i : IOff0::iter()) { ItemNameId id = sd->status.inventory[i].nameid; - sd->inventory_data[i] = itemdb_search(id); + // If you think you understand this line, you're wrong. + // It does not do what you think it does. Rather, you need to + // understand it in the context in which it is used. Despite this, + // it is quite common for elements to be None. + sd->inventory_data[i] = Some(itemdb_search(id)); } return 0; } @@ -531,40 +554,8 @@ int pc_calcweapontype(dumb_ptr<map_session_data> sd) { nullpo_retz(sd); - if (sd->weapontype1 != ItemLook::NONE - && sd->weapontype2 == ItemLook::NONE) - sd->status.weapon = sd->weapontype1; - if (sd->weapontype1 == ItemLook::NONE - && sd->weapontype2 != ItemLook::NONE) - sd->status.weapon = sd->weapontype2; - else if (sd->weapontype1 == ItemLook::BLADE - && sd->weapontype2 == ItemLook::BLADE) - sd->status.weapon = ItemLook::DUAL_BLADE; - else if (sd->weapontype1 == ItemLook::_2 - && sd->weapontype2 == ItemLook::_2) - sd->status.weapon = ItemLook::DUAL_2; - else if (sd->weapontype1 == ItemLook::_6 - && sd->weapontype2 == ItemLook::_6) - sd->status.weapon = ItemLook::DUAL_6; - else if ((sd->weapontype1 == ItemLook::BLADE - && sd->weapontype2 == ItemLook::_2) - || (sd->weapontype1 == ItemLook::_2 - && sd->weapontype2 == ItemLook::BLADE)) - sd->status.weapon = ItemLook::DUAL_12; - else if ( - (sd->weapontype1 == ItemLook::BLADE - && sd->weapontype2 == ItemLook::_6) - || (sd->weapontype1 == ItemLook::_6 - && sd->weapontype2 == ItemLook::BLADE)) - sd->status.weapon = ItemLook::DUAL_16; - else if ( - (sd->weapontype1 == ItemLook::_2 - && sd->weapontype2 == ItemLook::_6) - || (sd->weapontype1 == ItemLook::_6 - && sd->weapontype2 == ItemLook::_2)) - sd->status.weapon = ItemLook::DUAL_26; - else - sd->status.weapon = sd->weapontype1; + // TODO now that there is no calculation here, store only once + sd->status.weapon = sd->weapontype1; return 0; } @@ -588,27 +579,30 @@ int pc_setequipindex(dumb_ptr<map_session_data> sd) sd->equip_index_maybe[j] = i; if (bool(sd->status.inventory[i].equip & EPOS::WEAPON)) { - if (sd->inventory_data[i]) - sd->weapontype1 = sd->inventory_data[i]->look; - else - sd->weapontype1 = ItemLook::NONE; + OMATCH_BEGIN (sd->inventory_data[i]) + { + OMATCH_CASE_SOME (sdidi) + { + sd->weapontype1 = sdidi->look; + } + OMATCH_CASE_NONE () + { + sd->weapontype1 = ItemLook::NONE; + } + } + OMATCH_END (); } if (bool(sd->status.inventory[i].equip & EPOS::SHIELD)) { - if (sd->inventory_data[i]) + OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[i]) { - if (sd->inventory_data[i]->type == ItemType::WEAPON) + if (sdidi->type == ItemType::WEAPON) { if (sd->status.inventory[i].equip == EPOS::SHIELD) - sd->weapontype2 = sd->inventory_data[i]->look; - else - sd->weapontype2 = ItemLook::NONE; + assert(0 && "unreachable - offhand weapons are not supported"); } - else - sd->weapontype2 = ItemLook::NONE; } - else - sd->weapontype2 = ItemLook::NONE; + OMATCH_END (); } } } @@ -620,21 +614,18 @@ int pc_setequipindex(dumb_ptr<map_session_data> sd) static int pc_isequip(dumb_ptr<map_session_data> sd, IOff0 n) { - struct item_data *item; eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; //転生や養子の場合の元の職業を算出する nullpo_retz(sd); - item = sd->inventory_data[n]; sc_data = battle_get_sc_data(sd); - GmLevel gm_all_equipment = GmLevel::from(static_cast<uint32_t>(battle_config.gm_all_equipment)); + GmLevel gm_all_equipment = battle_config.gm_all_equipment; if (gm_all_equipment && pc_isGM(sd).satisfies(gm_all_equipment)) return 1; - if (item == nullptr) - return 0; + P<struct item_data> item = TRY_UNWRAP(sd->inventory_data[n], return 0); if (item->sex != SEX::NEUTRAL && sd->status.sex != item->sex) return 0; if (item->elv > 0 && sd->status.base_level < item->elv) @@ -648,12 +639,11 @@ int pc_isequip(dumb_ptr<map_session_data> sd, IOff0 n) * char鯖から送られてきたステータスを設定 *------------------------------------------ */ -int pc_authok(AccountId id, int login_id2, TimeT connect_until_time, +int pc_authok(AccountId id, int login_id2, short tmw_version, const CharKey *st_key, const CharData *st_data) { dumb_ptr<map_session_data> sd = nullptr; - PartyPair p; tick_t tick = gettick(); sd = map_id2sd(account_to_block(id)); @@ -681,7 +671,7 @@ int pc_authok(AccountId id, int login_id2, TimeT connect_until_time, sd->state.connect_new = 1; sd->bl_prev = sd->bl_next = nullptr; - sd->weapontype1 = sd->weapontype2 = ItemLook::NONE; + sd->weapontype1 = ItemLook::NONE; sd->speed = DEFAULT_WALK_SPEED; sd->state.dead_sit = 0; sd->dir = DIR::S; @@ -727,21 +717,6 @@ int pc_authok(AccountId id, int login_id2, TimeT connect_until_time, // sd->sc_data[i].timer = nullptr; sd->sc_data[i].val1 = 0; } - sd->sc_count = 0; - { - Option old_option = sd->status.option; - sd->status.option = Option::ZERO; - - // This would leak information. - // It's better to make it obvious that players can see you. - if (false && bool(old_option & Option::INVISIBILITY)) - is_atcommand(sd->sess, sd, "@invisible"_s, GmLevel()); - - if (bool(old_option & Option::HIDE)) - is_atcommand(sd->sess, sd, "@hide"_s, GmLevel()); - // atcommand_hide might already send it, but also might not - clif_changeoption(sd); - } // パーティー関係の初期化 sd->party_sended = 0; @@ -757,9 +732,24 @@ int pc_authok(AccountId id, int login_id2, TimeT connect_until_time, pc_setpos(sd, sd->status.last_point.map_, sd->status.last_point.x, sd->status.last_point.y, BeingRemoveWhy::GONE); + { + Opt0 old_option = sd->status.option; + sd->status.option = Opt0::ZERO; + + // This would leak information. + // It's better to make it obvious that players can see you. + if (false && bool(old_option & Opt0::INVISIBILITY)) + is_atcommand(sd->sess, sd, "@invisible"_s, GmLevel()); + + if (bool(old_option & Opt0::HIDE)) + is_atcommand(sd->sess, sd, "@hide"_s, GmLevel()); + // atcommand_hide might already send it, but also might not + clif_changeoption(sd); + } + // パーティ、ギルドデータの要求 if (sd->status.party_id - && !(p = party_search(sd->status.party_id))) + && party_search(sd->status.party_id).is_none()) party_request_info(sd->status.party_id); // pvpの設定 @@ -793,27 +783,21 @@ int pc_authok(AccountId id, int login_id2, TimeT connect_until_time, sd->auto_ban_info.in_progress = 0; // Initialize antispam vars - sd->chat_reset_due = TimeT(); + sd->chat_reset_due = tick_t(); sd->chat_lines_in = sd->chat_total_repeats = 0; - sd->chat_repeat_reset_due = TimeT(); + sd->chat_repeat_reset_due = tick_t(); sd->chat_lastmsg = RString(); for (tick_t& t : sd->flood_rates) t = tick_t(); - sd->packet_flood_reset_due = TimeT(); + sd->packet_flood_reset_due = tick_t(); sd->packet_flood_in = 0; - // message of the limited time of the account - if (connect_until_time) - { - timestamp_seconds_buffer buffer; - stamp_time(buffer, &connect_until_time); - AString tmpstr = STRPRINTF("Your account time limit is: %s"_fmt, buffer); - - clif_wis_message(sd->sess, wisp_server_name, tmpstr); - } pc_calcstatus(sd, 1); + npc_event_doall_l(stringish<ScriptLabel>("OnPCLoginEvent"_s), sd->bl_id, nullptr); + // Init Quest Log + clif_sendallquest(sd); return 0; } @@ -830,7 +814,7 @@ void pc_show_motd(dumb_ptr<map_session_data> sd) clif_displaymessage(sd->sess, "This server is Free Software, for details type @source in chat or use the tmwa-source tool"_s); sd->state.seen_motd = true; - io::ReadFile in(motd_txt); + io::ReadFile in(map_conf.motd_txt); if (in.is_open()) { AString buf; @@ -865,7 +849,8 @@ int pc_calc_skillpoint(dumb_ptr<map_session_data> sd) nullpo_retz(sd); - for (i = 0; i < skill_pool_skills_size; i++) { + for (i = 0; i < skill_pool_skills.size(); i++) + { int lv = sd->status.skill[skill_pool_skills[i]].lv; if (lv) skill_points += ((lv * (lv - 1)) >> 1) - 1; @@ -940,6 +925,7 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) int bl; int aspd_rate, refinedef = 0; int str, dstr, dex; + int b_pvpchannel = 0; nullpo_retz(sd); @@ -968,6 +954,8 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) b_mdef = sd->mdef; b_mdef2 = sd->mdef2; b_base_atk = sd->base_atk; + if (!pc_isdead(sd) && sd->state.pvpchannel == 1) + b_pvpchannel = sd->state.pvpchannel; sd->max_weight = max_weight_base_0 + sd->status.attrs[ATTR::STR] * 300; @@ -976,11 +964,11 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) sd->weight = 0; for (IOff0 i : IOff0::iter()) { - if (!sd->status.inventory[i].nameid - || sd->inventory_data[i] == nullptr) + if (!sd->status.inventory[i].nameid) continue; + P<struct item_data> sdidi = TRY_UNWRAP(sd->inventory_data[i], continue); sd->weight += - sd->inventory_data[i]->weight * + sdidi->weight * sd->status.inventory[i].amount; } // used to fill cart @@ -1005,7 +993,6 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) sd->status.max_hp = 0; sd->status.max_sp = 0; sd->attackrange = 0; - sd->attackrange_ = 0; sd->matk1 = 0; sd->matk2 = 0; sd->speed = DEFAULT_WALK_SPEED; @@ -1016,13 +1003,9 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) sd->arrow_atk = 0; sd->arrow_hit = 0; sd->arrow_range = 0; - sd->nhealhp = sd->nhealsp = sd->nshealhp = sd->nshealsp = sd->nsshealhp = - sd->nsshealsp = 0; + sd->nhealhp = sd->nhealsp = 0; really_memzero_this(&sd->special_state); - sd->watk_ = 0; //二刀流用(仮) - sd->watk_2 = 0; - sd->aspd_rate = 100; sd->speed_rate = 100; sd->hprecov_rate = 100; @@ -1038,8 +1021,6 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) sd->double_add_rate = sd->perfect_hit_add = 0; sd->hp_drain_rate = sd->hp_drain_per = sd->sp_drain_rate = sd->sp_drain_per = 0; - sd->hp_drain_rate_ = sd->hp_drain_per_ = sd->sp_drain_rate_ = - sd->sp_drain_per_ = 0; sd->spellpower_bonus_target = 0; @@ -1057,13 +1038,14 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) || sd->equip_index_maybe[EQUIP::LEGS] == index)) continue; - if (sd->inventory_data[index]) + OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[index]) { sd->spellpower_bonus_target += - sd->inventory_data[index]->magic_bonus; + sdidi->magic_bonus; // used to apply cards } + OMATCH_END (); } #ifdef USE_ASTRAL_SOUL_SKILL @@ -1092,31 +1074,15 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) && (sd->equip_index_maybe[EQUIP::TORSO] == index || sd->equip_index_maybe[EQUIP::LEGS] == index)) continue; - if (sd->inventory_data[index]) + OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[index]) { - sd->def += sd->inventory_data[index]->def; - if (sd->inventory_data[index]->type == ItemType::WEAPON) + sd->def += sdidi->def; + if (sdidi->type == ItemType::WEAPON) { if (i == EQUIP::SHIELD && sd->status.inventory[index].equip == EPOS::SHIELD) { - //二刀流用データ入力 - sd->watk_ += sd->inventory_data[index]->atk; - sd->watk_2 = 0; - - sd->attackrange_ += sd->inventory_data[index]->range; - sd->state.lr_flag = 1; - { - argrec_t arg[2] = - { - {"@slotId"_s, static_cast<int>(i)}, - {"@itemId"_s, unwrap<ItemNameId>(sd->inventory_data[index]->nameid)}, - }; - run_script_l(ScriptPointer(sd->inventory_data[index]->equip_script.get(), 0), - sd->bl_id, BlockId(), - arg); - } - sd->state.lr_flag = 0; + assert(0 && "unreachable - offhand weapons are not supported"); } else { @@ -1124,66 +1090,62 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) argrec_t arg[2] = { {"@slotId"_s, static_cast<int>(i)}, - {"@itemId"_s, unwrap<ItemNameId>(sd->inventory_data[index]->nameid)}, + {"@itemId"_s, unwrap<ItemNameId>(sdidi->nameid)}, }; - sd->watk += sd->inventory_data[index]->atk; + sd->watk += sdidi->atk; - sd->attackrange += sd->inventory_data[index]->range; - run_script_l(ScriptPointer(sd->inventory_data[index]->equip_script.get(), 0), + sd->attackrange += sdidi->range; + run_script_l(ScriptPointer(borrow(*sdidi->equip_script), 0), sd->bl_id, BlockId(), arg); } } - else if (sd->inventory_data[index]->type == ItemType::ARMOR) + else if (sdidi->type == ItemType::ARMOR) { argrec_t arg[2] = { {"@slotId"_s, static_cast<int>(i)}, - {"@itemId"_s, unwrap<ItemNameId>(sd->inventory_data[index]->nameid)}, + {"@itemId"_s, unwrap<ItemNameId>(sdidi->nameid)}, }; - sd->watk += sd->inventory_data[index]->atk; - run_script_l(ScriptPointer(sd->inventory_data[index]->equip_script.get(), 0), + sd->watk += sdidi->atk; + run_script_l(ScriptPointer(borrow(*sdidi->equip_script), 0), sd->bl_id, BlockId(), arg); } } + OMATCH_END (); } if (battle_is_unarmed(sd)) { sd->watk += skill_power(sd, SkillID::TMW_BRAWLING) / 3; // +66 for 200 sd->watk2 += skill_power(sd, SkillID::TMW_BRAWLING) >> 3; // +25 for 200 - sd->watk_ += skill_power(sd, SkillID::TMW_BRAWLING) / 3; // +66 for 200 - sd->watk_2 += skill_power(sd, SkillID::TMW_BRAWLING) >> 3; // +25 for 200 } IOff0 aidx = sd->equip_index_maybe[EQUIP::ARROW]; if (aidx.ok()) { IOff0 index = aidx; - if (sd->inventory_data[index]) + OMATCH_BEGIN_SOME (sdidi, sd->inventory_data[index]) { //まだ属性が入っていない argrec_t arg[2] = { {"@slotId"_s, static_cast<int>(EQUIP::ARROW)}, - {"@itemId"_s, unwrap<ItemNameId>(sd->inventory_data[index]->nameid)}, + {"@itemId"_s, unwrap<ItemNameId>(sdidi->nameid)}, }; - sd->state.lr_flag = 2; - run_script_l(ScriptPointer(sd->inventory_data[index]->equip_script.get(), 0), + sd->state.lr_flag_is_arrow_2 = 1; + run_script_l(ScriptPointer(borrow(*sdidi->equip_script), 0), sd->bl_id, BlockId(), arg); - sd->state.lr_flag = 0; - sd->arrow_atk += sd->inventory_data[index]->atk; + sd->state.lr_flag_is_arrow_2 = 0; + sd->arrow_atk += sdidi->atk; } + OMATCH_END (); } sd->def += (refinedef + 50) / 100; if (sd->attackrange < 1) sd->attackrange = 1; - if (sd->attackrange_ < 1) - sd->attackrange_ = 1; - if (sd->attackrange < sd->attackrange_) - sd->attackrange = sd->attackrange_; if (sd->status.weapon == ItemLook::BOW) sd->attackrange += sd->arrow_range; sd->double_rate += sd->double_add_rate; @@ -1201,9 +1163,7 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) for (ATTR attr : ATTRs) sd->paramc[attr] = std::max(0, sd->status.attrs[attr] + sd->paramb[attr] + sd->parame[attr]); - if (sd->status.weapon == ItemLook::BOW - || sd->status.weapon == ItemLook::_13 - || sd->status.weapon == ItemLook::_14) + if (sd->status.weapon == ItemLook::BOW) { str = sd->paramc[ATTR::DEX]; dex = sd->paramc[ATTR::STR]; @@ -1291,20 +1251,11 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) sd->mdef2 = 1; // 二刀流 ASPD 修正 - if (sd->status.weapon < ItemLook::SINGLE_HANDED_COUNT) + { sd->aspd += aspd_base_0[sd->status.weapon] - (sd->paramc[ATTR::AGI] * 4 + sd->paramc[ATTR::DEX]) * aspd_base_0[sd->status.weapon] / 1000; - else - sd->aspd += ( - (aspd_base_0[sd->weapontype1] - - (sd->paramc[ATTR::AGI] * 4 + sd->paramc[ATTR::DEX]) - * aspd_base_0[sd->weapontype1] / 1000) - + (aspd_base_0[sd->weapontype2] - - (sd->paramc[ATTR::AGI] * 4 + sd->paramc[ATTR::DEX]) - * aspd_base_0[sd->weapontype2] / 1000) - ) - * 140 / 200; + } aspd_rate = sd->aspd_rate; @@ -1366,7 +1317,6 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) } // スキルやステータス異常による残りのパラメータ補正 - if (sd->sc_count) { // ATK/DEF変化形 if (sd->sc_data[StatusChange::SC_POISON].timer) // 毒状態 @@ -1401,7 +1351,7 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) if (sd->attack_spell_override) sd->aspd = sd->attack_spell_delay; - sd->aspd = std::max(sd->aspd, static_cast<interval_t>(battle_config.max_aspd)); + sd->aspd = std::max(sd->aspd, battle_config.max_aspd); sd->amotion = sd->aspd; sd->dmotion = std::chrono::milliseconds(800 - sd->paramc[ATTR::AGI] * 4); sd->dmotion = std::max(sd->dmotion, 400_ms); @@ -1478,6 +1428,8 @@ int pc_calcstatus(dumb_ptr<map_session_data> sd, int first) clif_updatestatus(sd, SP::HP); if (b_sp != sd->status.sp) clif_updatestatus(sd, SP::SP); + if (b_pvpchannel != sd->state.pvpchannel) + sd->state.pvpchannel = b_pvpchannel; return 0; } @@ -1498,122 +1450,116 @@ int pc_bonus(dumb_ptr<map_session_data> sd, SP type, int val) case SP::INT: case SP::DEX: case SP::LUK: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->parame[sp_to_attr(type)] += val; break; #if 0 case SP::ATK1: - if (!sd->state.lr_flag) + if (!sd->state.lr_flag_is_arrow_2) sd->watk += val; - else if (sd->state.lr_flag == 1) - sd->watk_ += val; break; #endif #if 0 case SP::ATK2: - if (!sd->state.lr_flag) + if (!sd->state.lr_flag_is_arrow_2) sd->watk2 += val; - else if (sd->state.lr_flag == 1) - sd->watk_2 += val; break; #endif #if 0 case SP::BASE_ATK: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->base_atk += val; break; #endif #if 0 case SP::MATK1: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->matk1 += val; break; #endif #if 0 case SP::MATK2: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->matk2 += val; break; #endif #if 0 case SP::DEF1: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->def += val; break; #endif case SP::MDEF1: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->mdef += val; break; #if 0 case SP::MDEF2: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->mdef += val; break; #endif case SP::HIT: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->hit += val; else sd->arrow_hit += val; break; case SP::FLEE1: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->flee += val; break; #if 0 case SP::FLEE2: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->flee2 += val * 10; break; #endif case SP::CRITICAL: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->critical += val * 10; else sd->arrow_cri += val * 10; break; case SP::MAXHP: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->status.max_hp += val; break; case SP::MAXSP: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->status.max_sp += val; break; case SP::MAXHPRATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->hprate += val; break; #if 0 case SP::MAXSPRATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->sprate += val; break; #endif #if 0 case SP::SPRATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->dsprate += val; break; #endif case SP::ATTACKRANGE: - if (!sd->state.lr_flag) + if (!sd->state.lr_flag_is_arrow_2) sd->attackrange += val; - else if (sd->state.lr_flag == 1) - sd->attackrange_ += val; - else if (sd->state.lr_flag == 2) + else sd->arrow_range += val; break; #if 0 case SP::ADD_SPEED: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->speed -= val; break; #endif #if 0 case SP::SPEED_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) { if (sd->speed_rate > 100 - val) sd->speed_rate = 100 - val; @@ -1621,17 +1567,17 @@ int pc_bonus(dumb_ptr<map_session_data> sd, SP type, int val) break; #endif case SP::SPEED_ADDRATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->speed_add_rate = sd->speed_add_rate * (100 - val) / 100; break; #if 0 case SP::ASPD: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->aspd -= val * 10; break; #endif case SP::ASPD_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) { if (sd->aspd_rate > 100 - val) sd->aspd_rate = 100 - val; @@ -1639,99 +1585,99 @@ int pc_bonus(dumb_ptr<map_session_data> sd, SP type, int val) break; #if 0 case SP::ASPD_ADDRATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->aspd_add_rate = sd->aspd_add_rate * (100 - val) / 100; break; #endif case SP::HP_RECOV_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->hprecov_rate += val; break; #if 0 case SP::SP_RECOV_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->sprecov_rate += val; break; #endif case SP::CRITICAL_DEF: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->critical_def += val; break; #if 0 case SP::DOUBLE_RATE: - if (sd->state.lr_flag == 0 && sd->double_rate < val) + if (!sd->state.lr_flag_is_arrow_2 && sd->double_rate < val) sd->double_rate = val; break; #endif case SP::DOUBLE_ADD_RATE: - if (sd->state.lr_flag == 0) + if (!sd->state.lr_flag_is_arrow_2) sd->double_add_rate += val; break; #if 0 case SP::MATK_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->matk_rate += val; break; #endif #if 0 case SP::ATK_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->atk_rate += val; break; #endif #if 0 case SP::PERFECT_HIT_RATE: - if (sd->state.lr_flag != 2 && sd->perfect_hit < val) + if (!sd->state.lr_flag_is_arrow_2 && sd->perfect_hit < val) sd->perfect_hit = val; break; #endif #if 0 case SP::PERFECT_HIT_ADD_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->perfect_hit_add += val; break; #endif #if 0 case SP::CRITICAL_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->critical_rate += val; break; #endif #if 0 case SP::HIT_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->hit_rate += val; break; #endif #if 0 case SP::FLEE_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->flee_rate += val; break; #endif #if 0 case SP::FLEE2_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->flee2_rate += val; break; #endif case SP::DEF_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->def_rate += val; break; case SP::DEF2_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->def2_rate += val; break; #if 0 case SP::MDEF_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->mdef_rate += val; break; #endif #if 0 case SP::MDEF2_RATE: - if (sd->state.lr_flag != 2) + if (!sd->state.lr_flag_is_arrow_2) sd->mdef2_rate += val; break; #endif @@ -1758,29 +1704,19 @@ int pc_bonus2(dumb_ptr<map_session_data> sd, SP type, int type2, int val) switch (type) { case SP::HP_DRAIN_RATE: - if (!sd->state.lr_flag) + if (!sd->state.lr_flag_is_arrow_2) { sd->hp_drain_rate += type2; sd->hp_drain_per += val; } - else if (sd->state.lr_flag == 1) - { - sd->hp_drain_rate_ += type2; - sd->hp_drain_per_ += val; - } break; #if 0 case SP::SP_DRAIN_RATE: - if (!sd->state.lr_flag) + if (!sd->state.lr_flag_is_arrow_2) { sd->sp_drain_rate += type2; sd->sp_drain_per += val; } - else if (sd->state.lr_flag == 1) - { - sd->sp_drain_rate_ += type2; - sd->sp_drain_per_ += val; - } break; #endif default: @@ -1971,7 +1907,6 @@ int pc_remove_items(dumb_ptr<map_session_data> player, ItemNameId item_id, int c PickupFail pc_additem(dumb_ptr<map_session_data> sd, Item *item_data, int amount) { - struct item_data *data; int w; MAP_LOG_PC(sd, "PICKUP %d %d"_fmt, item_data->nameid, amount); @@ -1981,7 +1916,7 @@ PickupFail pc_additem(dumb_ptr<map_session_data> sd, Item *item_data, if (!item_data->nameid || amount <= 0) return PickupFail::BAD_ITEM; - data = itemdb_search(item_data->nameid); + P<struct item_data> data = itemdb_search(item_data->nameid); if ((w = data->weight * amount) + sd->weight > sd->max_weight) return PickupFail::TOO_HEAVY; @@ -2013,7 +1948,7 @@ PickupFail pc_additem(dumb_ptr<map_session_data> sd, Item *item_data, sd->status.inventory[i].equip = EPOS::ZERO; sd->status.inventory[i].amount = amount; - sd->inventory_data[i] = data; + sd->inventory_data[i] = Some(data); clif_additem(sd, i, amount, PickupFail::OKAY); } else @@ -2037,18 +1972,18 @@ int pc_delitem(dumb_ptr<map_session_data> sd, IOff0 n, int amount, int type) trade_tradecancel(sd); if (!sd->status.inventory[n].nameid || amount <= 0 - || sd->status.inventory[n].amount < amount - || sd->inventory_data[n] == nullptr) + || sd->status.inventory[n].amount < amount) return 1; + P<struct item_data> sdidn = TRY_UNWRAP(sd->inventory_data[n], return 1); sd->status.inventory[n].amount -= amount; - sd->weight -= sd->inventory_data[n]->weight * amount; + sd->weight -= sdidn->weight * amount; if (sd->status.inventory[n].amount <= 0) { if (bool(sd->status.inventory[n].equip)) pc_unequipitem(sd, n, CalcStatus::NOW); sd->status.inventory[n] = Item{}; - sd->inventory_data[n] = nullptr; + sd->inventory_data[n] = None; } if (!(type & 1)) clif_delitem(sd, n, amount); @@ -2097,8 +2032,6 @@ int pc_dropitem(dumb_ptr<map_session_data> sd, IOff0 n, int amount) static int can_pick_item_up_from(dumb_ptr<map_session_data> self, BlockId other_id) { - PartyPair p = party_search(self->status.party_id); - /* From ourselves or from no-one? */ if (!self || self->bl_id == other_id || !other_id) return 1; @@ -2114,9 +2047,10 @@ int can_pick_item_up_from(dumb_ptr<map_session_data> self, BlockId other_id) return 1; /* From a party member? */ + Option<PartyPair> p = party_search(self->status.party_id); if (self->status.party_id && self->status.party_id == other->status.party_id - && p && p->item != 0) + && p.pmd_pget(&PartyMost::item).copy_or(0) != 0) return 1; /* From someone who is far away? */ @@ -2192,16 +2126,13 @@ int pc_takeitem(dumb_ptr<map_session_data> sd, dumb_ptr<flooritem_data> fitem) static int pc_isUseitem(dumb_ptr<map_session_data> sd, IOff0 n) { - struct item_data *item; ItemNameId nameid; nullpo_retz(sd); - item = sd->inventory_data[n]; + P<struct item_data> item = TRY_UNWRAP(sd->inventory_data[n], return 0); nameid = sd->status.inventory[n].nameid; - if (item == nullptr) - return 0; if (itemdb_type(nameid) != ItemType::USE) return 0; @@ -2223,7 +2154,9 @@ int pc_useitem(dumb_ptr<map_session_data> sd, IOff0 n) nullpo_retr(1, sd); - if (n.ok() && sd->inventory_data[n]) + if (!n.ok()) + return 0; + OMATCH_BEGIN_SOME (sdidn, sd->inventory_data[n]) { amount = sd->status.inventory[n].amount; if (!sd->status.inventory[n].nameid @@ -2234,12 +2167,13 @@ int pc_useitem(dumb_ptr<map_session_data> sd, IOff0 n) return 1; } - const ScriptBuffer *script = sd->inventory_data[n]->use_script.get(); + P<const ScriptBuffer> script = borrow(*sdidn->use_script); clif_useitemack(sd, n, amount - 1, 1); pc_delitem(sd, n, 1, 1); run_script(ScriptPointer(script, 0), sd->bl_id, BlockId()); } + OMATCH_END (); return 0; } @@ -2277,18 +2211,19 @@ int pc_setpos(dumb_ptr<map_session_data> sd, mapname_ = mapname_org; - map_local *m = map_mapname2mapid(mapname_); - if (!m) + Option<P<map_local>> m_ = map_mapname2mapid(mapname_); + if (m_.is_none()) { if (sd->mapname_) { IP4Address ip; int port; - if (map_mapname2ipport(mapname_, &ip, &port) == 0) + if (map_mapname2ipport(mapname_, borrow(ip), borrow(port)) == 0) { skill_stop_dancing(sd, 1); clif_clearchar(sd, clrtype); map_delblock(sd); + // *cringe* sd->mapname_ = mapname_; sd->bl_x = x; sd->bl_y = y; @@ -2310,6 +2245,7 @@ int pc_setpos(dumb_ptr<map_session_data> sd, #endif return 1; } + P<map_local> m = TRY_UNWRAP(m_, abort()); if (x < 0 || x >= m->xs || y < 0 || y >= m->ys) x = y = 0; @@ -2353,35 +2289,6 @@ int pc_setpos(dumb_ptr<map_session_data> sd, } /*========================================== - * PCのランダムワープ - *------------------------------------------ - */ -int pc_randomwarp(dumb_ptr<map_session_data> sd, BeingRemoveWhy type) -{ - int x, y, i = 0; - - nullpo_retz(sd); - - map_local *m = sd->bl_m; - - if (sd->bl_m->flag.get(MapFlag::NOTELEPORT)) // テレポート禁止 - return 0; - - do - { - x = random_::in(1, m->xs - 2); - y = random_::in(1, m->ys - 2); - } - while (bool(read_gatp(m, x, y) & MapCell::UNWALKABLE) - && (i++) < 1000); - - if (i < 1000) - pc_setpos(sd, m->name_, x, y, type); - - return 0; -} - -/*========================================== * *------------------------------------------ */ @@ -2504,8 +2411,8 @@ void pc_walk(TimerData *, tick_t tick, BlockId id, unsigned char data) if (sd->status.party_id) { // パーティのHP情報通知検査 - PartyPair p = party_search(sd->status.party_id); - if (p) + Option<PartyPair> p = party_search(sd->status.party_id); + if (p.is_some()) { int p_flag = 0; map_foreachinmovearea(std::bind(party_send_hp_check, ph::_1, sd->status.party_id, &p_flag), @@ -2631,72 +2538,6 @@ void pc_touch_all_relevant_npcs(dumb_ptr<map_session_data> sd) sd->areanpc_id = BlockId(); } -/*========================================== - * - *------------------------------------------ - */ -int pc_movepos(dumb_ptr<map_session_data> sd, int dst_x, int dst_y) -{ - int moveblock; - int dx, dy; - - struct walkpath_data wpd; - - nullpo_retz(sd); - - if (path_search(&wpd, sd->bl_m, sd->bl_x, sd->bl_y, dst_x, dst_y, 0)) - return 1; - - sd->dir = sd->head_dir = map_calc_dir(sd, dst_x, dst_y); - - dx = dst_x - sd->bl_x; - dy = dst_y - sd->bl_y; - - moveblock = (sd->bl_x / BLOCK_SIZE != dst_x / BLOCK_SIZE - || sd->bl_y / BLOCK_SIZE != dst_y / BLOCK_SIZE); - - map_foreachinmovearea(std::bind(clif_pcoutsight, ph::_1, sd), - sd->bl_m, - sd->bl_x - AREA_SIZE, sd->bl_y - AREA_SIZE, - sd->bl_x + AREA_SIZE, sd->bl_y + AREA_SIZE, - dx, dy, - BL::NUL); - - if (moveblock) - map_delblock(sd); - sd->bl_x = dst_x; - sd->bl_y = dst_y; - if (moveblock) - map_addblock(sd); - - map_foreachinmovearea(std::bind(clif_pcinsight, ph::_1, sd), - sd->bl_m, - sd->bl_x - AREA_SIZE, sd->bl_y - AREA_SIZE, - sd->bl_x + AREA_SIZE, sd->bl_y + AREA_SIZE, - -dx, -dy, - BL::NUL); - - if (sd->status.party_id) - { // パーティのHP情報通知検査 - PartyPair p = party_search(sd->status.party_id); - if (p) - { - int flag = 0; - map_foreachinmovearea(std::bind(party_send_hp_check, ph::_1, sd->status.party_id, &flag), - sd->bl_m, - sd->bl_x - AREA_SIZE, sd->bl_y - AREA_SIZE, - sd->bl_x + AREA_SIZE, sd->bl_y + AREA_SIZE, - -dx, -dy, - BL::PC); - if (flag) - sd->party_hp = -1; - } - } - - pc_touch_all_relevant_npcs(sd); - return 0; -} - // // 武器戦闘 // @@ -2764,8 +2605,8 @@ void pc_attack_timer(TimerData *, tick_t tick, BlockId id) if (sd->opt1 != Opt1::ZERO) return; - Option *opt = battle_get_option(bl); - if (opt != nullptr && bool(*opt & Option::REAL_ANY_HIDE)) + Opt0 *opt = battle_get_option(bl); + if (opt != nullptr && bool(*opt & Opt0::REAL_ANY_HIDE)) return; if (!battle_config.skill_delay_attack_enable) @@ -2824,7 +2665,7 @@ void pc_attack_timer(TimerData *, tick_t tick, BlockId id) sd->attackabletime = tick + (sd->aspd * 2); } if (sd->attackabletime <= tick) - sd->attackabletime = tick + static_cast<interval_t>(battle_config.max_aspd) * 2; + sd->attackabletime = tick + battle_config.max_aspd * 2; } } @@ -3017,6 +2858,12 @@ int pc_gainexp_reason(dumb_ptr<map_session_data> sd, int base_exp, int job_exp, } } + // Double Xp Weekends + base_exp = (base_exp * static_cast<double>(battle_config.base_exp_rate) / 100.); + if (base_exp <= 0) + base_exp = 0; + else if (base_exp > 1000000000) + base_exp = 1000000000; sd->status.base_exp += base_exp; // [Fate] Adjust experience points that healers can extract from this character @@ -3024,7 +2871,6 @@ int pc_gainexp_reason(dumb_ptr<map_session_data> sd, int base_exp, int job_exp, { const int max_heal_xp = 20 + (sd->status.base_level * sd->status.base_level); - sd->heal_xp += base_exp; if (sd->heal_xp > max_heal_xp) sd->heal_xp = max_heal_xp; @@ -3047,6 +2893,12 @@ int pc_gainexp_reason(dumb_ptr<map_session_data> sd, int base_exp, int job_exp, } } + // Double Xp Weekends + job_exp = (job_exp * static_cast<double>(battle_config.job_exp_rate) / 100.); + if (job_exp <= 0) + job_exp = 0; + else if (job_exp > 1000000000) + job_exp = 1000000000; sd->status.job_exp += job_exp; if (sd->status.job_exp < 0) sd->status.job_exp = 0; @@ -3247,94 +3099,6 @@ int pc_skillup(dumb_ptr<map_session_data> sd, SkillID skill_num) } /*========================================== - * /resetlvl - *------------------------------------------ - */ -int pc_resetlvl(dumb_ptr<map_session_data> sd, int type) -{ - nullpo_retz(sd); - - for (SkillID i : erange(SkillID(1), MAX_SKILL)) - { - sd->status.skill[i].lv = 0; - } - - if (type == 1) - { - sd->status.skill_point = 0; - sd->status.base_level = 1; - sd->status.job_level = 1; - sd->status.base_exp = 0; - sd->status.job_exp = 0; - sd->status.option = Option::ZERO; - - for (ATTR attr : ATTRs) - sd->status.attrs[attr] = 1; - } - - if (type == 2) - { - sd->status.skill_point = 0; - sd->status.base_level = 1; - sd->status.job_level = 1; - sd->status.base_exp = 0; - sd->status.job_exp = 0; - } - if (type == 3) - { - sd->status.base_level = 1; - sd->status.base_exp = 0; - } - if (type == 4) - { - sd->status.job_level = 1; - sd->status.job_exp = 0; - } - - clif_updatestatus(sd, SP::STATUSPOINT); - clif_updatestatus(sd, SP::STR); - clif_updatestatus(sd, SP::AGI); - clif_updatestatus(sd, SP::VIT); - clif_updatestatus(sd, SP::INT); - clif_updatestatus(sd, SP::DEX); - clif_updatestatus(sd, SP::LUK); - clif_updatestatus(sd, SP::BASELEVEL); - clif_updatestatus(sd, SP::JOBLEVEL); - clif_updatestatus(sd, SP::STATUSPOINT); - clif_updatestatus(sd, SP::NEXTBASEEXP); - clif_updatestatus(sd, SP::NEXTJOBEXP); - clif_updatestatus(sd, SP::SKILLPOINT); - - clif_updatestatus(sd, SP::USTR); // Updates needed stat points - Valaris - clif_updatestatus(sd, SP::UAGI); - clif_updatestatus(sd, SP::UVIT); - clif_updatestatus(sd, SP::UINT); - clif_updatestatus(sd, SP::UDEX); - clif_updatestatus(sd, SP::ULUK); // End Addition - - for (EQUIP i : EQUIPs) - { - // unequip items that can't be equipped by base 1 [Valaris] - IOff0 *idx = &sd->equip_index_maybe[i]; - if ((*idx).ok()) - { - if (!pc_isequip(sd, *idx)) - { - pc_unequipitem(sd, *idx, CalcStatus::LATER); - *idx = IOff0::from(-1); - } - } - } - - clif_skillinfoblock(sd); - pc_calcstatus(sd, 0); - - MAP_LOG_STATS(sd, "STATRESET"_fmt); - - return 0; -} - -/*========================================== * /resetstate *------------------------------------------ */ @@ -3435,9 +3199,12 @@ int pc_damage(dumb_ptr<block_list> src, dumb_ptr<map_session_data> sd, if (sd->status.party_id) { // on-the-fly party hp updates [Valaris] - PartyPair p = party_search(sd->status.party_id); - if (p) + Option<PartyPair> p_ = party_search(sd->status.party_id); + OMATCH_BEGIN_SOME (p, p_) + { clif_party_hp(p, sd); + } + OMATCH_END (); } // end addition [Valaris] return 0; @@ -3728,8 +3495,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; @@ -3755,7 +3521,7 @@ int pc_setparam(dumb_ptr<map_session_data> sd, SP type, int val) case SP::INT: case SP::DEX: case SP::LUK: - sd->status.attrs[sp_to_attr(type)] = val; + pc_statusup2(sd, type, (val - sd->status.attrs[sp_to_attr(type)])); break; } clif_updatestatus(sd, type); @@ -3803,9 +3569,12 @@ int pc_heal(dumb_ptr<map_session_data> sd, int hp, int sp) if (sd->status.party_id) { // on-the-fly party hp updates [Valaris] - PartyPair p = party_search(sd->status.party_id); - if (p) + Option<PartyPair> p_ = party_search(sd->status.party_id); + OMATCH_BEGIN_SOME (p, p_) + { clif_party_hp(p, sd); + } + OMATCH_END (); } // end addition [Valaris] return hp + sp; @@ -3933,75 +3702,6 @@ int pc_itemheal_effect(dumb_ptr<map_session_data> sd, int hp, int sp) } /*========================================== - * HP/SP回復 - *------------------------------------------ - */ -int pc_percentheal(dumb_ptr<map_session_data> sd, int hp, int sp) -{ - nullpo_retz(sd); - - if (pc_checkoverhp(sd)) - { - if (hp > 0) - hp = 0; - } - if (pc_checkoversp(sd)) - { - if (sp > 0) - sp = 0; - } - if (hp) - { - if (hp >= 100) - { - sd->status.hp = sd->status.max_hp; - } - else if (hp <= -100) - { - sd->status.hp = 0; - pc_damage(nullptr, sd, 1); - } - else - { - sd->status.hp += sd->status.max_hp * hp / 100; - if (sd->status.hp > sd->status.max_hp) - sd->status.hp = sd->status.max_hp; - if (sd->status.hp <= 0) - { - sd->status.hp = 0; - pc_damage(nullptr, sd, 1); - hp = 0; - } - } - } - if (sp) - { - if (sp >= 100) - { - sd->status.sp = sd->status.max_sp; - } - else if (sp <= -100) - { - sd->status.sp = 0; - } - else - { - sd->status.sp += sd->status.max_sp * sp / 100; - if (sd->status.sp > sd->status.max_sp) - sd->status.sp = sd->status.max_sp; - if (sd->status.sp < 0) - sd->status.sp = 0; - } - } - if (hp) - clif_updatestatus(sd, SP::HP); - if (sp) - clif_updatestatus(sd, SP::SP); - - return 0; -} - -/*========================================== * 見た目変更 *------------------------------------------ */ @@ -4044,21 +3744,6 @@ int pc_changelook(dumb_ptr<map_session_data> sd, LOOK type, int val) } /*========================================== - * 付属品(鷹,ペコ,カート)設定 - *------------------------------------------ - */ -int pc_setoption(dumb_ptr<map_session_data> sd, Option type) -{ - nullpo_retz(sd); - - sd->status.option = type; - clif_changeoption(sd); - pc_calcstatus(sd, 0); - - return 0; -} - -/*========================================== * script用変数の値を読む *------------------------------------------ */ @@ -4088,11 +3773,8 @@ ZString pc_readregstr(dumb_ptr<map_session_data> sd, SIR reg) { nullpo_retr(ZString(), sd); - RString *s = sd->regstrm.search(reg); - if (s) - return *s; - - return ZString(); + Option<P<RString>> s = sd->regstrm.search(reg); + return s.map([](P<RString> s_) -> ZString { return *s_; }).copy_or(""_s); } /*========================================== @@ -4119,14 +3801,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; @@ -4139,8 +3844,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) @@ -4148,6 +3858,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) { @@ -4155,9 +3876,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; } } @@ -4167,14 +3897,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; } @@ -4400,7 +4140,6 @@ int pc_signal_advanced_equipment_change(dumb_ptr<map_session_data> sd, IOff0 n) int pc_equipitem(dumb_ptr<map_session_data> sd, IOff0 n, EPOS) { ItemNameId nameid; - struct item_data *id; //ソス]ソスソスソスソスソス{ソスqソスフ場合ソスフ鯉ソスソスフ職ソスニゑソスソスZソスoソスソスソスソス nullpo_retz(sd); @@ -4412,9 +4151,8 @@ int pc_equipitem(dumb_ptr<map_session_data> sd, IOff0 n, EPOS) } nameid = sd->status.inventory[n].nameid; - id = sd->inventory_data[n]; - if (!id) // can't actually happen - the only caller checks this. - return 0; + // can't actually happen - the only caller checks this. + P<struct item_data> id = TRY_UNWRAP(sd->inventory_data[n], return 0); EPOS pos = pc_equippoint(sd, n); if (battle_config.battle_log) @@ -4475,17 +4213,18 @@ int pc_equipitem(dumb_ptr<map_session_data> sd, IOff0 n, EPOS) ItemNameId view_i; ItemLook view_l = ItemLook::NONE; // TODO: This is ugly. - if (sd->inventory_data[n]) + OMATCH_BEGIN_SOME (sdidn, sd->inventory_data[n]) { - bool look_not_weapon = sd->inventory_data[n]->look == ItemLook::NONE; + bool look_not_weapon = sdidn->look == ItemLook::NONE; bool equip_is_weapon = bool(sd->status.inventory[n].equip & EPOS::WEAPON); assert (look_not_weapon != equip_is_weapon); if (look_not_weapon) - view_i = sd->inventory_data[n]->nameid; + view_i = sdidn->nameid; else - view_l = sd->inventory_data[n]->look; + view_l = sdidn->look; } + OMATCH_END (); if (bool(sd->status.inventory[n].equip & EPOS::WEAPON)) { @@ -4495,25 +4234,27 @@ int pc_equipitem(dumb_ptr<map_session_data> sd, IOff0 n, EPOS) } if (bool(sd->status.inventory[n].equip & EPOS::SHIELD)) { - if (sd->inventory_data[n]) + OMATCH_BEGIN (sd->inventory_data[n]) { - if (sd->inventory_data[n]->type == ItemType::WEAPON) + OMATCH_CASE_SOME (sdidn) { - sd->status.shield = ItemNameId(); - if (sd->status.inventory[n].equip == EPOS::SHIELD) - sd->weapontype2 = view_l; + if (sdidn->type == ItemType::WEAPON) + { + sd->status.shield = ItemNameId(); + if (sd->status.inventory[n].equip == EPOS::SHIELD) + assert(0 && "unreachable - offhand weapons are not supported"); + } + else if (sdidn->type == ItemType::ARMOR) + { + sd->status.shield = view_i; + } } - else if (sd->inventory_data[n]->type == ItemType::ARMOR) + OMATCH_CASE_NONE () { - sd->status.shield = view_i; - sd->weapontype2 = ItemLook::NONE; + sd->status.shield = ItemNameId(); } } - else - { - sd->status.shield = ItemNameId(); - sd->weapontype2 = ItemLook::NONE; - } + OMATCH_END (); pc_calcweapontype(sd); clif_changelook(sd, LOOK::SHIELD, unwrap<ItemNameId>(sd->status.shield)); } @@ -4563,14 +4304,14 @@ int pc_unequipitem(dumb_ptr<map_session_data> sd, IOff0 n, CalcStatus type) if (bool(sd->status.inventory[n].equip & EPOS::WEAPON)) { sd->weapontype1 = ItemLook::NONE; - sd->status.weapon = sd->weapontype2; + // when reading the diff, think twice about this + sd->status.weapon = ItemLook::NONE; pc_calcweapontype(sd); pc_set_weapon_look(sd); } if (bool(sd->status.inventory[n].equip & EPOS::SHIELD)) { sd->status.shield = ItemNameId(); - sd->weapontype2 = ItemLook::NONE; pc_calcweapontype(sd); clif_changelook(sd, LOOK::SHIELD, unwrap<ItemNameId>(sd->status.shield)); } @@ -4728,8 +4469,7 @@ void pc_calc_pvprank_sub(dumb_ptr<block_list> bl, dumb_ptr<map_session_data> sd2 int pc_calc_pvprank(dumb_ptr<map_session_data> sd) { nullpo_retz(sd); - map_local *m = sd->bl_m; - nullpo_retz(m); + P<map_local> m = sd->bl_m; if (!(m->flag.get(MapFlag::PVP))) return 0; @@ -4816,15 +4556,12 @@ int pc_divorce(dumb_ptr<map_session_data> sd) } p_sd->status.partner_id = CharId(); sd->status.partner_id = CharId(); - - if (sd->npc_flags.divorce) - { - sd->npc_flags.divorce = 0; - map_scriptcont(sd, sd->npc_id); - } } else + { + sd->status.partner_id = CharId(); chrif_send_divorce(sd->status_key.char_id); + } return 0; } @@ -4857,10 +4594,6 @@ dumb_ptr<map_session_data> pc_get_partner(dumb_ptr<map_session_data> sd) * SP回復量計算 *------------------------------------------ */ -static -tick_t natural_heal_tick, natural_heal_prev_tick; -static -interval_t natural_heal_diff_tick; static interval_t pc_spheal(dumb_ptr<map_session_data> sd) @@ -4918,12 +4651,12 @@ int pc_natural_heal_hp(dumb_ptr<map_session_data> sd) return 0; } - if (sd->hp_sub >= static_cast<interval_t>(battle_config.natural_healhp_interval)) + if (sd->hp_sub >= battle_config.natural_healhp_interval) { bonus = sd->nhealhp; - while (sd->hp_sub >= static_cast<interval_t>(battle_config.natural_healhp_interval)) + while (sd->hp_sub >= battle_config.natural_healhp_interval) { - sd->hp_sub -= static_cast<interval_t>(battle_config.natural_healhp_interval); + sd->hp_sub -= battle_config.natural_healhp_interval; if (sd->status.hp + bonus <= sd->status.max_hp) sd->status.hp += bonus; else @@ -4936,28 +4669,7 @@ int pc_natural_heal_hp(dumb_ptr<map_session_data> sd) if (bhp != sd->status.hp) clif_updatestatus(sd, SP::HP); - if (sd->nshealhp > 0) - { - if (sd->inchealhptick >= static_cast<interval_t>(battle_config.natural_heal_skill_interval) - && sd->status.hp < sd->status.max_hp) - { - bonus = sd->nshealhp; - while (sd->inchealhptick >= static_cast<interval_t>(battle_config.natural_heal_skill_interval)) - { - sd->inchealhptick -= static_cast<interval_t>(battle_config.natural_heal_skill_interval); - if (sd->status.hp + bonus <= sd->status.max_hp) - sd->status.hp += bonus; - else - { - bonus = sd->status.max_hp - sd->status.hp; - sd->status.hp = sd->status.max_hp; - sd->hp_sub = sd->inchealhptick = interval_t::zero(); - } - } - } - } - else - sd->inchealhptick = interval_t::zero(); + sd->inchealhptick = interval_t::zero(); return 0; } @@ -4985,12 +4697,12 @@ int pc_natural_heal_sp(dumb_ptr<map_session_data> sd) else sd->inchealsptick = interval_t::zero(); - if (sd->sp_sub >= static_cast<interval_t>(battle_config.natural_healsp_interval)) + if (sd->sp_sub >= battle_config.natural_healsp_interval) { bonus = sd->nhealsp; - while (sd->sp_sub >= static_cast<interval_t>(battle_config.natural_healsp_interval)) + while (sd->sp_sub >= battle_config.natural_healsp_interval) { - sd->sp_sub -= static_cast<interval_t>(battle_config.natural_healsp_interval); + sd->sp_sub -= battle_config.natural_healsp_interval; if (sd->status.sp + bonus <= sd->status.max_sp) sd->status.sp += bonus; else @@ -5004,28 +4716,7 @@ int pc_natural_heal_sp(dumb_ptr<map_session_data> sd) if (bsp != sd->status.sp) clif_updatestatus(sd, SP::SP); - if (sd->nshealsp > 0) - { - if (sd->inchealsptick >= static_cast<interval_t>(battle_config.natural_heal_skill_interval) - && sd->status.sp < sd->status.max_sp) - { - bonus = sd->nshealsp; - while (sd->inchealsptick >= static_cast<interval_t>(battle_config.natural_heal_skill_interval)) - { - sd->inchealsptick -= static_cast<interval_t>(battle_config.natural_heal_skill_interval); - if (sd->status.sp + bonus <= sd->status.max_sp) - sd->status.sp += bonus; - else - { - bonus = sd->status.max_sp - sd->status.sp; - sd->status.sp = sd->status.max_sp; - sd->sp_sub = sd->inchealsptick = interval_t::zero(); - } - } - } - } - else - sd->inchealsptick = interval_t::zero(); + sd->inchealsptick = interval_t::zero(); return 0; } @@ -5147,8 +4838,6 @@ void pc_setsavepoint(dumb_ptr<map_session_data> sd, MapName mapname, int x, int *------------------------------------------ */ static -int last_save_fd, save_flag; -static void pc_autosave_sub(dumb_ptr<map_session_data> sd) { nullpo_retv(sd); @@ -5175,7 +4864,7 @@ void pc_autosave(TimerData *, tick_t) if (save_flag == 0) last_save_fd = -1; - interval_t interval = autosave_time / (clif_countusers() + 1); + interval_t interval = map_conf.autosave_time / (clif_countusers() + 1); if (interval <= interval_t::zero()) interval = 1_ms; Timer(gettick() + interval, @@ -5228,7 +4917,7 @@ void do_init_pc(void) pc_natural_heal, NATURAL_HEAL_INTERVAL ).detach(); - Timer(gettick() + autosave_time, + Timer(gettick() + map_conf.autosave_time, pc_autosave ).detach(); } @@ -5240,15 +4929,15 @@ void pc_cleanup(dumb_ptr<map_session_data> sd) void pc_invisibility(dumb_ptr<map_session_data> sd, int enabled) { - if (enabled && !bool(sd->status.option & Option::INVISIBILITY)) + if (enabled && !bool(sd->status.option & Opt0::INVISIBILITY)) { clif_clearchar(sd, BeingRemoveWhy::WARPED); - sd->status.option |= Option::INVISIBILITY; + sd->status.option |= Opt0::INVISIBILITY; clif_status_change(sd, StatusChange::CLIF_OPTION_SC_INVISIBILITY, 1); } else if (!enabled) { - sd->status.option &= ~Option::INVISIBILITY; + sd->status.option &= ~Opt0::INVISIBILITY; clif_status_change(sd, StatusChange::CLIF_OPTION_SC_INVISIBILITY, 0); pc_setpos(sd, sd->bl_m->name_, sd->bl_x, sd->bl_y, BeingRemoveWhy::WARPED); } @@ -5280,4 +4969,5 @@ int pc_logout(dumb_ptr<map_session_data> sd) // [fate] Player logs out MAP_LOG_STATS(sd, "LOGOUT"_fmt); return 0; } +} // namespace map } // namespace tmwa diff --git a/src/map/pc.hpp b/src/map/pc.hpp index 3187cd9..d100938 100644 --- a/src/map/pc.hpp +++ b/src/map/pc.hpp @@ -20,24 +20,21 @@ // 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 "pc.t.hpp" -#include "../strings/fwd.hpp" +#include "fwd.hpp" #include "../generic/dumb_ptr.hpp" -#include "../mmo/utils.hpp" - -#include "../proto2/fwd.hpp" - -#include "clif.t.hpp" +#include "../mmo/clif.t.hpp" #include "map.hpp" +#include "quest.hpp" namespace tmwa { +namespace map +{ inline void pc_setsit(dumb_ptr<map_session_data> sd) { @@ -62,7 +59,7 @@ void pc_setdir(dumb_ptr<map_session_data> sd, DIR b) inline bool pc_isinvisible(dumb_ptr<map_session_data> sd) { - return bool(sd->status.option & Option::HIDE); + return bool(sd->status.option & Opt0::HIDE); } inline bool pc_is90overweight(dumb_ptr<map_session_data> sd) @@ -83,7 +80,7 @@ int pc_counttargeted(dumb_ptr<map_session_data> sd, dumb_ptr<block_list> src, int pc_setrestartvalue(dumb_ptr<map_session_data> sd, int type); void pc_makesavestatus(dumb_ptr<map_session_data>); int pc_setnewpc(dumb_ptr<map_session_data>, AccountId, CharId, int, uint32_t /*tick_t*/, SEX); -int pc_authok(AccountId, int, TimeT, short tmw_version, const CharKey *, const CharData *); +int pc_authok(AccountId, int, short tmw_version, const CharKey *, const CharData *); int pc_authfail(AccountId accid); EPOS pc_equippoint(dumb_ptr<map_session_data> sd, IOff0 n); @@ -93,10 +90,8 @@ IOff0 pc_checkequip(dumb_ptr<map_session_data> sd, EPOS pos); int pc_walktoxy(dumb_ptr<map_session_data>, int, int); int pc_stop_walking(dumb_ptr<map_session_data>, int); -int pc_movepos(dumb_ptr<map_session_data>, int, int); int pc_setpos(dumb_ptr<map_session_data>, MapName, int, int, BeingRemoveWhy); void pc_setsavepoint(dumb_ptr<map_session_data>, MapName, int, int); -int pc_randomwarp(dumb_ptr<map_session_data> sd, BeingRemoveWhy type); ADDITEM pc_checkadditem(dumb_ptr<map_session_data>, ItemNameId, int); int pc_inventoryblank(dumb_ptr<map_session_data>); @@ -133,7 +128,6 @@ int pc_need_status_point(dumb_ptr<map_session_data>, SP); int pc_statusup(dumb_ptr<map_session_data>, SP); int pc_statusup2(dumb_ptr<map_session_data>, SP, int); int pc_skillup(dumb_ptr<map_session_data>, SkillID); -int pc_resetlvl(dumb_ptr<map_session_data>, int type); int pc_resetstate(dumb_ptr<map_session_data>); int pc_resetskill(dumb_ptr<map_session_data>); int pc_equipitem(dumb_ptr<map_session_data>, IOff0, EPOS); @@ -144,8 +138,6 @@ int pc_useitem(dumb_ptr<map_session_data>, IOff0); int pc_damage(dumb_ptr<block_list>, dumb_ptr<map_session_data>, int); int pc_heal(dumb_ptr<map_session_data>, int, int); int pc_itemheal(dumb_ptr<map_session_data> sd, int hp, int sp); -int pc_percentheal(dumb_ptr<map_session_data> sd, int, int); -int pc_setoption(dumb_ptr<map_session_data>, Option); int pc_changelook(dumb_ptr<map_session_data>, LOOK, int); int pc_readparam(dumb_ptr<map_session_data>, SP); @@ -154,6 +146,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 ); @@ -177,6 +171,8 @@ void pc_setstand(dumb_ptr<map_session_data> sd); void pc_cleanup(dumb_ptr<map_session_data> sd); // [Fate] Clean up after a logged-out PC int pc_read_gm_account(Session *, const std::vector<Packet_Repeat<0x2b15>>&); +int pc_setpvptimer(dumb_ptr<map_session_data> sd, interval_t); +int pc_delpvptimer(dumb_ptr<map_session_data> sd); int pc_setinvincibletimer(dumb_ptr<map_session_data> sd, interval_t); int pc_delinvincibletimer(dumb_ptr<map_session_data> sd); int pc_logout(dumb_ptr<map_session_data> sd); // [fate] Player logs out @@ -184,4 +180,5 @@ int pc_logout(dumb_ptr<map_session_data> sd); // [fate] Player logs out void pc_show_motd(dumb_ptr<map_session_data> sd); void do_init_pc(void); +} // namespace map } // namespace tmwa diff --git a/src/map/pc.t.hpp b/src/map/pc.t.hpp index 427e8c3..c9235fa 100644 --- a/src/map/pc.t.hpp +++ b/src/map/pc.t.hpp @@ -28,6 +28,8 @@ namespace tmwa { +namespace map +{ enum class PC_GAINEXP_REASON { KILLING = 0, @@ -54,4 +56,5 @@ enum class CalcStatus NOW, LATER, }; +} // namespace map } // namespace tmwa 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/sexpr/bind.cpp b/src/map/quest.hpp index d8d0caa..65e6f4e 100644 --- a/src/sexpr/bind.cpp +++ b/src/map/quest.hpp @@ -1,7 +1,7 @@ -#include "bind.hpp" -// bind.cpp - Just include the header file. +#pragma once +// quest.hpp - Quest Log. // -// Copyright © 2012 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © 2015 Ed Pasek <pasekei@gmail.com> // // This file is part of The Mana World (Athena server) // @@ -18,12 +18,34 @@ // 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 "../poison.hpp" +#include "fwd.hpp" +#include "../mmo/ids.hpp" +#include "../high/mmo.hpp" + +#include "map.hpp" +#include "script-buffer.hpp" namespace tmwa { -namespace sexpr +namespace map +{ +constexpr int MAX_QUEST_DB (60355+1); +struct quest_data { -} // namespace sexpr + 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-buffer.hpp b/src/map/script-buffer.hpp new file mode 100644 index 0000000..e720044 --- /dev/null +++ b/src/map/script-buffer.hpp @@ -0,0 +1,45 @@ +#pragma once +// script-buffer.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include <memory> + + +namespace tmwa +{ +namespace map +{ +class ScriptBuffer; +} // namespace map +} // namespace tmwa + +namespace std +{ +template<> +struct default_delete<const tmwa::map::ScriptBuffer> +{ + default_delete() {} + default_delete(default_delete<tmwa::map::ScriptBuffer>) {} + void operator()(const tmwa::map::ScriptBuffer *sd); +}; +} // namespace std diff --git a/src/map/script-call-internal.hpp b/src/map/script-call-internal.hpp new file mode 100644 index 0000000..b9b3a9f --- /dev/null +++ b/src/map/script-call-internal.hpp @@ -0,0 +1,100 @@ +#pragma once +// script-call-internal.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "script-call.hpp" +#include "fwd.hpp" + +#include "../mmo/ids.hpp" + +#include "script-persist.hpp" + + +namespace tmwa +{ +namespace map +{ +enum class VariableCode : uint8_t +{ + PARAM, + VARIABLE, +}; + +struct script_stack +{ + std::vector<struct script_data> stack_datav; +}; + +enum class ScriptEndState; +// future improvements coming! +class ScriptState +{ +public: + struct script_stack *stack; + int start, end; + ScriptEndState state; + BlockId rid, oid; + ScriptPointer scriptp, new_scriptp; + int defsp, new_defsp, freeloop; +}; + +void run_func(ScriptState *st); + +enum class ScriptEndState +{ + ZERO, + STOP, + END, + RERUNLINE, + GOTO, + RETFUNC, +}; + +dumb_ptr<map_session_data> script_rid2sd(ScriptState *st); +void get_val(dumb_ptr<map_session_data> sd, struct script_data *data); +__attribute__((deprecated)) +void get_val(ScriptState *st, struct script_data *data); +struct script_data get_val2(ScriptState *st, SIR reg); +void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd); +void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id); +void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd); +__attribute__((warn_unused_result)) +RString conv_str(ScriptState *st, struct script_data *data); +__attribute__((warn_unused_result)) +int conv_num(ScriptState *st, struct script_data *data); +__attribute__((warn_unused_result)) +Borrowed<const ScriptBuffer> conv_script(ScriptState *st, struct script_data *data); + +template<class T> +void push_int(struct script_stack *stack, int val); +template<class T> +void push_reg(struct script_stack *stack, SIR reg); +template<class T> +void push_script(struct script_stack *stack, Borrowed<const ScriptBuffer> code); +template<class T> +void push_str(struct script_stack *stack, RString str); + +void push_copy(struct script_stack *stack, int pos_); +void pop_stack(struct script_stack *stack, int start, int end); +} // namespace map +} // namespace tmwa + +#include "script-call-internal.tcc" diff --git a/src/map/script-call-internal.tcc b/src/map/script-call-internal.tcc new file mode 100644 index 0000000..e10b69c --- /dev/null +++ b/src/map/script-call-internal.tcc @@ -0,0 +1,79 @@ +// script-call-internal.tcc - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "script-persist.hpp" + + +namespace tmwa +{ +namespace map +{ +template<class D> +bool first_type_is_any() +{ + return false; +} + +template<class D, class F, class... R> +constexpr +bool first_type_is_any() +{ + return std::is_same<D, F>::value || first_type_is_any<D, R...>(); +} + + +template<class T> +void push_int(struct script_stack *stack, int val) +{ + static_assert(first_type_is_any<T, ScriptDataPos, ScriptDataInt, ScriptDataArg, ScriptDataFuncRef>(), "not int type"); + + script_data nsd = T{.numi= val}; + stack->stack_datav.push_back(nsd); +} + +template<class T> +void push_reg(struct script_stack *stack, SIR reg) +{ + static_assert(first_type_is_any<T, ScriptDataParam, ScriptDataVariable>(), "not reg type"); + + script_data nsd = T{.reg= reg}; + stack->stack_datav.push_back(nsd); +} + +template<class T> +void push_script(struct script_stack *stack, Borrowed<const ScriptBuffer> code) +{ + static_assert(first_type_is_any<T, ScriptDataRetInfo>(), "not scriptbuf type"); + + script_data nsd = T{.script= code}; + stack->stack_datav.push_back(nsd); +} + +template<class T> +void push_str(struct script_stack *stack, RString str) +{ + static_assert(first_type_is_any<T, ScriptDataStr>(), "not str type"); + + script_data nsd = T{.str= str}; + stack->stack_datav.push_back(nsd); +} +} // namespace map +} // namespace tmwa diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp new file mode 100644 index 0000000..c3c6aa1 --- /dev/null +++ b/src/map/script-call.cpp @@ -0,0 +1,926 @@ +#include "script-call-internal.hpp" +// script-call.cpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011 Chuck Miller +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © 2013 wushin +// +// 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 "../generic/intern-pool.hpp" + +#include "../io/cxxstdio.hpp" + +#include "../mmo/cxxstdio_enums.hpp" + +#include "battle.hpp" +#include "battle_conf.hpp" +#include "globals.hpp" +#include "map.hpp" +#include "npc.hpp" +#include "pc.hpp" +#include "script-fun.hpp" +#include "script-parse-internal.hpp" +#include "script-persist.hpp" +#include "script-startup-internal.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace map +{ +constexpr bool DEBUG_RUN = false; + +static +struct ScriptConfigRun +{ + static const + int check_cmdcount = 8192; + static const + int check_gotocount = 512; +} script_config; + + +/*========================================== + * ridからsdへの解決 + *------------------------------------------ + */ +dumb_ptr<map_session_data> script_rid2sd(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = map_id2sd(st->rid); + if (!sd) + { + PRINTF("script_rid2sd: fatal error ! player not attached!\n"_fmt); + } + return sd; +} + +/*========================================== + * 変数の読み取り + *------------------------------------------ + */ +void get_val(dumb_ptr<map_session_data> sd, struct script_data *data) +{ + MATCH_BEGIN (*data) + { + MATCH_CASE (const ScriptDataParam&, u) + { + if (sd == nullptr) + PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp()); + int numi = 0; + if (sd) + numi = pc_readparam(sd, u.reg.sp()); + *data = ScriptDataInt{numi}; + } + MATCH_CASE (const ScriptDataVariable&, u) + { + ZString name_ = variable_names.outtern(u.reg.base()); + VarName name = stringish<VarName>(name_); + char prefix = name.front(); + char postfix = name.back(); + + if (prefix != '$') + { + if (sd == nullptr) + PRINTF("get_val error name?:%s\n"_fmt, name); + } + if (postfix == '$') + { + RString str; + if (prefix == '@') + { + if (sd) + str = pc_readregstr(sd, u.reg); + } + else if (prefix == '$') + { + Option<P<RString>> s_ = mapregstr_db.search(u.reg); + OMATCH_BEGIN_SOME (s, s_) + { + str = *s; + } + OMATCH_END (); + } + else + { + PRINTF("script: get_val: illegal scope string variable.\n"_fmt); + str = "!!ERROR!!"_s; + } + *data = ScriptDataStr{str}; + } + else + { + int numi = 0; + if (prefix == '@') + { + if (sd) + numi = pc_readreg(sd, u.reg); + } + else if (prefix == '$') + { + numi = mapreg_db.get(u.reg); + } + else if (prefix == '#') + { + if (name[1] == '#') + { + if (sd) + numi = pc_readaccountreg2(sd, name); + } + else + { + if (sd) + numi = pc_readaccountreg(sd, name); + } + } + else + { + if (sd) + numi = pc_readglobalreg(sd, name); + } + *data = ScriptDataInt{numi}; + } + } + } + MATCH_END (); +} + +void get_val(ScriptState *st, struct script_data *data) +{ + dumb_ptr<map_session_data> sd = st->rid ? map_id2sd(st->rid) : nullptr; + get_val(sd, data); +} + +/*========================================== + * 変数の読み取り2 + *------------------------------------------ + */ +struct script_data get_val2(ScriptState *st, SIR reg) +{ + struct script_data dat = ScriptDataVariable{reg}; + get_val(st, &dat); + return dat; +} + +/*========================================== + * 変数設定用 + *------------------------------------------ + */ +void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd) +{ + if (type == VariableCode::PARAM) + { + int val = vd.get_if<ScriptDataInt>()->numi; + pc_setparam(sd, reg.sp(), val); + return; + } + assert (type == VariableCode::VARIABLE); + + ZString name_ = variable_names.outtern(reg.base()); + VarName name = stringish<VarName>(name_); + char prefix = name.front(); + char postfix = name.back(); + + if (postfix == '$') + { + RString str = vd.get_if<ScriptDataStr>()->str; + if (prefix == '@') + { + pc_setregstr(sd, reg, str); + } + else if (prefix == '$') + { + mapreg_setregstr(reg, str); + } + else + { + PRINTF("script: set_reg: illegal scope string variable !"_fmt); + } + } + else + { + int val = vd.get_if<ScriptDataInt>()->numi; + if (prefix == '@') + { + pc_setreg(sd, reg, val); + } + else if (prefix == '$') + { + mapreg_setreg(reg, val); + } + else if (prefix == '#') + { + if (name[1] == '#') + pc_setaccountreg2(sd, name, val); + else + pc_setaccountreg(sd, name, val); + } + else + { + pc_setglobalreg(sd, name, val); + } + } +} + +void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id) +{ + struct script_data vd = ScriptDataInt{id}; + set_reg(sd, type, reg, vd); +} + +void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd) +{ + struct script_data vd = ScriptDataStr{zd}; + set_reg(sd, type, reg, vd); +} + +/*========================================== + * 文字列への変換 + *------------------------------------------ + */ +RString conv_str(ScriptState *st, struct script_data *data) +{ + get_val(st, data); + assert (!data->is<ScriptDataRetInfo>()); + if (auto *u = data->get_if<ScriptDataInt>()) + { + AString buf = STRPRINTF("%d"_fmt, u->numi); + *data = ScriptDataStr{buf}; + } + return data->get_if<ScriptDataStr>()->str; +} + +/*========================================== + * 数値へ変換 + *------------------------------------------ + */ +int conv_num(ScriptState *st, struct script_data *data) +{ + int rv = 0; + get_val(st, data); + assert (!data->is<ScriptDataRetInfo>()); + MATCH_BEGIN (*data) + { + MATCH_DEFAULT () + { + abort(); + } + MATCH_CASE (const ScriptDataStr&, u) + { + RString p = u.str; + rv = atoi(p.c_str()); + } + MATCH_CASE (const ScriptDataInt&, u) + { + return u.numi; + } + MATCH_CASE (const ScriptDataPos&, u) + { + return u.numi; + } + } + MATCH_END () + *data = ScriptDataInt{rv}; + return rv; +} + +Borrowed<const ScriptBuffer> conv_script(ScriptState *st, struct script_data *data) +{ + get_val(st, data); + return data->get_if<ScriptDataRetInfo>()->script; +} + +void push_copy(struct script_stack *stack, int pos_) +{ + script_data csd = stack->stack_datav[pos_]; + stack->stack_datav.push_back(csd); +} + +void pop_stack(struct script_stack *stack, int start, int end) +{ + auto it = stack->stack_datav.begin(); + stack->stack_datav.erase(it + start, it + end); +} + +// +// 実行部main +// +/*========================================== + * コマンドの読み取り + *------------------------------------------ + */ +static +ByteCode get_com(ScriptPointer *script) +{ + if (static_cast<uint8_t>(script->peek()) >= 0x80) + { + // synthetic! Does not advance pos yet. + return ByteCode::INT; + } + return script->pop(); +} + +/*========================================== + * 数値の所得 + *------------------------------------------ + */ +static +int get_num(ScriptPointer *scr) +{ + int i = 0; + int j = 0; + uint8_t val; + do + { + val = static_cast<uint8_t>(scr->pop()); + i += (val & 0x7f) << j; + j += 6; + } + while (val >= 0xc0); + return i; +} + +/*========================================== + * スタックから値を取り出す + *------------------------------------------ + */ +static +int pop_val(ScriptState *st) +{ + if (st->stack->stack_datav.empty()) + return 0; + script_data& back = st->stack->stack_datav.back(); + get_val(st, &back); + int rv = 0; + if (auto *u = back.get_if<ScriptDataInt>()) + rv = u->numi; + st->stack->stack_datav.pop_back(); + return rv; +} + +static +bool isstr(struct script_data& c) +{ + return c.is<ScriptDataStr>(); +} + +/*========================================== + * 加算演算子 + *------------------------------------------ + */ +static +void op_add(ScriptState *st) +{ + get_val(st, &st->stack->stack_datav.back()); + script_data back = st->stack->stack_datav.back(); + st->stack->stack_datav.pop_back(); + + script_data& back1 = st->stack->stack_datav.back(); + get_val(st, &back1); + + if (!(isstr(back) || isstr(back1))) + { + back1.get_if<ScriptDataInt>()->numi += back.get_if<ScriptDataInt>()->numi; + } + else + { + RString sb = conv_str(st, &back); + RString sb1 = conv_str(st, &back1); + MString buf; + buf += sb1; + buf += sb; + back1 = ScriptDataStr{.str= AString(buf)}; + } +} + +/*========================================== + * 二項演算子(文字列) + *------------------------------------------ + */ +static +void op_2str(ScriptState *st, ByteCode op, ZString s1, ZString s2) +{ + int a = 0; + + switch (op) + { + case ByteCode::EQ: + a = s1 == s2; + break; + case ByteCode::NE: + a = s1 != s2; + break; + case ByteCode::GT: + a = s1 > s2; + break; + case ByteCode::GE: + a = s1 >= s2; + break; + case ByteCode::LT: + a = s1 < s2; + break; + case ByteCode::LE: + a = s1 <= s2; + break; + default: + PRINTF("illegal string operater\n"_fmt); + break; + } + + push_int<ScriptDataInt>(st->stack, a); +} + +/*========================================== + * 二項演算子(数値) + *------------------------------------------ + */ +static +void op_2num(ScriptState *st, ByteCode op, int i1, int i2) +{ + switch (op) + { + case ByteCode::SUB: + i1 -= i2; + break; + case ByteCode::MUL: + i1 *= i2; + break; + case ByteCode::DIV: + i1 /= i2; + break; + case ByteCode::MOD: + i1 %= i2; + break; + case ByteCode::AND: + i1 &= i2; + break; + case ByteCode::OR: + i1 |= i2; + break; + case ByteCode::XOR: + i1 ^= i2; + break; + case ByteCode::LAND: + i1 = i1 && i2; + break; + case ByteCode::LOR: + i1 = i1 || i2; + break; + case ByteCode::EQ: + i1 = i1 == i2; + break; + case ByteCode::NE: + i1 = i1 != i2; + break; + case ByteCode::GT: + i1 = i1 > i2; + break; + case ByteCode::GE: + i1 = i1 >= i2; + break; + case ByteCode::LT: + i1 = i1 < i2; + break; + case ByteCode::LE: + i1 = i1 <= i2; + break; + case ByteCode::R_SHIFT: + i1 = i1 >> i2; + break; + case ByteCode::L_SHIFT: + i1 = i1 << i2; + break; + } + push_int<ScriptDataInt>(st->stack, i1); +} + +/*========================================== + * 二項演算子 + *------------------------------------------ + */ +static +void op_2(ScriptState *st, ByteCode op) +{ + // pop_val has unfortunate implications here + script_data d2 = st->stack->stack_datav.back(); + st->stack->stack_datav.pop_back(); + get_val(st, &d2); + script_data d1 = st->stack->stack_datav.back(); + st->stack->stack_datav.pop_back(); + get_val(st, &d1); + + if (isstr(d1) && isstr(d2)) + { + // ss => op_2str + op_2str(st, op, d1.get_if<ScriptDataStr>()->str, d2.get_if<ScriptDataStr>()->str); + } + else if (!(isstr(d1) || isstr(d2))) + { + // ii => op_2num + op_2num(st, op, d1.get_if<ScriptDataInt>()->numi, d2.get_if<ScriptDataInt>()->numi); + } + else + { + // si,is => error + PRINTF("script: op_2: int&str, str&int not allow.\n"_fmt); + push_int<ScriptDataInt>(st->stack, 0); + } +} + +/*========================================== + * 単項演算子 + *------------------------------------------ + */ +static +void op_1num(ScriptState *st, ByteCode op) +{ + int i1; + i1 = pop_val(st); + switch (op) + { + case ByteCode::NEG: + i1 = -i1; + break; + case ByteCode::NOT: + i1 = ~i1; + break; + case ByteCode::LNOT: + i1 = !i1; + break; + } + push_int<ScriptDataInt>(st->stack, i1); +} + +/*========================================== + * 関数の実行 + *------------------------------------------ + */ +void run_func(ScriptState *st) +{ + size_t end_sp = st->stack->stack_datav.size(); + size_t start_sp = end_sp - 1; + while (!st->stack->stack_datav[start_sp].is<ScriptDataArg>()) + { + start_sp--; + if (start_sp == 0) + { + if (battle_config.error_log) + PRINTF("function not found\n"_fmt); + st->state = ScriptEndState::END; + return; + } + } + // the func is before the arg + start_sp--; + st->start = start_sp; + st->end = end_sp; + + if (!st->stack->stack_datav[st->start].is<ScriptDataFuncRef>()) + { + PRINTF("run_func: not function and command! \n"_fmt); + st->state = ScriptEndState::END; + return; + } + size_t func = st->stack->stack_datav[st->start].get_if<ScriptDataFuncRef>()->numi; + + if (DEBUG_RUN && battle_config.etc_log) + { + PRINTF("run_func : %s\n"_fmt, + builtin_functions[func].name); + PRINTF("stack dump :"_fmt); + for (script_data& d : st->stack->stack_datav) + { + MATCH_BEGIN (d) + { + MATCH_CASE (const ScriptDataInt&, u) + { + PRINTF(" int(%d)"_fmt, u.numi); + } + MATCH_CASE (const ScriptDataRetInfo&, u) + { + PRINTF(" retinfo(%p)"_fmt, static_cast<const void *>(&*u.script)); + } + MATCH_CASE (const ScriptDataParam&, u) + { + PRINTF(" param(%d)"_fmt, u.reg.sp()); + } + MATCH_CASE (const ScriptDataVariable&, u) + { + PRINTF(" name(%s)[%d]"_fmt, variable_names.outtern(u.reg.base()), u.reg.index()); + } + MATCH_CASE (const ScriptDataArg&, u) + { + (void)u; + PRINTF(" arg"_fmt); + } + MATCH_CASE (const ScriptDataPos&, u) + { + (void)u; + PRINTF(" pos(%d)"_fmt, u.numi); + } + MATCH_CASE (const ScriptDataStr&, u) + { + (void)u; + PRINTF(" str(%s)"_fmt, u.str); + } + MATCH_CASE (const ScriptDataFuncRef&, u) + { + (void)u; + PRINTF(" func(%s)"_fmt, builtin_functions[u.numi].name); + } + } + MATCH_END (); + } + PRINTF("\n"_fmt); + } + builtin_functions[func].func(st); + + pop_stack(st->stack, start_sp, end_sp); + + if (st->state == ScriptEndState::RETFUNC) + { + // ユーザー定義関数からの復帰 + int olddefsp = st->defsp; + + pop_stack(st->stack, st->defsp, start_sp); // 復帰に邪魔なスタック削除 + if (st->defsp < 4 + || !st->stack->stack_datav[st->defsp - 1].is<ScriptDataRetInfo>()) + { + PRINTF("script:run_func (return) return without callfunc or callsub!\n"_fmt); + st->state = ScriptEndState::END; + return; + } + assert (olddefsp == st->defsp); // pretty sure it hasn't changed yet + st->scriptp.code = Some(conv_script(st, &st->stack->stack_datav[olddefsp - 1])); // スクリプトを復元 + st->scriptp.pos = conv_num(st, &st->stack->stack_datav[olddefsp - 2]); // スクリプト位置の復元 + st->defsp = conv_num(st, &st->stack->stack_datav[olddefsp - 3]); // 基準スタックポインタを復元 + // Number of arguments. + int i = conv_num(st, &st->stack->stack_datav[olddefsp - 4]); // 引数の数所得 + assert (i == 0); + + pop_stack(st->stack, olddefsp - 4 - i, olddefsp); // 要らなくなったスタック(引数と復帰用データ)削除 + + st->state = ScriptEndState::GOTO; + } +} + +/*========================================== + * スクリプトの実行メイン部分 + *------------------------------------------ + */ +static +void run_script_main(ScriptState *st, Borrowed<const ScriptBuffer> rootscript) +{ + int cmdcount = script_config.check_cmdcount; + int gotocount = script_config.check_gotocount; + struct script_stack *stack = st->stack; + + st->defsp = stack->stack_datav.size(); + + int rerun_pos = st->scriptp.pos; + st->state = ScriptEndState::ZERO; + while (st->state == ScriptEndState::ZERO) + { + switch (ByteCode c = get_com(&st->scriptp)) + { + case ByteCode::EOL: + if (stack->stack_datav.size() != st->defsp) + { + if (true) + PRINTF("stack.sp (%zu) != default (%d)\n"_fmt, + stack->stack_datav.size(), + st->defsp); + abort(); + } + rerun_pos = st->scriptp.pos; + break; + case ByteCode::INT: + // synthesized! + push_int<ScriptDataInt>(stack, get_num(&st->scriptp)); + break; + + case ByteCode::POS: + case ByteCode::VARIABLE: + case ByteCode::FUNC_REF: + case ByteCode::PARAM: + // Note that these 3 have *very* different meanings, + // despite being encoded similarly. + { + int arg = 0; + arg |= static_cast<uint8_t>(st->scriptp.pop()) << 0; + arg |= static_cast<uint8_t>(st->scriptp.pop()) << 8; + arg |= static_cast<uint8_t>(st->scriptp.pop()) << 16; + switch(c) + { + case ByteCode::POS: + push_int<ScriptDataPos>(stack, arg); + break; + case ByteCode::VARIABLE: + push_reg<ScriptDataVariable>(stack, SIR::from(arg)); + break; + case ByteCode::FUNC_REF: + push_int<ScriptDataFuncRef>(stack, arg); + break; + case ByteCode::PARAM: + SP arg_sp = static_cast<SP>(arg); + push_reg<ScriptDataParam>(stack, SIR::from(arg_sp)); + break; + } + } + break; + case ByteCode::ARG: + push_int<ScriptDataArg>(stack, 0); + break; + case ByteCode::STR: + push_str<ScriptDataStr>(stack, st->scriptp.pops()); + break; + case ByteCode::FUNC: + run_func(st); + if (st->state == ScriptEndState::GOTO) + { + rerun_pos = st->scriptp.pos; + st->state = ScriptEndState::ZERO; + if (st->freeloop != 1 && gotocount > 0 && (--gotocount) <= 0) + { + PRINTF("run_script: infinity loop !\n"_fmt); + st->state = ScriptEndState::END; + } + } + break; + + case ByteCode::ADD: + op_add(st); + break; + + case ByteCode::SUB: + case ByteCode::MUL: + case ByteCode::DIV: + case ByteCode::MOD: + case ByteCode::EQ: + case ByteCode::NE: + case ByteCode::GT: + case ByteCode::GE: + case ByteCode::LT: + case ByteCode::LE: + case ByteCode::AND: + case ByteCode::OR: + case ByteCode::XOR: + case ByteCode::LAND: + case ByteCode::LOR: + case ByteCode::R_SHIFT: + case ByteCode::L_SHIFT: + op_2(st, c); + break; + + case ByteCode::NEG: + case ByteCode::NOT: + case ByteCode::LNOT: + op_1num(st, c); + break; + + case ByteCode::NOP: + st->state = ScriptEndState::END; + break; + + default: + if (battle_config.error_log) + PRINTF("unknown command : %d @ %zu\n"_fmt, + c, st->scriptp.pos); + st->state = ScriptEndState::END; + break; + } + if (st->freeloop != 1 && cmdcount > 0 && (--cmdcount) <= 0) + { + PRINTF("run_script: infinity loop !\n"_fmt); + st->state = ScriptEndState::END; + } + } + switch (st->state) + { + case ScriptEndState::STOP: + break; + case ScriptEndState::END: + { + dumb_ptr<map_session_data> sd = map_id2sd(st->rid); + st->scriptp.code = None; + st->scriptp.pos = -1; + if (sd && sd->npc_id == st->oid) + npc_event_dequeue(sd); + } + break; + case ScriptEndState::RERUNLINE: + st->scriptp.pos = rerun_pos; + break; + } + + if (st->state != ScriptEndState::END) + { + // 再開するためにスタック情報を保存 + dumb_ptr<map_session_data> sd = map_id2sd(st->rid); + if (sd) + { + sd->npc_stackbuf = stack->stack_datav; + sd->npc_script = st->scriptp.code; + // sd->npc_pos is set later ... ??? + sd->npc_scriptroot = Some(rootscript); + } + } +} + +/*========================================== + * スクリプトの実行 + *------------------------------------------ + */ +int run_script(ScriptPointer sp, BlockId rid, BlockId oid) +{ + return run_script_l(sp, rid, oid, nullptr); +} + +int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid, + Slice<argrec_t> args) +{ + struct script_stack stack; + ScriptState st; + dumb_ptr<map_session_data> sd = map_id2sd(rid); + P<const ScriptBuffer> rootscript = TRY_UNWRAP(sp.code, return -1); + int i; + if (sp.pos >> 24) + return -1; + + if (sd && !sd->npc_stackbuf.empty() && sd->npc_scriptroot == Some(rootscript)) + { + // 前回のスタックを復帰 + sp.code = sd->npc_script; + stack.stack_datav = std::move(sd->npc_stackbuf); + } + st.stack = &stack; + st.scriptp = sp; + st.rid = rid; + st.oid = oid; + for (i = 0; i < args.size(); i++) + { + if (args[i].name.back() == '$') + pc_setregstr(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.s); + else + pc_setreg(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.i); + } + run_script_main(&st, rootscript); + + stack.stack_datav.clear(); + return st.scriptp.pos; +} + +void set_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e, int val) +{ + size_t k = variable_names.intern(var); + SIR reg = SIR::from(k, e); + set_reg(sd, VariableCode::VARIABLE, reg, val); +} +void set_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e, XString val) +{ + size_t k = variable_names.intern(var); + SIR reg = SIR::from(k, e); + set_reg(sd, VariableCode::VARIABLE, reg, val); +} +int get_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e) +{ + size_t k = variable_names.intern(var); + SIR reg = SIR::from(k, e); + struct script_data dat = ScriptDataVariable{.reg= reg}; + get_val(sd, &dat); + if (auto *u = dat.get_if<ScriptDataInt>()) + return u->numi; + PRINTF("Warning: you lied about the type and I'm too lazy to fix it!"_fmt); + return 0; +} +ZString get_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e) +{ + size_t k = variable_names.intern(var); + SIR reg = SIR::from(k, e); + struct script_data dat = ScriptDataVariable{.reg= reg}; + get_val(sd, &dat); + if (auto *u = dat.get_if<ScriptDataStr>()) + // this is almost certainly a memory leak after CONSTSTR removal + return u->str; + PRINTF("Warning: you lied about the type and I can't fix it!"_fmt); + return ZString(); +} +} // namespace map +} // namespace tmwa diff --git a/src/map/script-call.hpp b/src/map/script-call.hpp new file mode 100644 index 0000000..d494326 --- /dev/null +++ b/src/map/script-call.hpp @@ -0,0 +1,67 @@ +#pragma once +// script-call.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "script-call.t.hpp" + +#include "fwd.hpp" + +#include "../compat/borrow.hpp" + +#include "script-buffer.hpp" + + +namespace tmwa +{ +namespace map +{ +enum class ByteCode : uint8_t; + +// implemented in script-parse.cpp because reasons +struct ScriptPointer +{ + Option<Borrowed<const ScriptBuffer>> code; + size_t pos; + + ScriptPointer() + : code(None) + , pos() + {} + ScriptPointer(Borrowed<const ScriptBuffer> c, size_t p) + : code(Some(c)) + , pos(p) + {} + + ByteCode peek() const; + ByteCode pop(); + ZString pops(); +}; + +int run_script_l(ScriptPointer, BlockId, BlockId, Slice<argrec_t> args); +int run_script(ScriptPointer, BlockId, BlockId); + +void set_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e, int val); +void set_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e, XString val); + +int get_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e); +ZString get_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e); +} // namespace map +} // namespace tmwa diff --git a/src/map/script-call.t.hpp b/src/map/script-call.t.hpp new file mode 100644 index 0000000..5ef7de6 --- /dev/null +++ b/src/map/script-call.t.hpp @@ -0,0 +1,48 @@ +#pragma once +// script-call.t.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include "../strings/zstring.hpp" + + +namespace tmwa +{ +namespace map +{ +struct argrec_t +{ + ZString name; + union _aru + { + int i; + ZString s; + + _aru(int n) : i(n) {} + _aru(ZString z) : s(z) {} + } v; + + argrec_t(ZString n, int i) : name(n), v(i) {} + argrec_t(ZString n, ZString z) : name(n), v(z) {} +}; +} // namespace map +} // namespace tmwa diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp new file mode 100644 index 0000000..3291cfc --- /dev/null +++ b/src/map/script-fun.cpp @@ -0,0 +1,3164 @@ +#include "script-fun.hpp" +// script-fun.cpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011 Chuck Miller +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © 2013 wushin +// +// 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 "../compat/fun.hpp" + +#include "../generic/db.hpp" +#include "../generic/dumb_ptr.hpp" +#include "../generic/intern-pool.hpp" +#include "../generic/random.hpp" + +#include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" + +#include "../net/timer.hpp" + +#include "../proto2/net-HumanTimeDiff.hpp" + +#include "../high/core.hpp" +#include "../high/extract_mmo.hpp" + +#include "atcommand.hpp" +#include "battle.hpp" +#include "battle_conf.hpp" +#include "chrif.hpp" +#include "clif.hpp" +#include "globals.hpp" +#include "intif.hpp" +#include "itemdb.hpp" +#include "magic-interpreter-base.hpp" +#include "map.hpp" +#include "mob.hpp" +#include "npc.hpp" +#include "party.hpp" +#include "pc.hpp" +#include "script-call-internal.hpp" +#include "script-parse-internal.hpp" +#include "script-persist.hpp" +#include "skill.hpp" +#include "storage.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace map +{ +static +Array<LString, 11> pos_str //= +{{ + "Head"_s, + "Body"_s, + "Left hand"_s, + "Right hand"_s, + "Robe"_s, + "Shoes"_s, + "Accessory 1"_s, + "Accessory 2"_s, + "Head 2"_s, + "Head 3"_s, + "Not Equipped"_s, +}}; + +#define AARG(n) (st->stack->stack_datav[st->start + 2 + (n)]) +#define HARG(n) (st->end > st->start + 2 + (n)) + +// +// 埋め込み関数 +// +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_mes(ScriptState *st) +{ + RString mes = conv_str(st, &AARG(0)); + clif_scriptmes(script_rid2sd(st), st->oid, mes); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_goto(ScriptState *st) +{ + if (!AARG(0).is<ScriptDataPos>()) + { + PRINTF("script: goto: that's not a label!\n"_fmt); + st->state = ScriptEndState::END; + return; + } + + st->scriptp.pos = conv_num(st, &AARG(0)); + st->state = ScriptEndState::GOTO; +} + +/*========================================== + * ユーザー定義関数の呼び出し + *------------------------------------------ + */ +static +void builtin_callfunc(ScriptState *st) +{ + RString str = conv_str(st, &AARG(0)); + Option<P<const ScriptBuffer>> scr_ = userfunc_db.get(str); + + OMATCH_BEGIN (scr_) + { + OMATCH_CASE_SOME (scr) + { + int j = 0; + assert (st->start + 3 == st->end); +#if 0 + for (int i = st->start + 3; i < st->end; i++, j++) + push_copy(st->stack, i); +#endif + + push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ + push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ + push_int<ScriptDataInt>(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ + push_script<ScriptDataRetInfo>(st->stack, TRY_UNWRAP(st->scriptp.code, abort())); // 現在のスクリプトをプッシュ + + st->scriptp = ScriptPointer(scr, 0); + st->defsp = st->start + 4 + j; + st->state = ScriptEndState::GOTO; + } + OMATCH_CASE_NONE () + { + PRINTF("script: callfunc: function not found! [%s]\n"_fmt, str); + st->state = ScriptEndState::END; + } + } + OMATCH_END (); +} + +/*========================================== + * サブルーティンの呼び出し + *------------------------------------------ + */ +static +void builtin_callsub(ScriptState *st) +{ + int pos_ = conv_num(st, &AARG(0)); + int j = 0; + assert (st->start + 3 == st->end); +#if 0 + for (int i = st->start + 3; i < st->end; i++, j++) + push_copy(st->stack, i); +#endif + + push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ + push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ + push_int<ScriptDataInt>(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ + push_script<ScriptDataRetInfo>(st->stack, TRY_UNWRAP(st->scriptp.code, abort())); // 現在のスクリプトをプッシュ + + st->scriptp.pos = pos_; + st->defsp = st->start + 4 + j; + st->state = ScriptEndState::GOTO; +} + +/*========================================== + * サブルーチン/ユーザー定義関数の終了 + *------------------------------------------ + */ +static +void builtin_return(ScriptState *st) +{ +#if 0 + if (HARG(0)) + { // 戻り値有り + push_copy(st->stack, st->start + 2); + } +#endif + st->state = ScriptEndState::RETFUNC; +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_next(ScriptState *st) +{ + st->state = ScriptEndState::STOP; + clif_scriptnext(script_rid2sd(st), st->oid); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_close(ScriptState *st) +{ + st->state = ScriptEndState::END; + clif_scriptclose(script_rid2sd(st), st->oid); +} + +static +void builtin_close2(ScriptState *st) +{ + st->state = ScriptEndState::STOP; + clif_scriptclose(script_rid2sd(st), st->oid); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_menu(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + if (sd->state.menu_or_input == 0) + { + // First half: show menu. + st->state = ScriptEndState::RERUNLINE; + sd->state.menu_or_input = 1; + + MString buf; + for (int i = 0; i < (st->end - (st->start + 2)) / 2; i++) + { + RString choice_str = conv_str(st, &AARG(i * 2 + 0)); + if (!choice_str) + break; + buf += choice_str; + buf += ':'; + } + + clif_scriptmenu(script_rid2sd(st), st->oid, AString(buf)); + } + else + { + // Rerun: item is chosen from menu. + if (sd->npc_menu == 0xff) + { + // cancel + sd->state.menu_or_input = 0; + st->state = ScriptEndState::END; + return; + } + + // Actually jump to the label. + // Logic change: menu_choices is the *total* number of labels, + // not just the displayed number that ends with the "". + // (Would it be better to pop the stack before rerunning?) + int menu_choices = (st->end - (st->start + 2)) / 2; + pc_setreg(sd, SIR::from(variable_names.intern("@menu"_s)), sd->npc_menu); + sd->state.menu_or_input = 0; + if (sd->npc_menu > 0 && sd->npc_menu <= menu_choices) + { + int arg_index = (sd->npc_menu - 1) * 2 + 1; + if (!AARG(arg_index).is<ScriptDataPos>()) + { + st->state = ScriptEndState::END; + return; + } + st->scriptp.pos = AARG(arg_index).get_if<ScriptDataPos>()->numi; + st->state = ScriptEndState::GOTO; + } + } +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_rand(ScriptState *st) +{ + if (HARG(1)) + { + int min = conv_num(st, &AARG(0)); + int max = conv_num(st, &AARG(1)); + if (min > max) + std::swap(max, min); + push_int<ScriptDataInt>(st->stack, random_::in(min, max)); + } + else + { + int range = conv_num(st, &AARG(0)); + push_int<ScriptDataInt>(st->stack, range <= 0 ? 0 : random_::to(range)); + } +} + +/*========================================== + * Check whether the PC is at the specified location + *------------------------------------------ + */ +static +void builtin_isat(ScriptState *st) +{ + int x, y; + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x = conv_num(st, &AARG(1)); + y = conv_num(st, &AARG(2)); + + if (!sd) + return; + + push_int<ScriptDataInt>(st->stack, + (x == sd->bl_x) && (y == sd->bl_y) + && (str == sd->bl_m->name_)); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_warp(ScriptState *st) +{ + int x, y; + dumb_ptr<map_session_data> sd = script_rid2sd(st); + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x = conv_num(st, &AARG(1)); + y = conv_num(st, &AARG(2)); + pc_setpos(sd, str, x, y, BeingRemoveWhy::GONE); +} + +/*========================================== + * エリア指定ワープ + *------------------------------------------ + */ +static +void builtin_areawarp_sub(dumb_ptr<block_list> bl, MapName mapname, int x, int y) +{ + dumb_ptr<map_session_data> sd = bl->is_player(); + pc_setpos(sd, mapname, x, y, BeingRemoveWhy::GONE); +} + +static +void builtin_areawarp(ScriptState *st) +{ + int x, y; + int x0, y0, x1, y1; + + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x0 = conv_num(st, &AARG(1)); + y0 = conv_num(st, &AARG(2)); + x1 = conv_num(st, &AARG(3)); + y1 = conv_num(st, &AARG(4)); + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(5)))); + x = conv_num(st, &AARG(6)); + y = conv_num(st, &AARG(7)); + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return); + + map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y), + m, + x0, y0, + x1, y1, + BL::PC); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_heal(ScriptState *st) +{ + int hp, sp; + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + hp = conv_num(st, &AARG(0)); + sp = conv_num(st, &AARG(1)); + + if(sd != nullptr && (sd->status.hp < 1 && hp > 0)){ + pc_setstand(sd); + if (battle_config.player_invincible_time > interval_t::zero()) + pc_setinvincibletimer(sd, battle_config.player_invincible_time); + clif_resurrection(sd, 1); + } + + if(HARG(2) && bool(conv_num(st, &AARG(2))) && hp > 0) + pc_itemheal(sd, hp, sp); + else + pc_heal(sd, hp, sp); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_input(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = nullptr; + script_data& scrd = AARG(0); + assert (scrd.is<ScriptDataVariable>()); + + SIR reg = scrd.get_if<ScriptDataVariable>()->reg; + ZString name = variable_names.outtern(reg.base()); +// char prefix = name.front(); + char postfix = name.back(); + + sd = script_rid2sd(st); + if (sd->state.menu_or_input) + { + // Second time (rerun) + sd->state.menu_or_input = 0; + if (postfix == '$') + { + set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_str); + } + else + { + //commented by Lupus (check Value Number Input fix in clif.c) + //** Fix by fritz :X keeps people from abusing old input bugs + // wtf? + if (sd->npc_amount < 0) //** If input amount is less then 0 + { + clif_tradecancelled(sd); // added "Deal has been cancelled" message by Valaris + builtin_close(st); //** close + } + + set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_amount); + } + } + else + { + // First time - send prompt to client, then wait + st->state = ScriptEndState::RERUNLINE; + if (postfix == '$') + clif_scriptinputstr(sd, st->oid); + else + clif_scriptinput(sd, st->oid); + sd->state.menu_or_input = 1; + } +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_if (ScriptState *st) +{ + int sel, i; + + sel = conv_num(st, &AARG(0)); + if (!sel) + return; + + // 関数名をコピー + push_copy(st->stack, st->start + 3); + // 間に引数マーカを入れて + push_int<ScriptDataArg>(st->stack, 0); + // 残りの引数をコピー + for (i = st->start + 4; i < st->end; i++) + { + push_copy(st->stack, i); + } + run_func(st); +} + +/*========================================== + * 変数設定 + *------------------------------------------ + */ +static +void builtin_set(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = nullptr; + if (auto *u = AARG(0).get_if<ScriptDataParam>()) + { + SIR reg = u->reg; + sd = script_rid2sd(st); + + int val = conv_num(st, &AARG(1)); + set_reg(sd, VariableCode::PARAM, reg, val); + return; + } + + SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg; + + ZString name = variable_names.outtern(reg.base()); + char prefix = name.front(); + char postfix = name.back(); + + if (prefix != '$') + sd = script_rid2sd(st); + + if (postfix == '$') + { + // 文字列 + RString str = conv_str(st, &AARG(1)); + set_reg(sd, VariableCode::VARIABLE, reg, str); + } + else + { + // 数値 + int val = conv_num(st, &AARG(1)); + set_reg(sd, VariableCode::VARIABLE, reg, val); + } + +} + +/*========================================== + * 配列変数設定 + *------------------------------------------ + */ +static +void builtin_setarray(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = nullptr; + SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg; + ZString name = variable_names.outtern(reg.base()); + char prefix = name.front(); + char postfix = name.back(); + + if (prefix != '$' && prefix != '@') + { + PRINTF("builtin_setarray: illegal scope!\n"_fmt); + return; + } + if (prefix != '$') + sd = script_rid2sd(st); + + for (int j = 0, i = 1; i < st->end - st->start - 2 && j < 256; i++, j++) + { + if (postfix == '$') + set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_str(st, &AARG(i))); + else + set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_num(st, &AARG(i))); + } +} + +/*========================================== + * 配列変数クリア + *------------------------------------------ + */ +static +void builtin_cleararray(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = nullptr; + SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg; + ZString name = variable_names.outtern(reg.base()); + char prefix = name.front(); + char postfix = name.back(); + int sz = conv_num(st, &AARG(2)); + + if (prefix != '$' && prefix != '@') + { + PRINTF("builtin_cleararray: illegal scope!\n"_fmt); + return; + } + if (prefix != '$') + sd = script_rid2sd(st); + + for (int i = 0; i < sz; i++) + { + if (postfix == '$') + set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARG(1))); + else + set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_num(st, &AARG(1))); + } + +} + +/*========================================== + * 配列変数のサイズ所得 + *------------------------------------------ + */ +static +int getarraysize(ScriptState *st, SIR reg) +{ + int i = reg.index(), c = i; + for (; i < 256; i++) + { + struct script_data vd = get_val2(st, reg.iplus(i)); + MATCH_BEGIN (vd) + { + MATCH_CASE (const ScriptDataStr&, u) + { + if (u.str[0]) + c = i; + continue; + } + MATCH_CASE (const ScriptDataInt&, u) + { + if (u.numi) + c = i; + continue; + } + } + MATCH_END (); + abort(); + } + return c + 1; +} + +static +void builtin_getarraysize(ScriptState *st) +{ + SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg; + ZString name = variable_names.outtern(reg.base()); + char prefix = name.front(); + + if (prefix != '$' && prefix != '@') + { + PRINTF("builtin_copyarray: illegal scope!\n"_fmt); + return; + } + + push_int<ScriptDataInt>(st->stack, getarraysize(st, reg)); +} + +/*========================================== + * 指定要素を表す値(キー)を所得する + *------------------------------------------ + */ +static +void builtin_getelementofarray(ScriptState *st) +{ + if (auto *u = AARG(0).get_if<ScriptDataVariable>()) + { + int i = conv_num(st, &AARG(1)); + if (i > 255 || i < 0) + { + PRINTF("script: getelementofarray (operator[]): param2 illegal number: %d\n"_fmt, + i); + push_int<ScriptDataInt>(st->stack, 0); + } + else + { + push_reg<ScriptDataVariable>(st->stack, + u->reg.iplus(i)); + } + } + else + { + PRINTF("script: getelementofarray (operator[]): param1 not named!\n"_fmt); + push_int<ScriptDataInt>(st->stack, 0); + } +} + +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)); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_setlook(ScriptState *st) +{ + LOOK type = LOOK(conv_num(st, &AARG(0))); + int val = conv_num(st, &AARG(1)); + + pc_changelook(script_rid2sd(st), type, val); + +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_countitem(ScriptState *st) +{ + ItemNameId nameid; + int count = 0; + dumb_ptr<map_session_data> sd; + + struct script_data *data; + + sd = script_rid2sd(st); + + data = &AARG(0); + get_val(st, data); + if (data->is<ScriptDataStr>()) + { + ZString name = ZString(conv_str(st, data)); + Option<P<struct item_data>> item_data_ = itemdb_searchname(name); + OMATCH_BEGIN_SOME (item_data, item_data_) + { + nameid = item_data->nameid; + } + OMATCH_END (); + } + else + nameid = wrap<ItemNameId>(conv_num(st, data)); + + if (nameid) + { + for (IOff0 i : IOff0::iter()) + { + if (sd->status.inventory[i].nameid == nameid) + count += sd->status.inventory[i].amount; + } + } + else + { + if (battle_config.error_log) + PRINTF("wrong item ID: countitem (%i)\n"_fmt, nameid); + } + push_int<ScriptDataInt>(st->stack, count); + +} + +/*========================================== + * 重量チェック + *------------------------------------------ + */ +static +void builtin_checkweight(ScriptState *st) +{ + ItemNameId nameid; + int amount; + dumb_ptr<map_session_data> sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data = &AARG(0); + get_val(st, data); + if (data->is<ScriptDataStr>()) + { + ZString name = ZString(conv_str(st, data)); + Option<P<struct item_data>> item_data_ = itemdb_searchname(name); + OMATCH_BEGIN_SOME (item_data, item_data_) + { + nameid = item_data->nameid; + } + OMATCH_END (); + } + else + nameid = wrap<ItemNameId>(conv_num(st, data)); + + amount = conv_num(st, &AARG(1)); + if (amount <= 0 || !nameid) + { + //If it gets the wrong item ID or the amount<=0, don't count its weight (assume it's a non-existent item) + push_int<ScriptDataInt>(st->stack, 0); + return; + } + + if (itemdb_weight(nameid) * amount + sd->weight > sd->max_weight) + { + push_int<ScriptDataInt>(st->stack, 0); + } + else + { + push_int<ScriptDataInt>(st->stack, 1); + } + +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_getitem(ScriptState *st) +{ + ItemNameId nameid; + int amount; + dumb_ptr<map_session_data> sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data = &AARG(0); + get_val(st, data); + if (data->is<ScriptDataStr>()) + { + ZString name = ZString(conv_str(st, data)); + Option<P<struct item_data>> item_data_ = itemdb_searchname(name); + OMATCH_BEGIN_SOME (item_data, item_data_) + { + nameid = item_data->nameid; + } + OMATCH_END (); + } + else + nameid = wrap<ItemNameId>(conv_num(st, data)); + + if ((amount = + conv_num(st, &AARG(1))) <= 0) + { + return; //return if amount <=0, skip the useles iteration + } + + if (nameid) + { + Item item_tmp {}; + item_tmp.nameid = nameid; + if (HARG(3)) //アイテムを指定したIDに渡す + sd = map_id2sd(wrap<BlockId>(conv_num(st, &AARG(3)))); + if (sd == nullptr) //アイテムを渡す相手がいなかったらお帰り + return; + PickupFail flag; + if ((flag = pc_additem(sd, &item_tmp, amount)) != PickupFail::OKAY) + { + clif_additem(sd, IOff0::from(0), 0, flag); + map_addflooritem(&item_tmp, amount, + sd->bl_m, sd->bl_x, sd->bl_y, + nullptr, nullptr, nullptr); + } + } + +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_makeitem(ScriptState *st) +{ + ItemNameId nameid; + int amount; + int x, y; + dumb_ptr<map_session_data> sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data = &AARG(0); + get_val(st, data); + if (data->is<ScriptDataStr>()) + { + ZString name = ZString(conv_str(st, data)); + Option<P<struct item_data>> item_data_ = itemdb_searchname(name); + OMATCH_BEGIN_SOME (item_data, item_data_) + { + nameid = item_data->nameid; + } + OMATCH_END (); + } + else + nameid = wrap<ItemNameId>(conv_num(st, data)); + + amount = conv_num(st, &AARG(1)); + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(2)))); + x = conv_num(st, &AARG(3)); + y = conv_num(st, &AARG(4)); + + P<map_local> m = ((sd && mapname == MOB_THIS_MAP) + ? sd->bl_m + : TRY_UNWRAP(map_mapname2mapid(mapname), return)); + + if (nameid) + { + Item item_tmp {}; + item_tmp.nameid = nameid; + + map_addflooritem(&item_tmp, amount, m, x, y, nullptr, nullptr, nullptr); + } +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_delitem(ScriptState *st) +{ + ItemNameId nameid; + int amount; + dumb_ptr<map_session_data> sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data = &AARG(0); + get_val(st, data); + if (data->is<ScriptDataStr>()) + { + ZString name = ZString(conv_str(st, data)); + Option<P<struct item_data>> item_data_ = itemdb_searchname(name); + OMATCH_BEGIN_SOME (item_data, item_data_) + { + nameid = item_data->nameid; + } + OMATCH_END (); + } + else + nameid = wrap<ItemNameId>(conv_num(st, data)); + + amount = conv_num(st, &AARG(1)); + + if (!nameid || amount <= 0) + { + //By Lupus. Don't run FOR if you've got the wrong item ID or amount<=0 + return; + } + + for (IOff0 i : IOff0::iter()) + { + if (sd->status.inventory[i].nameid == nameid) + { + if (sd->status.inventory[i].amount >= amount) + { + pc_delitem(sd, i, amount, 0); + break; + } + else + { + amount -= sd->status.inventory[i].amount; + if (amount == 0) + amount = sd->status.inventory[i].amount; + pc_delitem(sd, i, amount, 0); + break; + } + } + } + +} + +/*========================================== + *キャラ関係のID取得 + *------------------------------------------ + */ +static +void builtin_getcharid(ScriptState *st) +{ + int num; + dumb_ptr<map_session_data> sd; + + num = conv_num(st, &AARG(0)); + if (HARG(1)) + sd = map_nick2sd(stringish<CharName>(ZString(conv_str(st, &AARG(1))))); + else + sd = script_rid2sd(st); + if (sd == nullptr) + { + push_int<ScriptDataInt>(st->stack, -1); + return; + } + if (num == 0) + push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status_key.char_id)); + if (num == 1) + push_int<ScriptDataInt>(st->stack, unwrap<PartyId>(sd->status.party_id)); + if (num == 2) + push_int<ScriptDataInt>(st->stack, 0/*guild_id*/); + if (num == 3) + push_int<ScriptDataInt>(st->stack, unwrap<AccountId>(sd->status_key.account_id)); +} + +/*========================================== + *指定IDのPT名取得 + *------------------------------------------ + */ +static +RString builtin_getpartyname_sub(PartyId party_id) +{ + Option<PartyPair> p = party_search(party_id); + + return p.pmd_pget(&PartyMost::name).copy_or(PartyName()); +} + +/*========================================== + * キャラクタの名前 + *------------------------------------------ + */ +static +void builtin_strcharinfo(ScriptState *st) +{ + dumb_ptr<map_session_data> sd; + int num; + + sd = script_rid2sd(st); + num = conv_num(st, &AARG(0)); + if (num == 0) + { + RString buf = sd->status_key.name.to__actual(); + push_str<ScriptDataStr>(st->stack, buf); + } + if (num == 1) + { + RString buf = builtin_getpartyname_sub(sd->status.party_id); + if (buf) + push_str<ScriptDataStr>(st->stack, buf); + else + push_str<ScriptDataStr>(st->stack, ""_s); + } + if (num == 2) + { + // was: guild name + push_str<ScriptDataStr>(st->stack, ""_s); + } + +} + +// indexed by the equip_* in db/const.txt +// TODO change to use EQUIP +static +Array<EPOS, 11> equip //= +{{ + EPOS::HAT, + EPOS::MISC1, + EPOS::SHIELD, + EPOS::WEAPON, + EPOS::GLOVES, + EPOS::SHOES, + EPOS::CAPE, + EPOS::MISC2, + EPOS::TORSO, + EPOS::LEGS, + EPOS::ARROW, +}}; + +/*========================================== + * GetEquipID(Pos); Pos: 1-10 + *------------------------------------------ + */ +static +void builtin_getequipid(ScriptState *st) +{ + int num; + dumb_ptr<map_session_data> sd; + + sd = script_rid2sd(st); + if (sd == nullptr) + { + PRINTF("getequipid: sd == nullptr\n"_fmt); + return; + } + num = conv_num(st, &AARG(0)); + IOff0 i = pc_checkequip(sd, equip[num - 1]); + if (i.ok()) + { + Option<P<struct item_data>> item_ = sd->inventory_data[i]; + OMATCH_BEGIN (item_) + { + OMATCH_CASE_SOME (item) + { + push_int<ScriptDataInt>(st->stack, unwrap<ItemNameId>(item->nameid)); + } + OMATCH_CASE_NONE () + { + push_int<ScriptDataInt>(st->stack, 0); + } + } + OMATCH_END (); + } + else + { + push_int<ScriptDataInt>(st->stack, -1); + } +} + +/*========================================== + * freeloop + *------------------------------------------ + */ +static +void builtin_freeloop(ScriptState *st) +{ + int num; + num = conv_num(st, &AARG(0)); + if(num == 1) + { + st->freeloop = 1; + } + else + { + st->freeloop = 0; + } +} + +/*========================================== + * 装備名文字列(精錬メニュー用) + *------------------------------------------ + */ +static +void builtin_getequipname(ScriptState *st) +{ + int num; + dumb_ptr<map_session_data> sd; + + AString buf; + + sd = script_rid2sd(st); + num = conv_num(st, &AARG(0)); + IOff0 i = pc_checkequip(sd, equip[num - 1]); + if (i.ok()) + { + Option<P<struct item_data>> item_ = sd->inventory_data[i]; + OMATCH_BEGIN (item_) + { + OMATCH_CASE_SOME (item) + { + buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], item->jname); + } + OMATCH_CASE_NONE () + { + buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]); + } + } + OMATCH_END (); + } + else + { + buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]); + } + push_str<ScriptDataStr>(st->stack, buf); + +} + +/*========================================== + * 装備品による能力値ボーナス + *------------------------------------------ + */ +static +void builtin_bonus(ScriptState *st) +{ + SP type = SP(conv_num(st, &AARG(0))); + int val = conv_num(st, &AARG(1)); + dumb_ptr<map_session_data> sd = script_rid2sd(st); + pc_bonus(sd, type, val); + +} + +/*========================================== + * 装備品による能力値ボーナス + *------------------------------------------ + */ +static +void builtin_bonus2(ScriptState *st) +{ + SP type = SP(conv_num(st, &AARG(0))); + int type2 = conv_num(st, &AARG(1)); + int val = conv_num(st, &AARG(2)); + dumb_ptr<map_session_data> sd = script_rid2sd(st); + pc_bonus2(sd, type, type2, val); + +} + +/*========================================== + * スキル所得 + *------------------------------------------ + */ +static +void builtin_skill(ScriptState *st) +{ + int level, flag = 1; + dumb_ptr<map_session_data> sd; + + SkillID id = SkillID(conv_num(st, &AARG(0))); + level = conv_num(st, &AARG(1)); + if (HARG(2)) + flag = conv_num(st, &AARG(2)); + sd = script_rid2sd(st); + pc_skill(sd, id, level, flag); + clif_skillinfoblock(sd); + +} + +/*========================================== + * [Fate] Sets the skill level permanently + *------------------------------------------ + */ +static +void builtin_setskill(ScriptState *st) +{ + int level; + dumb_ptr<map_session_data> sd; + + SkillID id = static_cast<SkillID>(conv_num(st, &AARG(0))); + level = conv_num(st, &AARG(1)); + sd = script_rid2sd(st); + + level = std::min(level, MAX_SKILL_LEVEL); + level = std::max(level, 0); + sd->status.skill[id].lv = level; + clif_skillinfoblock(sd); +} + +/*========================================== + * スキルレベル所得 + *------------------------------------------ + */ +static +void builtin_getskilllv(ScriptState *st) +{ + SkillID id = SkillID(conv_num(st, &AARG(0))); + push_int<ScriptDataInt>(st->stack, pc_checkskill(script_rid2sd(st), id)); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_getgmlevel(ScriptState *st) +{ + push_int<ScriptDataInt>(st->stack, pc_isGM(script_rid2sd(st)).get_all_bits()); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_end(ScriptState *st) +{ + st->state = ScriptEndState::END; +} + +/*========================================== + * [Freeyorp] Return the current opt2 + *------------------------------------------ + */ + +static +void builtin_getopt2(ScriptState *st) +{ + dumb_ptr<map_session_data> sd; + + sd = script_rid2sd(st); + + push_int<ScriptDataInt>(st->stack, static_cast<uint16_t>(sd->opt2)); + +} + +/*========================================== + * [Freeyorp] Sets opt2 + *------------------------------------------ + */ + +static +void builtin_setopt2(ScriptState *st) +{ + dumb_ptr<map_session_data> sd; + + Opt2 new_opt2 = Opt2(conv_num(st, &AARG(0))); + sd = script_rid2sd(st); + if (new_opt2 == sd->opt2) + return; + sd->opt2 = new_opt2; + clif_changeoption(sd); + pc_calcstatus(sd, 0); + +} + +/*========================================== + * セーブポイントの保存 + *------------------------------------------ + */ +static +void builtin_savepoint(ScriptState *st) +{ + int x, y; + + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x = conv_num(st, &AARG(1)); + y = conv_num(st, &AARG(2)); + pc_setsavepoint(script_rid2sd(st), str, x, y); +} + +/*========================================== + * gettimetick(type) + * + * type The type of time measurement. + * Specify 0 for the system tick, 1 for + * seconds elapsed today, or 2 for seconds + * since Unix epoch. Defaults to 0 for any + * other value. + *------------------------------------------ + */ +static +void builtin_gettimetick(ScriptState *st) /* Asgard Version */ +{ + int type; + type = conv_num(st, &AARG(0)); + + switch (type) + { + /* Number of seconds elapsed today(0-86399, 00:00:00-23:59:59). */ + case 1: + { + struct tm t = TimeT::now(); + push_int<ScriptDataInt>(st->stack, + t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec); + break; + } + /* Seconds since Unix epoch. */ + case 2: + push_int<ScriptDataInt>(st->stack, static_cast<time_t>(TimeT::now())); + break; + /* System tick(unsigned int, and yes, it will wrap). */ + case 0: + default: + push_int<ScriptDataInt>(st->stack, gettick().time_since_epoch().count()); + break; + } +} + +/*========================================== + * GetTime(Type); + * 1: Sec 2: Min 3: Hour + * 4: WeekDay 5: MonthDay 6: Month + * 7: Year + *------------------------------------------ + */ +static +void builtin_gettime(ScriptState *st) /* Asgard Version */ +{ + int type = conv_num(st, &AARG(0)); + + struct tm t = TimeT::now(); + + switch (type) + { + case 1: //Sec(0~59) + push_int<ScriptDataInt>(st->stack, t.tm_sec); + break; + case 2: //Min(0~59) + push_int<ScriptDataInt>(st->stack, t.tm_min); + break; + case 3: //Hour(0~23) + push_int<ScriptDataInt>(st->stack, t.tm_hour); + break; + case 4: //WeekDay(0~6) + push_int<ScriptDataInt>(st->stack, t.tm_wday); + break; + case 5: //MonthDay(01~31) + push_int<ScriptDataInt>(st->stack, t.tm_mday); + break; + case 6: //Month(01~12) + push_int<ScriptDataInt>(st->stack, t.tm_mon + 1); + break; + case 7: //Year(20xx) + push_int<ScriptDataInt>(st->stack, t.tm_year + 1900); + break; + default: //(format error) + push_int<ScriptDataInt>(st->stack, -1); + break; + } +} + +/*========================================== + * カプラ倉庫を開く + *------------------------------------------ + */ +static +void builtin_openstorage(ScriptState *st) +{ +// int sync = 0; +// if (st->end >= 3) sync = conv_num(st,& (st->stack->stack_data[st->start+2])); + dumb_ptr<map_session_data> sd = script_rid2sd(st); + +// if (sync) { + st->state = ScriptEndState::STOP; + sd->npc_flags.storage = 1; +// } else st->state = ScriptEndState::END; + + storage_storageopen(sd); +} + +/*========================================== + * NPCで経験値上げる + *------------------------------------------ + */ +static +void builtin_getexp(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + int base = 0, job = 0; + + base = conv_num(st, &AARG(0)); + job = conv_num(st, &AARG(1)); + if (base < 0 || job < 0) + return; + if (sd) + pc_gainexp_reason(sd, base, job, PC_GAINEXP_REASON::SCRIPT); + +} + +/*========================================== + * モンスター発生 + *------------------------------------------ + */ +static +void builtin_monster(ScriptState *st) +{ + Species mob_class; + int amount, x, y; + NpcEvent event; + + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x = conv_num(st, &AARG(1)); + y = conv_num(st, &AARG(2)); + MobName str = stringish<MobName>(ZString(conv_str(st, &AARG(3)))); + mob_class = wrap<Species>(conv_num(st, &AARG(4))); + amount = conv_num(st, &AARG(5)); + if (HARG(6)) + extract(ZString(conv_str(st, &AARG(6))), &event); + + mob_once_spawn(map_id2sd(st->rid), mapname, x, y, str, mob_class, amount, + event); +} + +/*========================================== + * モンスター発生 + *------------------------------------------ + */ +static +void builtin_areamonster(ScriptState *st) +{ + Species mob_class; + int amount, x0, y0, x1, y1; + NpcEvent event; + + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x0 = conv_num(st, &AARG(1)); + y0 = conv_num(st, &AARG(2)); + x1 = conv_num(st, &AARG(3)); + y1 = conv_num(st, &AARG(4)); + MobName str = stringish<MobName>(ZString(conv_str(st, &AARG(5)))); + mob_class = wrap<Species>(conv_num(st, &AARG(6))); + amount = conv_num(st, &AARG(7)); + if (HARG(8)) + extract(ZString(conv_str(st, &AARG(8))), &event); + + mob_once_spawn_area(map_id2sd(st->rid), mapname, x0, y0, x1, y1, str, mob_class, + amount, event); +} + +/*========================================== + * モンスター削除 + *------------------------------------------ + */ +static +void builtin_killmonster_sub(dumb_ptr<block_list> bl, NpcEvent event) +{ + dumb_ptr<mob_data> md = bl->is_mob(); + if (event) + { + if (event == md->npc_event) + mob_delete(md); + return; + } + else if (!event) + { + if (md->spawn.delay1 == static_cast<interval_t>(-1) + && md->spawn.delay2 == static_cast<interval_t>(-1)) + mob_delete(md); + return; + } +} + +static +void builtin_killmonster(ScriptState *st) +{ + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + ZString event_ = ZString(conv_str(st, &AARG(1))); + NpcEvent event; + if (event_ != "All"_s) + extract(event_, &event); + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return); + map_foreachinarea(std::bind(builtin_killmonster_sub, ph::_1, event), + m, + 0, 0, + m->xs, m->ys, + BL::MOB); +} + +/*========================================== + * NPC主体イベント実行 + *------------------------------------------ + */ +static +void builtin_donpcevent(ScriptState *st) +{ + ZString event_ = ZString(conv_str(st, &AARG(0))); + NpcEvent event; + extract(event_, &event); + npc_event_do(event); +} + +/*========================================== + * イベントタイマー追加 + *------------------------------------------ + */ +static +void builtin_addtimer(ScriptState *st) +{ + interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(0))); + ZString event_ = ZString(conv_str(st, &AARG(1))); + NpcEvent event; + extract(event_, &event); + pc_addeventtimer(script_rid2sd(st), tick, event); +} + +/*========================================== + * NPCタイマー初期化 + *------------------------------------------ + */ +static +void builtin_initnpctimer(ScriptState *st) +{ + dumb_ptr<npc_data> nd_; + if (HARG(0)) + nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(0))))); + else + nd_ = map_id_is_npc(st->oid); + assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); + dumb_ptr<npc_data_script> nd = nd_->is_script(); + + npc_settimerevent_tick(nd, interval_t::zero()); + npc_timerevent_start(nd); +} + +/*========================================== + * NPCタイマー開始 + *------------------------------------------ + */ +static +void builtin_startnpctimer(ScriptState *st) +{ + dumb_ptr<npc_data> nd_; + if (HARG(0)) + nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(0))))); + else + nd_ = map_id_is_npc(st->oid); + assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); + dumb_ptr<npc_data_script> nd = nd_->is_script(); + + npc_timerevent_start(nd); +} + +/*========================================== + * NPCタイマー停止 + *------------------------------------------ + */ +static +void builtin_stopnpctimer(ScriptState *st) +{ + dumb_ptr<npc_data> nd_; + if (HARG(0)) + nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(0))))); + else + nd_ = map_id_is_npc(st->oid); + assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); + dumb_ptr<npc_data_script> nd = nd_->is_script(); + + npc_timerevent_stop(nd); +} + +/*========================================== + * NPCタイマー情報所得 + *------------------------------------------ + */ +static +void builtin_getnpctimer(ScriptState *st) +{ + dumb_ptr<npc_data> nd_; + int type = conv_num(st, &AARG(0)); + int val = 0; + if (HARG(1)) + nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(1))))); + else + nd_ = map_id_is_npc(st->oid); + assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); + dumb_ptr<npc_data_script> nd = nd_->is_script(); + + switch (type) + { + case 0: + val = npc_gettimerevent_tick(nd).count(); + break; + case 1: + val = nd->scr.timer_active; + break; + case 2: + val = nd->scr.timer_eventv.size(); + break; + } + push_int<ScriptDataInt>(st->stack, val); +} + +/*========================================== + * NPCタイマー値設定 + *------------------------------------------ + */ +static +void builtin_setnpctimer(ScriptState *st) +{ + dumb_ptr<npc_data> nd_; + interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(0))); + if (HARG(1)) + nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(1))))); + else + nd_ = map_id_is_npc(st->oid); + assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); + dumb_ptr<npc_data_script> nd = nd_->is_script(); + + npc_settimerevent_tick(nd, tick); +} + +/*========================================== + * 天の声アナウンス + *------------------------------------------ + */ +static +void builtin_announce(ScriptState *st) +{ + int flag; + ZString str = ZString(conv_str(st, &AARG(0))); + flag = conv_num(st, &AARG(1)); + + if (flag & 0x0f) + { + dumb_ptr<block_list> bl; + if (flag & 0x08) + bl = map_id2bl(st->oid); + else + bl = script_rid2sd(st); + clif_GMmessage(bl, str, flag); + } + else + intif_GMmessage(str); +} + +/*========================================== + * 天の声アナウンス(特定マップ) + *------------------------------------------ + */ +static +void builtin_mapannounce_sub(dumb_ptr<block_list> bl, XString str, int flag) +{ + clif_GMmessage(bl, str, flag | 3); +} + +static +void builtin_mapannounce(ScriptState *st) +{ + int flag; + + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + ZString str = ZString(conv_str(st, &AARG(1))); + flag = conv_num(st, &AARG(2)); + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return); + map_foreachinarea(std::bind(builtin_mapannounce_sub, ph::_1, str, flag & 0x10), + m, + 0, 0, + m->xs, m->ys, + BL::PC); +} + +/*========================================== + * ユーザー数所得 + *------------------------------------------ + */ +static +void builtin_getusers(ScriptState *st) +{ + int flag = conv_num(st, &AARG(0)); + dumb_ptr<block_list> bl = map_id2bl((flag & 0x08) ? st->oid : st->rid); + int val = 0; + switch (flag & 0x07) + { + case 0: + val = bl->bl_m->users; + break; + case 1: + val = map_getusers(); + break; + } + push_int<ScriptDataInt>(st->stack, val); +} + +/*========================================== + * マップ指定ユーザー数所得 + *------------------------------------------ + */ +static +void builtin_getmapusers(ScriptState *st) +{ + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str), + { + push_int<ScriptDataInt>(st->stack, -1); + return; + }); + push_int<ScriptDataInt>(st->stack, m->users); +} + +/*========================================== + * エリア指定ユーザー数所得 + *------------------------------------------ + */ +static +void builtin_getareausers_sub(dumb_ptr<block_list> bl, int *users) +{ + if (bool(bl->is_player()->status.option & Opt0::HIDE)) + return; + (*users)++; +} + +static +void builtin_getareausers_living_sub(dumb_ptr<block_list> bl, int *users) +{ + if (bool(bl->is_player()->status.option & Opt0::HIDE)) + return; + if (!pc_isdead(bl->is_player())) + (*users)++; +} + +static +void builtin_getareausers(ScriptState *st) +{ + int x0, y0, x1, y1, users = 0; + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x0 = conv_num(st, &AARG(1)); + y0 = conv_num(st, &AARG(2)); + x1 = conv_num(st, &AARG(3)); + y1 = conv_num(st, &AARG(4)); + + int living = 0; + if (HARG(5)) + { + living = conv_num(st, &AARG(5)); + } + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str), + { + push_int<ScriptDataInt>(st->stack, -1); + return; + }); + map_foreachinarea(std::bind(living ? builtin_getareausers_living_sub: builtin_getareausers_sub, ph::_1, &users), + m, + x0, y0, + x1, y1, + BL::PC); + push_int<ScriptDataInt>(st->stack, users); +} + +/*========================================== + * エリア指定ドロップアイテム数所得 + *------------------------------------------ + */ +static +void builtin_getareadropitem_sub(dumb_ptr<block_list> bl, ItemNameId item, int *amount) +{ + dumb_ptr<flooritem_data> drop = bl->is_item(); + + if (drop->item_data.nameid == item) + (*amount) += drop->item_data.amount; + +} + +static +void builtin_getareadropitem_sub_anddelete(dumb_ptr<block_list> bl, ItemNameId item, int *amount) +{ + dumb_ptr<flooritem_data> drop = bl->is_item(); + + if (drop->item_data.nameid == item) + { + (*amount) += drop->item_data.amount; + clif_clearflooritem(drop, nullptr); + map_delobject(drop->bl_id, drop->bl_type); + } +} + +static +void builtin_getareadropitem(ScriptState *st) +{ + ItemNameId item; + int x0, y0, x1, y1, amount = 0, delitems = 0; + struct script_data *data; + + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x0 = conv_num(st, &AARG(1)); + y0 = conv_num(st, &AARG(2)); + x1 = conv_num(st, &AARG(3)); + y1 = conv_num(st, &AARG(4)); + + data = &AARG(5); + get_val(st, data); + if (data->is<ScriptDataStr>()) + { + ZString name = ZString(conv_str(st, data)); + Option<P<struct item_data>> item_data_ = itemdb_searchname(name); + OMATCH_BEGIN_SOME (item_data, item_data_) + { + item = item_data->nameid; + } + OMATCH_END (); + } + else + item = wrap<ItemNameId>(conv_num(st, data)); + + if (HARG(6)) + delitems = conv_num(st, &AARG(6)); + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str), + { + push_int<ScriptDataInt>(st->stack, -1); + return; + }); + if (delitems) + map_foreachinarea(std::bind(builtin_getareadropitem_sub_anddelete, ph::_1, item, &amount), + m, + x0, y0, + x1, y1, + BL::ITEM); + else + map_foreachinarea(std::bind(builtin_getareadropitem_sub, ph::_1, item, &amount), + m, + x0, y0, + x1, y1, + BL::ITEM); + + push_int<ScriptDataInt>(st->stack, amount); +} + +/*========================================== + * NPCの有効化 + *------------------------------------------ + */ +static +void builtin_enablenpc(ScriptState *st) +{ + NpcName str = stringish<NpcName>(ZString(conv_str(st, &AARG(0)))); + npc_enable(str, 1); +} + +/*========================================== + * NPCの無効化 + *------------------------------------------ + */ +static +void builtin_disablenpc(ScriptState *st) +{ + NpcName str = stringish<NpcName>(ZString(conv_str(st, &AARG(0)))); + npc_enable(str, 0); +} + +/*========================================== + * 状態異常にかかる + *------------------------------------------ + */ +static +void builtin_sc_start(ScriptState *st) +{ + dumb_ptr<block_list> bl; + int val1; + StatusChange type = static_cast<StatusChange>(conv_num(st, &AARG(0))); + interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(1))); + if (tick < 1_s) + // work around old behaviour of: + // speed potion + // atk potion + // matk potion + // + // which used to use seconds + // all others used milliseconds + tick *= 1000; + val1 = conv_num(st, &AARG(2)); + if (HARG(3)) //指定したキャラを状態異常にする + bl = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(3)))); + else + bl = map_id2bl(st->rid); + skill_status_change_start(bl, type, val1, tick); +} + +/*========================================== + * 状態異常が直る + *------------------------------------------ + */ +static +void builtin_sc_end(ScriptState *st) +{ + dumb_ptr<block_list> bl; + StatusChange type = StatusChange(conv_num(st, &AARG(0))); + bl = map_id2bl(st->rid); + skill_status_change_end(bl, type, nullptr); +} + +static +void builtin_sc_check(ScriptState *st) +{ + dumb_ptr<block_list> bl; + StatusChange type = StatusChange(conv_num(st, &AARG(0))); + bl = map_id2bl(st->rid); + + push_int<ScriptDataInt>(st->stack, skill_status_change_active(bl, type)); + +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_debugmes(ScriptState *st) +{ + RString mes = conv_str(st, &AARG(0)); + PRINTF("script debug: %d %d: '%s'\n"_fmt, + st->rid, st->oid, mes); +} + +/*========================================== + * ステータスリセット + *------------------------------------------ + */ +static +void builtin_resetstatus(ScriptState *st) +{ + dumb_ptr<map_session_data> sd; + sd = script_rid2sd(st); + pc_resetstate(sd); +} + +/*========================================== + * RIDのアタッチ + *------------------------------------------ + */ +static +void builtin_attachrid(ScriptState *st) +{ + st->rid = wrap<BlockId>(conv_num(st, &AARG(0))); + push_int<ScriptDataInt>(st->stack, (map_id2sd(st->rid) != nullptr)); +} + +/*========================================== + * RIDのデタッチ + *------------------------------------------ + */ +static +void builtin_detachrid(ScriptState *st) +{ + st->rid = BlockId(); +} + +/*========================================== + * 存在チェック + *------------------------------------------ + */ +static +void builtin_isloggedin(ScriptState *st) +{ + push_int<ScriptDataInt>(st->stack, + map_id2sd(wrap<BlockId>(conv_num(st, &AARG(0)))) != nullptr); +} + +static +void builtin_setmapflag(ScriptState *st) +{ + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + int i = conv_num(st, &AARG(1)); + MapFlag mf = map_flag_from_int(i); + Option<P<map_local>> m_ = map_mapname2mapid(str); + OMATCH_BEGIN_SOME (m, m_) + { + m->flag.set(mf, 1); + } + OMATCH_END (); +} + +static +void builtin_removemapflag(ScriptState *st) +{ + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + int i = conv_num(st, &AARG(1)); + MapFlag mf = map_flag_from_int(i); + Option<P<map_local>> m_ = map_mapname2mapid(str); + OMATCH_BEGIN_SOME (m, m_) + { + m->flag.set(mf, 0); + } + OMATCH_END (); +} + +static +void builtin_getmapflag(ScriptState *st) +{ + int r = -1; + + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + int i = conv_num(st, &AARG(1)); + MapFlag mf = map_flag_from_int(i); + Option<P<map_local>> m_ = map_mapname2mapid(str); + OMATCH_BEGIN_SOME (m, m_) + { + r = m->flag.get(mf); + } + OMATCH_END (); + + push_int<ScriptDataInt>(st->stack, r); +} + +static +void builtin_pvpon(ScriptState *st) +{ + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str), return); + if (!m->flag.get(MapFlag::PVP) && !m->flag.get(MapFlag::NOPVP)) + { + m->flag.set(MapFlag::PVP, 1); + + if (battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris] + return; + + for (io::FD i : iter_fds()) + { + Session *s = get_session(i); + if (!s) + continue; + map_session_data *pl_sd = static_cast<map_session_data *>(s->session_data.get()); + if (pl_sd && pl_sd->state.auth) + { + if (m == pl_sd->bl_m && !pl_sd->pvp_timer) + { + pl_sd->pvp_timer = Timer(gettick() + 200_ms, + std::bind(pc_calc_pvprank_timer, ph::_1, ph::_2, + pl_sd->bl_id)); + pl_sd->pvp_rank = 0; + pl_sd->pvp_point = 5; + } + } + } + } +} + +static +void builtin_pvpoff(ScriptState *st) +{ + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str), return); + if (m->flag.get(MapFlag::PVP) && m->flag.get(MapFlag::NOPVP)) + { + m->flag.set(MapFlag::PVP, 0); + + if (battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris] + return; + + for (io::FD i : iter_fds()) + { + Session *s = get_session(i); + if (!s) + continue; + map_session_data *pl_sd = static_cast<map_session_data *>(s->session_data.get()); + if (pl_sd && pl_sd->state.auth) + { + if (m == pl_sd->bl_m) + { + pl_sd->pvp_timer.cancel(); + } + } + } + } +} + +static +void builtin_setpvpchannel(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + int flag; + flag = conv_num(st, &AARG(0)); + if (flag < 1) + flag = 0; + + sd->state.pvpchannel = flag; +} + +static +void builtin_getpvpflag(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + int num = conv_num(st, &AARG(0)); + int flag = 0; + + switch (num){ + case 0: + flag = sd->state.pvpchannel; + break; + case 1: + flag = bool(sd->status.option & Opt0::HIDE); + break; + } + + push_int<ScriptDataInt>(st->stack, flag); +} + +/*========================================== + * NPCエモーション + *------------------------------------------ + */ + +static +void builtin_emotion(ScriptState *st) +{ + 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; + 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 +void builtin_mapwarp(ScriptState *st) // Added by RoVeRT +{ + int x, y; + int x0, y0, x1, y1; + + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x0 = 0; + y0 = 0; + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return); + x1 = m->xs; + y1 = m->ys; + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(1)))); + x = conv_num(st, &AARG(2)); + y = conv_num(st, &AARG(3)); + + map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y), + m, + x0, y0, + x1, y1, + BL::PC); +} + +static +void builtin_mobcount_sub(dumb_ptr<block_list> bl, NpcEvent event, int *c) +{ + if (event == bl->is_mob()->npc_event) + (*c)++; +} + +static +void builtin_mobcount(ScriptState *st) // Added by RoVeRT +{ + int c = 0; + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + ZString event_ = ZString(conv_str(st, &AARG(1))); + NpcEvent event; + extract(event_, &event); + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), + { + push_int<ScriptDataInt>(st->stack, -1); + return; + }); + map_foreachinarea(std::bind(builtin_mobcount_sub, ph::_1, event, &c), + m, + 0, 0, + m->xs, m->ys, + BL::MOB); + + push_int<ScriptDataInt>(st->stack, (c - 1)); + +} + +static +void builtin_marriage(ScriptState *st) +{ + CharName partner = stringish<CharName>(ZString(conv_str(st, &AARG(0)))); + dumb_ptr<map_session_data> sd = script_rid2sd(st); + dumb_ptr<map_session_data> p_sd = map_nick2sd(partner); + + if (sd == nullptr || p_sd == nullptr || pc_marriage(sd, p_sd) < 0) + { + push_int<ScriptDataInt>(st->stack, 0); + return; + } + push_int<ScriptDataInt>(st->stack, 1); +} + +static +void builtin_divorce(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + if (sd == nullptr || pc_divorce(sd) < 0) + { + push_int<ScriptDataInt>(st->stack, 0); + return; + } + + push_int<ScriptDataInt>(st->stack, 1); +} + +/*========================================== + * IDからItem名 + *------------------------------------------ + */ +static +void builtin_getitemname(ScriptState *st) +{ + Option<P<struct item_data>> i_data = None; + struct script_data *data; + + data = &AARG(0); + get_val(st, data); + if (data->is<ScriptDataStr>()) + { + ZString name = ZString(conv_str(st, data)); + i_data = itemdb_searchname(name); + } + else + { + ItemNameId item_id = wrap<ItemNameId>(conv_num(st, data)); + i_data = Some(itemdb_search(item_id)); + } + + RString item_name = i_data.pmd_pget(&item_data::jname).copy_or(stringish<ItemName>("Unknown Item"_s)); + + push_str<ScriptDataStr>(st->stack, item_name); +} + +static +void builtin_getitemlink(ScriptState *st) +{ + struct script_data *data; + AString buf; + data = &AARG(0); + ZString name = conv_str(st, data); + + ItemNameId item_id; + Option<P<struct item_data>> item_data_ = itemdb_searchname(name); + OMATCH_BEGIN (item_data_) + { + OMATCH_CASE_SOME (item_data) + { + buf = STRPRINTF("@@%d|@@"_fmt, item_data->nameid); + } + OMATCH_CASE_NONE () + { + buf = "Unknown Item"_s; + } + } + OMATCH_END (); + push_str<ScriptDataStr>(st->stack, buf); +} + +static +void builtin_getspellinvocation(ScriptState *st) +{ + RString name = conv_str(st, &AARG(0)); + + AString invocation = magic::magic_find_invocation(name); + if (!invocation) + invocation = "..."_s; + + push_str<ScriptDataStr>(st->stack, invocation); +} + +static +void builtin_getpartnerid2(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status.partner_id)); +} + +/*========================================== + * PCの所持品情報読み取り + *------------------------------------------ + */ +static +void builtin_getinventorylist(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + int j = 0; + if (!sd) + return; + for (IOff0 i : IOff0::iter()) + { + if (sd->status.inventory[i].nameid + && sd->status.inventory[i].amount > 0) + { + pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_id"_s), j), + unwrap<ItemNameId>(sd->status.inventory[i].nameid)); + pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_amount"_s), j), + sd->status.inventory[i].amount); + pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_equip"_s), j), + static_cast<uint16_t>(sd->status.inventory[i].equip)); + j++; + } + } + pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_count"_s)), j); +} + +static +void builtin_getactivatedpoolskilllist(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + SkillID pool_skills[MAX_SKILL_POOL]; + int skill_pool_size = skill_pool(sd, pool_skills); + int i, count = 0; + + if (!sd) + return; + + for (i = 0; i < skill_pool_size; i++) + { + SkillID skill_id = pool_skills[i]; + + if (sd->status.skill[skill_id].lv) + { + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count), + static_cast<uint16_t>(skill_id)); + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count), + sd->status.skill[skill_id].lv); + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count), + static_cast<uint16_t>(sd->status.skill[skill_id].flags)); + pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count), + skill_name(skill_id)); + ++count; + } + } + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count); + +} + +static +void builtin_getunactivatedpoolskilllist(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + int i, count = 0; + + if (!sd) + return; + + for (i = 0; i < skill_pool_skills.size(); i++) + { + SkillID skill_id = skill_pool_skills[i]; + + if (sd->status.skill[skill_id].lv + && !bool(sd->status.skill[skill_id].flags & SkillFlags::POOL_ACTIVATED)) + { + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count), + static_cast<uint16_t>(skill_id)); + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count), + sd->status.skill[skill_id].lv); + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count), + static_cast<uint16_t>(sd->status.skill[skill_id].flags)); + pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count), + skill_name(skill_id)); + ++count; + } + } + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count); +} + +static +void builtin_poolskill(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + SkillID skill_id = SkillID(conv_num(st, &AARG(0))); + + skill_pool_activate(sd, skill_id); + clif_skillinfoblock(sd); + +} + +static +void builtin_unpoolskill(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + SkillID skill_id = SkillID(conv_num(st, &AARG(0))); + + skill_pool_deactivate(sd, skill_id); + clif_skillinfoblock(sd); + +} + +/*========================================== + * NPCから発生するエフェクト + * misceffect(effect, [target]) + * + * effect The effect type/ID. + * target The player name or being ID on + * which to display the effect. If not + * specified, it attempts to default to + * the current NPC or invoking PC. + *------------------------------------------ + */ +static +void builtin_misceffect(ScriptState *st) +{ + int type; + BlockId id; + CharName name; + dumb_ptr<block_list> bl = nullptr; + + type = conv_num(st, &AARG(0)); + + if (HARG(1)) + { + struct script_data *sdata = &AARG(1); + + get_val(st, sdata); + + if (sdata->is<ScriptDataStr>()) + name = stringish<CharName>(ZString(conv_str(st, sdata))); + else + id = wrap<BlockId>(conv_num(st, sdata)); + } + + if (name.to__actual()) + { + dumb_ptr<map_session_data> sd = map_nick2sd(name); + if (sd) + bl = sd; + } + else if (id) + bl = map_id2bl(id); + else if (st->oid) + bl = map_id2bl(st->oid); + else + { + dumb_ptr<map_session_data> sd = script_rid2sd(st); + if (sd) + bl = sd; + } + + if (bl) + clif_misceffect(bl, type); + +} + +/*========================================== + * Special effects [Valaris] + *------------------------------------------ + */ +static +void builtin_specialeffect(ScriptState *st) +{ + dumb_ptr<block_list> bl = map_id2bl(st->oid); + + if (bl == nullptr) + return; + + clif_specialeffect(bl, + conv_num(st, + &AARG(0)), + 0); + +} + +static +void builtin_specialeffect2(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + if (sd == nullptr) + return; + + clif_specialeffect(sd, + conv_num(st, + &AARG(0)), + 0); + +} + +/*========================================== + * Nude [Valaris] + *------------------------------------------ + */ + +static +void builtin_nude(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + if (sd == nullptr) + return; + + for (EQUIP i : EQUIPs) + { + IOff0 idx = sd->equip_index_maybe[i]; + if (idx.ok()) + pc_unequipitem(sd, idx, CalcStatus::LATER); + } + pc_calcstatus(sd, 0); + +} + +/*========================================== + * UnequipById [Freeyorp] + *------------------------------------------ + */ + +static +void builtin_unequipbyid(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + if (sd == nullptr) + return; + + EQUIP slot_id = EQUIP(conv_num(st, &AARG(0))); + + if (slot_id >= EQUIP() && slot_id < EQUIP::COUNT) + { + IOff0 idx = sd->equip_index_maybe[slot_id]; + if (idx.ok()) + pc_unequipitem(sd, idx, CalcStatus::LATER); + } + + pc_calcstatus(sd, 0); + +} + +/*========================================== + * npcwarp [remoitnane] + * Move NPC to a new position on the same map. + *------------------------------------------ + */ +static +void builtin_npcwarp(ScriptState *st) +{ + int x, y; + dumb_ptr<npc_data> nd = nullptr; + + x = conv_num(st, &AARG(0)); + y = conv_num(st, &AARG(1)); + NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(2)))); + nd = npc_name2id(npc); + + if (!nd) + { + PRINTF("builtin_npcwarp: no such npc: '%s'\n"_fmt, npc); + return; + } + + P<map_local> m = nd->bl_m; + + /* Crude sanity checks. */ + if (!nd->bl_prev + || x < 0 || x > m->xs -1 + || y < 0 || y > m->ys - 1) + return; + + npc_enable(npc, 0); + map_delblock(nd); /* [Freeyorp] */ + nd->bl_x = x; + nd->bl_y = y; + map_addblock(nd); + npc_enable(npc, 1); + +} + +/*========================================== + * npcareawarp [remoitnane] [wushin] + * Move NPC to a new area on the same map. + *------------------------------------------ + */ +static +void builtin_npcareawarp(ScriptState *st) +{ + int x0, y0, x1, y1, x, y, max, cb, lx = -1, ly = -1, j = 0; + dumb_ptr<npc_data> nd = nullptr; + + NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(5)))); + nd = npc_name2id(npc); + + x0 = conv_num(st, &AARG(0)); + y0 = conv_num(st, &AARG(1)); + x1 = conv_num(st, &AARG(2)); + y1 = conv_num(st, &AARG(3)); + cb = conv_num(st, &AARG(4)); + + if (!nd) + { + PRINTF("builtin_npcareawarp: no such npc: '%s'\n"_fmt, npc); + return; + } + + max = (y1 - y0 + 1) * (x1 - x0 + 1) * 3; + if (max > 1000) + max = 1000; + + P<map_local> m = nd->bl_m; + if (cb) { + 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) + { + if (lx >= 0) + { // Since reference went wrong, the place which boiled before is used. + x = lx; + y = ly; + } + else + return; // Since reference of the place which boils first went wrong, it stops. + } + } + else + x = random_::in(x0, x1); + y = random_::in(y0, y1); + + npc_enable(npc, 0); + map_delblock(nd); /* [Freeyorp] */ + nd->bl_x = x; + nd->bl_y = y; + map_addblock(nd); + npc_enable(npc, 1); + +} + +/*========================================== + * message [MouseJstr] + *------------------------------------------ + */ + +static +void builtin_message(ScriptState *st) +{ + CharName player = stringish<CharName>(ZString(conv_str(st, &AARG(0)))); + ZString msg = ZString(conv_str(st, &AARG(1))); + + dumb_ptr<map_session_data> pl_sd = map_nick2sd(player); + if (pl_sd == nullptr) + return; + clif_displaymessage(pl_sd->sess, msg); + +} + +/*========================================== + * npctalk (sends message to surrounding + * area) [Valaris] + *------------------------------------------ + */ + +static +void builtin_npctalk(ScriptState *st) +{ + dumb_ptr<npc_data> nd = map_id_is_npc(st->oid); + RString str = conv_str(st, &AARG(0)); + + if (nd) + { + clif_message(nd, str); + } +} + +/*========================================== + * getlook char info. getlook(arg) + *------------------------------------------ + */ +static +void builtin_getlook(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + LOOK type = LOOK(conv_num(st, &AARG(0))); + int val = -1; + switch (type) + { + case LOOK::HAIR: //1 + val = sd->status.hair; + break; + case LOOK::WEAPON: //2 + val = static_cast<uint16_t>(sd->status.weapon); + break; + case LOOK::HEAD_BOTTOM: //3 + val = unwrap<ItemNameId>(sd->status.head_bottom); + break; + case LOOK::HEAD_TOP: //4 + val = unwrap<ItemNameId>(sd->status.head_top); + break; + case LOOK::HEAD_MID: //5 + val = unwrap<ItemNameId>(sd->status.head_mid); + break; + case LOOK::HAIR_COLOR: //6 + val = sd->status.hair_color; + break; + case LOOK::CLOTHES_COLOR: //7 + val = sd->status.clothes_color; + break; + case LOOK::SHIELD: //8 + val = unwrap<ItemNameId>(sd->status.shield); + break; + case LOOK::SHOES: //9 + break; + } + + push_int<ScriptDataInt>(st->stack, val); +} + +/*========================================== + * get char save point. argument: 0- map name, 1- x, 2- y + *------------------------------------------ +*/ +static +void builtin_getsavepoint(ScriptState *st) +{ + int x, y, type; + dumb_ptr<map_session_data> sd; + + sd = script_rid2sd(st); + + type = conv_num(st, &AARG(0)); + + x = sd->status.save_point.x; + y = sd->status.save_point.y; + switch (type) + { + case 0: + { + RString mapname = sd->status.save_point.map_; + push_str<ScriptDataStr>(st->stack, mapname); + } + break; + case 1: + push_int<ScriptDataInt>(st->stack, x); + break; + case 2: + push_int<ScriptDataInt>(st->stack, y); + break; + } +} + +/*========================================== + * areatimer + *------------------------------------------ + */ +static +void builtin_areatimer_sub(dumb_ptr<block_list> bl, interval_t tick, NpcEvent event) +{ + pc_addeventtimer(bl->is_player(), tick, event); +} + +static +void builtin_areatimer(ScriptState *st) +{ + int x0, y0, x1, y1; + + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x0 = conv_num(st, &AARG(1)); + y0 = conv_num(st, &AARG(2)); + x1 = conv_num(st, &AARG(3)); + y1 = conv_num(st, &AARG(4)); + interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(5))); + ZString event_ = ZString(conv_str(st, &AARG(6))); + NpcEvent event; + extract(event_, &event); + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return); + + map_foreachinarea(std::bind(builtin_areatimer_sub, ph::_1, tick, event), + m, + x0, y0, + x1, y1, + BL::PC); +} + +/*========================================== + * Check whether the PC is in the specified rectangle + *------------------------------------------ + */ +static +void builtin_isin(ScriptState *st) +{ + int x1, y1, x2, y2; + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x1 = conv_num(st, &AARG(1)); + y1 = conv_num(st, &AARG(2)); + x2 = conv_num(st, &AARG(3)); + y2 = conv_num(st, &AARG(4)); + + if (!sd) + return; + + push_int<ScriptDataInt>(st->stack, + (sd->bl_x >= x1 && sd->bl_x <= x2) + && (sd->bl_y >= y1 && sd->bl_y <= y2) + && (str == sd->bl_m->name_)); +} + +/*========================================== + * Check whether the coords are collision + *------------------------------------------ + */ +static +void builtin_iscollision(ScriptState *st) +{ + int x, y; + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return); + + x = conv_num(st, &AARG(1)); + y = conv_num(st, &AARG(2)); + + push_int<ScriptDataInt>(st->stack, + bool(map_getcell(m, x, y) & MapCell::UNWALKABLE)); +} + +// Trigger the shop on a (hopefully) nearby shop NPC +static +void builtin_shop(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + dumb_ptr<npc_data> nd; + + if (!sd) + return; + + NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARG(0)))); + nd = npc_name2id(name); + if (!nd) + { + PRINTF("builtin_shop: no such npc: '%s'\n"_fmt, name); + return; + } + + builtin_close(st); + clif_npcbuysell(sd, nd->bl_id); +} + +/*========================================== + * Check whether the PC is dead + *------------------------------------------ + */ +static +void builtin_isdead(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + push_int<ScriptDataInt>(st->stack, pc_isdead(sd)); +} + +/*======================================== + * Changes a NPC name, and sprite + *---------------------------------------- + */ +static +void builtin_fakenpcname(ScriptState *st) +{ + NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARG(0)))); + NpcName newname = stringish<NpcName>(ZString(conv_str(st, &AARG(1)))); + Species newsprite = wrap<Species>(static_cast<uint16_t>(conv_num(st, &AARG(2)))); + dumb_ptr<npc_data> nd = npc_name2id(name); + if (!nd) + { + PRINTF("builtin_fakenpcname: no such npc: '%s'\n"_fmt, name); + return; + } + nd->name = newname; + nd->npc_class = newsprite; + + // Refresh this npc + npc_enable(name, 0); + npc_enable(name, 1); + +} + +/*============================ + * Gets the PC's x pos + *---------------------------- + */ +static +void builtin_getx(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + push_int<ScriptDataInt>(st->stack, sd->bl_x); +} + +/*============================ + * Gets the PC's y pos + *---------------------------- + */ +static +void builtin_gety(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + push_int<ScriptDataInt>(st->stack, sd->bl_y); +} + +/* + * Get the PC's current map's name + */ +static +void builtin_getmap(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + push_str<ScriptDataStr>(st->stack, sd->bl_m->name_); +} + +/* + * Get the NPC's info + */ +static +void builtin_strnpcinfo(ScriptState *st) +{ + int num = conv_num(st, &AARG(0)); + RString name; + dumb_ptr<npc_data> nd; + + if(HARG(1)){ + NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(1)))); + nd = npc_name2id(npc); + if (!nd) + { + PRINTF("builtin_strnpcinfo: no such npc: '%s'\n"_fmt, npc); + return; + } + } else { + nd = map_id_is_npc(st->oid); + } + + switch(num) + { + case 0: + name = nd->name; + break; + case 1: + name = nd->name.xislice_h(std::find(nd->name.begin(), nd->name.end(), '#')); + break; + case 2: + name = nd->name.xislice_t(std::find(nd->name.begin(), nd->name.end(), '#')); + break; + case 3: + name = nd->bl_m->name_; + break; + } + + push_str<ScriptDataStr>(st->stack, name); +} + +/*============================ + * Gets the NPC's x pos + *---------------------------- + */ +static +void builtin_getnpcx(ScriptState *st) +{ + dumb_ptr<npc_data> nd; + + if(HARG(0)){ + NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARG(0)))); + nd = npc_name2id(name); + if (!nd) + { + PRINTF("builtin_getnpcx: no such npc: '%s'\n"_fmt, name); + return; + } + } else { + nd = map_id_is_npc(st->oid); + } + + push_int<ScriptDataInt>(st->stack, nd->bl_x); +} + +/*============================ + * Gets the NPC's y pos + *---------------------------- + */ +static +void builtin_getnpcy(ScriptState *st) +{ + dumb_ptr<npc_data> nd; + + if(HARG(0)){ + NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARG(0)))); + nd = npc_name2id(name); + if (!nd) + { + PRINTF("builtin_getnpcy: no such npc: '%s'\n"_fmt, name); + return; + } + } else { + nd = map_id_is_npc(st->oid); + } + + push_int<ScriptDataInt>(st->stack, nd->bl_y); +} + +static +void builtin_mapexit(ScriptState *) +{ + runflag = 0; +} + + +#define BUILTIN(func, args, ret) \ +{builtin_##func, #func ## _s, args, ret} + +BuiltinFunction builtin_functions[] = +{ + BUILTIN(mes, "s"_s, '\0'), + BUILTIN(goto, "L"_s, '\0'), + BUILTIN(callfunc, "F"_s, '\0'), + BUILTIN(callsub, "L"_s, '\0'), + BUILTIN(return, ""_s, '\0'), + BUILTIN(next, ""_s, '\0'), + BUILTIN(close, ""_s, '\0'), + BUILTIN(close2, ""_s, '\0'), + BUILTIN(menu, "sL**"_s, '\0'), + BUILTIN(rand, "i?"_s, 'i'), + BUILTIN(isat, "Mxy"_s, 'i'), + BUILTIN(warp, "Mxy"_s, '\0'), + BUILTIN(areawarp, "MxyxyMxy"_s, '\0'), + BUILTIN(heal, "ii?"_s, '\0'), + BUILTIN(input, "N"_s, '\0'), + BUILTIN(if, "iF*"_s, '\0'), + BUILTIN(set, "Ne"_s, '\0'), + BUILTIN(setarray, "Ne*"_s, '\0'), + BUILTIN(cleararray, "Nei"_s, '\0'), + BUILTIN(getarraysize, "N"_s, 'i'), + BUILTIN(getelementofarray, "Ni"_s, '.'), + BUILTIN(setlook, "ii"_s, '\0'), + BUILTIN(countitem, "I"_s, 'i'), + BUILTIN(checkweight, "Ii"_s, 'i'), + BUILTIN(getitem, "Ii??"_s, '\0'), + BUILTIN(makeitem, "IiMxy"_s, '\0'), + BUILTIN(delitem, "Ii"_s, '\0'), + BUILTIN(getcharid, "i?"_s, 'i'), + BUILTIN(strcharinfo, "i"_s, 's'), + BUILTIN(getequipid, "i"_s, 'i'), + BUILTIN(getequipname, "i"_s, 's'), + BUILTIN(bonus, "ii"_s, '\0'), + BUILTIN(bonus2, "iii"_s, '\0'), + BUILTIN(skill, "ii?"_s, '\0'), + BUILTIN(setskill, "ii"_s, '\0'), + BUILTIN(getskilllv, "i"_s, 'i'), + BUILTIN(getgmlevel, ""_s, 'i'), + BUILTIN(end, ""_s, '\0'), + BUILTIN(getopt2, ""_s, 'i'), + BUILTIN(setopt2, "i"_s, '\0'), + BUILTIN(savepoint, "Mxy"_s, '\0'), + BUILTIN(gettimetick, "i"_s, 'i'), + BUILTIN(gettime, "i"_s, 'i'), + BUILTIN(openstorage, ""_s, '\0'), + BUILTIN(getexp, "ii"_s, '\0'), + BUILTIN(monster, "Mxysmi?"_s, '\0'), + BUILTIN(areamonster, "Mxyxysmi?"_s, '\0'), + BUILTIN(killmonster, "ME"_s, '\0'), + BUILTIN(donpcevent, "E"_s, '\0'), + BUILTIN(addtimer, "tE"_s, '\0'), + BUILTIN(initnpctimer, "?"_s, '\0'), + BUILTIN(startnpctimer, "?"_s, '\0'), + BUILTIN(stopnpctimer, "?"_s, '\0'), + BUILTIN(getnpctimer, "i?"_s, 'i'), + BUILTIN(setnpctimer, "i?"_s, '\0'), + BUILTIN(announce, "si"_s, '\0'), + BUILTIN(mapannounce, "Msi"_s, '\0'), + BUILTIN(getusers, "i"_s, 'i'), + BUILTIN(getmapusers, "M"_s, 'i'), + BUILTIN(getareausers, "Mxyxy?"_s, 'i'), + BUILTIN(getareadropitem, "Mxyxyi?"_s, 'i'), + BUILTIN(enablenpc, "s"_s, '\0'), + BUILTIN(disablenpc, "s"_s, '\0'), + BUILTIN(sc_start, "iTi?"_s, '\0'), + BUILTIN(sc_end, "i"_s, '\0'), + BUILTIN(sc_check, "i"_s, 'i'), + BUILTIN(debugmes, "s"_s, '\0'), + BUILTIN(wgm, "s"_s, '\0'), + BUILTIN(gmlog, "s"_s, '\0'), + BUILTIN(resetstatus, ""_s, '\0'), + BUILTIN(attachrid, "i"_s, 'i'), + BUILTIN(detachrid, ""_s, '\0'), + BUILTIN(isloggedin, "i"_s, 'i'), + BUILTIN(setmapflag, "Mi"_s, '\0'), + BUILTIN(removemapflag, "Mi"_s, '\0'), + BUILTIN(getmapflag, "Mi"_s, 'i'), + BUILTIN(pvpon, "M"_s, '\0'), + BUILTIN(pvpoff, "M"_s, '\0'), + BUILTIN(setpvpchannel, "i"_s, '\0'), + BUILTIN(getpvpflag, "i"_s, 'i'), + BUILTIN(emotion, "i?"_s, '\0'), + BUILTIN(mapwarp, "MMxy"_s, '\0'), + BUILTIN(mobcount, "ME"_s, 'i'), + BUILTIN(marriage, "P"_s, 'i'), + BUILTIN(divorce, ""_s, 'i'), + BUILTIN(getitemname, "I"_s, 's'), + BUILTIN(getitemlink, "I"_s, 's'), + BUILTIN(getspellinvocation, "s"_s, 's'), + BUILTIN(getpartnerid2, ""_s, 'i'), + BUILTIN(getinventorylist, ""_s, '\0'), + BUILTIN(getactivatedpoolskilllist, ""_s, '\0'), + BUILTIN(getunactivatedpoolskilllist, ""_s, '\0'), + BUILTIN(poolskill, "i"_s, '\0'), + BUILTIN(unpoolskill, "i"_s, '\0'), + BUILTIN(misceffect, "i?"_s, '\0'), + BUILTIN(specialeffect, "i"_s, '\0'), + BUILTIN(specialeffect2, "i"_s, '\0'), + BUILTIN(nude, ""_s, '\0'), + BUILTIN(unequipbyid, "i"_s, '\0'), + BUILTIN(npcwarp, "xys"_s, '\0'), + BUILTIN(npcareawarp, "xyxyis"_s, '\0'), + BUILTIN(message, "Ps"_s, '\0'), + BUILTIN(npctalk, "s"_s, '\0'), + BUILTIN(getlook, "i"_s, 'i'), + BUILTIN(getsavepoint, "i"_s, '.'), + BUILTIN(areatimer, "MxyxytE"_s, '\0'), + BUILTIN(isin, "Mxyxy"_s, 'i'), + BUILTIN(iscollision, "Mxy"_s, 'i'), + BUILTIN(shop, "s"_s, '\0'), + BUILTIN(isdead, ""_s, 'i'), + BUILTIN(fakenpcname, "ssi"_s, '\0'), + BUILTIN(getx, ""_s, 'i'), + BUILTIN(gety, ""_s, 'i'), + BUILTIN(getnpcx, "?"_s, 'i'), + BUILTIN(getnpcy, "?"_s, 'i'), + BUILTIN(strnpcinfo, "i?"_s, 's'), + BUILTIN(getmap, ""_s, 's'), + BUILTIN(mapexit, ""_s, '\0'), + BUILTIN(freeloop, "i"_s, '\0'), + {nullptr, ""_s, ""_s, '\0'}, +}; +} // namespace map +} // namespace tmwa diff --git a/src/sexpr/variant.cpp b/src/map/script-fun.hpp index b1f500a..81d68fe 100644 --- a/src/sexpr/variant.cpp +++ b/src/map/script-fun.hpp @@ -1,7 +1,9 @@ -#include "variant.hpp" -// variant.cpp - Just include the header file. +#pragma once +// script-fun.hpp - EAthena script frontend, engine, and library. // -// Copyright © 2012 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> // // This file is part of The Mana World (Athena server) // @@ -18,23 +20,22 @@ // 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 "../poison.hpp" +#include "fwd.hpp" +#include "../strings/literal.hpp" namespace tmwa { -namespace sexpr +namespace map { -namespace +struct BuiltinFunction { - struct Foo - { - Foo() {} - ~Foo() {} - Foo(Foo&&) {} - Foo& operator = (Foo&&) { return *this; } - }; -} // anonymous namespace - static Variant<int, Foo> v; -} // namespace sexpr + void (*func)(ScriptState *); + LString name; + LString arg; + char ret; +}; + +extern BuiltinFunction builtin_functions[]; +} // namespace map } // namespace tmwa diff --git a/src/map/script-parse-internal.hpp b/src/map/script-parse-internal.hpp new file mode 100644 index 0000000..ddaeef0 --- /dev/null +++ b/src/map/script-parse-internal.hpp @@ -0,0 +1,67 @@ +#pragma once +// script-parse-internal.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "script-parse.hpp" +#include "fwd.hpp" + +#include "../strings/rstring.hpp" + + +namespace tmwa +{ +namespace map +{ +enum class StringCode : uint8_t +{ + NOP, POS, INT, PARAM, FUNC, + VARIABLE, +}; +enum class ByteCode : uint8_t +{ + // types and specials + // Note that 'INT' is synthetic, and does not occur in the data stream + NOP, POS, INT, PARAM, FUNC, STR, ARG, + VARIABLE, EOL, + + // unary and binary operators + LOR, LAND, LE, LT, GE, GT, EQ, NE, + XOR, OR, AND, ADD, SUB, MUL, DIV, MOD, + NEG, LNOT, NOT, R_SHIFT, L_SHIFT, + + // additions + // needed because FUNC is used for the actual call + FUNC_REF, +}; + +struct str_data_t +{ + StringCode type; + RString strs; + int backpatch; + int label_; + int val; +}; + +Option<Borrowed<str_data_t>> search_strp(XString p); +Borrowed<str_data_t> add_strp(XString p); +} // namespace map +} // namespace tmwa diff --git a/src/map/script-parse.cpp b/src/map/script-parse.cpp new file mode 100644 index 0000000..fb306c5 --- /dev/null +++ b/src/map/script-parse.cpp @@ -0,0 +1,851 @@ +#include "script-parse-internal.hpp" +// script-parse.cpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011 Chuck Miller +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © 2013 wushin +// +// 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 <set> + +#include "../generic/array.hpp" +#include "../generic/db.hpp" +#include "../generic/intern-pool.hpp" + +#include "../strings/rstring.hpp" + +#include "../io/cxxstdio.hpp" + +#include "../mmo/cxxstdio_enums.hpp" + +#include "../ast/script.hpp" + +#include "globals.hpp" +#include "map.t.hpp" +#include "script-buffer.hpp" +#include "script-call.hpp" +#include "script-fun.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace map +{ +constexpr bool DEBUG_DISP = false; + +class ScriptBuffer +{ + typedef ZString::iterator ZSit; + + std::vector<ByteCode> script_buf; + RString debug_name; + std::vector<std::pair<ScriptLabel, size_t>> debug_labels; +public: + ScriptBuffer(RString name) : debug_name(std::move(name)) {} + + // construction methods + void add_scriptc(ByteCode a); + void add_scriptb(uint8_t a); + void add_scripti(uint32_t a); + void add_scriptl(Borrowed<str_data_t> a); + void set_label(Borrowed<str_data_t> ld, int pos_); + ZSit parse_simpleexpr(ZSit p); + ZSit parse_subexpr(ZSit p, int limit); + ZSit parse_expr(ZSit p); + ZSit parse_line(ZSit p, bool *canstep); + void parse_script(ZString src, int line, bool implicit_end); + + // consumption methods + ByteCode operator[](size_t i) const { return script_buf[i]; } + ZString get_str(size_t i) const + { + return ZString(strings::really_construct_from_a_pointer, reinterpret_cast<const char *>(&script_buf[i]), nullptr); + } +}; +} // namespace map +} // namespace tmwa + +void std::default_delete<const tmwa::map::ScriptBuffer>::operator()(const tmwa::map::ScriptBuffer *sd) +{ + really_delete1 sd; +} + +namespace tmwa +{ +namespace map +{ +// implemented for script-call.hpp because reasons +ByteCode ScriptPointer::peek() const { return (*TRY_UNWRAP(code, abort()))[pos]; } +ByteCode ScriptPointer::pop() { return (*TRY_UNWRAP(code, abort()))[pos++]; } +ZString ScriptPointer::pops() +{ + ZString rv = TRY_UNWRAP(code, abort())->get_str(pos); + pos += rv.size(); + ++pos; + return rv; +} + +static +struct ScriptConfigParse +{ + static const + int warn_func_no_comma = 1; + static const + int warn_cmd_no_comma = 1; + static const + int warn_func_mismatch_paramnum = 1; + static const + int warn_cmd_mismatch_paramnum = 1; +} script_config; + + +Option<Borrowed<str_data_t>> search_strp(XString p) +{ + return str_datam.search(p); +} + +Borrowed<str_data_t> add_strp(XString p) +{ + Option<P<str_data_t>> rv_ = search_strp(p); + OMATCH_BEGIN_SOME (rv, rv_) + { + return rv; + } + OMATCH_END (); + + RString p2 = p; + P<str_data_t> datum = str_datam.init(p2); + datum->type = StringCode::NOP; + datum->strs = p2; + datum->backpatch = -1; + datum->label_ = -1; + return datum; +} + +/*========================================== + * スクリプトバッファに1バイト書き込む + *------------------------------------------ + */ +void ScriptBuffer::add_scriptc(ByteCode a) +{ + script_buf.push_back(a); +} + +/*========================================== + * スクリプトバッファにデータタイプを書き込む + *------------------------------------------ + */ +void ScriptBuffer::add_scriptb(uint8_t a) +{ + add_scriptc(static_cast<ByteCode>(a)); +} + +/*========================================== + * スクリプトバッファに整数を書き込む + *------------------------------------------ + */ +void ScriptBuffer::add_scripti(uint32_t a) +{ + while (a >= 0x40) + { + add_scriptb(a | 0xc0); + a = (a - 0x40) >> 6; + } + add_scriptb(a | 0x80); +} + +/*========================================== + * スクリプトバッファにラベル/変数/関数を書き込む + *------------------------------------------ + */ +// 最大16Mまで +void ScriptBuffer::add_scriptl(P<str_data_t> ld) +{ + int backpatch = ld->backpatch; + + switch (ld->type) + { + case StringCode::POS: + add_scriptc(ByteCode::POS); + add_scriptb(static_cast<uint8_t>(ld->label_)); + add_scriptb(static_cast<uint8_t>(ld->label_ >> 8)); + add_scriptb(static_cast<uint8_t>(ld->label_ >> 16)); + break; + case StringCode::NOP: + // need to set backpatch, because it might become a label later + add_scriptc(ByteCode::VARIABLE); + ld->backpatch = script_buf.size(); + add_scriptb(static_cast<uint8_t>(backpatch)); + add_scriptb(static_cast<uint8_t>(backpatch >> 8)); + add_scriptb(static_cast<uint8_t>(backpatch >> 16)); + break; + case StringCode::INT: + add_scripti(ld->val); + break; + case StringCode::FUNC: + add_scriptc(ByteCode::FUNC_REF); + add_scriptb(static_cast<uint8_t>(ld->val)); + add_scriptb(static_cast<uint8_t>(ld->val >> 8)); + add_scriptb(static_cast<uint8_t>(ld->val >> 16)); + break; + case StringCode::PARAM: + add_scriptc(ByteCode::PARAM); + add_scriptb(static_cast<uint8_t>(ld->val)); + add_scriptb(static_cast<uint8_t>(ld->val >> 8)); + add_scriptb(static_cast<uint8_t>(ld->val >> 16)); + break; + default: + abort(); + } +} + +/*========================================== + * ラベルを解決する + *------------------------------------------ + */ +void ScriptBuffer::set_label(Borrowed<str_data_t> ld, int pos_) +{ + int next; + + ld->type = StringCode::POS; + ld->label_ = pos_; + for (int i = ld->backpatch; i >= 0 && i != 0x00ffffff; i = next) + { + next = 0; + // woot! no longer endian-dependent! + next |= static_cast<uint8_t>(script_buf[i + 0]) << 0; + next |= static_cast<uint8_t>(script_buf[i + 1]) << 8; + next |= static_cast<uint8_t>(script_buf[i + 2]) << 16; + script_buf[i - 1] = ByteCode::POS; + script_buf[i] = static_cast<ByteCode>(pos_); + script_buf[i + 1] = static_cast<ByteCode>(pos_ >> 8); + script_buf[i + 2] = static_cast<ByteCode>(pos_ >> 16); + } +} + +/*========================================== + * スペース/コメント読み飛ばし + *------------------------------------------ + */ +static +ZString::iterator skip_space(ZString::iterator p) +{ + while (1) + { + while (isspace(*p)) + p++; + if (p[0] == '/' && p[1] == '/') + { + while (*p && *p != '\n') + p++; + } + else if (p[0] == '/' && p[1] == '*') + { + p++; + while (*p && (p[-1] != '*' || p[0] != '/')) + p++; + if (*p) + p++; + } + else + break; + } + return p; +} + +/*========================================== + * 1単語スキップ + *------------------------------------------ + */ +static +ZString::iterator skip_word(ZString::iterator p) +{ + // prefix + if (*p == '$') + p++; // MAP鯖内共有変数用 + if (*p == '@') + p++; // 一時的変数用(like weiss) + if (*p == '#') + p++; // account変数用 + if (*p == '#') + p++; // ワールドaccount変数用 + + while (isalnum(*p) || *p == '_') + p++; + + // postfix + if (*p == '$') + p++; // 文字列変数 + + return p; +} + +/*========================================== + * エラーメッセージ出力 + *------------------------------------------ + */ +static +void disp_error_message(ZString mes, ZString::iterator pos_) +{ + script_errors++; + + assert (startptr.begin() <= pos_ && pos_ <= startptr.end()); + + int line; + ZString::iterator p; + + for (line = startline, p = startptr.begin(); p != startptr.end(); line++) + { + ZString::iterator linestart = p; + ZString::iterator lineend = std::find(p, startptr.end(), '\n'); + if (pos_ < lineend) + { + PRINTF("\n%s\nline %d : "_fmt, mes, line); + for (int i = 0; linestart + i != lineend; i++) + { + if (linestart + i != pos_) + PRINTF("%c"_fmt, linestart[i]); + else + PRINTF("\'%c\'"_fmt, linestart[i]); + } + PRINTF("\a\n"_fmt); + return; + } + p = lineend + 1; + } +} + +/*========================================== + * 項の解析 + *------------------------------------------ + */ +ZString::iterator ScriptBuffer::parse_simpleexpr(ZString::iterator p) +{ + p = skip_space(p); + + if (*p == ';' || *p == ',') + { + disp_error_message("unexpected expr end"_s, p); + exit(1); + } + if (*p == '(') + { + + p = parse_subexpr(p + 1, -1); + p = skip_space(p); + if ((*p++) != ')') + { + disp_error_message("unmatch ')'"_s, p); + exit(1); + } + } + else if (isdigit(*p) || ((*p == '-' || *p == '+') && isdigit(p[1]))) + { + char *np; + int i = strtoul(&*p, &np, 0); + add_scripti(i); + p += np - &*p; + } + else if (*p == '"') + { + add_scriptc(ByteCode::STR); + p++; + while (*p && *p != '"') + { + if (*p == '\\') + p++; + else if (*p == '\n') + { + disp_error_message("unexpected newline @ string"_s, p); + exit(1); + } + add_scriptb(*p++); + } + if (!*p) + { + disp_error_message("unexpected eof @ string"_s, p); + exit(1); + } + add_scriptb(0); + p++; //'"' + } + else + { + // label , register , function etc + ZString::iterator p2 = skip_word(p); + if (p2 == p) + { + disp_error_message("unexpected character"_s, p); + exit(1); + } + XString word(&*p, &*p2, nullptr); + if (word.startswith("On"_s) || word.startswith("L_"_s) || word.startswith("S_"_s)) + probable_labels.insert(stringish<ScriptLabel>(word)); + if (parse_cmd_if && (word == "callsub"_s || word == "callfunc"_s || word == "return"_s)) + { + disp_error_message("Sorry, callsub/callfunc/return have never worked properly in an if statement."_s, p); + } + P<str_data_t> ld = add_strp(word); + + parse_cmdp = Some(ld); // warn_*_mismatch_paramnumのために必要 + // why not just check l->str == "if"_s or std::string(p, p2) == "if"_s? + if (Some(ld) == search_strp("if"_s)) // warn_cmd_no_commaのために必要 + parse_cmd_if++; + p = p2; + + if (ld->type != StringCode::FUNC && *p == '[') + { + // array(name[i] => getelementofarray(name,i) ) + add_scriptl(TRY_UNWRAP(search_strp("getelementofarray"_s), abort())); + add_scriptc(ByteCode::ARG); + add_scriptl(ld); + p = parse_subexpr(p + 1, -1); + p = skip_space(p); + if (*p != ']') + { + disp_error_message("unmatch ']'"_s, p); + exit(1); + } + p++; + add_scriptc(ByteCode::FUNC); + } + else + add_scriptl(ld); + + } + + return p; +} + +/*========================================== + * 式の解析 + *------------------------------------------ + */ +ZString::iterator ScriptBuffer::parse_subexpr(ZString::iterator p, int limit) +{ + ByteCode op; + int opl, len; + + p = skip_space(p); + + if (*p == '-') + { + ZString::iterator tmpp = skip_space(p + 1); + if (*tmpp == ';' || *tmpp == ',') + { + --script_errors; disp_error_message("deprecated: implicit 'next statement' label"_s, p); + add_scriptl(borrow(LABEL_NEXTLINE_)); + p++; + return p; + } + } + ZString::iterator tmpp = p; + if ((op = ByteCode::NEG, *p == '-') || (op = ByteCode::LNOT, *p == '!') + || (op = ByteCode::NOT, *p == '~')) + { + p = parse_subexpr(p + 1, 100); + add_scriptc(op); + } + else + p = parse_simpleexpr(p); + p = skip_space(p); + while (((op = ByteCode::ADD, opl = 6, len = 1, *p == '+') || + (op = ByteCode::SUB, opl = 6, len = 1, *p == '-') || + (op = ByteCode::MUL, opl = 7, len = 1, *p == '*') || + (op = ByteCode::DIV, opl = 7, len = 1, *p == '/') || + (op = ByteCode::MOD, opl = 7, len = 1, *p == '%') || + (op = ByteCode::FUNC, opl = 8, len = 1, *p == '(') || + (op = ByteCode::LAND, opl = 1, len = 2, *p == '&' && p[1] == '&') || + (op = ByteCode::AND, opl = 5, len = 1, *p == '&') || + (op = ByteCode::LOR, opl = 0, len = 2, *p == '|' && p[1] == '|') || + (op = ByteCode::OR, opl = 4, len = 1, *p == '|') || + (op = ByteCode::XOR, opl = 3, len = 1, *p == '^') || + (op = ByteCode::EQ, opl = 2, len = 2, *p == '=' && p[1] == '=') || + (op = ByteCode::NE, opl = 2, len = 2, *p == '!' && p[1] == '=') || + (op = ByteCode::R_SHIFT, opl = 5, len = 2, *p == '>' && p[1] == '>') || + (op = ByteCode::GE, opl = 2, len = 2, *p == '>' && p[1] == '=') || + (op = ByteCode::GT, opl = 2, len = 1, *p == '>') || + (op = ByteCode::L_SHIFT, opl = 5, len = 2, *p == '<' && p[1] == '<') || + (op = ByteCode::LE, opl = 2, len = 2, *p == '<' && p[1] == '=') || + (op = ByteCode::LT, opl = 2, len = 1, *p == '<')) && opl > limit) + { + p += len; + if (op == ByteCode::FUNC) + { + int i = 0; + P<str_data_t> funcp = TRY_UNWRAP(parse_cmdp, abort()); + Array<ZString::iterator, 128> plist; + + if (funcp->type != StringCode::FUNC) + { + disp_error_message("expect function"_s, tmpp); + exit(0); + } + + add_scriptc(ByteCode::ARG); + while (*p && *p != ')' && i < 128) + { + plist[i] = p; + p = parse_subexpr(p, -1); + p = skip_space(p); + if (*p == ',') + p++; + else if (*p != ')' && script_config.warn_func_no_comma) + { + disp_error_message("expect ',' or ')' at func params"_s, + p); + } + p = skip_space(p); + i++; + } + if (i == 128) + { + disp_error_message("PANIC: unrecoverable error in function argument list"_s, p); + abort(); + } + plist[i] = p; + if (*p != ')') + { + disp_error_message("func request '(' ')'"_s, p); + exit(1); + } + p++; + + if (funcp->type == StringCode::FUNC + && script_config.warn_func_mismatch_paramnum) + { + ZString arg = builtin_functions[funcp->val].arg; + int j = 0; + // TODO handle ? and multiple * correctly + for (j = 0; arg[j]; j++) + if (arg[j] == '*' || arg[j] == '?') + break; + if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j)) + { + disp_error_message("illegal number of parameters"_s, + plist[std::min(i, j)]); + } + if (!builtin_functions[funcp->val].ret) + { + disp_error_message("statement in function context"_s, tmpp); + } + } + } + else // not op == ByteCode::FUNC + { + p = parse_subexpr(p, opl); + } + add_scriptc(op); + p = skip_space(p); + } + return p; /* return first untreated operator */ +} + +/*========================================== + * 式の評価 + *------------------------------------------ + */ +ZString::iterator ScriptBuffer::parse_expr(ZString::iterator p) +{ + switch (*p) + { + case ')': + case ';': + case ':': + case '[': + case ']': + case '}': + disp_error_message("unexpected char"_s, p); + exit(1); + } + p = parse_subexpr(p, -1); + return p; +} + +/*========================================== + * 行の解析 + *------------------------------------------ + */ +ZString::iterator ScriptBuffer::parse_line(ZString::iterator p, bool *can_step) +{ + int i = 0; + Array<ZString::iterator, 128> plist; + + p = skip_space(p); + if (*p == ';') + { + disp_error_message("Double semi-colon"_s, p); + ++p; + return p; + } + + parse_cmd_if = 0; // warn_cmd_no_commaのために必要 + + // 最初は関数名 + ZString::iterator p2 = p; + p = parse_simpleexpr(p); + p = skip_space(p); + + P<str_data_t> cmd = TRY_UNWRAP(parse_cmdp, abort()); + if (cmd->type != StringCode::FUNC) + { + disp_error_message("expect command"_s, p2); + } + + { + // TODO should be LString, but no heterogenous lookup yet + static + std::set<ZString> terminators = + { + "goto"_s, + "return"_s, + "close"_s, + "menu"_s, + "end"_s, + "mapexit"_s, + "shop"_s, + }; + *can_step = terminators.count(cmd->strs) == 0; + } + + add_scriptc(ByteCode::ARG); + while (*p && *p != ';' && i < 128) + { + plist[i] = p; + + p = parse_expr(p); + p = skip_space(p); + // 引数区切りの,処理 + if (*p == ',') + p++; + else if (*p != ';' && script_config.warn_cmd_no_comma + && parse_cmd_if * 2 <= i) + { + disp_error_message("expect ',' or ';' at cmd params"_s, p); + } + p = skip_space(p); + i++; + } + if (i == 128) + { + disp_error_message("PANIC: unknown error in command argument list"_s, p); + abort(); + } + plist[i] = p; + if (*(p++) != ';') + { + disp_error_message("need ';'"_s, p); + exit(1); + } + add_scriptc(ByteCode::FUNC); + + if (cmd->type == StringCode::FUNC + && script_config.warn_cmd_mismatch_paramnum) + { + ZString arg = builtin_functions[cmd->val].arg; + int j = 0; + // TODO see above + for (j = 0; arg[j]; j++) + if (arg[j] == '*' || arg[j] == '?') + break; + if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j)) + { + disp_error_message("illegal number of parameters"_s, + plist[std::min(i, j)]); + } + if (builtin_functions[cmd->val].ret) + { + disp_error_message("function in statement context"_s, p2); + } + } + + return p; +} + +/*========================================== + * 組み込み関数の追加 + *------------------------------------------ + */ +static +void add_builtin_functions(void) +{ + for (int i = 0; builtin_functions[i].func; i++) + { + P<str_data_t> n = add_strp(builtin_functions[i].name); + n->type = StringCode::FUNC; + n->val = i; + } +} + +std::unique_ptr<const ScriptBuffer> compile_script(RString debug_name, const ast::script::ScriptBody& body, bool implicit_end) +{ + auto script_buf = make_unique<ScriptBuffer>(std::move(debug_name)); + script_buf->parse_script(body.braced_body, body.span.begin.line, implicit_end); + return std::move(script_buf); +} + +/*========================================== + * スクリプトの解析 + *------------------------------------------ + */ +void ScriptBuffer::parse_script(ZString src, int line, bool implicit_end) +{ + static int first = 1; + + if (first) + { + add_builtin_functions(); + } + first = 0; + LABEL_NEXTLINE_.type = StringCode::NOP; + LABEL_NEXTLINE_.backpatch = -1; + LABEL_NEXTLINE_.label_ = -1; + for (auto& pair : str_datam) + { + str_data_t& dit = pair.second; + if (dit.type == StringCode::POS || dit.type == StringCode::VARIABLE) + { + dit.type = StringCode::NOP; + dit.backpatch = -1; + dit.label_ = -1; + } + } + + // 外部用label dbの初期化 + scriptlabel_db.clear(); + + // for error message + startptr = src; + startline = line; + + bool can_step = true; + + ZString::iterator p = src.begin(); + p = skip_space(p); + if (*p != '{') + { + disp_error_message("not found '{'"_s, p); + abort(); + } + for (p++; *p && *p != '}';) + { + p = skip_space(p); + if (*skip_space(skip_word(p)) == ':') + { + if (can_step) + { + --script_errors; disp_error_message("deprecated: implicit fallthrough"_s, p); + } + can_step = true; + + ZString::iterator tmpp = skip_word(p); + XString str(&*p, &*tmpp, nullptr); + P<str_data_t> ld = add_strp(str); + bool e1 = ld->type != StringCode::NOP; + bool e2 = ld->type == StringCode::POS; + bool e3 = ld->label_ != -1; + assert (e1 == e2 && e2 == e3); + if (e3) + { + disp_error_message("dup label "_s, p); + exit(1); + } + set_label(ld, script_buf.size()); + scriptlabel_db.insert(stringish<ScriptLabel>(str), script_buf.size()); + debug_labels.push_back(std::make_pair(stringish<ScriptLabel>(str), script_buf.size())); + p = tmpp + 1; + continue; + } + + if (!can_step) + { + --script_errors; disp_error_message("deprecated: unreachable statement"_s, p); + } + // 他は全部一緒くた + p = parse_line(p, &can_step); + p = skip_space(p); + add_scriptc(ByteCode::EOL); + + set_label(borrow(LABEL_NEXTLINE_), script_buf.size()); + LABEL_NEXTLINE_.type = StringCode::NOP; + LABEL_NEXTLINE_.backpatch = -1; + LABEL_NEXTLINE_.label_ = -1; + } + + if (can_step && !implicit_end) + { + --script_errors; disp_error_message("deprecated: implicit end"_s, p); + } + add_scriptc(ByteCode::NOP); + + // resolve the unknown labels + for (auto& pair : str_datam) + { + str_data_t& sit = pair.second; + if (sit.type == StringCode::NOP) + { + sit.type = StringCode::VARIABLE; + sit.label_ = 0; // anything but -1. Shouldn't matter, but helps asserts. + size_t pool_index = variable_names.intern(sit.strs); + for (int next, j = sit.backpatch; j >= 0 && j != 0x00ffffff; j = next) + { + next = 0; + next |= static_cast<uint8_t>(script_buf[j + 0]) << 0; + next |= static_cast<uint8_t>(script_buf[j + 1]) << 8; + next |= static_cast<uint8_t>(script_buf[j + 2]) << 16; + script_buf[j] = static_cast<ByteCode>(pool_index); + script_buf[j + 1] = static_cast<ByteCode>(pool_index >> 8); + script_buf[j + 2] = static_cast<ByteCode>(pool_index >> 16); + } + } + } + + for (const auto& pair : scriptlabel_db) + { + ScriptLabel key = pair.first; + if (key.startswith("On"_s)) + continue; + if (!(key.startswith("L_"_s) || key.startswith("S_"_s))) + PRINTF("Warning: ugly label: %s\n"_fmt, key); + else if (!probable_labels.count(key)) + PRINTF("Warning: unused label: %s\n"_fmt, key); + } + for (ScriptLabel used : probable_labels) + { + if (scriptlabel_db.search(used).is_none()) + PRINTF("Warning: no such label: %s\n"_fmt, used); + } + probable_labels.clear(); + + if (!DEBUG_DISP) + return; + for (size_t i = 0; i < script_buf.size(); i++) + { + if ((i & 15) == 0) + PRINTF("%04zx : "_fmt, i); + PRINTF("%02x "_fmt, script_buf[i]); + if ((i & 15) == 15) + PRINTF("\n"_fmt); + } + PRINTF("\n"_fmt); +} +} // namespace map +} // namespace tmwa diff --git a/src/tests/test.cpp b/src/map/script-parse.hpp index d731d7c..e08c470 100644 --- a/src/tests/test.cpp +++ b/src/map/script-parse.hpp @@ -1,6 +1,9 @@ -// test.cpp - Driver for testwuite +#pragma once +// script-parse.hpp - EAthena script frontend, engine, and library. // -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> // // This file is part of The Mana World (Athena server) // @@ -17,17 +20,17 @@ // 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 <gtest/gtest.h> +#include "fwd.hpp" -#include "../poison.hpp" +#include <memory> + +#include "script-buffer.hpp" namespace tmwa { -} // namespace tmwa - -int main(int argc, char **argv) +namespace map { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} +std::unique_ptr<const ScriptBuffer> compile_script(RString debug_name, const ast::script::ScriptBody& body, bool implicit_end); +} // namespace map +} // namespace tmwa diff --git a/src/map/script-parse.py b/src/map/script-parse.py new file mode 100644 index 0000000..0309f54 --- /dev/null +++ b/src/map/script-parse.py @@ -0,0 +1,553 @@ +class ScriptBuffer(object): + __slots__ = ('_value') + name = 'tmwa::map::ScriptBuffer' + enabled = True + + def __init__(self, value): + self._value = value + + def to_string(self): + return self._value['debug_name'] + + def get_com(self, b, i, r, labels_dict): + # see script-parse-internal.hpp:ByteCode and script-call.cpp:get_com + ops = ''' + NOP, POS, INT, PARAM, FUNC, STR, ARG, + VARIABLE, EOL, + LOR, LAND, LE, LT, GE, GT, EQ, NE, + XOR, OR, AND, ADD, SUB, MUL, DIV, MOD, + NEG, LNOT, NOT, R_SHIFT, L_SHIFT, + FUNC_REF, + '''.replace(',', '').split() + ci = int(b[i]) + if ci >= 0x80: + rv = 0 + sh = 0 + # Because of how the python iterator up a frame consumed the i already, + # have to manually unroll the first iteration of the C loop. + # TODO itertools.chain([i], r) or something? + if True: + if True: + rv += (ci & 0x7f) << sh + sh += 6 + if ci >= 0xc0: + while True: + i = next(r) + ci = int(b[i]) + + rv += (ci & 0x7f) << sh + sh += 6 + if not (ci >= 0xc0): + break + return 'INT %d' % rv + + cs = ops[ci] + if cs in {'POS', 'VARIABLE', 'FUNC_REF', 'PARAM'}: + ai = 0 + ai |= (int(b[next(r)]) << 0) + ai |= (int(b[next(r)]) << 8) + ai |= (int(b[next(r)]) << 16) + if cs == 'POS': + ln = labels_dict.get(ai) + if ln is not None: + return 'POS %d %s' % (ai, ln[0]) + elif cs == 'VARIABLE': + global rstring_disable_children + rstring_disable_children = True + try: + rv = 'VARIABLE %s' % gdb.parse_and_eval('tmwa::map::variable_names.names._M_impl._M_start[{ai}]'.format(ai=ai)) + finally: + rstring_disable_children = False + return rv + elif cs == 'FUNC_REF': + return 'FUNC_REF %s' % gdb.parse_and_eval('tmwa::map::builtin_functions[{ai}].name'.format(ai=ai)) + elif cs == 'PARAM': + # https://sourceware.org/bugzilla/show_bug.cgi?id=17568 + try: + # this should work + SP = gdb.lookup_type('tmwa::SP') + except gdb.error: + # this should not work + SP = gdb.parse_and_eval('tmwa::SP').type + return 'PARAM %s' % gdb.Value(ai).cast(SP) + else: + assert False + return '%s %d' % (cs, ai) + elif cs == 'STR': + buf = bytearray() + while True: + i = next(r) + ci = int(b[i]) + if ci == 0: + break + buf.append(ci) + return 'STR "%s"' % str(buf).replace('\\', '\\\\').replace('"', '\\"') + elif cs == 'EOL': + return cs + '\n' + return cs + + def children(self): + labels = self._value['debug_labels'] + labels_begin = labels['_M_impl']['_M_start'] + labels_end = labels['_M_impl']['_M_finish'] + labels_size = int(labels_end - labels_begin) + labels_list = [labels_begin[i] for i in range(labels_size)] + labels_dict = {} + char_ptr = gdb.lookup_type('char').pointer() + for e in labels_list: + offset = int(e['second']) + label_name = str(e['first'].address.cast(char_ptr)) + labels_dict.setdefault(offset, []).append(label_name) + code = self._value['script_buf'] + code_begin = code['_M_impl']['_M_start'] + code_end = code['_M_impl']['_M_finish'] + code_size = int(code_end - code_begin) + r = iter(range(code_size)) + for i in r: + buf = [] + for label in labels_dict.get(i, []): + if label.startswith('On'): + yield 'blah', 'event %s:' % label + else: + yield 'blah', 'label %s:' % label + c = self.get_com(code_begin, i, r, labels_dict) + yield 'blah', '%6d: %s' % (i, c) + + + def display_hint(self): + return 'array' + + src = ''' + { + end; + S_Sub: + return; + OnFoo: + callsub S_Sub; + setarray @a, + -1, 0, 1, + 0x0, 0x1, 0x2, + 0x1, 0x2, 0x3, + 0x3, 0x4, 0x5, + 0x7, 0x8, 0x9, + 0xf, 0x10, 0x11, + 0x1f, 0x20, 0x21, + 0x3f, 0x40, 0x41, + 0x7f, 0x80, 0x81, + 0xff, 0x100, 0x101, + 0x1ff, 0x200, 0x201, + 0x3ff, 0x400, 0x401, + 0x7ff, 0x800, 0x801, + 0xfff, 0x1000, 0x1001, + 0x1fff, 0x2000, 0x2001, + 0x3fff, 0x4000, 0x4001, + 0x7fff, 0x8000, 0x8001, + 0xffff, 0x10000, 0x10001, + 0x1ffff, 0x20000, 0x20001, + 0x3ffff, 0x40000, 0x40001, + 0x7ffff, 0x80000, 0x80001, + 0xfffff, 0x100000, 0x100001, + 0x1fffff, 0x200000, 0x200001, + 0x3fffff, 0x400000, 0x400001, + 0x7fffff, 0x800000, 0x800001, + 0xffffff, 0x1000000, 0x1000001, + 0x1ffffff, 0x2000000, 0x2000001, + 0x3ffffff, 0x4000000, 0x4000001, + 0x7ffffff, 0x8000000, 0x8000001, + 0xfffffff, 0x10000000, 0x10000001, + 0x1fffffff, 0x20000000, 0x20000001, + 0x3fffffff, 0x40000000, 0x40000001, + 0x7fffffff, 0x80000000, 0x80000001, + 0xffffffff; + set TEST_FAKE_PARAM_BASELEVEL, TEST_FAKE_CONSTANT; + set @s$, "hello"; + set @i, a || b; + set @i, a && b; + set @i, a <= b; + set @i, a < b; + set @i, a >= b; + set @i, a > b; + set @i, a == b; + set @i, a != b; + set @i, a ^ b; + set @i, a | b; + set @i, a & b; + set @i, a + b; + set @i, a - b; + set @i, a * b; + set @i, a / b; + set @i, a % b; + set @i, - b; + set @i, ! b; + set @i, ~ b; + set @i, a >> b; + set @i, a << b; + } + '''.replace('\n', ' ') + + asm = ''\ + +''' 0: FUNC_REF "end", + 4: ARG, + 5: FUNC, + 6: EOL + , + label "S_Sub":, + 7: FUNC_REF "return", + 11: ARG, + 12: FUNC, + 13: EOL + , + label "OnFoo":, + 14: FUNC_REF "callsub", + 18: ARG, + 19: POS 7 "S_Sub", + 23: FUNC, + 24: EOL + , + 25: FUNC_REF "setarray", + 29: ARG, + 30: VARIABLE "@a", + 34: INT 1, + 35: NEG, + 36: INT 0, + 37: INT 1, + 38: INT 0, + 39: INT 1, + 40: INT 2, + 41: INT 1, + 42: INT 2, + 43: INT 3, + 44: INT 3, + 45: INT 4, + 46: INT 5, + 47: INT 7, + 48: INT 8, + 49: INT 9, + 50: INT 15, + 51: INT 16, + 52: INT 17, + 53: INT 31, + 54: INT 32, + 55: INT 33, + 56: INT 63, + 57: INT 64, + 59: INT 65, + 61: INT 127, + 63: INT 128, + 65: INT 129, + 67: INT 255, + 69: INT 256, + 71: INT 257, + 73: INT 511, + 75: INT 512, + 77: INT 513, + 79: INT 1023, + 81: INT 1024, + 83: INT 1025, + 85: INT 2047, + 87: INT 2048, + 89: INT 2049, + 91: INT 4095, + 93: INT 4096, + 95: INT 4097, + 97: INT 8191, + 100: INT 8192, + 103: INT 8193, + 106: INT 16383, + 109: INT 16384, + 112: INT 16385, + 115: INT 32767, + 118: INT 32768, + 121: INT 32769, + 124: INT 65535, + 127: INT 65536, + 130: INT 65537, + 133: INT 131071, + 136: INT 131072, + 139: INT 131073, + 142: INT 262143, + 145: INT 262144, + 148: INT 262145, + 151: INT 524287, + 155: INT 524288, + 159: INT 524289, + 163: INT 1048575, + 167: INT 1048576, + 171: INT 1048577, + 175: INT 2097151, + 179: INT 2097152, + 183: INT 2097153, + 187: INT 4194303, + 191: INT 4194304, + 195: INT 4194305, + 199: INT 8388607, + 203: INT 8388608, + 207: INT 8388609, + 211: INT 16777215, + 215: INT 16777216, + 219: INT 16777217, + 223: INT 33554431, + 228: INT 33554432, + 233: INT 33554433, + 238: INT 67108863, + 243: INT 67108864, + 248: INT 67108865, + 253: INT 134217727, + 258: INT 134217728, + 263: INT 134217729, + 268: INT 268435455, + 273: INT 268435456, + 278: INT 268435457, + 283: INT 536870911, + 288: INT 536870912, + 293: INT 536870913, + 298: INT 1073741823, + 303: INT 1073741824, + 308: INT 1073741825, + 313: INT 2147483647, + 319: INT 2147483648, + 325: INT 2147483649, + 331: INT 4294967295, + 337: FUNC, + 338: EOL + , + 339: FUNC_REF "set", + 343: ARG, + 344: PARAM tmwa::SP::BASELEVEL, + 348: INT 42, + 349: FUNC, + 350: EOL + , + 351: FUNC_REF "set", + 355: ARG, + 356: VARIABLE "@s$", + 360: STR "hello", + 367: FUNC, + 368: EOL + , + 369: FUNC_REF "set", + 373: ARG, + 374: VARIABLE "@i", + 378: VARIABLE "a", + 382: VARIABLE "b", + 386: LOR, + 387: FUNC, + 388: EOL + , + 389: FUNC_REF "set", + 393: ARG, + 394: VARIABLE "@i", + 398: VARIABLE "a", + 402: VARIABLE "b", + 406: LAND, + 407: FUNC, + 408: EOL + , + 409: FUNC_REF "set", + 413: ARG, + 414: VARIABLE "@i", + 418: VARIABLE "a", + 422: VARIABLE "b", + 426: LE, + 427: FUNC, + 428: EOL + , + 429: FUNC_REF "set", + 433: ARG, + 434: VARIABLE "@i", + 438: VARIABLE "a", + 442: VARIABLE "b", + 446: LT, + 447: FUNC, + 448: EOL + , + 449: FUNC_REF "set", + 453: ARG, + 454: VARIABLE "@i", + 458: VARIABLE "a", + 462: VARIABLE "b", + 466: GE, + 467: FUNC, + 468: EOL + , + 469: FUNC_REF "set", + 473: ARG, + 474: VARIABLE "@i", + 478: VARIABLE "a", + 482: VARIABLE "b", + 486: GT, + 487: FUNC, + 488: EOL + , + 489: FUNC_REF "set", + 493: ARG, + 494: VARIABLE "@i", + 498: VARIABLE "a", + 502: VARIABLE "b", + 506: EQ, + 507: FUNC, + 508: EOL + , + 509: FUNC_REF "set", + 513: ARG, + 514: VARIABLE "@i", + 518: VARIABLE "a", + 522: VARIABLE "b", + 526: NE, + 527: FUNC, + 528: EOL + , + 529: FUNC_REF "set", + 533: ARG, + 534: VARIABLE "@i", + 538: VARIABLE "a", + 542: VARIABLE "b", + 546: XOR, + 547: FUNC, + 548: EOL + , + 549: FUNC_REF "set", + 553: ARG, + 554: VARIABLE "@i", + 558: VARIABLE "a", + 562: VARIABLE "b", + 566: OR, + 567: FUNC, + 568: EOL + , + 569: FUNC_REF "set", + 573: ARG, + 574: VARIABLE "@i", + 578: VARIABLE "a", + 582: VARIABLE "b", + 586: AND, + 587: FUNC, + 588: EOL + , + 589: FUNC_REF "set", + 593: ARG, + 594: VARIABLE "@i", + 598: VARIABLE "a", + 602: VARIABLE "b", + 606: ADD, + 607: FUNC, + 608: EOL + , + 609: FUNC_REF "set", + 613: ARG, + 614: VARIABLE "@i", + 618: VARIABLE "a", + 622: VARIABLE "b", + 626: SUB, + 627: FUNC, + 628: EOL + , + 629: FUNC_REF "set", + 633: ARG, + 634: VARIABLE "@i", + 638: VARIABLE "a", + 642: VARIABLE "b", + 646: MUL, + 647: FUNC, + 648: EOL + , + 649: FUNC_REF "set", + 653: ARG, + 654: VARIABLE "@i", + 658: VARIABLE "a", + 662: VARIABLE "b", + 666: DIV, + 667: FUNC, + 668: EOL + , + 669: FUNC_REF "set", + 673: ARG, + 674: VARIABLE "@i", + 678: VARIABLE "a", + 682: VARIABLE "b", + 686: MOD, + 687: FUNC, + 688: EOL + , + 689: FUNC_REF "set", + 693: ARG, + 694: VARIABLE "@i", + 698: VARIABLE "b", + 702: NEG, + 703: FUNC, + 704: EOL + , + 705: FUNC_REF "set", + 709: ARG, + 710: VARIABLE "@i", + 714: VARIABLE "b", + 718: LNOT, + 719: FUNC, + 720: EOL + , + 721: FUNC_REF "set", + 725: ARG, + 726: VARIABLE "@i", + 730: VARIABLE "b", + 734: NOT, + 735: FUNC, + 736: EOL + , + 737: FUNC_REF "set", + 741: ARG, + 742: VARIABLE "@i", + 746: VARIABLE "a", + 750: VARIABLE "b", + 754: R_SHIFT, + 755: FUNC, + 756: EOL + , + 757: FUNC_REF "set", + 761: ARG, + 762: VARIABLE "@i", + 766: VARIABLE "a", + 770: VARIABLE "b", + 774: L_SHIFT, + 775: FUNC, + 776: EOL + , + 777: NOP'''.replace(''' + ''', '\n') + + test_extra = (''' + #include "../compat/borrow.hpp" + #include "../io/line.hpp" + #include "../mmo/clif.t.hpp" + #include "../ast/script.hpp" + #include "../map/script-parse-internal.hpp" + + using tmwa::operator "" _s; + + static + const tmwa::map::ScriptBuffer& test_script_buffer(tmwa::LString source) + { + auto p = tmwa::map::add_strp("TEST_FAKE_PARAM_BASELEVEL"_s); + p->type = tmwa::map::StringCode::PARAM; + p->val = static_cast<uint16_t>(tmwa::SP::BASELEVEL); + p = tmwa::map::add_strp("TEST_FAKE_CONSTANT"_s); + p->type = tmwa::map::StringCode::INT; + p->val = 42; + + tmwa::io::LineCharReader lr(tmwa::io::from_string, "<script debug print test>"_s, source); + tmwa::ast::script::ScriptOptions opt; + opt.implicit_start = true; + opt.implicit_end = true; + auto code_res = tmwa::ast::script::parse_script_body(lr, opt); + auto code = TRY_UNWRAP(code_res.get_success(), abort()); + auto ups = tmwa::map::compile_script("script debug print test"_s, code, opt.implicit_end); + assert(ups); + return *ups.release(); + } + ''') + + # the pretty-printer is designed to be run with 'set print array on' + # but the testsuite runs with all settings default, in this case off. + tests = [ + ('test_script_buffer("' + src.replace('\\', '\\\\').replace('"', '\\"') + '"_s)', + '"script debug print test" = {' + asm.replace('\n', ' ').replace('EOL ,', 'EOL\n,') + '}'), + ] diff --git a/src/map/script-persist.hpp b/src/map/script-persist.hpp new file mode 100644 index 0000000..45c2761 --- /dev/null +++ b/src/map/script-persist.hpp @@ -0,0 +1,128 @@ +#pragma once +// script-persist.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include "../compat/borrow.hpp" + +#include "../strings/rstring.hpp" + +#include "../sexpr/variant.hpp" + +#include "../mmo/clif.t.hpp" + +#include "script-buffer.hpp" + + +namespace tmwa +{ +namespace map +{ +class SIR +{ + uint32_t impl; + SIR(SP v) + : impl(static_cast<uint32_t>(v)) + {} + SIR(unsigned v, uint8_t i) + : impl((i << 24) | v) + {} +public: + SIR() : impl() {} + + unsigned base() const { return impl & 0x00ffffff; } + uint8_t index() const { return impl >> 24; } + SIR iplus(uint8_t i) const { return SIR(base(), index() + i); } + static SIR from(unsigned v, uint8_t i=0) { return SIR(v, i); } + + SP sp() const { return static_cast<SP>(impl); } + static SIR from(SP v) { return SIR(v); } + + friend bool operator == (SIR l, SIR r) { return l.impl == r.impl; } + friend bool operator < (SIR l, SIR r) { return l.impl < r.impl; } +}; + +struct ScriptDataPos +{ + int numi; +}; +struct ScriptDataInt +{ + int numi; +}; +struct ScriptDataParam +{ + SIR reg; +}; +struct ScriptDataStr +{ + RString str; +}; +struct ScriptDataArg +{ + int numi; +}; +struct ScriptDataVariable +{ + SIR reg; +}; +struct ScriptDataRetInfo +{ + // Not a ScriptPointer - pos is stored in a separate slot, + // to avoid exploding the struct for everyone. + Borrowed<const ScriptBuffer> script; +}; +struct ScriptDataFuncRef +{ + int numi; +}; + +using ScriptDataVariantBase = Variant< + ScriptDataPos, + ScriptDataInt, + ScriptDataParam, + ScriptDataStr, + ScriptDataArg, + ScriptDataVariable, + ScriptDataRetInfo, + ScriptDataFuncRef +>; +struct script_data : ScriptDataVariantBase +{ + script_data() = delete; + // TODO see if I can delete the copy ctor/assign instead of defaulting + script_data(script_data&&) = default; + script_data(const script_data&) = default /*delete*/; + script_data& operator = (script_data&&) = default; + script_data& operator = (const script_data&) = default /*delete*/; + + script_data(ScriptDataPos v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataInt v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataParam v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataStr v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataArg v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataVariable v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataRetInfo v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataFuncRef v) : ScriptDataVariantBase(std::move(v)) {} +}; +} // namespace map +} // namespace tmwa diff --git a/src/map/script-persist.py b/src/map/script-persist.py new file mode 100644 index 0000000..3181344 --- /dev/null +++ b/src/map/script-persist.py @@ -0,0 +1,37 @@ +class script_data(object): + enabled = True + + test_extra = ''' + #include "../strings/literal.hpp" + using tmwa::operator "" _s; + + #include "../map/script-parse-internal.hpp" + + static + tmwa::Borrowed<const tmwa::map::ScriptBuffer> fake_script() + { + static + const std::vector<tmwa::map::ByteCode> buffer; + return tmwa::borrow(reinterpret_cast<const tmwa::map::ScriptBuffer&>(buffer)); + } + + ''' + + tests = [ + ('tmwa::map::script_data(tmwa::map::ScriptDataPos{42})', + '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataPos) = {numi = 42}}, <No data fields>}'), + ('tmwa::map::script_data(tmwa::map::ScriptDataInt{123})', + '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataInt) = {numi = 123}}, <No data fields>}'), + ('tmwa::map::script_data(tmwa::map::ScriptDataParam{tmwa::map::SIR()})', + '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataParam) = {reg = {impl = 0}}}, <No data fields>}'), + ('tmwa::map::script_data(tmwa::map::ScriptDataStr{"Hello"_s})', + '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataStr) = {str = "Hello"}}, <No data fields>}'), + ('tmwa::map::script_data(tmwa::map::ScriptDataArg{0})', + '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataArg) = {numi = 0}}, <No data fields>}'), + ('tmwa::map::script_data(tmwa::map::ScriptDataVariable{tmwa::map::SIR()})', + '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataVariable) = {reg = {impl = 0}}}, <No data fields>}'), + ('tmwa::map::script_data(tmwa::map::ScriptDataRetInfo{fake_script()})', + '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataRetInfo) = {script = <fake_script()::buffer>}}, <No data fields>}'), + ('tmwa::map::script_data(tmwa::map::ScriptDataFuncRef{404})', + '{<tmwa::sexpr::Variant<tmwa::map::ScriptDataPos, tmwa::map::ScriptDataInt, tmwa::map::ScriptDataParam, tmwa::map::ScriptDataStr, tmwa::map::ScriptDataArg, tmwa::map::ScriptDataVariable, tmwa::map::ScriptDataRetInfo, tmwa::map::ScriptDataFuncRef>> = {(tmwa::map::ScriptDataFuncRef) = {numi = 404}}, <No data fields>}'), + ] diff --git a/src/map/script-startup-internal.hpp b/src/map/script-startup-internal.hpp new file mode 100644 index 0000000..91ce09b --- /dev/null +++ b/src/map/script-startup-internal.hpp @@ -0,0 +1,36 @@ +#pragma once +// script-startup-internal.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "script-startup.hpp" +#include "fwd.hpp" + +#include "script-persist.hpp" + + +namespace tmwa +{ +namespace map +{ +void mapreg_setreg(SIR reg, int val); +void mapreg_setregstr(SIR reg, XString str); +} // namespace map +} // namespace tmwa diff --git a/src/map/script-startup.cpp b/src/map/script-startup.cpp new file mode 100644 index 0000000..ad809db --- /dev/null +++ b/src/map/script-startup.cpp @@ -0,0 +1,265 @@ +#include "script-startup-internal.hpp" +// script-startup.cpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011 Chuck Miller +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © 2013 wushin +// +// 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 "../strings/zstring.hpp" + +#include "../generic/db.hpp" +#include "../generic/intern-pool.hpp" + +#include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" +#include "../io/read.hpp" +#include "../io/lock.hpp" + +#include "../net/timer.hpp" + +#include "globals.hpp" +#include "map.hpp" +#include "map_conf.hpp" +#include "script-parse-internal.hpp" +#include "script-persist.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +namespace map +{ +constexpr std::chrono::milliseconds MAPREG_AUTOSAVE_INTERVAL = 10_s; + +bool read_constdb(ZString filename) +{ + io::ReadFile in(filename); + if (!in.is_open()) + { + PRINTF("can't read %s\n"_fmt, filename); + return false; + } + + bool rv = true; + AString line_; + while (in.getline(line_)) + { + // is_comment only works for whole-line comments + // that could change once the Z dependency is dropped ... + LString comment = "//"_s; + XString line = line_.xislice_h(std::search(line_.begin(), line_.end(), comment.begin(), comment.end())).rstrip(); + if (!line) + continue; + // "%m[A-Za-z0-9_] %i %i" + + // TODO promote either qsplit() or asplit() + auto _it = std::find(line.begin(), line.end(), ' '); + auto name = line.xislice_h(_it); + auto _rest = line.xislice_t(_it); + while (_rest.startswith(' ')) + _rest = _rest.xslice_t(1); + auto _it2 = std::find(_rest.begin(), _rest.end(), ' '); + auto val_ = _rest.xislice_h(_it2); + auto type_ = _rest.xislice_t(_it2); + while (type_.startswith(' ')) + type_ = type_.xslice_t(1); + // yes, the above actually DTRT even for underlength input + + int val; + int type = 0; + // Note for future archeaologists: this code is indented correctly + if (std::find_if_not(name.begin(), name.end(), + [](char c) + { + return ('0' <= c && c <= '9') + || ('A' <= c && c <= 'Z') + || ('a' <= c && c <= 'z') + || (c == '_'); + }) != name.end() + || !extract(val_, &val) + || (!extract(type_, &type) && type_)) + { + PRINTF("Bad const line: %s\n"_fmt, line_); + rv = false; + continue; + } + P<str_data_t> n = add_strp(name); + n->type = type ? StringCode::PARAM : StringCode::INT; + n->val = val; + } + return rv; +} + +/*========================================== + * マップ変数の変更 + *------------------------------------------ + */ +void mapreg_setreg(SIR reg, int val) +{ + mapreg_db.put(reg, val); + + mapreg_dirty = 1; +} + +/*========================================== + * 文字列型マップ変数の変更 + *------------------------------------------ + */ +void mapreg_setregstr(SIR reg, XString str) +{ + if (!str) + mapregstr_db.erase(reg); + else + mapregstr_db.insert(reg, str); + + mapreg_dirty = 1; +} + +/*========================================== + * 永続的マップ変数の読み込み + *------------------------------------------ + */ +static +void script_load_mapreg(void) +{ + io::ReadFile in(map_conf.mapreg_txt); + + if (!in.is_open()) + return; + + AString line; + while (in.getline(line)) + { + XString buf1, buf2; + int index = 0; + if (extract(line, + record<'\t'>( + record<','>(&buf1, &index), + &buf2)) + || extract(line, + record<'\t'>( + record<','>(&buf1), + &buf2))) + { + int s = variable_names.intern(buf1); + SIR key = SIR::from(s, index); + if (buf1.back() == '$') + { + mapregstr_db.insert(key, buf2); + } + else + { + int v; + if (!extract(buf2, &v)) + goto borken; + mapreg_db.put(key, v); + } + } + else + { + borken: + PRINTF("%s: %s broken data !\n"_fmt, map_conf.mapreg_txt, AString(buf1)); + continue; + } + } + mapreg_dirty = 0; +} + +/*========================================== + * 永続的マップ変数の書き込み + *------------------------------------------ + */ +static +void script_save_mapreg_intsub(SIR key, int data, io::WriteFile& fp) +{ + int num = key.base(), i = key.index(); + ZString name = variable_names.outtern(num); + if (name[1] != '@') + { + if (i == 0) + FPRINTF(fp, "%s\t%d\n"_fmt, name, data); + else + FPRINTF(fp, "%s,%d\t%d\n"_fmt, name, i, data); + } +} + +static +void script_save_mapreg_strsub(SIR key, ZString data, io::WriteFile& fp) +{ + int num = key.base(), i = key.index(); + ZString name = variable_names.outtern(num); + if (name[1] != '@') + { + if (i == 0) + FPRINTF(fp, "%s\t%s\n"_fmt, name, data); + else + FPRINTF(fp, "%s,%d\t%s\n"_fmt, name, i, data); + } +} + +static +void script_save_mapreg(void) +{ + io::WriteLock fp(map_conf.mapreg_txt); + if (!fp.is_open()) + return; + for (auto& pair : mapreg_db) + script_save_mapreg_intsub(pair.first, pair.second, fp); + for (auto& pair : mapregstr_db) + script_save_mapreg_strsub(pair.first, pair.second, fp); + mapreg_dirty = 0; +} + +static +void script_autosave_mapreg(TimerData *, tick_t) +{ + if (mapreg_dirty) + script_save_mapreg(); +} + +void do_final_script(void) +{ + if (mapreg_dirty >= 0) + script_save_mapreg(); + + mapreg_db.clear(); + mapregstr_db.clear(); + scriptlabel_db.clear(); + userfunc_db.clear(); + + str_datam.clear(); +} + +/*========================================== + * 初期化 + *------------------------------------------ + */ +void do_init_script(void) +{ + script_load_mapreg(); + + Timer(gettick() + MAPREG_AUTOSAVE_INTERVAL, + script_autosave_mapreg, + MAPREG_AUTOSAVE_INTERVAL + ).detach(); +} +} // namespace map +} // namespace tmwa diff --git a/src/compat/memory.cpp b/src/map/script-startup.hpp index f9f2c22..1691c94 100644 --- a/src/compat/memory.cpp +++ b/src/map/script-startup.hpp @@ -1,7 +1,9 @@ -#include "memory.hpp" -// memory.cpp - I forget ... +#pragma once +// script-startup.hpp - EAthena script frontend, engine, and library. // -// Copyright © 2013-2014 Ben Longbons <b.r.longbons@gmail.com> +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> // // This file is part of The Mana World (Athena server) // @@ -18,9 +20,15 @@ // 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 "../poison.hpp" - +#include "fwd.hpp" namespace tmwa { +namespace map +{ +void do_init_script(void); +void do_final_script(void); + +bool read_constdb(ZString filename); +} // namespace map } // namespace tmwa diff --git a/src/map/script.cpp b/src/map/script.cpp deleted file mode 100644 index 40dfc0e..0000000 --- a/src/map/script.cpp +++ /dev/null @@ -1,5100 +0,0 @@ -#include "script.hpp" -// script.cpp - EAthena script frontend, engine, and library. -// -// Copyright © ????-2004 Athena Dev Teams -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011 Chuck Miller -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// Copyright © 2013 wushin -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include <cassert> -#include <cmath> -#include <cstdlib> -#include <ctime> - -#include <algorithm> -#include <set> - -#include "../compat/fun.hpp" - -#include "../strings/mstring.hpp" -#include "../strings/rstring.hpp" -#include "../strings/astring.hpp" -#include "../strings/zstring.hpp" -#include "../strings/xstring.hpp" -#include "../strings/literal.hpp" - -#include "../generic/db.hpp" -#include "../generic/intern-pool.hpp" -#include "../generic/random.hpp" - -#include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" -#include "../io/lock.hpp" -#include "../io/read.hpp" -#include "../io/write.hpp" - -#include "../net/socket.hpp" -#include "../net/timer.hpp" - -#include "../mmo/core.hpp" -#include "../mmo/extract.hpp" -#include "../mmo/human_time_diff.hpp" -#include "../mmo/utils.hpp" - -#include "atcommand.hpp" -#include "battle.hpp" -#include "chrif.hpp" -#include "clif.hpp" -#include "intif.hpp" -#include "itemdb.hpp" -#include "magic-interpreter-base.hpp" -#include "map.hpp" -#include "mob.hpp" -#include "npc.hpp" -#include "party.hpp" -#include "pc.hpp" -#include "skill.hpp" -#include "storage.hpp" - -#include "../poison.hpp" - - -namespace tmwa -{ -constexpr bool DEBUG_DISP = false; -constexpr bool DEBUG_RUN = false; - -struct str_data_t -{ - StringCode type; - RString strs; - int backpatch; - int label_; - int val; -}; -static -Map<RString, str_data_t> str_datam; -static -str_data_t LABEL_NEXTLINE_; - -static -DMap<SIR, int> mapreg_db; -static -Map<SIR, RString> mapregstr_db; -static -int mapreg_dirty = -1; -AString mapreg_txt = "save/mapreg.txt"_s; -constexpr std::chrono::milliseconds MAPREG_AUTOSAVE_INTERVAL = 10_s; - -Map<ScriptLabel, int> scriptlabel_db; -static -std::set<ScriptLabel> probable_labels; -UPMap<RString, const ScriptBuffer> userfunc_db; - -static -Array<LString, 11> pos_str //= -{{ - "Head"_s, - "Body"_s, - "Left hand"_s, - "Right hand"_s, - "Robe"_s, - "Shoes"_s, - "Accessory 1"_s, - "Accessory 2"_s, - "Head 2"_s, - "Head 3"_s, - "Not Equipped"_s, -}}; - -static -struct Script_Config -{ - static const - int warn_func_no_comma = 1; - static const - int warn_cmd_no_comma = 1; - static const - int warn_func_mismatch_paramnum = 1; - static const - int warn_cmd_mismatch_paramnum = 1; - static const - int check_cmdcount = 8192; - static const - int check_gotocount = 512; -} script_config; - -static -int parse_cmd_if = 0; -static -str_data_t *parse_cmdp; - -static -void run_func(ScriptState *st); - -static -void mapreg_setreg(SIR num, int val); -static -void mapreg_setregstr(SIR num, XString str); - -struct BuiltinFunction -{ - void (*func)(ScriptState *); - LString name; - LString arg; - char ret; -}; -// defined later -extern BuiltinFunction builtin_functions[]; - -static -InternPool variable_names; - - -template<class D> -bool first_type_is_any() -{ - return false; -} - -template<class D, class F, class... R> -constexpr -bool first_type_is_any() -{ - return std::is_same<D, F>::value || first_type_is_any<D, R...>(); -} - - -static -str_data_t *search_strp(XString p) -{ - return str_datam.search(p); -} - -static -str_data_t *add_strp(XString p) -{ - if (str_data_t *rv = search_strp(p)) - return rv; - - RString p2 = p; - str_data_t *datum = str_datam.init(p2); - datum->type = StringCode::NOP; - datum->strs = p2; - datum->backpatch = -1; - datum->label_ = -1; - return datum; -} - -/*========================================== - * スクリプトバッファに1バイト書き込む - *------------------------------------------ - */ -void ScriptBuffer::add_scriptc(ByteCode a) -{ - script_buf.push_back(a); -} - -/*========================================== - * スクリプトバッファにデータタイプを書き込む - *------------------------------------------ - */ -void ScriptBuffer::add_scriptb(uint8_t a) -{ - add_scriptc(static_cast<ByteCode>(a)); -} - -/*========================================== - * スクリプトバッファに整数を書き込む - *------------------------------------------ - */ -void ScriptBuffer::add_scripti(uint32_t a) -{ - while (a >= 0x40) - { - add_scriptb(a | 0xc0); - a = (a - 0x40) >> 6; - } - add_scriptb(a | 0x80); -} - -/*========================================== - * スクリプトバッファにラベル/変数/関数を書き込む - *------------------------------------------ - */ -// 最大16Mまで -void ScriptBuffer::add_scriptl(str_data_t *ld) -{ - int backpatch = ld->backpatch; - - switch (ld->type) - { - case StringCode::POS: - add_scriptc(ByteCode::POS); - add_scriptb(static_cast<uint8_t>(ld->label_)); - add_scriptb(static_cast<uint8_t>(ld->label_ >> 8)); - add_scriptb(static_cast<uint8_t>(ld->label_ >> 16)); - break; - case StringCode::NOP: - // need to set backpatch, because it might become a label later - add_scriptc(ByteCode::VARIABLE); - ld->backpatch = script_buf.size(); - add_scriptb(static_cast<uint8_t>(backpatch)); - add_scriptb(static_cast<uint8_t>(backpatch >> 8)); - add_scriptb(static_cast<uint8_t>(backpatch >> 16)); - break; - case StringCode::INT: - add_scripti(ld->val); - break; - case StringCode::FUNC: - add_scriptc(ByteCode::FUNC_REF); - add_scriptb(static_cast<uint8_t>(ld->val)); - add_scriptb(static_cast<uint8_t>(ld->val >> 8)); - add_scriptb(static_cast<uint8_t>(ld->val >> 16)); - break; - case StringCode::PARAM: - add_scriptc(ByteCode::PARAM); - add_scriptb(static_cast<uint8_t>(ld->val)); - add_scriptb(static_cast<uint8_t>(ld->val >> 8)); - add_scriptb(static_cast<uint8_t>(ld->val >> 16)); - break; - default: - abort(); - } -} - -/*========================================== - * ラベルを解決する - *------------------------------------------ - */ -void ScriptBuffer::set_label(str_data_t *ld, int pos_) -{ - int next; - - ld->type = StringCode::POS; - ld->label_ = pos_; - for (int i = ld->backpatch; i >= 0 && i != 0x00ffffff; i = next) - { - next = 0; - // woot! no longer endian-dependent! - next |= static_cast<uint8_t>(script_buf[i + 0]) << 0; - next |= static_cast<uint8_t>(script_buf[i + 1]) << 8; - next |= static_cast<uint8_t>(script_buf[i + 2]) << 16; - script_buf[i - 1] = ByteCode::POS; - script_buf[i] = static_cast<ByteCode>(pos_); - script_buf[i + 1] = static_cast<ByteCode>(pos_ >> 8); - script_buf[i + 2] = static_cast<ByteCode>(pos_ >> 16); - } -} - -/*========================================== - * スペース/コメント読み飛ばし - *------------------------------------------ - */ -static -ZString::iterator skip_space(ZString::iterator p) -{ - while (1) - { - while (isspace(*p)) - p++; - if (p[0] == '/' && p[1] == '/') - { - while (*p && *p != '\n') - p++; - } - else if (p[0] == '/' && p[1] == '*') - { - p++; - while (*p && (p[-1] != '*' || p[0] != '/')) - p++; - if (*p) - p++; - } - else - break; - } - return p; -} - -/*========================================== - * 1単語スキップ - *------------------------------------------ - */ -static -ZString::iterator skip_word(ZString::iterator p) -{ - // prefix - if (*p == '$') - p++; // MAP鯖内共有変数用 - if (*p == '@') - p++; // 一時的変数用(like weiss) - if (*p == '#') - p++; // account変数用 - if (*p == '#') - p++; // ワールドaccount変数用 - - while (isalnum(*p) || *p == '_') - p++; - - // postfix - if (*p == '$') - p++; // 文字列変数 - - return p; -} - -// TODO: replace this whole mess with some sort of input stream that works -// a line at a time. -static -ZString startptr; -static -int startline; - -int script_errors = 0; -/*========================================== - * エラーメッセージ出力 - *------------------------------------------ - */ -static -void disp_error_message(ZString mes, ZString::iterator pos_) -{ - script_errors++; - - assert (startptr.begin() <= pos_ && pos_ <= startptr.end()); - - int line; - ZString::iterator p; - - for (line = startline, p = startptr.begin(); p != startptr.end(); line++) - { - ZString::iterator linestart = p; - ZString::iterator lineend = std::find(p, startptr.end(), '\n'); - if (pos_ < lineend) - { - PRINTF("\n%s\nline %d : "_fmt, mes, line); - for (int i = 0; linestart + i != lineend; i++) - { - if (linestart + i != pos_) - PRINTF("%c"_fmt, linestart[i]); - else - PRINTF("\'%c\'"_fmt, linestart[i]); - } - PRINTF("\a\n"_fmt); - return; - } - p = lineend + 1; - } -} - -/*========================================== - * 項の解析 - *------------------------------------------ - */ -ZString::iterator ScriptBuffer::parse_simpleexpr(ZString::iterator p) -{ - p = skip_space(p); - - if (*p == ';' || *p == ',') - { - disp_error_message("unexpected expr end"_s, p); - exit(1); - } - if (*p == '(') - { - - p = parse_subexpr(p + 1, -1); - p = skip_space(p); - if ((*p++) != ')') - { - disp_error_message("unmatch ')'"_s, p); - exit(1); - } - } - else if (isdigit(*p) || ((*p == '-' || *p == '+') && isdigit(p[1]))) - { - char *np; - int i = strtoul(&*p, &np, 0); - add_scripti(i); - p += np - &*p; - } - else if (*p == '"') - { - add_scriptc(ByteCode::STR); - p++; - while (*p && *p != '"') - { - if (*p == '\\') - p++; - else if (*p == '\n') - { - disp_error_message("unexpected newline @ string"_s, p); - exit(1); - } - add_scriptb(*p++); - } - if (!*p) - { - disp_error_message("unexpected eof @ string"_s, p); - exit(1); - } - add_scriptb(0); - p++; //'"' - } - else - { - // label , register , function etc - ZString::iterator p2 = skip_word(p); - if (p2 == p) - { - disp_error_message("unexpected character"_s, p); - exit(1); - } - XString word(&*p, &*p2, nullptr); - if (word.startswith("On"_s) || word.startswith("L_"_s) || word.startswith("S_"_s)) - probable_labels.insert(stringish<ScriptLabel>(word)); - if (parse_cmd_if && (word == "callsub"_s || word == "callfunc"_s || word == "return"_s)) - { - disp_error_message("Sorry, callsub/callfunc/return have never worked properly in an if statement."_s, p); - } - str_data_t *ld = add_strp(word); - - parse_cmdp = ld; // warn_*_mismatch_paramnumのために必要 - // why not just check l->str == "if"_s or std::string(p, p2) == "if"_s? - if (ld == search_strp("if"_s)) // warn_cmd_no_commaのために必要 - parse_cmd_if++; - p = p2; - - if (ld->type != StringCode::FUNC && *p == '[') - { - // array(name[i] => getelementofarray(name,i) ) - add_scriptl(search_strp("getelementofarray"_s)); - add_scriptc(ByteCode::ARG); - add_scriptl(ld); - p = parse_subexpr(p + 1, -1); - p = skip_space(p); - if (*p != ']') - { - disp_error_message("unmatch ']'"_s, p); - exit(1); - } - p++; - add_scriptc(ByteCode::FUNC); - } - else - add_scriptl(ld); - - } - - return p; -} - -/*========================================== - * 式の解析 - *------------------------------------------ - */ -ZString::iterator ScriptBuffer::parse_subexpr(ZString::iterator p, int limit) -{ - ByteCode op; - int opl, len; - - p = skip_space(p); - - if (*p == '-') - { - ZString::iterator tmpp = skip_space(p + 1); - if (*tmpp == ';' || *tmpp == ',') - { - --script_errors; disp_error_message("deprecated: implicit 'next statement' label"_s, p); - add_scriptl(&LABEL_NEXTLINE_); - p++; - return p; - } - } - ZString::iterator tmpp = p; - if ((op = ByteCode::NEG, *p == '-') || (op = ByteCode::LNOT, *p == '!') - || (op = ByteCode::NOT, *p == '~')) - { - p = parse_subexpr(p + 1, 100); - add_scriptc(op); - } - else - p = parse_simpleexpr(p); - p = skip_space(p); - while (((op = ByteCode::ADD, opl = 6, len = 1, *p == '+') || - (op = ByteCode::SUB, opl = 6, len = 1, *p == '-') || - (op = ByteCode::MUL, opl = 7, len = 1, *p == '*') || - (op = ByteCode::DIV, opl = 7, len = 1, *p == '/') || - (op = ByteCode::MOD, opl = 7, len = 1, *p == '%') || - (op = ByteCode::FUNC, opl = 8, len = 1, *p == '(') || - (op = ByteCode::LAND, opl = 1, len = 2, *p == '&' && p[1] == '&') || - (op = ByteCode::AND, opl = 5, len = 1, *p == '&') || - (op = ByteCode::LOR, opl = 0, len = 2, *p == '|' && p[1] == '|') || - (op = ByteCode::OR, opl = 4, len = 1, *p == '|') || - (op = ByteCode::XOR, opl = 3, len = 1, *p == '^') || - (op = ByteCode::EQ, opl = 2, len = 2, *p == '=' && p[1] == '=') || - (op = ByteCode::NE, opl = 2, len = 2, *p == '!' && p[1] == '=') || - (op = ByteCode::R_SHIFT, opl = 5, len = 2, *p == '>' && p[1] == '>') || - (op = ByteCode::GE, opl = 2, len = 2, *p == '>' && p[1] == '=') || - (op = ByteCode::GT, opl = 2, len = 1, *p == '>') || - (op = ByteCode::L_SHIFT, opl = 5, len = 2, *p == '<' && p[1] == '<') || - (op = ByteCode::LE, opl = 2, len = 2, *p == '<' && p[1] == '=') || - (op = ByteCode::LT, opl = 2, len = 1, *p == '<')) && opl > limit) - { - p += len; - if (op == ByteCode::FUNC) - { - int i = 0; - str_data_t *funcp = parse_cmdp; - ZString::iterator plist[128]; - - if (funcp->type != StringCode::FUNC) - { - disp_error_message("expect function"_s, tmpp); - exit(0); - } - - add_scriptc(ByteCode::ARG); - while (*p && *p != ')' && i < 128) - { - plist[i] = p; - p = parse_subexpr(p, -1); - p = skip_space(p); - if (*p == ',') - p++; - else if (*p != ')' && script_config.warn_func_no_comma) - { - disp_error_message("expect ',' or ')' at func params"_s, - p); - } - p = skip_space(p); - i++; - } - plist[i] = p; - if (*p != ')') - { - disp_error_message("func request '(' ')'"_s, p); - exit(1); - } - p++; - - if (funcp->type == StringCode::FUNC - && script_config.warn_func_mismatch_paramnum) - { - ZString arg = builtin_functions[funcp->val].arg; - int j = 0; - // TODO handle ? and multiple * correctly - for (j = 0; arg[j]; j++) - if (arg[j] == '*' || arg[j] == '?') - break; - if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j)) - { - disp_error_message("illegal number of parameters"_s, - plist[std::min(i, j)]); - } - if (!builtin_functions[funcp->val].ret) - { - disp_error_message("statement in function context"_s, tmpp); - } - } - } - else // not op == ByteCode::FUNC - { - p = parse_subexpr(p, opl); - } - add_scriptc(op); - p = skip_space(p); - } - return p; /* return first untreated operator */ -} - -/*========================================== - * 式の評価 - *------------------------------------------ - */ -ZString::iterator ScriptBuffer::parse_expr(ZString::iterator p) -{ - switch (*p) - { - case ')': - case ';': - case ':': - case '[': - case ']': - case '}': - disp_error_message("unexpected char"_s, p); - exit(1); - } - p = parse_subexpr(p, -1); - return p; -} - -/*========================================== - * 行の解析 - *------------------------------------------ - */ -ZString::iterator ScriptBuffer::parse_line(ZString::iterator p, bool *can_step) -{ - int i = 0; - ZString::iterator plist[128]; - - p = skip_space(p); - if (*p == ';') - return p; - - parse_cmd_if = 0; // warn_cmd_no_commaのために必要 - - // 最初は関数名 - ZString::iterator p2 = p; - p = parse_simpleexpr(p); - p = skip_space(p); - - str_data_t *cmd = parse_cmdp; - if (cmd->type != StringCode::FUNC) - { - disp_error_message("expect command"_s, p2); - } - - { - // TODO should be LString, but no heterogenous lookup yet - static - std::set<ZString> terminators = - { - "goto"_s, - "return"_s, - "close"_s, - "menu"_s, - "end"_s, - "mapexit"_s, - "shop"_s, - }; - *can_step = terminators.count(cmd->strs) == 0; - } - - add_scriptc(ByteCode::ARG); - while (*p && *p != ';' && i < 128) - { - plist[i] = p; - - p = parse_expr(p); - p = skip_space(p); - // 引数区切りの,処理 - if (*p == ',') - p++; - else if (*p != ';' && script_config.warn_cmd_no_comma - && parse_cmd_if * 2 <= i) - { - disp_error_message("expect ',' or ';' at cmd params"_s, p); - } - p = skip_space(p); - i++; - } - plist[i] = p; - if (*(p++) != ';') - { - disp_error_message("need ';'"_s, p); - exit(1); - } - add_scriptc(ByteCode::FUNC); - - if (cmd->type == StringCode::FUNC - && script_config.warn_cmd_mismatch_paramnum) - { - ZString arg = builtin_functions[cmd->val].arg; - int j = 0; - // TODO see above - for (j = 0; arg[j]; j++) - if (arg[j] == '*' || arg[j] == '?') - break; - if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j)) - { - disp_error_message("illegal number of parameters"_s, - plist[std::min(i, j)]); - } - if (builtin_functions[cmd->val].ret) - { - disp_error_message("function in statement context"_s, p2); - } - } - - return p; -} - -/*========================================== - * 組み込み関数の追加 - *------------------------------------------ - */ -static -void add_builtin_functions(void) -{ - for (int i = 0; builtin_functions[i].func; i++) - { - str_data_t *n = add_strp(builtin_functions[i].name); - n->type = StringCode::FUNC; - n->val = i; - } -} - -bool read_constdb(ZString filename) -{ - io::ReadFile in(filename); - if (!in.is_open()) - { - PRINTF("can't read %s\n"_fmt, filename); - return false; - } - - bool rv = true; - AString line_; - while (in.getline(line_)) - { - // is_comment only works for whole-line comments - // that could change once the Z dependency is dropped ... - LString comment = "//"_s; - XString line = line_.xislice_h(std::search(line_.begin(), line_.end(), comment.begin(), comment.end())).rstrip(); - if (!line) - continue; - // "%m[A-Za-z0-9_] %i %i" - - // TODO promote either qsplit() or asplit() - auto _it = std::find(line.begin(), line.end(), ' '); - auto name = line.xislice_h(_it); - auto _rest = line.xislice_t(_it); - while (_rest.startswith(' ')) - _rest = _rest.xslice_t(1); - auto _it2 = std::find(_rest.begin(), _rest.end(), ' '); - auto val_ = _rest.xislice_h(_it2); - auto type_ = _rest.xislice_t(_it2); - while (type_.startswith(' ')) - type_ = type_.xslice_t(1); - // yes, the above actually DTRT even for underlength input - - int val; - int type = 0; - // Note for future archeaologists: this code is indented correctly - if (std::find_if_not(name.begin(), name.end(), - [](char c) - { - return ('0' <= c && c <= '9') - || ('A' <= c && c <= 'Z') - || ('a' <= c && c <= 'z') - || (c == '_'); - }) != name.end() - || !extract(val_, &val) - || (!extract(type_, &type) && type_)) - { - PRINTF("Bad const line: %s\n"_fmt, line_); - rv = false; - continue; - } - str_data_t *n = add_strp(name); - n->type = type ? StringCode::PARAM : StringCode::INT; - n->val = val; - } - return rv; -} - -std::unique_ptr<const ScriptBuffer> parse_script(ZString src, int line, bool implicit_end) -{ - auto script_buf = make_unique<ScriptBuffer>(); - script_buf->parse_script(src, line, implicit_end); - return std::move(script_buf); -} - -/*========================================== - * スクリプトの解析 - *------------------------------------------ - */ -void ScriptBuffer::parse_script(ZString src, int line, bool implicit_end) -{ - static int first = 1; - - if (first) - { - add_builtin_functions(); - } - first = 0; - LABEL_NEXTLINE_.type = StringCode::NOP; - LABEL_NEXTLINE_.backpatch = -1; - LABEL_NEXTLINE_.label_ = -1; - for (auto& pair : str_datam) - { - str_data_t& dit = pair.second; - if (dit.type == StringCode::POS || dit.type == StringCode::VARIABLE) - { - dit.type = StringCode::NOP; - dit.backpatch = -1; - dit.label_ = -1; - } - } - - // 外部用label dbの初期化 - scriptlabel_db.clear(); - - // for error message - startptr = src; - startline = line; - - bool can_step = true; - - ZString::iterator p = src.begin(); - p = skip_space(p); - if (*p != '{') - { - disp_error_message("not found '{'"_s, p); - abort(); - } - for (p++; *p && *p != '}';) - { - p = skip_space(p); - if (*skip_space(skip_word(p)) == ':') - { - if (can_step) - { - --script_errors; disp_error_message("deprecated: implicit fallthrough"_s, p); - } - can_step = true; - - ZString::iterator tmpp = skip_word(p); - XString str(&*p, &*tmpp, nullptr); - str_data_t *ld = add_strp(str); - bool e1 = ld->type != StringCode::NOP; - bool e2 = ld->type == StringCode::POS; - bool e3 = ld->label_ != -1; - assert (e1 == e2 && e2 == e3); - if (e3) - { - disp_error_message("dup label "_s, p); - exit(1); - } - set_label(ld, script_buf.size()); - scriptlabel_db.insert(stringish<ScriptLabel>(str), script_buf.size()); - p = tmpp + 1; - continue; - } - - if (!can_step) - { - --script_errors; disp_error_message("deprecated: unreachable statement"_s, p); - } - // 他は全部一緒くた - p = parse_line(p, &can_step); - p = skip_space(p); - add_scriptc(ByteCode::EOL); - - set_label(&LABEL_NEXTLINE_, script_buf.size()); - LABEL_NEXTLINE_.type = StringCode::NOP; - LABEL_NEXTLINE_.backpatch = -1; - LABEL_NEXTLINE_.label_ = -1; - } - - if (can_step && !implicit_end) - { - --script_errors; disp_error_message("deprecated: implicit end"_s, p); - } - add_scriptc(ByteCode::NOP); - - // resolve the unknown labels - for (auto& pair : str_datam) - { - str_data_t& sit = pair.second; - if (sit.type == StringCode::NOP) - { - sit.type = StringCode::VARIABLE; - sit.label_ = 0; // anything but -1. Shouldn't matter, but helps asserts. - size_t pool_index = variable_names.intern(sit.strs); - for (int next, j = sit.backpatch; j >= 0 && j != 0x00ffffff; j = next) - { - next = 0; - next |= static_cast<uint8_t>(script_buf[j + 0]) << 0; - next |= static_cast<uint8_t>(script_buf[j + 1]) << 8; - next |= static_cast<uint8_t>(script_buf[j + 2]) << 16; - script_buf[j] = static_cast<ByteCode>(pool_index); - script_buf[j + 1] = static_cast<ByteCode>(pool_index >> 8); - script_buf[j + 2] = static_cast<ByteCode>(pool_index >> 16); - } - } - } - - for (const auto& pair : scriptlabel_db) - { - ScriptLabel key = pair.first; - if (key.startswith("On"_s)) - continue; - if (!(key.startswith("L_"_s) || key.startswith("S_"_s))) - PRINTF("Warning: ugly label: %s\n"_fmt, key); - else if (!probable_labels.count(key)) - PRINTF("Warning: unused label: %s\n"_fmt, key); - } - for (ScriptLabel used : probable_labels) - { - if (!scriptlabel_db.search(used)) - PRINTF("Warning: no such label: %s\n"_fmt, used); - } - probable_labels.clear(); - - if (!DEBUG_DISP) - return; - for (size_t i = 0; i < script_buf.size(); i++) - { - if ((i & 15) == 0) - PRINTF("%04zx : "_fmt, i); - PRINTF("%02x "_fmt, script_buf[i]); - if ((i & 15) == 15) - PRINTF("\n"_fmt); - } - PRINTF("\n"_fmt); -} - -// -// 実行系 -// -enum class ScriptEndState -{ - ZERO, - STOP, - END, - RERUNLINE, - GOTO, - RETFUNC, -}; - -/*========================================== - * ridからsdへの解決 - *------------------------------------------ - */ -static -dumb_ptr<map_session_data> script_rid2sd(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = map_id2sd(st->rid); - if (!sd) - { - PRINTF("script_rid2sd: fatal error ! player not attached!\n"_fmt); - } - return sd; -} - -/*========================================== - * 変数の読み取り - *------------------------------------------ - */ -static -void get_val(dumb_ptr<map_session_data> sd, struct script_data *data) -{ - MATCH (*data) - { - CASE (const ScriptDataParam&, u) - { - if (sd == nullptr) - PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp()); - int numi = 0; - if (sd) - numi = pc_readparam(sd, u.reg.sp()); - *data = ScriptDataInt{numi}; - } - CASE (const ScriptDataVariable&, u) - { - ZString name_ = variable_names.outtern(u.reg.base()); - VarName name = stringish<VarName>(name_); - char prefix = name.front(); - char postfix = name.back(); - - if (prefix != '$') - { - if (sd == nullptr) - PRINTF("get_val error name?:%s\n"_fmt, name); - } - if (postfix == '$') - { - RString str; - if (prefix == '@') - { - if (sd) - str = pc_readregstr(sd, u.reg); - } - else if (prefix == '$') - { - RString *s = mapregstr_db.search(u.reg); - if (s) - str = *s; - } - else - { - PRINTF("script: get_val: illegal scope string variable.\n"_fmt); - str = "!!ERROR!!"_s; - } - *data = ScriptDataStr{str}; - } - else - { - int numi = 0; - if (prefix == '@') - { - if (sd) - numi = pc_readreg(sd, u.reg); - } - else if (prefix == '$') - { - numi = mapreg_db.get(u.reg); - } - else if (prefix == '#') - { - if (name[1] == '#') - { - if (sd) - numi = pc_readaccountreg2(sd, name); - } - else - { - if (sd) - numi = pc_readaccountreg(sd, name); - } - } - else - { - if (sd) - numi = pc_readglobalreg(sd, name); - } - *data = ScriptDataInt{numi}; - } - } - } -} - -static __attribute__((deprecated)) -void get_val(ScriptState *st, struct script_data *data) -{ - dumb_ptr<map_session_data> sd = st->rid ? map_id2sd(st->rid) : nullptr; - get_val(sd, data); -} - -/*========================================== - * 変数の読み取り2 - *------------------------------------------ - */ -static -struct script_data get_val2(ScriptState *st, SIR reg) -{ - struct script_data dat = ScriptDataVariable{reg}; - get_val(st, &dat); - return dat; -} - -/*========================================== - * 変数設定用 - *------------------------------------------ - */ -static -void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd) -{ - if (type == VariableCode::PARAM) - { - int val = vd.get_if<ScriptDataInt>()->numi; - pc_setparam(sd, reg.sp(), val); - return; - } - assert (type == VariableCode::VARIABLE); - - ZString name_ = variable_names.outtern(reg.base()); - VarName name = stringish<VarName>(name_); - char prefix = name.front(); - char postfix = name.back(); - - if (postfix == '$') - { - RString str = vd.get_if<ScriptDataStr>()->str; - if (prefix == '@') - { - pc_setregstr(sd, reg, str); - } - else if (prefix == '$') - { - mapreg_setregstr(reg, str); - } - else - { - PRINTF("script: set_reg: illegal scope string variable !"_fmt); - } - } - else - { - int val = vd.get_if<ScriptDataInt>()->numi; - if (prefix == '@') - { - pc_setreg(sd, reg, val); - } - else if (prefix == '$') - { - mapreg_setreg(reg, val); - } - else if (prefix == '#') - { - if (name[1] == '#') - pc_setaccountreg2(sd, name, val); - else - pc_setaccountreg(sd, name, val); - } - else - { - pc_setglobalreg(sd, name, val); - } - } -} - -static -void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id) -{ - struct script_data vd = ScriptDataInt{id}; - set_reg(sd, type, reg, vd); -} - -static -void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd) -{ - struct script_data vd = ScriptDataStr{zd}; - set_reg(sd, type, reg, vd); -} - -/*========================================== - * 文字列への変換 - *------------------------------------------ - */ -static __attribute__((warn_unused_result)) -RString conv_str(ScriptState *st, struct script_data *data) -{ - get_val(st, data); - assert (!data->is<ScriptDataRetInfo>()); - if (auto *u = data->get_if<ScriptDataInt>()) - { - AString buf = STRPRINTF("%d"_fmt, u->numi); - *data = ScriptDataStr{buf}; - } - return data->get_if<ScriptDataStr>()->str; -} - -/*========================================== - * 数値へ変換 - *------------------------------------------ - */ -static __attribute__((warn_unused_result)) -int conv_num(ScriptState *st, struct script_data *data) -{ - int rv = 0; - get_val(st, data); - assert (!data->is<ScriptDataRetInfo>()); - MATCH (*data) - { - default: - abort(); - CASE (const ScriptDataStr&, u) - { - RString p = u.str; - rv = atoi(p.c_str()); - } - CASE (const ScriptDataInt&, u) - { - return u.numi; - } - CASE (const ScriptDataPos&, u) - { - return u.numi; - } - } - *data = ScriptDataInt{rv}; - return rv; -} - -static __attribute__((warn_unused_result)) -const ScriptBuffer *conv_script(ScriptState *st, struct script_data *data) -{ - get_val(st, data); - return data->get_if<ScriptDataRetInfo>()->script; -} - - -template<class T> -static -void push_int(struct script_stack *stack, int val) -{ - static_assert(first_type_is_any<T, ScriptDataPos, ScriptDataInt, ScriptDataArg, ScriptDataFuncRef>(), "not int type"); - - script_data nsd = T{.numi= val}; - stack->stack_datav.push_back(nsd); -} - -template<class T> -static -void push_reg(struct script_stack *stack, SIR reg) -{ - static_assert(first_type_is_any<T, ScriptDataParam, ScriptDataVariable>(), "not reg type"); - - script_data nsd = T{.reg= reg}; - stack->stack_datav.push_back(nsd); -} - -template<class T> -static -void push_script(struct script_stack *stack, const ScriptBuffer *code) -{ - static_assert(first_type_is_any<T, ScriptDataRetInfo>(), "not scriptbuf type"); - - script_data nsd = T{.script= code}; - stack->stack_datav.push_back(nsd); -} - -template<class T> -static -void push_str(struct script_stack *stack, RString str) -{ - static_assert(first_type_is_any<T, ScriptDataStr>(), "not str type"); - - script_data nsd = T{.str= str}; - stack->stack_datav.push_back(nsd); -} - -static -void push_copy(struct script_stack *stack, int pos_) -{ - script_data csd = stack->stack_datav[pos_]; - stack->stack_datav.push_back(csd); -} - -static -void pop_stack(struct script_stack *stack, int start, int end) -{ - auto it = stack->stack_datav.begin(); - stack->stack_datav.erase(it + start, it + end); -} - - -#define AARGO2(n) (st->stack->stack_datav[st->start + (n)]) -#define HARGO2(n) (st->end > st->start + (n)) - -// -// 埋め込み関数 -// -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_mes(ScriptState *st) -{ - RString mes = conv_str(st, &AARGO2(2)); - clif_scriptmes(script_rid2sd(st), st->oid, mes); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_goto(ScriptState *st) -{ - if (!AARGO2(2).is<ScriptDataPos>()) - { - PRINTF("script: goto: not label !\n"_fmt); - st->state = ScriptEndState::END; - return; - } - - st->scriptp.pos = conv_num(st, &AARGO2(2)); - st->state = ScriptEndState::GOTO; -} - -/*========================================== - * ユーザー定義関数の呼び出し - *------------------------------------------ - */ -static -void builtin_callfunc(ScriptState *st) -{ - RString str = conv_str(st, &AARGO2(2)); - const ScriptBuffer *scr = userfunc_db.get(str); - - if (scr) - { - int j = 0; - assert (st->start + 3 == st->end); -#if 0 - for (int i = st->start + 3; i < st->end; i++, j++) - push_copy(st->stack, i); -#endif - - push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ - push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ - push_int<ScriptDataInt>(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ - push_script<ScriptDataRetInfo>(st->stack, st->scriptp.code); // 現在のスクリプトをプッシュ - - st->scriptp = ScriptPointer(scr, 0); - st->defsp = st->start + 4 + j; - st->state = ScriptEndState::GOTO; - } - else - { - PRINTF("script:callfunc: function not found! [%s]\n"_fmt, str); - st->state = ScriptEndState::END; - } -} - -/*========================================== - * サブルーティンの呼び出し - *------------------------------------------ - */ -static -void builtin_callsub(ScriptState *st) -{ - int pos_ = conv_num(st, &AARGO2(2)); - int j = 0; - assert (st->start + 3 == st->end); -#if 0 - for (int i = st->start + 3; i < st->end; i++, j++) - push_copy(st->stack, i); -#endif - - push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ - push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ - push_int<ScriptDataInt>(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ - push_script<ScriptDataRetInfo>(st->stack, st->scriptp.code); // 現在のスクリプトをプッシュ - - st->scriptp.pos = pos_; - st->defsp = st->start + 4 + j; - st->state = ScriptEndState::GOTO; -} - -/*========================================== - * サブルーチン/ユーザー定義関数の終了 - *------------------------------------------ - */ -static -void builtin_return(ScriptState *st) -{ -#if 0 - if (HARGO2(2)) - { // 戻り値有り - push_copy(st->stack, st->start + 2); - } -#endif - st->state = ScriptEndState::RETFUNC; -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_next(ScriptState *st) -{ - st->state = ScriptEndState::STOP; - clif_scriptnext(script_rid2sd(st), st->oid); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_close(ScriptState *st) -{ - st->state = ScriptEndState::END; - clif_scriptclose(script_rid2sd(st), st->oid); -} - -static -void builtin_close2(ScriptState *st) -{ - st->state = ScriptEndState::STOP; - clif_scriptclose(script_rid2sd(st), st->oid); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_menu(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - if (sd->state.menu_or_input == 0) - { - // First half: show menu. - st->state = ScriptEndState::RERUNLINE; - sd->state.menu_or_input = 1; - - MString buf; - for (int i = st->start + 2; i < st->end; i += 2) - { - RString choice_str = conv_str(st, &AARGO2(i - st->start)); - if (!choice_str) - break; - buf += choice_str; - buf += ':'; - } - - clif_scriptmenu(script_rid2sd(st), st->oid, AString(buf)); - } - else - { - // Rerun: item is chosen from menu. - if (sd->npc_menu == 0xff) - { - // cancel - sd->state.menu_or_input = 0; - st->state = ScriptEndState::END; - return; - } - - // Actually jump to the label. - // Logic change: menu_choices is the *total* number of labels, - // not just the displayed number that ends with the "". - // (Would it be better to pop the stack before rerunning?) - int menu_choices = (st->end - (st->start + 2)) / 2; - pc_setreg(sd, SIR::from(variable_names.intern("@menu"_s)), sd->npc_menu); - sd->state.menu_or_input = 0; - if (sd->npc_menu > 0 && sd->npc_menu <= menu_choices) - { - int arg_index = (sd->npc_menu - 1) * 2 + 1; - if (!AARGO2(arg_index + 2).is<ScriptDataPos>()) - { - st->state = ScriptEndState::END; - return; - } - st->scriptp.pos = AARGO2(arg_index + 2).get_if<ScriptDataPos>()->numi; - st->state = ScriptEndState::GOTO; - } - } -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_rand(ScriptState *st) -{ - if (HARGO2(3)) - { - int min = conv_num(st, &AARGO2(2)); - int max = conv_num(st, &AARGO2(3)); - if (min > max) - std::swap(max, min); - push_int<ScriptDataInt>(st->stack, random_::in(min, max)); - } - else - { - int range = conv_num(st, &AARGO2(2)); - push_int<ScriptDataInt>(st->stack, range <= 0 ? 0 : random_::to(range)); - } -} - -/*========================================== - * Check whether the PC is at the specified location - *------------------------------------------ - */ -static -void builtin_isat(ScriptState *st) -{ - int x, y; - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - x = conv_num(st, &AARGO2(3)); - y = conv_num(st, &AARGO2(4)); - - if (!sd) - return; - - push_int<ScriptDataInt>(st->stack, - (x == sd->bl_x) && (y == sd->bl_y) - && (str == sd->bl_m->name_)); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_warp(ScriptState *st) -{ - int x, y; - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - x = conv_num(st, &AARGO2(3)); - y = conv_num(st, &AARGO2(4)); - if (str == "Random"_s) - pc_randomwarp(sd, BeingRemoveWhy::WARPED); - else if (str == "SavePoint"_s or str == "Save"_s) - { - if (sd->bl_m->flag.get(MapFlag::NORETURN)) - return; - - pc_setpos(sd, sd->status.save_point.map_, sd->status.save_point.x, sd->status.save_point.y, - BeingRemoveWhy::WARPED); - } - else - pc_setpos(sd, str, x, y, BeingRemoveWhy::GONE); -} - -/*========================================== - * エリア指定ワープ - *------------------------------------------ - */ -static -void builtin_areawarp_sub(dumb_ptr<block_list> bl, MapName mapname, int x, int y) -{ - dumb_ptr<map_session_data> sd = bl->is_player(); - if (mapname == "Random"_s) - pc_randomwarp(sd, BeingRemoveWhy::WARPED); - else - pc_setpos(sd, mapname, x, y, BeingRemoveWhy::GONE); -} - -static -void builtin_areawarp(ScriptState *st) -{ - int x, y; - int x0, y0, x1, y1; - - MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - x0 = conv_num(st, &AARGO2(3)); - y0 = conv_num(st, &AARGO2(4)); - x1 = conv_num(st, &AARGO2(5)); - y1 = conv_num(st, &AARGO2(6)); - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(7)))); - x = conv_num(st, &AARGO2(8)); - y = conv_num(st, &AARGO2(9)); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return; - - map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y), - m, - x0, y0, - x1, y1, - BL::PC); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_heal(ScriptState *st) -{ - int hp, sp; - - hp = conv_num(st, &AARGO2(2)); - sp = conv_num(st, &AARGO2(3)); - pc_heal(script_rid2sd(st), hp, sp); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_itemheal(ScriptState *st) -{ - int hp, sp; - - hp = conv_num(st, &AARGO2(2)); - sp = conv_num(st, &AARGO2(3)); - pc_itemheal(script_rid2sd(st), hp, sp); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_percentheal(ScriptState *st) -{ - int hp, sp; - - hp = conv_num(st, &AARGO2(2)); - sp = conv_num(st, &AARGO2(3)); - pc_percentheal(script_rid2sd(st), hp, sp); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_input(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = nullptr; - script_data& scrd = AARGO2(2); - assert (scrd.is<ScriptDataVariable>()); - - SIR reg = scrd.get_if<ScriptDataVariable>()->reg; - ZString name = variable_names.outtern(reg.base()); -// char prefix = name.front(); - char postfix = name.back(); - - sd = script_rid2sd(st); - if (sd->state.menu_or_input) - { - // Second time (rerun) - sd->state.menu_or_input = 0; - if (postfix == '$') - { - set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_str); - } - else - { - //commented by Lupus (check Value Number Input fix in clif.c) - //** Fix by fritz :X keeps people from abusing old input bugs - // wtf? - if (sd->npc_amount < 0) //** If input amount is less then 0 - { - clif_tradecancelled(sd); // added "Deal has been cancelled" message by Valaris - builtin_close(st); //** close - } - - set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_amount); - } - } - else - { - // First time - send prompt to client, then wait - st->state = ScriptEndState::RERUNLINE; - if (postfix == '$') - clif_scriptinputstr(sd, st->oid); - else - clif_scriptinput(sd, st->oid); - sd->state.menu_or_input = 1; - } -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_if (ScriptState *st) -{ - int sel, i; - - sel = conv_num(st, &AARGO2(2)); - if (!sel) - return; - - // 関数名をコピー - push_copy(st->stack, st->start + 3); - // 間に引数マーカを入れて - push_int<ScriptDataArg>(st->stack, 0); - // 残りの引数をコピー - for (i = st->start + 4; i < st->end; i++) - { - push_copy(st->stack, i); - } - run_func(st); -} - -/*========================================== - * 変数設定 - *------------------------------------------ - */ -static -void builtin_set(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = nullptr; - if (auto *u = AARGO2(2).get_if<ScriptDataParam>()) - { - SIR reg = u->reg; - sd = script_rid2sd(st); - - int val = conv_num(st, &AARGO2(3)); - set_reg(sd, VariableCode::PARAM, reg, val); - return; - } - - SIR reg = AARGO2(2).get_if<ScriptDataVariable>()->reg; - - ZString name = variable_names.outtern(reg.base()); - char prefix = name.front(); - char postfix = name.back(); - - if (prefix != '$') - sd = script_rid2sd(st); - - if (postfix == '$') - { - // 文字列 - RString str = conv_str(st, &AARGO2(3)); - set_reg(sd, VariableCode::VARIABLE, reg, str); - } - else - { - // 数値 - int val = conv_num(st, &AARGO2(3)); - set_reg(sd, VariableCode::VARIABLE, reg, val); - } - -} - -/*========================================== - * 配列変数設定 - *------------------------------------------ - */ -static -void builtin_setarray(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = nullptr; - SIR reg = AARGO2(2).get_if<ScriptDataVariable>()->reg; - ZString name = variable_names.outtern(reg.base()); - char prefix = name.front(); - char postfix = name.back(); - - if (prefix != '$' && prefix != '@') - { - PRINTF("builtin_setarray: illegal scope !\n"_fmt); - return; - } - if (prefix != '$') - sd = script_rid2sd(st); - - for (int j = 0, i = st->start + 3; i < st->end && j < 256; i++, j++) - { - if (postfix == '$') - set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_str(st, &AARGO2(i - st->start))); - else - set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_num(st, &AARGO2(i - st->start))); - } -} - -/*========================================== - * 配列変数クリア - *------------------------------------------ - */ -static -void builtin_cleararray(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = nullptr; - SIR reg = AARGO2(2).get_if<ScriptDataVariable>()->reg; - ZString name = variable_names.outtern(reg.base()); - char prefix = name.front(); - char postfix = name.back(); - int sz = conv_num(st, &AARGO2(4)); - - if (prefix != '$' && prefix != '@') - { - PRINTF("builtin_cleararray: illegal scope !\n"_fmt); - return; - } - if (prefix != '$') - sd = script_rid2sd(st); - - for (int i = 0; i < sz; i++) - { - if (postfix == '$') - set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARGO2(3))); - else - set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_num(st, &AARGO2(3))); - } - -} - -/*========================================== - * 配列変数のサイズ所得 - *------------------------------------------ - */ -static -int getarraysize(ScriptState *st, SIR reg) -{ - int i = reg.index(), c = i; - for (; i < 256; i++) - { - struct script_data vd = get_val2(st, reg.iplus(i)); - MATCH (vd) - { - CASE (const ScriptDataStr&, u) - { - if (u.str[0]) - c = i; - goto continue_outer; - } - CASE (const ScriptDataInt&, u) - { - if (u.numi) - c = i; - goto continue_outer; - } - } - abort(); - continue_outer: - ; - } - return c + 1; -} - -static -void builtin_getarraysize(ScriptState *st) -{ - SIR reg = AARGO2(2).get_if<ScriptDataVariable>()->reg; - ZString name = variable_names.outtern(reg.base()); - char prefix = name.front(); - - if (prefix != '$' && prefix != '@') - { - PRINTF("builtin_copyarray: illegal scope !\n"_fmt); - return; - } - - push_int<ScriptDataInt>(st->stack, getarraysize(st, reg)); -} - -/*========================================== - * 指定要素を表す値(キー)を所得する - *------------------------------------------ - */ -static -void builtin_getelementofarray(ScriptState *st) -{ - if (auto *u = AARGO2(2).get_if<ScriptDataVariable>()) - { - int i = conv_num(st, &AARGO2(3)); - if (i > 255 || i < 0) - { - PRINTF("script: getelementofarray (operator[]): param2 illegal number %d\n"_fmt, - i); - push_int<ScriptDataInt>(st->stack, 0); - } - else - { - push_reg<ScriptDataVariable>(st->stack, - u->reg.iplus(i)); - } - } - else - { - PRINTF("script: getelementofarray (operator[]): param1 not name !\n"_fmt); - push_int<ScriptDataInt>(st->stack, 0); - } -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_setlook(ScriptState *st) -{ - LOOK type = LOOK(conv_num(st, &AARGO2(2))); - int val = conv_num(st, &AARGO2(3)); - - pc_changelook(script_rid2sd(st), type, val); - -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_countitem(ScriptState *st) -{ - ItemNameId nameid; - int count = 0; - dumb_ptr<map_session_data> sd; - - struct script_data *data; - - sd = script_rid2sd(st); - - data = &AARGO2(2); - get_val(st, data); - if (data->is<ScriptDataStr>()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data != nullptr) - nameid = item_data->nameid; - } - else - nameid = wrap<ItemNameId>(conv_num(st, data)); - - if (nameid) - { - for (IOff0 i : IOff0::iter()) - { - if (sd->status.inventory[i].nameid == nameid) - count += sd->status.inventory[i].amount; - } - } - else - { - if (battle_config.error_log) - PRINTF("wrong item ID : countitem (%i)\n"_fmt, nameid); - } - push_int<ScriptDataInt>(st->stack, count); - -} - -/*========================================== - * 重量チェック - *------------------------------------------ - */ -static -void builtin_checkweight(ScriptState *st) -{ - ItemNameId nameid; - int amount; - dumb_ptr<map_session_data> sd; - struct script_data *data; - - sd = script_rid2sd(st); - - data = &AARGO2(2); - get_val(st, data); - if (data->is<ScriptDataStr>()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data) - nameid = item_data->nameid; - } - else - nameid = wrap<ItemNameId>(conv_num(st, data)); - - amount = conv_num(st, &AARGO2(3)); - if (amount <= 0 || !nameid) - { - //if get wrong item ID or amount<=0, don't count weight of non existing items - push_int<ScriptDataInt>(st->stack, 0); - return; - } - - if (itemdb_weight(nameid) * amount + sd->weight > sd->max_weight) - { - push_int<ScriptDataInt>(st->stack, 0); - } - else - { - push_int<ScriptDataInt>(st->stack, 1); - } - -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_getitem(ScriptState *st) -{ - ItemNameId nameid; - int amount; - dumb_ptr<map_session_data> sd; - struct script_data *data; - - sd = script_rid2sd(st); - - data = &AARGO2(2); - get_val(st, data); - if (data->is<ScriptDataStr>()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data != nullptr) - nameid = item_data->nameid; - } - else - nameid = wrap<ItemNameId>(conv_num(st, data)); - - if ((amount = - conv_num(st, &AARGO2(3))) <= 0) - { - return; //return if amount <=0, skip the useles iteration - } - - if (nameid) - { - Item item_tmp {}; - item_tmp.nameid = nameid; - if (HARGO2(5)) //アイテムを指定したIDに渡す - sd = map_id2sd(wrap<BlockId>(conv_num(st, &AARGO2(5)))); - if (sd == nullptr) //アイテムを渡す相手がいなかったらお帰り - return; - PickupFail flag; - if ((flag = pc_additem(sd, &item_tmp, amount)) != PickupFail::OKAY) - { - clif_additem(sd, IOff0::from(0), 0, flag); - map_addflooritem(&item_tmp, amount, - sd->bl_m, sd->bl_x, sd->bl_y, - nullptr, nullptr, nullptr); - } - } - -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_makeitem(ScriptState *st) -{ - ItemNameId nameid; - int amount; - int x, y; - dumb_ptr<map_session_data> sd; - struct script_data *data; - - sd = script_rid2sd(st); - - data = &AARGO2(2); - get_val(st, data); - if (data->is<ScriptDataStr>()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data) - nameid = item_data->nameid; - } - else - nameid = wrap<ItemNameId>(conv_num(st, data)); - - amount = conv_num(st, &AARGO2(3)); - MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(4)))); - x = conv_num(st, &AARGO2(5)); - y = conv_num(st, &AARGO2(6)); - - map_local *m; - if (sd && mapname == MOB_THIS_MAP) - m = sd->bl_m; - else - m = map_mapname2mapid(mapname); - - if (nameid) - { - Item item_tmp {}; - item_tmp.nameid = nameid; - - map_addflooritem(&item_tmp, amount, m, x, y, nullptr, nullptr, nullptr); - } -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_delitem(ScriptState *st) -{ - ItemNameId nameid; - int amount; - dumb_ptr<map_session_data> sd; - struct script_data *data; - - sd = script_rid2sd(st); - - data = &AARGO2(2); - get_val(st, data); - if (data->is<ScriptDataStr>()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data) - nameid = item_data->nameid; - } - else - nameid = wrap<ItemNameId>(conv_num(st, data)); - - amount = conv_num(st, &AARGO2(3)); - - if (!nameid || amount <= 0) - { - //by Lupus. Don't run FOR if u got wrong item ID or amount<=0 - return; - } - - for (IOff0 i : IOff0::iter()) - { - if (sd->status.inventory[i].nameid == nameid) - { - if (sd->status.inventory[i].amount >= amount) - { - pc_delitem(sd, i, amount, 0); - break; - } - else - { - amount -= sd->status.inventory[i].amount; - if (amount == 0) - amount = sd->status.inventory[i].amount; - pc_delitem(sd, i, amount, 0); - break; - } - } - } - -} - -/*========================================== - *キャラ関係のパラメータ取得 - *------------------------------------------ - */ -static -void builtin_readparam(ScriptState *st) -{ - dumb_ptr<map_session_data> sd; - - SP type = SP(conv_num(st, &AARGO2(2))); - if (HARGO2(3)) - sd = map_nick2sd(stringish<CharName>(ZString(conv_str(st, &AARGO2(3))))); - else - sd = script_rid2sd(st); - - if (sd == nullptr) - { - push_int<ScriptDataInt>(st->stack, -1); - return; - } - - push_int<ScriptDataInt>(st->stack, pc_readparam(sd, type)); - -} - -/*========================================== - *キャラ関係のID取得 - *------------------------------------------ - */ -static -void builtin_getcharid(ScriptState *st) -{ - int num; - dumb_ptr<map_session_data> sd; - - num = conv_num(st, &AARGO2(2)); - if (HARGO2(3)) - sd = map_nick2sd(stringish<CharName>(ZString(conv_str(st, &AARGO2(3))))); - else - sd = script_rid2sd(st); - if (sd == nullptr) - { - push_int<ScriptDataInt>(st->stack, -1); - return; - } - if (num == 0) - push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status_key.char_id)); - if (num == 1) - push_int<ScriptDataInt>(st->stack, unwrap<PartyId>(sd->status.party_id)); - if (num == 2) - push_int<ScriptDataInt>(st->stack, 0/*guild_id*/); - if (num == 3) - push_int<ScriptDataInt>(st->stack, unwrap<AccountId>(sd->status_key.account_id)); -} - -/*========================================== - *指定IDのPT名取得 - *------------------------------------------ - */ -static -RString builtin_getpartyname_sub(PartyId party_id) -{ - PartyPair p = party_search(party_id); - - if (p) - return p->name; - - return RString(); -} - -/*========================================== - * キャラクタの名前 - *------------------------------------------ - */ -static -void builtin_strcharinfo(ScriptState *st) -{ - dumb_ptr<map_session_data> sd; - int num; - - sd = script_rid2sd(st); - num = conv_num(st, &AARGO2(2)); - if (num == 0) - { - RString buf = sd->status_key.name.to__actual(); - push_str<ScriptDataStr>(st->stack, buf); - } - if (num == 1) - { - RString buf = builtin_getpartyname_sub(sd->status.party_id); - if (buf) - push_str<ScriptDataStr>(st->stack, buf); - else - push_str<ScriptDataStr>(st->stack, ""_s); - } - if (num == 2) - { - // was: guild name - push_str<ScriptDataStr>(st->stack, ""_s); - } - -} - -// indexed by the equip_* in db/const.txt -// TODO change to use EQUIP -static -Array<EPOS, 11> equip //= -{{ - EPOS::HAT, - EPOS::MISC1, - EPOS::SHIELD, - EPOS::WEAPON, - EPOS::GLOVES, - EPOS::SHOES, - EPOS::CAPE, - EPOS::MISC2, - EPOS::TORSO, - EPOS::LEGS, - EPOS::ARROW, -}}; - -/*========================================== - * GetEquipID(Pos); Pos: 1-10 - *------------------------------------------ - */ -static -void builtin_getequipid(ScriptState *st) -{ - int num; - dumb_ptr<map_session_data> sd; - struct item_data *item; - - sd = script_rid2sd(st); - if (sd == nullptr) - { - PRINTF("getequipid: sd == nullptr\n"_fmt); - return; - } - num = conv_num(st, &AARGO2(2)); - IOff0 i = pc_checkequip(sd, equip[num - 1]); - if (i.ok()) - { - item = sd->inventory_data[i]; - if (item) - push_int<ScriptDataInt>(st->stack, unwrap<ItemNameId>(item->nameid)); - else - push_int<ScriptDataInt>(st->stack, 0); - } - else - { - push_int<ScriptDataInt>(st->stack, -1); - } -} - -/*========================================== - * 装備名文字列(精錬メニュー用) - *------------------------------------------ - */ -static -void builtin_getequipname(ScriptState *st) -{ - int num; - dumb_ptr<map_session_data> sd; - struct item_data *item; - - AString buf; - - sd = script_rid2sd(st); - num = conv_num(st, &AARGO2(2)); - IOff0 i = pc_checkequip(sd, equip[num - 1]); - if (i.ok()) - { - item = sd->inventory_data[i]; - if (item) - buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], item->jname); - else - buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]); - } - else - { - buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]); - } - push_str<ScriptDataStr>(st->stack, buf); - -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_statusup2(ScriptState *st) -{ - SP type = SP(conv_num(st, &AARGO2(2))); - int val = conv_num(st, &AARGO2(3)); - dumb_ptr<map_session_data> sd = script_rid2sd(st); - pc_statusup2(sd, type, val); - -} - -/*========================================== - * 装備品による能力値ボーナス - *------------------------------------------ - */ -static -void builtin_bonus(ScriptState *st) -{ - SP type = SP(conv_num(st, &AARGO2(2))); - int val = conv_num(st, &AARGO2(3)); - dumb_ptr<map_session_data> sd = script_rid2sd(st); - pc_bonus(sd, type, val); - -} - -/*========================================== - * 装備品による能力値ボーナス - *------------------------------------------ - */ -static -void builtin_bonus2(ScriptState *st) -{ - SP type = SP(conv_num(st, &AARGO2(2))); - int type2 = conv_num(st, &AARGO2(3)); - int val = conv_num(st, &AARGO2(4)); - dumb_ptr<map_session_data> sd = script_rid2sd(st); - pc_bonus2(sd, type, type2, val); - -} - -/*========================================== - * スキル所得 - *------------------------------------------ - */ -static -void builtin_skill(ScriptState *st) -{ - int level, flag = 1; - dumb_ptr<map_session_data> sd; - - SkillID id = SkillID(conv_num(st, &AARGO2(2))); - level = conv_num(st, &AARGO2(3)); - if (HARGO2(4)) - flag = conv_num(st, &AARGO2(4)); - sd = script_rid2sd(st); - pc_skill(sd, id, level, flag); - clif_skillinfoblock(sd); - -} - -/*========================================== - * [Fate] Sets the skill level permanently - *------------------------------------------ - */ -static -void builtin_setskill(ScriptState *st) -{ - int level; - dumb_ptr<map_session_data> sd; - - SkillID id = static_cast<SkillID>(conv_num(st, &AARGO2(2))); - level = conv_num(st, &AARGO2(3)); - sd = script_rid2sd(st); - - level = std::min(level, MAX_SKILL_LEVEL); - level = std::max(level, 0); - sd->status.skill[id].lv = level; - clif_skillinfoblock(sd); -} - -/*========================================== - * スキルレベル所得 - *------------------------------------------ - */ -static -void builtin_getskilllv(ScriptState *st) -{ - SkillID id = SkillID(conv_num(st, &AARGO2(2))); - push_int<ScriptDataInt>(st->stack, pc_checkskill(script_rid2sd(st), id)); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_getgmlevel(ScriptState *st) -{ - push_int<ScriptDataInt>(st->stack, pc_isGM(script_rid2sd(st)).get_all_bits()); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_end(ScriptState *st) -{ - st->state = ScriptEndState::END; -} - -/*========================================== - * [Freeyorp] Return the current opt2 - *------------------------------------------ - */ - -static -void builtin_getopt2(ScriptState *st) -{ - dumb_ptr<map_session_data> sd; - - sd = script_rid2sd(st); - - push_int<ScriptDataInt>(st->stack, static_cast<uint16_t>(sd->opt2)); - -} - -/*========================================== - * [Freeyorp] Sets opt2 - *------------------------------------------ - */ - -static -void builtin_setopt2(ScriptState *st) -{ - dumb_ptr<map_session_data> sd; - - Opt2 new_opt2 = Opt2(conv_num(st, &AARGO2(2))); - sd = script_rid2sd(st); - if (new_opt2 == sd->opt2) - return; - sd->opt2 = new_opt2; - clif_changeoption(sd); - pc_calcstatus(sd, 0); - -} - -/*========================================== - * セーブポイントの保存 - *------------------------------------------ - */ -static -void builtin_savepoint(ScriptState *st) -{ - int x, y; - - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - x = conv_num(st, &AARGO2(3)); - y = conv_num(st, &AARGO2(4)); - pc_setsavepoint(script_rid2sd(st), str, x, y); -} - -/*========================================== - * gettimetick(type) - * - * type The type of time measurement. - * Specify 0 for the system tick, 1 for - * seconds elapsed today, or 2 for seconds - * since Unix epoch. Defaults to 0 for any - * other value. - *------------------------------------------ - */ -static -void builtin_gettimetick(ScriptState *st) /* Asgard Version */ -{ - int type; - type = conv_num(st, &AARGO2(2)); - - switch (type) - { - /* Number of seconds elapsed today(0-86399, 00:00:00-23:59:59). */ - case 1: - { - struct tm t = TimeT::now(); - push_int<ScriptDataInt>(st->stack, - t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec); - break; - } - /* Seconds since Unix epoch. */ - case 2: - push_int<ScriptDataInt>(st->stack, static_cast<time_t>(TimeT::now())); - break; - /* System tick(unsigned int, and yes, it will wrap). */ - case 0: - default: - push_int<ScriptDataInt>(st->stack, gettick().time_since_epoch().count()); - break; - } -} - -/*========================================== - * GetTime(Type); - * 1: Sec 2: Min 3: Hour - * 4: WeekDay 5: MonthDay 6: Month - * 7: Year - *------------------------------------------ - */ -static -void builtin_gettime(ScriptState *st) /* Asgard Version */ -{ - int type = conv_num(st, &AARGO2(2)); - - struct tm t = TimeT::now(); - - switch (type) - { - case 1: //Sec(0~59) - push_int<ScriptDataInt>(st->stack, t.tm_sec); - break; - case 2: //Min(0~59) - push_int<ScriptDataInt>(st->stack, t.tm_min); - break; - case 3: //Hour(0~23) - push_int<ScriptDataInt>(st->stack, t.tm_hour); - break; - case 4: //WeekDay(0~6) - push_int<ScriptDataInt>(st->stack, t.tm_wday); - break; - case 5: //MonthDay(01~31) - push_int<ScriptDataInt>(st->stack, t.tm_mday); - break; - case 6: //Month(01~12) - push_int<ScriptDataInt>(st->stack, t.tm_mon + 1); - break; - case 7: //Year(20xx) - push_int<ScriptDataInt>(st->stack, t.tm_year + 1900); - break; - default: //(format error) - push_int<ScriptDataInt>(st->stack, -1); - break; - } -} - -/*========================================== - * カプラ倉庫を開く - *------------------------------------------ - */ -static -void builtin_openstorage(ScriptState *st) -{ -// int sync = 0; -// if (st->end >= 3) sync = conv_num(st,& (st->stack->stack_data[st->start+2])); - dumb_ptr<map_session_data> sd = script_rid2sd(st); - -// if (sync) { - st->state = ScriptEndState::STOP; - sd->npc_flags.storage = 1; -// } else st->state = ScriptEndState::END; - - storage_storageopen(sd); -} - -/*========================================== - * NPCで経験値上げる - *------------------------------------------ - */ -static -void builtin_getexp(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - int base = 0, job = 0; - - base = conv_num(st, &AARGO2(2)); - job = conv_num(st, &AARGO2(3)); - if (base < 0 || job < 0) - return; - if (sd) - pc_gainexp_reason(sd, base, job, PC_GAINEXP_REASON::SCRIPT); - -} - -/*========================================== - * モンスター発生 - *------------------------------------------ - */ -static -void builtin_monster(ScriptState *st) -{ - Species mob_class; - int amount, x, y; - NpcEvent event; - - MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - x = conv_num(st, &AARGO2(3)); - y = conv_num(st, &AARGO2(4)); - MobName str = stringish<MobName>(ZString(conv_str(st, &AARGO2(5)))); - mob_class = wrap<Species>(conv_num(st, &AARGO2(6))); - amount = conv_num(st, &AARGO2(7)); - if (HARGO2(8)) - extract(ZString(conv_str(st, &AARGO2(8))), &event); - - mob_once_spawn(map_id2sd(st->rid), mapname, x, y, str, mob_class, amount, - event); -} - -/*========================================== - * モンスター発生 - *------------------------------------------ - */ -static -void builtin_areamonster(ScriptState *st) -{ - Species mob_class; - int amount, x0, y0, x1, y1; - NpcEvent event; - - MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - x0 = conv_num(st, &AARGO2(3)); - y0 = conv_num(st, &AARGO2(4)); - x1 = conv_num(st, &AARGO2(5)); - y1 = conv_num(st, &AARGO2(6)); - MobName str = stringish<MobName>(ZString(conv_str(st, &AARGO2(7)))); - mob_class = wrap<Species>(conv_num(st, &AARGO2(8))); - amount = conv_num(st, &AARGO2(9)); - if (HARGO2(10)) - extract(ZString(conv_str(st, &AARGO2(10))), &event); - - mob_once_spawn_area(map_id2sd(st->rid), mapname, x0, y0, x1, y1, str, mob_class, - amount, event); -} - -/*========================================== - * モンスター削除 - *------------------------------------------ - */ -static -void builtin_killmonster_sub(dumb_ptr<block_list> bl, NpcEvent event) -{ - dumb_ptr<mob_data> md = bl->is_mob(); - if (event) - { - if (event == md->npc_event) - mob_delete(md); - return; - } - else if (!event) - { - if (md->spawn.delay1 == static_cast<interval_t>(-1) - && md->spawn.delay2 == static_cast<interval_t>(-1)) - mob_delete(md); - return; - } -} - -static -void builtin_killmonster(ScriptState *st) -{ - MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - ZString event_ = ZString(conv_str(st, &AARGO2(3))); - NpcEvent event; - if (event_ != "All"_s) - extract(event_, &event); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return; - map_foreachinarea(std::bind(builtin_killmonster_sub, ph::_1, event), - m, - 0, 0, - m->xs, m->ys, - BL::MOB); -} - -static -void builtin_killmonsterall_sub(dumb_ptr<block_list> bl) -{ - mob_delete(bl->is_mob()); -} - -static -void builtin_killmonsterall(ScriptState *st) -{ - MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return; - map_foreachinarea(builtin_killmonsterall_sub, - m, - 0, 0, - m->xs, m->ys, - BL::MOB); -} - -/*========================================== - * NPC主体イベント実行 - *------------------------------------------ - */ -static -void builtin_donpcevent(ScriptState *st) -{ - ZString event_ = ZString(conv_str(st, &AARGO2(2))); - NpcEvent event; - extract(event_, &event); - npc_event_do(event); -} - -/*========================================== - * イベントタイマー追加 - *------------------------------------------ - */ -static -void builtin_addtimer(ScriptState *st) -{ - interval_t tick = static_cast<interval_t>(conv_num(st, &AARGO2(2))); - ZString event_ = ZString(conv_str(st, &AARGO2(3))); - NpcEvent event; - extract(event_, &event); - pc_addeventtimer(script_rid2sd(st), tick, event); -} - -/*========================================== - * NPCタイマー初期化 - *------------------------------------------ - */ -static -void builtin_initnpctimer(ScriptState *st) -{ - dumb_ptr<npc_data> nd_; - if (HARGO2(2)) - nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARGO2(2))))); - else - nd_ = map_id_is_npc(st->oid); - assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); - dumb_ptr<npc_data_script> nd = nd_->is_script(); - - npc_settimerevent_tick(nd, interval_t::zero()); - npc_timerevent_start(nd); -} - -/*========================================== - * NPCタイマー開始 - *------------------------------------------ - */ -static -void builtin_startnpctimer(ScriptState *st) -{ - dumb_ptr<npc_data> nd_; - if (HARGO2(2)) - nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARGO2(2))))); - else - nd_ = map_id_is_npc(st->oid); - assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); - dumb_ptr<npc_data_script> nd = nd_->is_script(); - - npc_timerevent_start(nd); -} - -/*========================================== - * NPCタイマー停止 - *------------------------------------------ - */ -static -void builtin_stopnpctimer(ScriptState *st) -{ - dumb_ptr<npc_data> nd_; - if (HARGO2(2)) - nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARGO2(2))))); - else - nd_ = map_id_is_npc(st->oid); - assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); - dumb_ptr<npc_data_script> nd = nd_->is_script(); - - npc_timerevent_stop(nd); -} - -/*========================================== - * NPCタイマー情報所得 - *------------------------------------------ - */ -static -void builtin_getnpctimer(ScriptState *st) -{ - dumb_ptr<npc_data> nd_; - int type = conv_num(st, &AARGO2(2)); - int val = 0; - if (HARGO2(3)) - nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARGO2(3))))); - else - nd_ = map_id_is_npc(st->oid); - assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); - dumb_ptr<npc_data_script> nd = nd_->is_script(); - - switch (type) - { - case 0: - val = npc_gettimerevent_tick(nd).count(); - break; - case 1: - val = nd->scr.timer_active; - break; - case 2: - val = nd->scr.timer_eventv.size(); - break; - } - push_int<ScriptDataInt>(st->stack, val); -} - -/*========================================== - * NPCタイマー値設定 - *------------------------------------------ - */ -static -void builtin_setnpctimer(ScriptState *st) -{ - dumb_ptr<npc_data> nd_; - interval_t tick = static_cast<interval_t>(conv_num(st, &AARGO2(2))); - if (HARGO2(3)) - nd_ = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARGO2(3))))); - else - nd_ = map_id_is_npc(st->oid); - assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); - dumb_ptr<npc_data_script> nd = nd_->is_script(); - - npc_settimerevent_tick(nd, tick); -} - -/*========================================== - * 天の声アナウンス - *------------------------------------------ - */ -static -void builtin_announce(ScriptState *st) -{ - int flag; - ZString str = ZString(conv_str(st, &AARGO2(2))); - flag = conv_num(st, &AARGO2(3)); - - if (flag & 0x0f) - { - dumb_ptr<block_list> bl; - if (flag & 0x08) - bl = map_id2bl(st->oid); - else - bl = script_rid2sd(st); - clif_GMmessage(bl, str, flag); - } - else - intif_GMmessage(str); -} - -/*========================================== - * 天の声アナウンス(特定マップ) - *------------------------------------------ - */ -static -void builtin_mapannounce_sub(dumb_ptr<block_list> bl, XString str, int flag) -{ - clif_GMmessage(bl, str, flag | 3); -} - -static -void builtin_mapannounce(ScriptState *st) -{ - int flag; - - MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - ZString str = ZString(conv_str(st, &AARGO2(3))); - flag = conv_num(st, &AARGO2(4)); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return; - map_foreachinarea(std::bind(builtin_mapannounce_sub, ph::_1, str, flag & 0x10), - m, - 0, 0, - m->xs, m->ys, - BL::PC); -} - -/*========================================== - * ユーザー数所得 - *------------------------------------------ - */ -static -void builtin_getusers(ScriptState *st) -{ - int flag = conv_num(st, &AARGO2(2)); - dumb_ptr<block_list> bl = map_id2bl((flag & 0x08) ? st->oid : st->rid); - int val = 0; - switch (flag & 0x07) - { - case 0: - val = bl->bl_m->users; - break; - case 1: - val = map_getusers(); - break; - } - push_int<ScriptDataInt>(st->stack, val); -} - -/*========================================== - * マップ指定ユーザー数所得 - *------------------------------------------ - */ -static -void builtin_getmapusers(ScriptState *st) -{ - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - map_local *m = map_mapname2mapid(str); - if (m == nullptr) - { - push_int<ScriptDataInt>(st->stack, -1); - return; - } - push_int<ScriptDataInt>(st->stack, m->users); -} - -/*========================================== - * エリア指定ユーザー数所得 - *------------------------------------------ - */ -static -void builtin_getareausers_sub(dumb_ptr<block_list> bl, int *users) -{ - if (bool(bl->is_player()->status.option & Option::HIDE)) - return; - (*users)++; -} - -static -void builtin_getareausers_living_sub(dumb_ptr<block_list> bl, int *users) -{ - if (bool(bl->is_player()->status.option & Option::HIDE)) - return; - if (!pc_isdead(bl->is_player())) - (*users)++; -} - -static -void builtin_getareausers(ScriptState *st) -{ - int x0, y0, x1, y1, users = 0; - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - x0 = conv_num(st, &AARGO2(3)); - y0 = conv_num(st, &AARGO2(4)); - x1 = conv_num(st, &AARGO2(5)); - y1 = conv_num(st, &AARGO2(6)); - - int living = 0; - if (HARGO2(7)) - { - living = conv_num(st, &AARGO2(7)); - } - map_local *m = map_mapname2mapid(str); - if (m == nullptr) - { - push_int<ScriptDataInt>(st->stack, -1); - return; - } - map_foreachinarea(std::bind(living ? builtin_getareausers_living_sub: builtin_getareausers_sub, ph::_1, &users), - m, - x0, y0, - x1, y1, - BL::PC); - push_int<ScriptDataInt>(st->stack, users); -} - -/*========================================== - * エリア指定ドロップアイテム数所得 - *------------------------------------------ - */ -static -void builtin_getareadropitem_sub(dumb_ptr<block_list> bl, ItemNameId item, int *amount) -{ - dumb_ptr<flooritem_data> drop = bl->is_item(); - - if (drop->item_data.nameid == item) - (*amount) += drop->item_data.amount; - -} - -static -void builtin_getareadropitem_sub_anddelete(dumb_ptr<block_list> bl, ItemNameId item, int *amount) -{ - dumb_ptr<flooritem_data> drop = bl->is_item(); - - if (drop->item_data.nameid == item) - { - (*amount) += drop->item_data.amount; - clif_clearflooritem(drop, nullptr); - map_delobject(drop->bl_id, drop->bl_type); - } -} - -static -void builtin_getareadropitem(ScriptState *st) -{ - ItemNameId item; - int x0, y0, x1, y1, amount = 0, delitems = 0; - struct script_data *data; - - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - x0 = conv_num(st, &AARGO2(3)); - y0 = conv_num(st, &AARGO2(4)); - x1 = conv_num(st, &AARGO2(5)); - y1 = conv_num(st, &AARGO2(6)); - - data = &AARGO2(7); - get_val(st, data); - if (data->is<ScriptDataStr>()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data) - item = item_data->nameid; - } - else - item = wrap<ItemNameId>(conv_num(st, data)); - - if (HARGO2(8)) - delitems = conv_num(st, &AARGO2(8)); - - map_local *m = map_mapname2mapid(str); - if (m == nullptr) - { - push_int<ScriptDataInt>(st->stack, -1); - return; - } - if (delitems) - map_foreachinarea(std::bind(builtin_getareadropitem_sub_anddelete, ph::_1, item, &amount), - m, - x0, y0, - x1, y1, - BL::ITEM); - else - map_foreachinarea(std::bind(builtin_getareadropitem_sub, ph::_1, item, &amount), - m, - x0, y0, - x1, y1, - BL::ITEM); - - push_int<ScriptDataInt>(st->stack, amount); -} - -/*========================================== - * NPCの有効化 - *------------------------------------------ - */ -static -void builtin_enablenpc(ScriptState *st) -{ - NpcName str = stringish<NpcName>(ZString(conv_str(st, &AARGO2(2)))); - npc_enable(str, 1); -} - -/*========================================== - * NPCの無効化 - *------------------------------------------ - */ -static -void builtin_disablenpc(ScriptState *st) -{ - NpcName str = stringish<NpcName>(ZString(conv_str(st, &AARGO2(2)))); - npc_enable(str, 0); -} - -/*========================================== - * 状態異常にかかる - *------------------------------------------ - */ -static -void builtin_sc_start(ScriptState *st) -{ - dumb_ptr<block_list> bl; - int val1; - StatusChange type = static_cast<StatusChange>(conv_num(st, &AARGO2(2))); - interval_t tick = static_cast<interval_t>(conv_num(st, &AARGO2(3))); - if (tick < 1_s) - // work around old behaviour of: - // speed potion - // atk potion - // matk potion - // - // which used to use seconds - // all others used milliseconds - tick *= 1000; - val1 = conv_num(st, &AARGO2(4)); - if (HARGO2(5)) //指定したキャラを状態異常にする - bl = map_id2bl(wrap<BlockId>(conv_num(st, &AARGO2(5)))); - else - bl = map_id2bl(st->rid); - skill_status_change_start(bl, type, val1, tick); -} - -/*========================================== - * 状態異常が直る - *------------------------------------------ - */ -static -void builtin_sc_end(ScriptState *st) -{ - dumb_ptr<block_list> bl; - StatusChange type = StatusChange(conv_num(st, &AARGO2(2))); - bl = map_id2bl(st->rid); - skill_status_change_end(bl, type, nullptr); -} - -static -void builtin_sc_check(ScriptState *st) -{ - dumb_ptr<block_list> bl; - StatusChange type = StatusChange(conv_num(st, &AARGO2(2))); - bl = map_id2bl(st->rid); - - push_int<ScriptDataInt>(st->stack, skill_status_change_active(bl, type)); - -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_debugmes(ScriptState *st) -{ - RString mes = conv_str(st, &AARGO2(2)); - PRINTF("script debug : %d %d : %s\n"_fmt, - st->rid, st->oid, mes); -} - -/*========================================== - * ステータスリセット - *------------------------------------------ - */ -static -void builtin_resetstatus(ScriptState *st) -{ - dumb_ptr<map_session_data> sd; - sd = script_rid2sd(st); - pc_resetstate(sd); -} - -/*========================================== - * 性別変換 - *------------------------------------------ - */ -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のアタッチ - *------------------------------------------ - */ -static -void builtin_attachrid(ScriptState *st) -{ - st->rid = wrap<BlockId>(conv_num(st, &AARGO2(2))); - push_int<ScriptDataInt>(st->stack, (map_id2sd(st->rid) != nullptr)); -} - -/*========================================== - * RIDのデタッチ - *------------------------------------------ - */ -static -void builtin_detachrid(ScriptState *st) -{ - st->rid = BlockId(); -} - -/*========================================== - * 存在チェック - *------------------------------------------ - */ -static -void builtin_isloggedin(ScriptState *st) -{ - push_int<ScriptDataInt>(st->stack, - map_id2sd(wrap<BlockId>(conv_num(st, &AARGO2(2)))) != nullptr); -} - -static -void builtin_setmapflag(ScriptState *st) -{ - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - int i = conv_num(st, &AARGO2(3)); - MapFlag mf = map_flag_from_int(i); - map_local *m = map_mapname2mapid(str); - if (m != nullptr) - { - m->flag.set(mf, 1); - } -} - -static -void builtin_removemapflag(ScriptState *st) -{ - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - int i = conv_num(st, &AARGO2(3)); - MapFlag mf = map_flag_from_int(i); - map_local *m = map_mapname2mapid(str); - if (m != nullptr) - { - m->flag.set(mf, 0); - } -} - -static -void builtin_getmapflag(ScriptState *st) -{ - int r = -1; - - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - int i = conv_num(st, &AARGO2(3)); - MapFlag mf = map_flag_from_int(i); - map_local *m = map_mapname2mapid(str); - if (m != nullptr) - { - r = m->flag.get(mf); - } - - push_int<ScriptDataInt>(st->stack, r); -} - -static -void builtin_pvpon(ScriptState *st) -{ - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - map_local *m = map_mapname2mapid(str); - if (m != nullptr && !m->flag.get(MapFlag::PVP) && !m->flag.get(MapFlag::NOPVP)) - { - m->flag.set(MapFlag::PVP, 1); - - if (battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris] - return; - - for (io::FD i : iter_fds()) - { - Session *s = get_session(i); - if (!s) - continue; - map_session_data *pl_sd = static_cast<map_session_data *>(s->session_data.get()); - if (pl_sd && pl_sd->state.auth) - { - if (m == pl_sd->bl_m && !pl_sd->pvp_timer) - { - pl_sd->pvp_timer = Timer(gettick() + 200_ms, - std::bind(pc_calc_pvprank_timer, ph::_1, ph::_2, - pl_sd->bl_id)); - pl_sd->pvp_rank = 0; - pl_sd->pvp_lastusers = 0; - pl_sd->pvp_point = 5; - } - } - } - } - -} - -static -void builtin_pvpoff(ScriptState *st) -{ - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - map_local *m = map_mapname2mapid(str); - if (m != nullptr && m->flag.get(MapFlag::PVP) && m->flag.get(MapFlag::NOPVP)) - { - m->flag.set(MapFlag::PVP, 0); - - if (battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris] - return; - - for (io::FD i : iter_fds()) - { - Session *s = get_session(i); - if (!s) - continue; - map_session_data *pl_sd = static_cast<map_session_data *>(s->session_data.get()); - if (pl_sd && pl_sd->state.auth) - { - if (m == pl_sd->bl_m) - { - pl_sd->pvp_timer.cancel(); - } - } - } - } - -} - -/*========================================== - * NPCエモーション - *------------------------------------------ - */ - -static -void builtin_emotion(ScriptState *st) -{ - int type; - type = conv_num(st, &AARGO2(2)); - if (type < 0 || type > 100) - return; - clif_emotion(map_id2bl(st->oid), type); -} - -static -void builtin_mapwarp(ScriptState *st) // Added by RoVeRT -{ - int x, y; - int x0, y0, x1, y1; - - MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - x0 = 0; - y0 = 0; - map_local *m = map_mapname2mapid(mapname); - x1 = m->xs; - y1 = m->ys; - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(3)))); - x = conv_num(st, &AARGO2(4)); - y = conv_num(st, &AARGO2(5)); - - if (m == nullptr) - return; - - map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y), - m, - x0, y0, - x1, y1, - BL::PC); -} - -static -void builtin_cmdothernpc(ScriptState *st) // Added by RoVeRT -{ - NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARGO2(2)))); - ZString command = ZString(conv_str(st, &AARGO2(3))); - - npc_command(map_id2sd(st->rid), npc, command); -} - -static -void builtin_mobcount_sub(dumb_ptr<block_list> bl, NpcEvent event, int *c) -{ - if (event == bl->is_mob()->npc_event) - (*c)++; -} - -static -void builtin_mobcount(ScriptState *st) // Added by RoVeRT -{ - int c = 0; - MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - ZString event_ = ZString(conv_str(st, &AARGO2(3))); - NpcEvent event; - extract(event_, &event); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - { - push_int<ScriptDataInt>(st->stack, -1); - return; - } - map_foreachinarea(std::bind(builtin_mobcount_sub, ph::_1, event, &c), - m, - 0, 0, - m->xs, m->ys, - BL::MOB); - - push_int<ScriptDataInt>(st->stack, (c - 1)); - -} - -static -void builtin_marriage(ScriptState *st) -{ - CharName partner = stringish<CharName>(ZString(conv_str(st, &AARGO2(2)))); - dumb_ptr<map_session_data> sd = script_rid2sd(st); - dumb_ptr<map_session_data> p_sd = map_nick2sd(partner); - - if (sd == nullptr || p_sd == nullptr || pc_marriage(sd, p_sd) < 0) - { - push_int<ScriptDataInt>(st->stack, 0); - return; - } - push_int<ScriptDataInt>(st->stack, 1); -} - -static -void builtin_divorce(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - st->state = ScriptEndState::STOP; // rely on pc_divorce to restart - - sd->npc_flags.divorce = 1; - - if (sd == nullptr || pc_divorce(sd) < 0) - { - push_int<ScriptDataInt>(st->stack, 0); - return; - } - - push_int<ScriptDataInt>(st->stack, 1); -} - -/*========================================== - * IDからItem名 - *------------------------------------------ - */ -static -void builtin_getitemname(ScriptState *st) -{ - struct item_data *i_data; - struct script_data *data; - - data = &AARGO2(2); - get_val(st, data); - if (data->is<ScriptDataStr>()) - { - ZString name = ZString(conv_str(st, data)); - i_data = itemdb_searchname(name); - } - else - { - ItemNameId item_id = wrap<ItemNameId>(conv_num(st, data)); - i_data = itemdb_search(item_id); - } - - RString item_name; - if (i_data) - item_name = i_data->jname; - else - item_name = "Unknown Item"_s; - - push_str<ScriptDataStr>(st->stack, item_name); -} - -static -void builtin_getspellinvocation(ScriptState *st) -{ - RString name = conv_str(st, &AARGO2(2)); - - AString invocation = magic::magic_find_invocation(name); - if (!invocation) - invocation = "..."_s; - - push_str<ScriptDataStr>(st->stack, invocation); -} - -static -void builtin_getpartnerid2(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status.partner_id)); -} - -/*========================================== - * PCの所持品情報読み取り - *------------------------------------------ - */ -static -void builtin_getinventorylist(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - int j = 0; - if (!sd) - return; - for (IOff0 i : IOff0::iter()) - { - if (sd->status.inventory[i].nameid - && sd->status.inventory[i].amount > 0) - { - pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_id"_s), j), - unwrap<ItemNameId>(sd->status.inventory[i].nameid)); - pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_amount"_s), j), - sd->status.inventory[i].amount); - pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_equip"_s), j), - static_cast<uint16_t>(sd->status.inventory[i].equip)); - j++; - } - } - pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_count"_s)), j); -} - -static -void builtin_getactivatedpoolskilllist(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - SkillID pool_skills[MAX_SKILL_POOL]; - int skill_pool_size = skill_pool(sd, pool_skills); - int i, count = 0; - - if (!sd) - return; - - for (i = 0; i < skill_pool_size; i++) - { - SkillID skill_id = pool_skills[i]; - - if (sd->status.skill[skill_id].lv) - { - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count), - static_cast<uint16_t>(skill_id)); - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count), - sd->status.skill[skill_id].lv); - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count), - static_cast<uint16_t>(sd->status.skill[skill_id].flags)); - pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count), - skill_name(skill_id)); - ++count; - } - } - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count); - -} - -static -void builtin_getunactivatedpoolskilllist(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - int i, count = 0; - - if (!sd) - return; - - for (i = 0; i < skill_pool_skills_size; i++) - { - SkillID skill_id = skill_pool_skills[i]; - - if (sd->status.skill[skill_id].lv - && !bool(sd->status.skill[skill_id].flags & SkillFlags::POOL_ACTIVATED)) - { - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count), - static_cast<uint16_t>(skill_id)); - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count), - sd->status.skill[skill_id].lv); - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count), - static_cast<uint16_t>(sd->status.skill[skill_id].flags)); - pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count), - skill_name(skill_id)); - ++count; - } - } - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count); -} - -static -void builtin_poolskill(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - SkillID skill_id = SkillID(conv_num(st, &AARGO2(2))); - - skill_pool_activate(sd, skill_id); - clif_skillinfoblock(sd); - -} - -static -void builtin_unpoolskill(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - SkillID skill_id = SkillID(conv_num(st, &AARGO2(2))); - - skill_pool_deactivate(sd, skill_id); - clif_skillinfoblock(sd); - -} - -/*========================================== - * NPCから発生するエフェクト - * misceffect(effect, [target]) - * - * effect The effect type/ID. - * target The player name or being ID on - * which to display the effect. If not - * specified, it attempts to default to - * the current NPC or invoking PC. - *------------------------------------------ - */ -static -void builtin_misceffect(ScriptState *st) -{ - int type; - BlockId id; - CharName name; - dumb_ptr<block_list> bl = nullptr; - - type = conv_num(st, &AARGO2(2)); - - if (HARGO2(3)) - { - struct script_data *sdata = &AARGO2(3); - - get_val(st, sdata); - - if (sdata->is<ScriptDataStr>()) - name = stringish<CharName>(ZString(conv_str(st, sdata))); - else - id = wrap<BlockId>(conv_num(st, sdata)); - } - - if (name.to__actual()) - { - dumb_ptr<map_session_data> sd = map_nick2sd(name); - if (sd) - bl = sd; - } - else if (id) - bl = map_id2bl(id); - else if (st->oid) - bl = map_id2bl(st->oid); - else - { - dumb_ptr<map_session_data> sd = script_rid2sd(st); - if (sd) - bl = sd; - } - - if (bl) - clif_misceffect(bl, type); - -} - -/*========================================== - * Special effects [Valaris] - *------------------------------------------ - */ -static -void builtin_specialeffect(ScriptState *st) -{ - dumb_ptr<block_list> bl = map_id2bl(st->oid); - - if (bl == nullptr) - return; - - clif_specialeffect(bl, - conv_num(st, - &AARGO2(2)), - 0); - -} - -static -void builtin_specialeffect2(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - if (sd == nullptr) - return; - - clif_specialeffect(sd, - conv_num(st, - &AARGO2(2)), - 0); - -} - -/*========================================== - * Nude [Valaris] - *------------------------------------------ - */ - -static -void builtin_nude(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - if (sd == nullptr) - return; - - for (EQUIP i : EQUIPs) - { - IOff0 idx = sd->equip_index_maybe[i]; - if (idx.ok()) - pc_unequipitem(sd, idx, CalcStatus::LATER); - } - pc_calcstatus(sd, 0); - -} - -/*========================================== - * UnequipById [Freeyorp] - *------------------------------------------ - */ - -static -void builtin_unequipbyid(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - if (sd == nullptr) - return; - - EQUIP slot_id = EQUIP(conv_num(st, &AARGO2(2))); - - if (slot_id >= EQUIP() && slot_id < EQUIP::COUNT) - { - IOff0 idx = sd->equip_index_maybe[slot_id]; - if (idx.ok()) - pc_unequipitem(sd, idx, CalcStatus::LATER); - } - - pc_calcstatus(sd, 0); - -} - -/*========================================== - * 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, &AARGO2(2)); - - is_atcommand(sd->sess, sd, cmd, GmLevel::from(-1U)); - -} - -/*========================================== - * npcwarp [remoitnane] - * Move NPC to a new position on the same map. - *------------------------------------------ - */ -static -void builtin_npcwarp(ScriptState *st) -{ - int x, y; - dumb_ptr<npc_data> nd = nullptr; - - x = conv_num(st, &AARGO2(2)); - y = conv_num(st, &AARGO2(3)); - NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARGO2(4)))); - nd = npc_name2id(npc); - - if (!nd) - { - PRINTF("builtin_npcwarp: no such npc: %s\n"_fmt, npc); - return; - } - - map_local *m = nd->bl_m; - - /* Crude sanity checks. */ - if (m == nullptr || !nd->bl_prev - || x < 0 || x > m->xs -1 - || y < 0 || y > m->ys - 1) - return; - - npc_enable(npc, 0); - map_delblock(nd); /* [Freeyorp] */ - nd->bl_x = x; - nd->bl_y = y; - map_addblock(nd); - npc_enable(npc, 1); - -} - -/*========================================== - * message [MouseJstr] - *------------------------------------------ - */ - -static -void builtin_message(ScriptState *st) -{ - CharName player = stringish<CharName>(ZString(conv_str(st, &AARGO2(2)))); - ZString msg = ZString(conv_str(st, &AARGO2(3))); - - dumb_ptr<map_session_data> pl_sd = map_nick2sd(player); - if (pl_sd == nullptr) - return; - clif_displaymessage(pl_sd->sess, msg); - -} - -/*========================================== - * npctalk (sends message to surrounding - * area) [Valaris] - *------------------------------------------ - */ - -static -void builtin_npctalk(ScriptState *st) -{ - dumb_ptr<npc_data> nd = map_id_is_npc(st->oid); - RString str = conv_str(st, &AARGO2(2)); - - if (nd) - { - clif_message(nd, str); - } -} - -/*========================================== - * getlook char info. getlook(arg) - *------------------------------------------ - */ -static -void builtin_getlook(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - LOOK type = LOOK(conv_num(st, &AARGO2(2))); - int val = -1; - switch (type) - { - case LOOK::HAIR: //1 - val = sd->status.hair; - break; - case LOOK::WEAPON: //2 - val = static_cast<uint16_t>(sd->status.weapon); - break; - case LOOK::HEAD_BOTTOM: //3 - val = unwrap<ItemNameId>(sd->status.head_bottom); - break; - case LOOK::HEAD_TOP: //4 - val = unwrap<ItemNameId>(sd->status.head_top); - break; - case LOOK::HEAD_MID: //5 - val = unwrap<ItemNameId>(sd->status.head_mid); - break; - case LOOK::HAIR_COLOR: //6 - val = sd->status.hair_color; - break; - case LOOK::CLOTHES_COLOR: //7 - val = sd->status.clothes_color; - break; - case LOOK::SHIELD: //8 - val = unwrap<ItemNameId>(sd->status.shield); - break; - case LOOK::SHOES: //9 - break; - } - - push_int<ScriptDataInt>(st->stack, val); -} - -/*========================================== - * get char save point. argument: 0- map name, 1- x, 2- y - *------------------------------------------ -*/ -static -void builtin_getsavepoint(ScriptState *st) -{ - int x, y, type; - dumb_ptr<map_session_data> sd; - - sd = script_rid2sd(st); - - type = conv_num(st, &AARGO2(2)); - - x = sd->status.save_point.x; - y = sd->status.save_point.y; - switch (type) - { - case 0: - { - RString mapname = sd->status.save_point.map_; - push_str<ScriptDataStr>(st->stack, mapname); - } - break; - case 1: - push_int<ScriptDataInt>(st->stack, x); - break; - case 2: - push_int<ScriptDataInt>(st->stack, y); - break; - } -} - -/*========================================== - * areatimer - *------------------------------------------ - */ -static -void builtin_areatimer_sub(dumb_ptr<block_list> bl, interval_t tick, NpcEvent event) -{ - pc_addeventtimer(bl->is_player(), tick, event); -} - -static -void builtin_areatimer(ScriptState *st) -{ - int x0, y0, x1, y1; - - MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - x0 = conv_num(st, &AARGO2(3)); - y0 = conv_num(st, &AARGO2(4)); - x1 = conv_num(st, &AARGO2(5)); - y1 = conv_num(st, &AARGO2(6)); - interval_t tick = static_cast<interval_t>(conv_num(st, &AARGO2(7))); - ZString event_ = ZString(conv_str(st, &AARGO2(8))); - NpcEvent event; - extract(event_, &event); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return; - - map_foreachinarea(std::bind(builtin_areatimer_sub, ph::_1, tick, event), - m, - x0, y0, - x1, y1, - BL::PC); -} - -/*========================================== - * Check whether the PC is in the specified rectangle - *------------------------------------------ - */ -static -void builtin_isin(ScriptState *st) -{ - int x1, y1, x2, y2; - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - MapName str = stringish<MapName>(ZString(conv_str(st, &AARGO2(2)))); - x1 = conv_num(st, &AARGO2(3)); - y1 = conv_num(st, &AARGO2(4)); - x2 = conv_num(st, &AARGO2(5)); - y2 = conv_num(st, &AARGO2(6)); - - if (!sd) - return; - - push_int<ScriptDataInt>(st->stack, - (sd->bl_x >= x1 && sd->bl_x <= x2) - && (sd->bl_y >= y1 && sd->bl_y <= y2) - && (str == sd->bl_m->name_)); -} - -// Trigger the shop on a (hopefully) nearby shop NPC -static -void builtin_shop(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - dumb_ptr<npc_data> nd; - - if (!sd) - return; - - NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARGO2(2)))); - nd = npc_name2id(name); - if (!nd) - { - PRINTF("builtin_shop: no such npc: %s\n"_fmt, name); - return; - } - - builtin_close(st); - clif_npcbuysell(sd, nd->bl_id); -} - -/*========================================== - * Check whether the PC is dead - *------------------------------------------ - */ -static -void builtin_isdead(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - push_int<ScriptDataInt>(st->stack, pc_isdead(sd)); -} - -/*======================================== - * Changes a NPC name, and sprite - *---------------------------------------- - */ -static -void builtin_fakenpcname(ScriptState *st) -{ - NpcName name = stringish<NpcName>(ZString(conv_str(st, &AARGO2(2)))); - NpcName newname = stringish<NpcName>(ZString(conv_str(st, &AARGO2(3)))); - Species newsprite = wrap<Species>(static_cast<uint16_t>(conv_num(st, &AARGO2(4)))); - dumb_ptr<npc_data> nd = npc_name2id(name); - if (!nd) - { - PRINTF("builtin_fakenpcname: no such npc: %s\n"_fmt, name); - return; - } - nd->name = newname; - nd->npc_class = newsprite; - - // Refresh this npc - npc_enable(name, 0); - npc_enable(name, 1); - -} - -/*============================ - * Gets the PC's x pos - *---------------------------- - */ -static -void builtin_getx(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - push_int<ScriptDataInt>(st->stack, sd->bl_x); -} - -/*============================ - * Gets the PC's y pos - *---------------------------- - */ -static -void builtin_gety(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - push_int<ScriptDataInt>(st->stack, sd->bl_y); -} - -/* - * Get the PC's current map's name - */ -static -void builtin_getmap(ScriptState *st) -{ - dumb_ptr<map_session_data> sd = script_rid2sd(st); - - push_str<ScriptDataStr>(st->stack, sd->bl_m->name_); -} - -static -void builtin_mapexit(ScriptState *) -{ - runflag = 0; -} - - -// -// 実行部main -// -/*========================================== - * コマンドの読み取り - *------------------------------------------ - */ -static -ByteCode get_com(ScriptPointer *script) -{ - if (static_cast<uint8_t>(script->peek()) >= 0x80) - { - // synthetic! Does not advance pos yet. - return ByteCode::INT; - } - return script->pop(); -} - -/*========================================== - * 数値の所得 - *------------------------------------------ - */ -static -int get_num(ScriptPointer *scr) -{ - int i = 0; - int j = 0; - uint8_t val; - do - { - val = static_cast<uint8_t>(scr->pop()); - i += (val & 0x7f) << j; - j += 6; - } - while (val >= 0xc0); - return i; -} - -/*========================================== - * スタックから値を取り出す - *------------------------------------------ - */ -static -int pop_val(ScriptState *st) -{ - if (st->stack->stack_datav.empty()) - return 0; - script_data& back = st->stack->stack_datav.back(); - get_val(st, &back); - int rv = 0; - if (auto *u = back.get_if<ScriptDataInt>()) - rv = u->numi; - st->stack->stack_datav.pop_back(); - return rv; -} - -static -bool isstr(struct script_data& c) -{ - return c.is<ScriptDataStr>(); -} - -/*========================================== - * 加算演算子 - *------------------------------------------ - */ -static -void op_add(ScriptState *st) -{ - get_val(st, &st->stack->stack_datav.back()); - script_data back = st->stack->stack_datav.back(); - st->stack->stack_datav.pop_back(); - - script_data& back1 = st->stack->stack_datav.back(); - get_val(st, &back1); - - if (!(isstr(back) || isstr(back1))) - { - back1.get_if<ScriptDataInt>()->numi += back.get_if<ScriptDataInt>()->numi; - } - else - { - RString sb = conv_str(st, &back); - RString sb1 = conv_str(st, &back1); - MString buf; - buf += sb1; - buf += sb; - back1 = ScriptDataStr{.str= AString(buf)}; - } -} - -/*========================================== - * 二項演算子(文字列) - *------------------------------------------ - */ -static -void op_2str(ScriptState *st, ByteCode op, ZString s1, ZString s2) -{ - int a = 0; - - switch (op) - { - case ByteCode::EQ: - a = s1 == s2; - break; - case ByteCode::NE: - a = s1 != s2; - break; - case ByteCode::GT: - a = s1 > s2; - break; - case ByteCode::GE: - a = s1 >= s2; - break; - case ByteCode::LT: - a = s1 < s2; - break; - case ByteCode::LE: - a = s1 <= s2; - break; - default: - PRINTF("illegal string operater\n"_fmt); - break; - } - - push_int<ScriptDataInt>(st->stack, a); -} - -/*========================================== - * 二項演算子(数値) - *------------------------------------------ - */ -static -void op_2num(ScriptState *st, ByteCode op, int i1, int i2) -{ - switch (op) - { - case ByteCode::SUB: - i1 -= i2; - break; - case ByteCode::MUL: - i1 *= i2; - break; - case ByteCode::DIV: - i1 /= i2; - break; - case ByteCode::MOD: - i1 %= i2; - break; - case ByteCode::AND: - i1 &= i2; - break; - case ByteCode::OR: - i1 |= i2; - break; - case ByteCode::XOR: - i1 ^= i2; - break; - case ByteCode::LAND: - i1 = i1 && i2; - break; - case ByteCode::LOR: - i1 = i1 || i2; - break; - case ByteCode::EQ: - i1 = i1 == i2; - break; - case ByteCode::NE: - i1 = i1 != i2; - break; - case ByteCode::GT: - i1 = i1 > i2; - break; - case ByteCode::GE: - i1 = i1 >= i2; - break; - case ByteCode::LT: - i1 = i1 < i2; - break; - case ByteCode::LE: - i1 = i1 <= i2; - break; - case ByteCode::R_SHIFT: - i1 = i1 >> i2; - break; - case ByteCode::L_SHIFT: - i1 = i1 << i2; - break; - } - push_int<ScriptDataInt>(st->stack, i1); -} - -/*========================================== - * 二項演算子 - *------------------------------------------ - */ -static -void op_2(ScriptState *st, ByteCode op) -{ - // pop_val has unfortunate implications here - script_data d2 = st->stack->stack_datav.back(); - st->stack->stack_datav.pop_back(); - get_val(st, &d2); - script_data d1 = st->stack->stack_datav.back(); - st->stack->stack_datav.pop_back(); - get_val(st, &d1); - - if (isstr(d1) && isstr(d2)) - { - // ss => op_2str - op_2str(st, op, d1.get_if<ScriptDataStr>()->str, d2.get_if<ScriptDataStr>()->str); - } - else if (!(isstr(d1) || isstr(d2))) - { - // ii => op_2num - op_2num(st, op, d1.get_if<ScriptDataInt>()->numi, d2.get_if<ScriptDataInt>()->numi); - } - else - { - // si,is => error - PRINTF("script: op_2: int&str, str&int not allow.\n"_fmt); - push_int<ScriptDataInt>(st->stack, 0); - } -} - -/*========================================== - * 単項演算子 - *------------------------------------------ - */ -static -void op_1num(ScriptState *st, ByteCode op) -{ - int i1; - i1 = pop_val(st); - switch (op) - { - case ByteCode::NEG: - i1 = -i1; - break; - case ByteCode::NOT: - i1 = ~i1; - break; - case ByteCode::LNOT: - i1 = !i1; - break; - } - push_int<ScriptDataInt>(st->stack, i1); -} - -/*========================================== - * 関数の実行 - *------------------------------------------ - */ -void run_func(ScriptState *st) -{ - size_t end_sp = st->stack->stack_datav.size(); - size_t start_sp = end_sp - 1; - while (!st->stack->stack_datav[start_sp].is<ScriptDataArg>()) - { - start_sp--; - if (start_sp == 0) - { - if (battle_config.error_log) - PRINTF("function not found\n"_fmt); - st->state = ScriptEndState::END; - return; - } - } - // the func is before the arg - start_sp--; - st->start = start_sp; - st->end = end_sp; - - if (!st->stack->stack_datav[st->start].is<ScriptDataFuncRef>()) - { - PRINTF("run_func: not function and command! \n"_fmt); - st->state = ScriptEndState::END; - return; - } - size_t func = st->stack->stack_datav[st->start].get_if<ScriptDataFuncRef>()->numi; - - if (DEBUG_RUN && battle_config.etc_log) - { - PRINTF("run_func : %s\n"_fmt, - builtin_functions[func].name); - PRINTF("stack dump :"_fmt); - for (script_data& d : st->stack->stack_datav) - { - MATCH (d) - { - CASE (const ScriptDataInt&, u) - { - PRINTF(" int(%d)"_fmt, u.numi); - } - CASE (const ScriptDataRetInfo&, u) - { - PRINTF(" retinfo(%p)"_fmt, static_cast<const void *>(u.script)); - } - CASE (const ScriptDataParam&, u) - { - PRINTF(" param(%d)"_fmt, u.reg.sp()); - } - CASE (const ScriptDataVariable&, u) - { - PRINTF(" name(%s)[%d]"_fmt, variable_names.outtern(u.reg.base()), u.reg.index()); - } - CASE (const ScriptDataArg&, u) - { - (void)u; - PRINTF(" arg"_fmt); - } - CASE (const ScriptDataPos&, u) - { - (void)u; - PRINTF(" pos(%d)"_fmt, u.numi); - } - CASE (const ScriptDataStr&, u) - { - (void)u; - PRINTF(" str(%s)"_fmt, u.str); - } - CASE (const ScriptDataFuncRef&, u) - { - (void)u; - PRINTF(" func(%s)"_fmt, builtin_functions[u.numi].name); - } - } - } - PRINTF("\n"_fmt); - } - builtin_functions[func].func(st); - - pop_stack(st->stack, start_sp, end_sp); - - if (st->state == ScriptEndState::RETFUNC) - { - // ユーザー定義関数からの復帰 - int olddefsp = st->defsp; - - pop_stack(st->stack, st->defsp, start_sp); // 復帰に邪魔なスタック削除 - if (st->defsp < 4 - || !st->stack->stack_datav[st->defsp - 1].is<ScriptDataRetInfo>()) - { - PRINTF("script:run_func (return) return without callfunc or callsub!\n"_fmt); - st->state = ScriptEndState::END; - return; - } - assert (olddefsp == st->defsp); // pretty sure it hasn't changed yet - st->scriptp.code = conv_script(st, &st->stack->stack_datav[olddefsp - 1]); // スクリプトを復元 - st->scriptp.pos = conv_num(st, &st->stack->stack_datav[olddefsp - 2]); // スクリプト位置の復元 - st->defsp = conv_num(st, &st->stack->stack_datav[olddefsp - 3]); // 基準スタックポインタを復元 - // Number of arguments. - int i = conv_num(st, &st->stack->stack_datav[olddefsp - 4]); // 引数の数所得 - assert (i == 0); - - pop_stack(st->stack, olddefsp - 4 - i, olddefsp); // 要らなくなったスタック(引数と復帰用データ)削除 - - st->state = ScriptEndState::GOTO; - } -} - -// pretend it's external so this can be called in the debugger -void dump_script(const ScriptBuffer *script); -void dump_script(const ScriptBuffer *script) -{ - ScriptPointer scriptp(script, 0); - while (scriptp.pos < reinterpret_cast<const std::vector<ByteCode> *>(script)->size()) - { - PRINTF("%6zu: "_fmt, scriptp.pos); - switch (ByteCode c = get_com(&scriptp)) - { - case ByteCode::EOL: - PRINTF("EOL\n"_fmt); // extra newline between functions - break; - case ByteCode::INT: - // synthesized! - PRINTF("INT %d"_fmt, get_num(&scriptp)); - break; - - case ByteCode::POS: - case ByteCode::VARIABLE: - case ByteCode::FUNC_REF: - case ByteCode::PARAM: - { - int arg = 0; - arg |= static_cast<uint8_t>(scriptp.pop()) << 0; - arg |= static_cast<uint8_t>(scriptp.pop()) << 8; - arg |= static_cast<uint8_t>(scriptp.pop()) << 16; - switch(c) - { - case ByteCode::POS: - PRINTF("POS %d"_fmt, arg); - break; - case ByteCode::VARIABLE: - PRINTF("VARIABLE %s"_fmt, variable_names.outtern(arg)); - break; - case ByteCode::FUNC_REF: - PRINTF("FUNC_REF %s"_fmt, builtin_functions[arg].name); - break; - case ByteCode::PARAM: - PRINTF("PARAM SP::#%d (sorry)"_fmt, arg); - break; - } - } - break; - case ByteCode::ARG: - PRINTF("ARG"_fmt); - break; - case ByteCode::STR: - PRINTF("STR \"%s\""_fmt, scriptp.pops()); - break; - case ByteCode::FUNC: - PRINTF("FUNC"_fmt); - break; - - case ByteCode::ADD: - PRINTF("ADD"_fmt); - break; - case ByteCode::SUB: - PRINTF("SUB"_fmt); - break; - case ByteCode::MUL: - PRINTF("MUL"_fmt); - break; - case ByteCode::DIV: - PRINTF("DIV"_fmt); - break; - case ByteCode::MOD: - PRINTF("MOD"_fmt); - break; - case ByteCode::EQ: - PRINTF("EQ"_fmt); - break; - case ByteCode::NE: - PRINTF("NE"_fmt); - break; - case ByteCode::GT: - PRINTF("GT"_fmt); - break; - case ByteCode::GE: - PRINTF("GE"_fmt); - break; - case ByteCode::LT: - PRINTF("LT"_fmt); - break; - case ByteCode::LE: - PRINTF("LE"_fmt); - break; - case ByteCode::AND: - PRINTF("AND"_fmt); - break; - case ByteCode::OR: - PRINTF("OR"_fmt); - break; - case ByteCode::XOR: - PRINTF("XOR"_fmt); - break; - case ByteCode::LAND: - PRINTF("LAND"_fmt); - break; - case ByteCode::LOR: - PRINTF("LOR"_fmt); - break; - case ByteCode::R_SHIFT: - PRINTF("R_SHIFT"_fmt); - break; - case ByteCode::L_SHIFT: - PRINTF("L_SHIFT"_fmt); - break; - case ByteCode::NEG: - PRINTF("NEG"_fmt); - break; - case ByteCode::NOT: - PRINTF("NOT"_fmt); - break; - case ByteCode::LNOT: - PRINTF("LNOT"_fmt); - break; - - case ByteCode::NOP: - PRINTF("NOP"_fmt); - break; - - default: - PRINTF("??? %d"_fmt, c); - break; - } - PRINTF("\n"_fmt); - } -} - -/*========================================== - * スクリプトの実行メイン部分 - *------------------------------------------ - */ -static -void run_script_main(ScriptState *st, const ScriptBuffer *rootscript) -{ - int cmdcount = script_config.check_cmdcount; - int gotocount = script_config.check_gotocount; - struct script_stack *stack = st->stack; - - st->defsp = stack->stack_datav.size(); - - int rerun_pos = st->scriptp.pos; - st->state = ScriptEndState::ZERO; - while (st->state == ScriptEndState::ZERO) - { - switch (ByteCode c = get_com(&st->scriptp)) - { - case ByteCode::EOL: - if (stack->stack_datav.size() != st->defsp) - { - if (true) - PRINTF("stack.sp (%zu) != default (%d)\n"_fmt, - stack->stack_datav.size(), - st->defsp); - abort(); - } - rerun_pos = st->scriptp.pos; - break; - case ByteCode::INT: - // synthesized! - push_int<ScriptDataInt>(stack, get_num(&st->scriptp)); - break; - - case ByteCode::POS: - case ByteCode::VARIABLE: - case ByteCode::FUNC_REF: - case ByteCode::PARAM: - // Note that these 3 have *very* different meanings, - // despite being encoded similarly. - { - int arg = 0; - arg |= static_cast<uint8_t>(st->scriptp.pop()) << 0; - arg |= static_cast<uint8_t>(st->scriptp.pop()) << 8; - arg |= static_cast<uint8_t>(st->scriptp.pop()) << 16; - switch(c) - { - case ByteCode::POS: - push_int<ScriptDataPos>(stack, arg); - break; - case ByteCode::VARIABLE: - push_reg<ScriptDataVariable>(stack, SIR::from(arg)); - break; - case ByteCode::FUNC_REF: - push_int<ScriptDataFuncRef>(stack, arg); - break; - case ByteCode::PARAM: - SP arg_sp = static_cast<SP>(arg); - push_reg<ScriptDataParam>(stack, SIR::from(arg_sp)); - break; - } - } - break; - case ByteCode::ARG: - push_int<ScriptDataArg>(stack, 0); - break; - case ByteCode::STR: - push_str<ScriptDataStr>(stack, st->scriptp.pops()); - break; - case ByteCode::FUNC: - run_func(st); - if (st->state == ScriptEndState::GOTO) - { - rerun_pos = st->scriptp.pos; - st->state = ScriptEndState::ZERO; - if (gotocount > 0 && (--gotocount) <= 0) - { - PRINTF("run_script: infinity loop !\n"_fmt); - st->state = ScriptEndState::END; - } - } - break; - - case ByteCode::ADD: - op_add(st); - break; - - case ByteCode::SUB: - case ByteCode::MUL: - case ByteCode::DIV: - case ByteCode::MOD: - case ByteCode::EQ: - case ByteCode::NE: - case ByteCode::GT: - case ByteCode::GE: - case ByteCode::LT: - case ByteCode::LE: - case ByteCode::AND: - case ByteCode::OR: - case ByteCode::XOR: - case ByteCode::LAND: - case ByteCode::LOR: - case ByteCode::R_SHIFT: - case ByteCode::L_SHIFT: - op_2(st, c); - break; - - case ByteCode::NEG: - case ByteCode::NOT: - case ByteCode::LNOT: - op_1num(st, c); - break; - - case ByteCode::NOP: - st->state = ScriptEndState::END; - break; - - default: - if (battle_config.error_log) - PRINTF("unknown command : %d @ %zu\n"_fmt, - c, st->scriptp.pos); - st->state = ScriptEndState::END; - break; - } - if (cmdcount > 0 && (--cmdcount) <= 0) - { - PRINTF("run_script: infinity loop !\n"_fmt); - st->state = ScriptEndState::END; - } - } - switch (st->state) - { - case ScriptEndState::STOP: - break; - case ScriptEndState::END: - { - dumb_ptr<map_session_data> sd = map_id2sd(st->rid); - st->scriptp.code = nullptr; - st->scriptp.pos = -1; - if (sd && sd->npc_id == st->oid) - npc_event_dequeue(sd); - } - break; - case ScriptEndState::RERUNLINE: - st->scriptp.pos = rerun_pos; - break; - } - - if (st->state != ScriptEndState::END) - { - // 再開するためにスタック情報を保存 - dumb_ptr<map_session_data> sd = map_id2sd(st->rid); - if (sd) - { - sd->npc_stackbuf = stack->stack_datav; - sd->npc_script = st->scriptp.code; - // sd->npc_pos is set later ... ??? - sd->npc_scriptroot = rootscript; - } - } -} - -/*========================================== - * スクリプトの実行 - *------------------------------------------ - */ -int run_script(ScriptPointer sp, BlockId rid, BlockId oid) -{ - return run_script_l(sp, rid, oid, nullptr); -} - -int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid, - Slice<argrec_t> args) -{ - struct script_stack stack; - ScriptState st; - dumb_ptr<map_session_data> sd = map_id2sd(rid); - const ScriptBuffer *rootscript = sp.code; - int i; - if (sp.code == nullptr || sp.pos >> 24) - return -1; - - if (sd && !sd->npc_stackbuf.empty() && sd->npc_scriptroot == rootscript) - { - // 前回のスタックを復帰 - sp.code = sd->npc_script; - stack.stack_datav = std::move(sd->npc_stackbuf); - } - st.stack = &stack; - st.scriptp = sp; - st.rid = rid; - st.oid = oid; - for (i = 0; i < args.size(); i++) - { - if (args[i].name.back() == '$') - pc_setregstr(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.s); - else - pc_setreg(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.i); - } - run_script_main(&st, rootscript); - - stack.stack_datav.clear(); - return st.scriptp.pos; -} - -/*========================================== - * マップ変数の変更 - *------------------------------------------ - */ -void mapreg_setreg(SIR reg, int val) -{ - mapreg_db.put(reg, val); - - mapreg_dirty = 1; -} - -/*========================================== - * 文字列型マップ変数の変更 - *------------------------------------------ - */ -void mapreg_setregstr(SIR reg, XString str) -{ - if (!str) - mapregstr_db.erase(reg); - else - mapregstr_db.insert(reg, str); - - mapreg_dirty = 1; -} - -/*========================================== - * 永続的マップ変数の読み込み - *------------------------------------------ - */ -static -void script_load_mapreg(void) -{ - io::ReadFile in(mapreg_txt); - - if (!in.is_open()) - return; - - AString line; - while (in.getline(line)) - { - XString buf1, buf2; - int index = 0; - if (extract(line, - record<'\t'>( - record<','>(&buf1, &index), - &buf2)) - || extract(line, - record<'\t'>( - record<','>(&buf1), - &buf2))) - { - int s = variable_names.intern(buf1); - SIR key = SIR::from(s, index); - if (buf1.back() == '$') - { - mapregstr_db.insert(key, buf2); - } - else - { - int v; - if (!extract(buf2, &v)) - goto borken; - mapreg_db.put(key, v); - } - } - else - { - borken: - PRINTF("%s: %s broken data !\n"_fmt, mapreg_txt, AString(buf1)); - continue; - } - } - mapreg_dirty = 0; -} - -/*========================================== - * 永続的マップ変数の書き込み - *------------------------------------------ - */ -static -void script_save_mapreg_intsub(SIR key, int data, io::WriteFile& fp) -{ - int num = key.base(), i = key.index(); - ZString name = variable_names.outtern(num); - if (name[1] != '@') - { - if (i == 0) - FPRINTF(fp, "%s\t%d\n"_fmt, name, data); - else - FPRINTF(fp, "%s,%d\t%d\n"_fmt, name, i, data); - } -} - -static -void script_save_mapreg_strsub(SIR key, ZString data, io::WriteFile& fp) -{ - int num = key.base(), i = key.index(); - ZString name = variable_names.outtern(num); - if (name[1] != '@') - { - if (i == 0) - FPRINTF(fp, "%s\t%s\n"_fmt, name, data); - else - FPRINTF(fp, "%s,%d\t%s\n"_fmt, name, i, data); - } -} - -static -void script_save_mapreg(void) -{ - io::WriteLock fp(mapreg_txt); - if (!fp.is_open()) - return; - for (auto& pair : mapreg_db) - script_save_mapreg_intsub(pair.first, pair.second, fp); - for (auto& pair : mapregstr_db) - script_save_mapreg_strsub(pair.first, pair.second, fp); - mapreg_dirty = 0; -} - -static -void script_autosave_mapreg(TimerData *, tick_t) -{ - if (mapreg_dirty) - script_save_mapreg(); -} - -void do_final_script(void) -{ - if (mapreg_dirty >= 0) - script_save_mapreg(); - - mapreg_db.clear(); - mapregstr_db.clear(); - scriptlabel_db.clear(); - userfunc_db.clear(); - - str_datam.clear(); -} - -/*========================================== - * 初期化 - *------------------------------------------ - */ -void do_init_script(void) -{ - script_load_mapreg(); - - Timer(gettick() + MAPREG_AUTOSAVE_INTERVAL, - script_autosave_mapreg, - MAPREG_AUTOSAVE_INTERVAL - ).detach(); -} - -#define BUILTIN(func, args, ret) \ -{builtin_##func, #func ## _s, args, ret} - -BuiltinFunction builtin_functions[] = -{ - BUILTIN(mes, "s"_s, '\0'), - BUILTIN(goto, "L"_s, '\0'), - BUILTIN(callfunc, "F"_s, '\0'), - BUILTIN(callsub, "L"_s, '\0'), - BUILTIN(return, ""_s, '\0'), - BUILTIN(next, ""_s, '\0'), - BUILTIN(close, ""_s, '\0'), - BUILTIN(close2, ""_s, '\0'), - BUILTIN(menu, "sL**"_s, '\0'), - BUILTIN(rand, "i?"_s, 'i'), - BUILTIN(isat, "Mxy"_s, 'i'), - BUILTIN(warp, "Mxy"_s, '\0'), - BUILTIN(areawarp, "MxyxyMxy"_s, '\0'), - BUILTIN(heal, "ii"_s, '\0'), - BUILTIN(itemheal, "ii"_s, '\0'), - BUILTIN(percentheal, "ii"_s, '\0'), - BUILTIN(input, "N"_s, '\0'), - BUILTIN(if, "iF*"_s, '\0'), - BUILTIN(set, "Ne"_s, '\0'), - BUILTIN(setarray, "Ne*"_s, '\0'), - BUILTIN(cleararray, "Nei"_s, '\0'), - BUILTIN(getarraysize, "N"_s, 'i'), - BUILTIN(getelementofarray, "Ni"_s, '.'), - BUILTIN(setlook, "ii"_s, '\0'), - BUILTIN(countitem, "I"_s, 'i'), - BUILTIN(checkweight, "Ii"_s, 'i'), - BUILTIN(getitem, "Ii??"_s, '\0'), - BUILTIN(makeitem, "IiMxy"_s, '\0'), - BUILTIN(delitem, "Ii"_s, '\0'), - BUILTIN(readparam, "i?"_s, 'i'), - BUILTIN(getcharid, "i?"_s, 'i'), - BUILTIN(strcharinfo, "i"_s, 's'), - BUILTIN(getequipid, "i"_s, 'i'), - BUILTIN(getequipname, "i"_s, 's'), - BUILTIN(statusup2, "ii"_s, '\0'), - BUILTIN(bonus, "ii"_s, '\0'), - BUILTIN(bonus2, "iii"_s, '\0'), - BUILTIN(skill, "ii?"_s, '\0'), - BUILTIN(setskill, "ii"_s, '\0'), - BUILTIN(getskilllv, "i"_s, 'i'), - BUILTIN(getgmlevel, ""_s, 'i'), - BUILTIN(end, ""_s, '\0'), - BUILTIN(getopt2, ""_s, 'i'), - BUILTIN(setopt2, "i"_s, '\0'), - BUILTIN(savepoint, "Mxy"_s, '\0'), - BUILTIN(gettimetick, "i"_s, 'i'), - BUILTIN(gettime, "i"_s, 'i'), - BUILTIN(openstorage, ""_s, '\0'), - BUILTIN(getexp, "ii"_s, '\0'), - BUILTIN(monster, "Mxysmi?"_s, '\0'), - BUILTIN(areamonster, "Mxyxysmi?"_s, '\0'), - BUILTIN(killmonster, "ME"_s, '\0'), - BUILTIN(killmonsterall, "M"_s, '\0'), - BUILTIN(donpcevent, "E"_s, '\0'), - BUILTIN(addtimer, "tE"_s, '\0'), - BUILTIN(initnpctimer, ""_s, '\0'), - BUILTIN(startnpctimer, "?"_s, '\0'), - BUILTIN(stopnpctimer, ""_s, '\0'), - BUILTIN(getnpctimer, "i"_s, 'i'), - BUILTIN(setnpctimer, "i"_s, '\0'), - BUILTIN(announce, "si"_s, '\0'), - BUILTIN(mapannounce, "Msi"_s, '\0'), - BUILTIN(getusers, "i"_s, 'i'), - BUILTIN(getmapusers, "M"_s, 'i'), - BUILTIN(getareausers, "Mxyxy?"_s, 'i'), - BUILTIN(getareadropitem, "Mxyxyi?"_s, 'i'), - BUILTIN(enablenpc, "s"_s, '\0'), - BUILTIN(disablenpc, "s"_s, '\0'), - BUILTIN(sc_start, "iTi?"_s, '\0'), - BUILTIN(sc_end, "i"_s, '\0'), - BUILTIN(sc_check, "i"_s, 'i'), - BUILTIN(debugmes, "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'), - BUILTIN(setmapflag, "Mi"_s, '\0'), - BUILTIN(removemapflag, "Mi"_s, '\0'), - BUILTIN(getmapflag, "Mi"_s, 'i'), - BUILTIN(pvpon, "M"_s, '\0'), - BUILTIN(pvpoff, "M"_s, '\0'), - BUILTIN(emotion, "i"_s, '\0'), - BUILTIN(mapwarp, "MMxy"_s, '\0'), - BUILTIN(cmdothernpc, "ss"_s, '\0'), - BUILTIN(mobcount, "ME"_s, 'i'), - BUILTIN(marriage, "P"_s, 'i'), - BUILTIN(divorce, ""_s, 'i'), - BUILTIN(getitemname, "I"_s, 's'), - BUILTIN(getspellinvocation, "s"_s, 's'), - BUILTIN(getpartnerid2, ""_s, 'i'), - BUILTIN(getinventorylist, ""_s, '\0'), - BUILTIN(getactivatedpoolskilllist, ""_s, '\0'), - BUILTIN(getunactivatedpoolskilllist, ""_s, '\0'), - BUILTIN(poolskill, "i"_s, '\0'), - BUILTIN(unpoolskill, "i"_s, '\0'), - BUILTIN(misceffect, "i?"_s, '\0'), - BUILTIN(specialeffect, "i"_s, '\0'), - 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(message, "Ps"_s, '\0'), - BUILTIN(npctalk, "s"_s, '\0'), - BUILTIN(getlook, "i"_s, 'i'), - BUILTIN(getsavepoint, "i"_s, '.'), - BUILTIN(areatimer, "MxyxytE"_s, '\0'), - BUILTIN(isin, "Mxyxy"_s, 'i'), - BUILTIN(shop, "s"_s, '\0'), - BUILTIN(isdead, ""_s, 'i'), - BUILTIN(fakenpcname, "ssi"_s, '\0'), - BUILTIN(getx, ""_s, 'i'), - BUILTIN(gety, ""_s, 'i'), - BUILTIN(getmap, ""_s, 's'), - BUILTIN(mapexit, ""_s, '\0'), - {nullptr, ""_s, ""_s, '\0'}, -}; - -void set_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e, int val) -{ - size_t k = variable_names.intern(var); - SIR reg = SIR::from(k, e); - set_reg(sd, VariableCode::VARIABLE, reg, val); -} -void set_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e, XString val) -{ - size_t k = variable_names.intern(var); - SIR reg = SIR::from(k, e); - set_reg(sd, VariableCode::VARIABLE, reg, val); -} -int get_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e) -{ - size_t k = variable_names.intern(var); - SIR reg = SIR::from(k, e); - struct script_data dat = ScriptDataVariable{.reg= reg}; - get_val(sd, &dat); - if (auto *u = dat.get_if<ScriptDataInt>()) - return u->numi; - PRINTF("Warning: you lied about the type and I'm too lazy to fix it!"_fmt); - return 0; -} -ZString get_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e) -{ - size_t k = variable_names.intern(var); - SIR reg = SIR::from(k, e); - struct script_data dat = ScriptDataVariable{.reg= reg}; - get_val(sd, &dat); - if (auto *u = dat.get_if<ScriptDataStr>()) - // this is almost certainly a memory leak after CONSTSTR removal - return u->str; - PRINTF("Warning: you lied about the type and I can't fix it!"_fmt); - return ZString(); -} -} // namespace tmwa diff --git a/src/map/script.hpp b/src/map/script.hpp deleted file mode 100644 index d5200a6..0000000 --- a/src/map/script.hpp +++ /dev/null @@ -1,277 +0,0 @@ -#pragma once -// script.hpp - EAthena script frontend, engine, and library. -// -// Copyright © ????-2004 Athena Dev Teams -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "fwd.hpp" - -#include <cstdint> - -#include <vector> - -#include "../range/slice.hpp" - -#include "../strings/zstring.hpp" - -#include "../generic/db.hpp" -#include "../generic/dumb_ptr.hpp" - -#include "../sexpr/variant.hpp" - -#include "../mmo/ids.hpp" - -#include "clif.t.hpp" -#include "map.t.hpp" - - -namespace tmwa -{ -enum class VariableCode : uint8_t -{ - PARAM, - VARIABLE, -}; - -enum class StringCode : uint8_t -{ - NOP, POS, INT, PARAM, FUNC, - VARIABLE, -}; - -enum class ByteCode : uint8_t -{ - // types and specials - // Note that 'INT' is synthetic, and does not occur in the data stream - NOP, POS, INT, PARAM, FUNC, STR, ARG, - VARIABLE, EOL, - - // unary and binary operators - LOR, LAND, LE, LT, GE, GT, EQ, NE, - XOR, OR, AND, ADD, SUB, MUL, DIV, MOD, - NEG, LNOT, NOT, R_SHIFT, L_SHIFT, - - // additions - // needed because FUNC is used for the actual call - FUNC_REF, -}; - -struct str_data_t; - -class ScriptBuffer -{ - typedef ZString::iterator ZSit; - - std::vector<ByteCode> script_buf; -public: - // construction methods used only by script.cpp - void add_scriptc(ByteCode a); - void add_scriptb(uint8_t a); - void add_scripti(uint32_t a); - void add_scriptl(str_data_t *a); - void set_label(str_data_t *ld, int pos_); - ZSit parse_simpleexpr(ZSit p); - ZSit parse_subexpr(ZSit p, int limit); - ZSit parse_expr(ZSit p); - ZSit parse_line(ZSit p, bool *canstep); - void parse_script(ZString src, int line, bool implicit_end); - - // consumption methods used only by script.cpp - ByteCode operator[](size_t i) const { return script_buf[i]; } - ZString get_str(size_t i) const - { - return ZString(strings::really_construct_from_a_pointer, reinterpret_cast<const char *>(&script_buf[i]), nullptr); - } - - // method used elsewhere -}; - -struct ScriptPointer -{ - const ScriptBuffer *code; - size_t pos; - - ScriptPointer() - : code() - , pos() - {} - - ScriptPointer(const ScriptBuffer *c, size_t p) - : code(c) - , pos(p) - {} - - ByteCode peek() const { return (*code)[pos]; } - ByteCode pop() { return (*code)[pos++]; } - ZString pops() - { - ZString rv = code->get_str(pos); - pos += rv.size(); - ++pos; - return rv; - } -}; - -// internal -class SIR -{ - uint32_t impl; - SIR(SP v) - : impl(static_cast<uint32_t>(v)) - {} - SIR(unsigned v, uint8_t i) - : impl((i << 24) | v) - {} -public: - SIR() : impl() {} - - unsigned base() const { return impl & 0x00ffffff; } - uint8_t index() const { return impl >> 24; } - SIR iplus(uint8_t i) const { return SIR(base(), index() + i); } - static SIR from(unsigned v, uint8_t i=0) { return SIR(v, i); } - - SP sp() const { return static_cast<SP>(impl); } - static SIR from(SP v) { return SIR(v); } - - friend bool operator == (SIR l, SIR r) { return l.impl == r.impl; } - friend bool operator < (SIR l, SIR r) { return l.impl < r.impl; } -}; - -struct ScriptDataPos -{ - int numi; -}; -struct ScriptDataInt -{ - int numi; -}; -struct ScriptDataParam -{ - SIR reg; -}; -struct ScriptDataStr -{ - RString str; -}; -struct ScriptDataArg -{ - int numi; -}; -struct ScriptDataVariable -{ - SIR reg; -}; -struct ScriptDataRetInfo -{ - // Not a ScriptPointer - pos is stored in a separate slot, - // to avoid exploding the struct for everyone. - const ScriptBuffer *script; -}; -struct ScriptDataFuncRef -{ - int numi; -}; - -using ScriptDataVariantBase = Variant< - ScriptDataPos, - ScriptDataInt, - ScriptDataParam, - ScriptDataStr, - ScriptDataArg, - ScriptDataVariable, - ScriptDataRetInfo, - ScriptDataFuncRef ->; -struct script_data : ScriptDataVariantBase -{ - script_data() = delete; - // TODO see if I can delete the copy ctor/assign instead of defaulting - script_data(script_data&&) = default; - script_data(const script_data&) = default /*delete*/; - script_data& operator = (script_data&&) = default; - script_data& operator = (const script_data&) = default /*delete*/; - - script_data(ScriptDataPos v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataInt v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataParam v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataStr v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataArg v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataVariable v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataRetInfo v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataFuncRef v) : ScriptDataVariantBase(std::move(v)) {} -}; - -struct script_stack -{ - std::vector<struct script_data> stack_datav; -}; - -enum class ScriptEndState; -// future improvements coming! -class ScriptState -{ -public: - struct script_stack *stack; - int start, end; - ScriptEndState state; - BlockId rid, oid; - ScriptPointer scriptp, new_scriptp; - int defsp, new_defsp; -}; - -std::unique_ptr<const ScriptBuffer> parse_script(ZString, int, bool implicit_end); - -struct argrec_t -{ - ZString name; - union _aru - { - int i; - ZString s; - - _aru(int n) : i(n) {} - _aru(ZString z) : s(z) {} - } v; - - argrec_t(ZString n, int i) : name(n), v(i) {} - argrec_t(ZString n, ZString z) : name(n), v(z) {} -}; -int run_script_l(ScriptPointer, BlockId, BlockId, Slice<argrec_t> args); -int run_script(ScriptPointer, BlockId, BlockId); - -extern -Map<ScriptLabel, int> scriptlabel_db; -extern -UPMap<RString, const ScriptBuffer> userfunc_db; - -void do_init_script(void); -void do_final_script(void); - -extern AString mapreg_txt; - -extern int script_errors; - -bool read_constdb(ZString filename); - -void set_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e, int val); -void set_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e, XString val); - -int get_script_var_i(dumb_ptr<map_session_data> sd, VarName var, int e); -ZString get_script_var_s(dumb_ptr<map_session_data> sd, VarName var, int e); -} // namespace tmwa diff --git a/src/map/script.py b/src/map/script.py deleted file mode 100644 index a5010cd..0000000 --- a/src/map/script.py +++ /dev/null @@ -1,25 +0,0 @@ -class script_data(object): - enabled = True - - test_extra = ''' - using tmwa::operator "" _s; - ''' - - tests = [ - ('tmwa::script_data(tmwa::ScriptDataPos{42})', - '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataPos) = {numi = 42}}, <No data fields>}'), - ('tmwa::script_data(tmwa::ScriptDataInt{123})', - '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataInt) = {numi = 123}}, <No data fields>}'), - ('tmwa::script_data(tmwa::ScriptDataParam{tmwa::SIR()})', - '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataParam) = {reg = {impl = 0}}}, <No data fields>}'), - ('tmwa::script_data(tmwa::ScriptDataStr{"Hello"_s})', - '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataStr) = {str = "Hello"}}, <No data fields>}'), - ('tmwa::script_data(tmwa::ScriptDataArg{0})', - '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataArg) = {numi = 0}}, <No data fields>}'), - ('tmwa::script_data(tmwa::ScriptDataVariable{tmwa::SIR()})', - '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataVariable) = {reg = {impl = 0}}}, <No data fields>}'), - ('tmwa::script_data(tmwa::ScriptDataRetInfo{static_cast<const tmwa::ScriptBuffer *>(nullptr)})', - '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataRetInfo) = {script = 0x0}}, <No data fields>}'), - ('tmwa::script_data(tmwa::ScriptDataFuncRef{404})', - '{<tmwa::sexpr::Variant<tmwa::ScriptDataPos, tmwa::ScriptDataInt, tmwa::ScriptDataParam, tmwa::ScriptDataStr, tmwa::ScriptDataArg, tmwa::ScriptDataVariable, tmwa::ScriptDataRetInfo, tmwa::ScriptDataFuncRef>> = {(tmwa::ScriptDataFuncRef) = {numi = 404}}, <No data fields>}'), - ] diff --git a/src/map/skill-pools.cpp b/src/map/skill-pools.cpp index 89bf426..dfc70b0 100644 --- a/src/map/skill-pools.cpp +++ b/src/map/skill-pools.cpp @@ -21,9 +21,12 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" + +#include "../mmo/cxxstdio_enums.hpp" #include "battle.hpp" +#include "consts.hpp" +#include "globals.hpp" #include "pc.hpp" #include "../poison.hpp" @@ -31,26 +34,18 @@ namespace tmwa { -Array<SkillID, MAX_POOL_SKILLS> skill_pool_skills; -int skill_pool_skills_size = 0; - +namespace map +{ void skill_pool_register(SkillID id) { - if (skill_pool_skills_size + 1 >= MAX_POOL_SKILLS) - { - FPRINTF(stderr, - "Too many pool skills! Increase MAX_POOL_SKILLS and recompile."_fmt); - return; - } - - skill_pool_skills[skill_pool_skills_size++] = id; + skill_pool_skills.push_back(id); } int skill_pool(dumb_ptr<map_session_data> sd, SkillID *skills) { int i, count = 0; - for (i = 0; count < MAX_SKILL_POOL && i < skill_pool_skills_size; i++) + for (i = 0; count < MAX_SKILL_POOL && i < skill_pool_skills.size(); i++) { SkillID skill_id = skill_pool_skills[i]; if (bool(sd->status.skill[skill_id].flags & SkillFlags::POOL_ACTIVATED)) @@ -147,4 +142,5 @@ int skill_power_bl(dumb_ptr<block_list> bl, SkillID skill) else return 0; } +} // namespace map } // namespace tmwa diff --git a/src/map/skill-pools.hpp b/src/map/skill-pools.hpp index c8890e8..0f75e8e 100644 --- a/src/map/skill-pools.hpp +++ b/src/map/skill-pools.hpp @@ -23,4 +23,7 @@ namespace tmwa { +namespace map +{ +} // namespace map } // namespace tmwa diff --git a/src/map/skill.cpp b/src/map/skill.cpp index add6a42..8a397a3 100644 --- a/src/map/skill.cpp +++ b/src/map/skill.cpp @@ -39,16 +39,18 @@ #include "../generic/random.hpp" #include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" +#include "../io/extract.hpp" #include "../io/read.hpp" #include "../net/timer.hpp" -#include "../mmo/extract.hpp" +#include "../mmo/cxxstdio_enums.hpp" #include "../mmo/extract_enums.hpp" #include "battle.hpp" +#include "battle_conf.hpp" #include "clif.hpp" +#include "globals.hpp" #include "magic-stmt.hpp" #include "mob.hpp" #include "pc.hpp" @@ -58,7 +60,10 @@ namespace tmwa { -struct skill_name_db skill_names[] = +namespace map +{ +static +skill_name_db skill_names[] = { {SkillID::AC_OWL, "OWL"_s, "Owl's_Eye"_s}, @@ -90,9 +95,6 @@ struct skill_name_db skill_names[] = {SkillID::ZERO, ""_s, ""_s} }; -earray<skill_db_, SkillID, SkillID::MAX_SKILL_DB> skill_db; - - static int skill_attack(BF attack_type, dumb_ptr<block_list> src, dumb_ptr<block_list> dsrc, dumb_ptr<block_list> bl, @@ -286,7 +288,7 @@ int skill_attack(BF attack_type, dumb_ptr<block_list> src, lv = flag.level; dmg = battle_calc_attack(attack_type, src, bl, skillid, skilllv, flag.lo); //ダメージ計算 - damage = dmg.damage + dmg.damage2; + damage = dmg.damage; if (lv == 15) lv = -1; @@ -347,26 +349,16 @@ int skill_attack(BF attack_type, dumb_ptr<block_list> src, { hp += (dmg.damage * sd->hp_drain_per) / 100; } - if (sd->hp_drain_rate_ && dmg.damage2 > 0 - && random_::chance({sd->hp_drain_rate_, 100})) - { - hp += (dmg.damage2 * sd->hp_drain_per_) / 100; - } if (sd->sp_drain_rate > 0 && dmg.damage > 0 && random_::chance({sd->sp_drain_rate, 100})) { sp += (dmg.damage * sd->sp_drain_per) / 100; } - if (sd->sp_drain_rate_ > 0 && dmg.damage2 > 0 - && random_::chance({sd->sp_drain_rate_, 100})) - { - sp += (dmg.damage2 * sd->sp_drain_per_) / 100; - } if (hp || sp) pc_heal(sd, hp, sp); } - return (dmg.damage + dmg.damage2); /* 与ダメを返す */ + return (dmg.damage); /* 与ダメを返す */ } typedef int(*SkillFunc)(dumb_ptr<block_list>, dumb_ptr<block_list>, @@ -388,17 +380,6 @@ void skill_area_sub(dumb_ptr<block_list> bl, } -/* 範囲スキル使用処理小分けここまで - * ------------------------------------------------------------------------- - */ - -// these variables are set in the 'else' branches, -// and used in the (recursive) 'if' branch -// TODO kill it, kill it with fire. -static BlockId skill_area_temp_id; -static int skill_area_temp_hp; - - /*========================================== * スキル使用(詠唱完了、ID指定攻撃系) * (スパゲッティに向けて1歩前進!(ダメポ)) @@ -712,8 +693,7 @@ void skill_status_change_end(dumb_ptr<block_list> bl, StatusChange type, TimerDa { eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; int opt_flag = 0, calc_flag = 0; - short *sc_count; - Option *option; + Opt0 *option; Opt1 *opt1; Opt2 *opt2; Opt3 *opt3; @@ -728,8 +708,6 @@ void skill_status_change_end(dumb_ptr<block_list> bl, StatusChange type, TimerDa sc_data = battle_get_sc_data(bl); if (not sc_data) return; - sc_count = battle_get_sc_count(bl); - nullpo_retv(sc_count); option = battle_get_option(bl); nullpo_retv(option); opt1 = battle_get_opt1(bl); @@ -753,8 +731,6 @@ void skill_status_change_end(dumb_ptr<block_list> bl, StatusChange type, TimerDa // whether we are the timer or a cancel no longer matters assert (!sc_data[type].timer); - assert ((*sc_count) > 0); - (*sc_count)--; switch (type) { /* 異常の種類ごとの処理 */ @@ -835,7 +811,6 @@ void skill_status_change_timer(TimerData *tid, tick_t tick, BlockId id, StatusCh dumb_ptr<block_list> bl; dumb_ptr<map_session_data> sd = nullptr; eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; - //short *sc_count; //使ってない? if ((bl = map_id2bl(id)) == nullptr) return; @@ -847,8 +822,6 @@ void skill_status_change_timer(TimerData *tid, tick_t tick, BlockId id, StatusCh if (bl->bl_type == BL::PC) sd = bl->is_player(); - //sc_count=battle_get_sc_count(bl); //使ってない? - if (sc_data[type].spell_invocation) { // Must report termination magic::spell_effect_report_termination(sc_data[type].spell_invocation, @@ -936,8 +909,7 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type, { dumb_ptr<map_session_data> sd = nullptr; eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; - short *sc_count; - Option *option; + Opt0 *option; Opt1 *opt1; Opt2 *opt2; Opt3 *opt3; @@ -949,8 +921,6 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type, sc_data = battle_get_sc_data(bl); if (not sc_data) return 0; - sc_count = battle_get_sc_count(bl); - nullpo_retz(sc_count); option = battle_get_option(bl); nullpo_retz(option); opt1 = battle_get_opt1(bl); @@ -994,7 +964,6 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type, /* 継ぎ足しができない状態異常である時は状態異常を行わない */ { - (*sc_count)--; sc_data[type].timer.cancel(); } } @@ -1080,8 +1049,6 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type, if (opt_flag) /* optionの変更 */ clif_changeoption(bl); - (*sc_count)++; /* ステータス異常の数 */ - sc_data[type].val1 = val1; if (sc_data[type].spell_invocation) // Supplant by newer spell magic::spell_effect_report_termination(sc_data[type].spell_invocation, @@ -1110,8 +1077,7 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type, int skill_status_change_clear(dumb_ptr<block_list> bl, int type) { eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; - short *sc_count; - Option *option; + Opt0 *option; Opt1 *opt1; Opt2 *opt2; Opt3 *opt3; @@ -1120,8 +1086,6 @@ int skill_status_change_clear(dumb_ptr<block_list> bl, int type) sc_data = battle_get_sc_data(bl); if (not sc_data) return 0; - sc_count = battle_get_sc_count(bl); - nullpo_retz(sc_count); option = battle_get_option(bl); nullpo_retz(option); opt1 = battle_get_opt1(bl); @@ -1131,18 +1095,15 @@ int skill_status_change_clear(dumb_ptr<block_list> bl, int type) opt3 = battle_get_opt3(bl); nullpo_retz(opt3); - if (*sc_count == 0) - return 0; for (StatusChange i : erange(StatusChange(), StatusChange::MAX_STATUSCHANGE)) { if (sc_data[i].timer) skill_status_change_end(bl, i, nullptr); } - *sc_count = 0; *opt1 = Opt1::ZERO; *opt2 = Opt2::ZERO; *opt3 = Opt3::ZERO; - *option = Option::ZERO; + *option = Opt0::ZERO; if (type == 0 || type & 2) clif_changeoption(bl); @@ -1319,4 +1280,5 @@ skill_name_db& skill_lookup_by_name(XString name) return ner; return skill_names[num_names - 1]; } +} // namespace map } // namespace tmwa diff --git a/src/map/skill.hpp b/src/map/skill.hpp index ec353ce..23881d4 100644 --- a/src/map/skill.hpp +++ b/src/map/skill.hpp @@ -20,16 +20,15 @@ // 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 "../mmo/skill.t.hpp" + #include "fwd.hpp" -#include "skill.t.hpp" #include "skill-pools.hpp" -#include "../strings/fwd.hpp" #include "../strings/rstring.hpp" #include "../strings/literal.hpp" -#include "../generic/fwd.hpp" #include "../generic/array.hpp" #include "map.hpp" @@ -37,6 +36,8 @@ namespace tmwa { +namespace map +{ constexpr int MAX_SKILL_PRODUCE_DB = 150; constexpr int MAX_SKILL_ARROW_DB = 150; constexpr int MAX_SKILL_ABRA_DB = 350; @@ -60,8 +61,6 @@ struct skill_db_ int weapon; Array<int, MAX_SKILL_LEVEL> castnodex; }; -extern -earray<skill_db_, SkillID, SkillID::MAX_SKILL_DB> skill_db; struct skill_name_db { @@ -75,9 +74,6 @@ struct skill_name_db {} }; -// used only by @skillid for iteration - should be depublicized -extern struct skill_name_db skill_names[]; - skill_name_db& skill_lookup_by_id(SkillID id); skill_name_db& skill_lookup_by_name(XString name); @@ -135,11 +131,6 @@ void skill_reload(void); // Max. # of active entries in the skill pool constexpr int MAX_SKILL_POOL = 3; -// Max. # of skills that may be classified as pool skills in db/skill_db.txt -constexpr int MAX_POOL_SKILLS = 128; - -extern Array<SkillID, MAX_POOL_SKILLS> skill_pool_skills; // All pool skills -extern int skill_pool_skills_size; // Number of entries in skill_pool_skills // Yields all active skills in the skill pool; no more than MAX_SKILL_POOL. Return is number of skills. int skill_pool(dumb_ptr<map_session_data> sd, SkillID *skills); @@ -166,4 +157,5 @@ int skill_power_bl(dumb_ptr<block_list> bl, SkillID skill); // [Fate] Remember that a certain skill ID belongs to a pool skill void skill_pool_register(SkillID id); +} // namespace map } // namespace tmwa diff --git a/src/map/storage.cpp b/src/map/storage.cpp index a6e6a0d..1327146 100644 --- a/src/map/storage.cpp +++ b/src/map/storage.cpp @@ -25,10 +25,11 @@ #include "../generic/db.hpp" #include "../mmo/ids.hpp" -#include "../mmo/mmo.hpp" +#include "../high/mmo.hpp" #include "chrif.hpp" #include "clif.hpp" +#include "globals.hpp" #include "intif.hpp" #include "itemdb.hpp" #include "map.hpp" @@ -39,27 +40,22 @@ namespace tmwa { -static -Map<AccountId, Storage> storage_db; - +namespace map +{ void do_final_storage(void) { storage_db.clear(); } -Storage *account2storage(AccountId account_id) +Borrowed<Storage> account2storage(AccountId account_id) { - Storage *stor = storage_db.search(account_id); - if (stor == nullptr) - { - stor = storage_db.init(account_id); - stor->account_id = account_id; - } + P<Storage> stor = storage_db.init(account_id); + stor->account_id = account_id; return stor; } // Just to ask storage, without creation -Storage *account2storage2(AccountId account_id) +Option<Borrowed<Storage>> account2storage2(AccountId account_id) { return storage_db.search(account_id); } @@ -81,12 +77,11 @@ int storage_storageopen(dumb_ptr<map_session_data> sd) if (sd->state.storage_open) return 1; //Already open? - Storage *stor = storage_db.search(sd->status_key.account_id); - if (stor == nullptr) + P<Storage> stor = TRY_UNWRAP(storage_db.search(sd->status_key.account_id), { //Request storage. intif_request_storage(sd->status_key.account_id); return 1; - } + }); if (stor->storage_status) return 1; //Already open/player already has it open... @@ -104,15 +99,13 @@ int storage_storageopen(dumb_ptr<map_session_data> sd) *------------------------------------------ */ static -int storage_additem(dumb_ptr<map_session_data> sd, Storage *stor, +int storage_additem(dumb_ptr<map_session_data> sd, P<Storage> stor, Item *item_data, int amount) { - struct item_data *data; - if (!item_data->nameid || amount <= 0) return 1; - data = itemdb_search(item_data->nameid); + P<struct item_data> data = itemdb_search(item_data->nameid); if (!itemdb_isequip2(data)) { //Stackable @@ -152,7 +145,7 @@ int storage_additem(dumb_ptr<map_session_data> sd, Storage *stor, *------------------------------------------ */ static -int storage_delitem(dumb_ptr<map_session_data> sd, Storage *stor, +int storage_delitem(dumb_ptr<map_session_data> sd, P<Storage> stor, SOff0 n, int amount) { @@ -178,11 +171,8 @@ int storage_delitem(dumb_ptr<map_session_data> sd, Storage *stor, */ int storage_storageadd(dumb_ptr<map_session_data> sd, IOff0 index, int amount) { - Storage *stor; - nullpo_retz(sd); - stor = account2storage2(sd->status_key.account_id); - nullpo_retz(stor); + P<Storage> stor = TRY_UNWRAP(account2storage2(sd->status_key.account_id), return 0); if ((stor->storage_amount > MAX_STORAGE) || !stor->storage_status) return 0; // storage full / storage closed @@ -213,12 +203,10 @@ int storage_storageadd(dumb_ptr<map_session_data> sd, IOff0 index, int amount) */ int storage_storageget(dumb_ptr<map_session_data> sd, SOff0 index, int amount) { - Storage *stor; PickupFail flag; nullpo_retz(sd); - stor = account2storage2(sd->status_key.account_id); - nullpo_retz(stor); + P<Storage> stor = TRY_UNWRAP(account2storage2(sd->status_key.account_id), return 0); if (!index.ok()) return 0; @@ -243,11 +231,8 @@ int storage_storageget(dumb_ptr<map_session_data> sd, SOff0 index, int amount) */ int storage_storageclose(dumb_ptr<map_session_data> sd) { - Storage *stor; - nullpo_retz(sd); - stor = account2storage2(sd->status_key.account_id); - nullpo_retz(stor); + P<Storage> stor = TRY_UNWRAP(account2storage2(sd->status_key.account_id), return 0); clif_storageclose(sd); if (stor->storage_status) @@ -275,12 +260,9 @@ int storage_storageclose(dumb_ptr<map_session_data> sd) */ int storage_storage_quit(dumb_ptr<map_session_data> sd) { - Storage *stor; - nullpo_retz(sd); - stor = account2storage2(sd->status_key.account_id); - if (stor) + P<Storage> stor = TRY_UNWRAP(account2storage2(sd->status_key.account_id), return 0); { chrif_save(sd); //Invokes the storage saving as well. stor->storage_status = 0; @@ -292,11 +274,7 @@ int storage_storage_quit(dumb_ptr<map_session_data> sd) int storage_storage_save(AccountId account_id, int final) { - Storage *stor; - - stor = account2storage2(account_id); - if (!stor) - return 0; + P<Storage> stor = TRY_UNWRAP(account2storage2(account_id), return 0); if (stor->dirty) { @@ -320,9 +298,8 @@ int storage_storage_save(AccountId account_id, int final) //Ack from Char-server indicating the storage was saved. [Skotlex] int storage_storage_saved(AccountId account_id) { - Storage *stor = account2storage2(account_id); + P<Storage> stor = TRY_UNWRAP(account2storage2(account_id), return 0); - if (stor) { //Only mark it clean if it's not in use. [Skotlex] if (stor->dirty && stor->storage_status == 0) @@ -334,4 +311,5 @@ int storage_storage_saved(AccountId account_id) } return 0; } +} // namespace map } // namespace tmwa diff --git a/src/map/storage.hpp b/src/map/storage.hpp index f3875c8..5f118c3 100644 --- a/src/map/storage.hpp +++ b/src/map/storage.hpp @@ -22,23 +22,24 @@ #include "fwd.hpp" -#include "../generic/fwd.hpp" +#include "../proto2/net-Storage.hpp" -#include "../mmo/fwd.hpp" - -#include "clif.t.hpp" +#include "../mmo/clif.t.hpp" namespace tmwa { +namespace map +{ int storage_storageopen(dumb_ptr<map_session_data> sd); int storage_storageadd(dumb_ptr<map_session_data> sd, IOff0 index, int amount); int storage_storageget(dumb_ptr<map_session_data> sd, SOff0 index, int amount); int storage_storageclose(dumb_ptr<map_session_data> sd); void do_final_storage(void); -Storage *account2storage(AccountId account_id); -Storage *account2storage2(AccountId account_id); +Borrowed<Storage> account2storage(AccountId account_id); +Option<Borrowed<Storage>> account2storage2(AccountId account_id); int storage_storage_quit(dumb_ptr<map_session_data> sd); int storage_storage_save(AccountId account_id, int final); int storage_storage_saved(AccountId account_id); +} // namespace map } // namespace tmwa diff --git a/src/map/tmw.cpp b/src/map/tmw.cpp index 60b5027..df76720 100644 --- a/src/map/tmw.cpp +++ b/src/map/tmw.cpp @@ -28,13 +28,16 @@ #include "../io/cxxstdio.hpp" +#include "../net/timer.hpp" + #include "../mmo/human_time_diff.hpp" -#include "../mmo/utils.hpp" #include "atcommand.hpp" #include "battle.hpp" +#include "battle_conf.hpp" #include "chrif.hpp" #include "clif.hpp" +#include "globals.hpp" #include "intif.hpp" #include "map.hpp" #include "pc.hpp" @@ -44,8 +47,10 @@ namespace tmwa { +namespace map +{ static -void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, int length); +void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, std::chrono::hours length); static bool tmw_CheckChatLameness(dumb_ptr<map_session_data> sd, XString message); @@ -53,21 +58,21 @@ bool tmw_CheckChatLameness(dumb_ptr<map_session_data> sd, XString message); int tmw_CheckChatSpam(dumb_ptr<map_session_data> sd, XString message) { nullpo_retr(1, sd); - TimeT now = TimeT::now(); + tick_t now = gettick(); if (pc_isGM(sd)) return 0; if (now > sd->chat_reset_due) { - sd->chat_reset_due = static_cast<time_t>(now) + battle_config.chat_spam_threshold; + sd->chat_reset_due = now + battle_config.chat_spam_threshold; sd->chat_lines_in = 0; } if (now > sd->chat_repeat_reset_due) { sd->chat_repeat_reset_due = - static_cast<time_t>(now) + (battle_config.chat_spam_threshold * 60); + now + (battle_config.chat_spam_threshold * 60); sd->chat_total_repeats = 0; } @@ -100,20 +105,20 @@ int tmw_CheckChatSpam(dumb_ptr<map_session_data> sd, XString message) return 1; } - if (battle_config.chat_spam_ban && + if (battle_config.chat_spam_ban != std::chrono::hours::zero() && (sd->chat_lines_in >= battle_config.chat_spam_warn || sd->chat_total_repeats >= battle_config.chat_spam_warn)) { - clif_displaymessage(sd->sess, "WARNING: You are about to be automatically banned for spam!"_s); - clif_displaymessage(sd->sess, "WARNING: Please slow down, do not repeat, and do not SHOUT!"_s); + clif_displaymessage(sd->sess, "##1WARNING : ##BYou are about to be automatically banned for spam!"_s); + clif_displaymessage(sd->sess, "##1WARNING : ##BPlease slow down, do not repeat, and do not SHOUT!"_s); } return 0; } -void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, int length) +void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, std::chrono::hours length) { - if (length == 0 || sd->auto_ban_info.in_progress) + if (length == std::chrono::hours::zero() || sd->auto_ban_info.in_progress) return; sd->auto_ban_info.in_progress = 1; @@ -124,7 +129,7 @@ void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, int length) tmw_GmHackMsg(hack_msg); AString fake_command = STRPRINTF("@autoban %s %dh (%s spam)"_fmt, - sd->status_key.name, length, reason); + sd->status_key.name, static_cast<uint16_t>(length.count()), reason); log_atcommand(sd, fake_command); AString anotherbuf = STRPRINTF("You have been banned for %s spamming. Please do not spam."_fmt, @@ -133,7 +138,7 @@ void tmw_AutoBan(dumb_ptr<map_session_data> sd, ZString reason, int length) clif_displaymessage(sd->sess, anotherbuf); /* type: 2 - ban(year, month, day, hour, minute, second) */ HumanTimeDiff ban_len {}; - ban_len.hour = length; + ban_len.hour = length.count(); chrif_char_ask_name(AccountId(), sd->status_key.name, 2, ban_len); clif_setwaitclose(sd->sess); } @@ -162,8 +167,9 @@ bool tmw_CheckChatLameness(dumb_ptr<map_session_data>, XString message) // Sends a whisper to all GMs void tmw_GmHackMsg(ZString line) { - intif_wis_message_to_gm(wisp_server_name, - GmLevel::from(static_cast<uint32_t>(battle_config.hack_info_GM_level)), + intif_wis_message_to_gm(WISP_SERVER_NAME, + battle_config.hack_info_GM_level, line); } +} // namespace map } // namespace tmwa diff --git a/src/map/tmw.hpp b/src/map/tmw.hpp index ffd6f7f..bc0043c 100644 --- a/src/map/tmw.hpp +++ b/src/map/tmw.hpp @@ -21,13 +21,12 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" - -#include "../generic/fwd.hpp" - namespace tmwa { +namespace map +{ int tmw_CheckChatSpam(dumb_ptr<map_session_data> sd, XString message); void tmw_GmHackMsg(ZString line); +} // namespace map } // namespace tmwa diff --git a/src/map/trade.cpp b/src/map/trade.cpp index c52377d..c03609c 100644 --- a/src/map/trade.cpp +++ b/src/map/trade.cpp @@ -25,7 +25,9 @@ #include "../io/cxxstdio.hpp" #include "battle.hpp" +#include "battle_conf.hpp" #include "clif.hpp" +#include "globals.hpp" #include "itemdb.hpp" #include "map.hpp" #include "npc.hpp" @@ -37,6 +39,8 @@ namespace tmwa { +namespace map +{ /*========================================== * 取引要請を相手に送る *------------------------------------------ @@ -129,7 +133,6 @@ void trade_tradeack(dumb_ptr<map_session_data> sd, int type) void trade_tradeadditem(dumb_ptr<map_session_data> sd, IOff2 index, int amount) { dumb_ptr<map_session_data> target_sd; - struct item_data *id; int trade_i; int trade_weight = 0; int free_ = 0; @@ -156,7 +159,7 @@ void trade_tradeadditem(dumb_ptr<map_session_data> sd, IOff2 index, int amount) for (IOff0 i : IOff0::iter()) { if (!target_sd->status.inventory[i].nameid - && target_sd->inventory_data[i] == nullptr) + && target_sd->inventory_data[i].is_none()) free_++; } for (trade_i = 0; trade_i < TRADE_MAX; trade_i++) @@ -164,26 +167,31 @@ void trade_tradeadditem(dumb_ptr<map_session_data> sd, IOff2 index, int amount) if (sd->deal_item_amount[trade_i] == 0) { // calculate trade weight + // note: 'abort' branch is protected by 'amount' check above trade_weight += - sd->inventory_data[index.unshift()]->weight * amount; + TRY_UNWRAP(sd->inventory_data[index.unshift()], abort())->weight * amount; // determine if item is a stackable already in receivers inventory, and up free count for (IOff0 i : IOff0::iter()) { - if (target_sd->status.inventory[i].nameid == - sd->status.inventory[index.unshift()].nameid - && target_sd->inventory_data[i] != nullptr) + if (target_sd->status.inventory[i].nameid != + sd->status.inventory[index.unshift()].nameid) + continue; + + OMATCH_BEGIN_SOME (id, target_sd->inventory_data[i]) { - id = target_sd->inventory_data[i]; if (id->type != ItemType::WEAPON && id->type != ItemType::ARMOR && id->type != ItemType::_7 && id->type != ItemType::_8) { free_++; - break; + goto break_outer1; } } + OMATCH_END (); + break_outer1: + break; } if (target_sd->weight + trade_weight > @@ -218,28 +226,32 @@ void trade_tradeadditem(dumb_ptr<map_session_data> sd, IOff2 index, int amount) else { // calculate weight for stored deal + // note: 'abort' branch is protected by 'amount' check above trade_weight += - sd->inventory_data[sd->deal_item_index[trade_i].unshift() - ]->weight * + TRY_UNWRAP(sd->inventory_data[sd->deal_item_index[trade_i].unshift() + ], abort())->weight * sd->deal_item_amount[trade_i]; // count free stackables in stored deal for (IOff0 i : IOff0::iter()) { - if (target_sd->status.inventory[i].nameid == + if (target_sd->status.inventory[i].nameid != sd->status. - inventory[sd->deal_item_index[trade_i].unshift()].nameid - && target_sd->inventory_data[i] != nullptr) + inventory[sd->deal_item_index[trade_i].unshift()].nameid) + continue; + OMATCH_BEGIN_SOME (id, target_sd->inventory_data[i]) { - id = target_sd->inventory_data[i]; if (id->type != ItemType::WEAPON && id->type != ItemType::ARMOR && id->type != ItemType::_7 && id->type != ItemType::_8) { free_++; - break; + goto break_outer2; } } + OMATCH_END (); + break_outer2: + break; } } // used a slot, but might be cancelled out by stackable checks above @@ -465,4 +477,5 @@ void trade_verifyzeny(dumb_ptr<map_session_data> sd) } } } +} // namespace map } // namespace tmwa diff --git a/src/map/trade.hpp b/src/map/trade.hpp index 91ed954..569524b 100644 --- a/src/map/trade.hpp +++ b/src/map/trade.hpp @@ -22,13 +22,13 @@ #include "fwd.hpp" -#include "../generic/fwd.hpp" - -#include "clif.t.hpp" +#include "../mmo/clif.t.hpp" namespace tmwa { +namespace map +{ void trade_traderequest(dumb_ptr<map_session_data> sd, BlockId target_id); void trade_tradeack(dumb_ptr<map_session_data> sd, int type); void trade_tradeadditem(dumb_ptr<map_session_data> sd, IOff2 index, int amount); @@ -36,4 +36,5 @@ void trade_tradeok(dumb_ptr<map_session_data> sd); void trade_tradecancel(dumb_ptr<map_session_data> sd); void trade_tradecommit(dumb_ptr<map_session_data> sd); void trade_verifyzeny(dumb_ptr<map_session_data> sd); +} // namespace map } // namespace tmwa diff --git a/src/map/clif.t.hpp b/src/mmo/clif.t.hpp index 1789ee8..0a51523 100644 --- a/src/map/clif.t.hpp +++ b/src/mmo/clif.t.hpp @@ -30,8 +30,8 @@ #include "../generic/enum.hpp" -#include "../mmo/consts.hpp" -#include "../mmo/enums.hpp" +#include "consts.hpp" +#include "enums.hpp" namespace tmwa @@ -40,7 +40,7 @@ namespace e { // [Fate] status.option properties. These are persistent status changes. // IDs that are not listed are not used in the code (to the best of my knowledge) -enum class Option : uint16_t +enum class Opt0 : uint16_t { ZERO = 0x0000, @@ -88,11 +88,11 @@ enum class Opt3 : uint16_t _assumptio = 0x0800, }; -ENUM_BITWISE_OPERATORS(Option) +ENUM_BITWISE_OPERATORS(Opt0) ENUM_BITWISE_OPERATORS(Opt2) ENUM_BITWISE_OPERATORS(Opt3) } -using e::Option; +using e::Opt0; using e::Opt1; using e::Opt2; using e::Opt3; diff --git a/src/mmo/config_parse.cpp b/src/mmo/config_parse.cpp index 8362810..6e43471 100644 --- a/src/mmo/config_parse.cpp +++ b/src/mmo/config_parse.cpp @@ -23,7 +23,10 @@ #include "../strings/xstring.hpp" #include "../strings/zstring.hpp" +#include "../compat/borrow.hpp" + #include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" #include "../io/line.hpp" #include "version.hpp" @@ -39,39 +42,138 @@ bool is_comment(XString line) } template<class ZS> +static +bool do_split(io::Spanned<ZS> line, io::Spanned<XString> *key, io::Spanned<ZS> *value) +{ + typename ZS::iterator colon = std::find(line.data.begin(), line.data.end(), ':'); + if (colon == line.data.end()) + return false; + key->data = line.data.xislice_h(colon); + key->span = line.span; + key->span.end.column = key->span.begin.column + key->data.size() - 1; + ++colon; + value->data = line.data.xislice_t(colon); + value->span = line.span; + value->span.begin.column = value->span.end.column - value->data.size() + 1; + return true; +} + +template<class ZS> +static +io::Spanned<ZS> do_lstrip(io::Spanned<ZS> value) +{ + io::Spanned<ZS> rv; + rv.data = value.data.lstrip(); + rv.span = value.span; + rv.span.begin.column += (value.data.size() - rv.data.size()); + return rv; +} + +static +io::Spanned<XString> do_rstrip(io::Spanned<XString> value) +{ + io::Spanned<XString> rv; + rv.data = value.data.rstrip(); + rv.span = value.span; + rv.span.end.column -= (value.data.size() - rv.data.size()); + return rv; +} + +static +io::Spanned<XString> do_strip(io::Spanned<XString> value) +{ + return do_lstrip(do_rstrip(value)); +} + +template<class ZS> inline -bool config_split_impl(ZS line, XString *key, ZS *value) +bool config_split_impl(io::Spanned<ZS> line, io::Spanned<XString> *key, io::Spanned<ZS> *value) { // unconditionally fail if line contains control characters - if (std::find_if(line.begin(), line.end(), + if (std::find_if(line.data.begin(), line.data.end(), [](unsigned char c) { return c < ' '; } - ) != line.end()) - return false; - // try to find a colon, fail if not found - typename ZS::iterator colon = std::find(line.begin(), line.end(), ':'); - if (colon == line.end()) + ) != line.data.end()) return false; - *key = line.xislice_h(colon).strip(); - // move past the colon and any spaces - ++colon; - *value = line.xislice_t(colon).lstrip(); + if (!do_split(line, key, value)) + return false; + *key = do_strip(*key); + *value = do_lstrip(*value); return true; } // eventually this should go away -bool config_split(ZString line, XString *key, ZString *value) +// currently the only real offenders are io::FD::open and *PRINTF +bool config_split(io::Spanned<ZString> line, io::Spanned<XString> *key, io::Spanned<ZString> *value) { return config_split_impl(line, key, value); } -// and use this instead -bool config_split(XString line, XString *key, XString *value) + +static +bool check_version(io::Spanned<XString> avers, Borrowed<bool> valid) { - return config_split_impl(line, key, value); + enum + { + GE, LE, GT, LT + } cmp; + + if (avers.data.startswith(">="_s)) + { + cmp = GE; + avers.data = avers.data.xslice_t(2); + avers.span.begin.column += 2; + } + else if (avers.data.startswith('>')) + { + cmp = GT; + avers.data = avers.data.xslice_t(1); + avers.span.begin.column += 1; + } + else if (avers.data.startswith("<="_s)) + { + cmp = LE; + avers.data = avers.data.xslice_t(2); + avers.span.begin.column += 2; + } + else if (avers.data.startswith('<')) + { + cmp = LT; + avers.data = avers.data.xslice_t(1); + avers.span.begin.column += 1; + } + else + { + avers.span.error("Version check must begin with one of: '>=', '>', '<=', '<'"_s); + *valid = false; + return false; + } + + Version vers; + if (!extract(avers.data, &vers)) + { + avers.span.error("Bad value"_s); + *valid = false; + return false; + } + switch (cmp) + { + case GE: + return CURRENT_VERSION >= vers; + case GT: + return CURRENT_VERSION > vers; + case LE: + return CURRENT_VERSION <= vers; + case LT: + return CURRENT_VERSION < vers; + } + abort(); } /// Master config parser. This handles 'import' and 'version-ge' etc. /// Then it defers to the inferior parser for a line it does not understand. +/// +/// Note: old-style 'version-ge: 1.2.3' etc apply to the rest of the file, but +/// new-style 'version: >= 1.2.3' apply only up to the next 'version:' bool load_config_file(ZString filename, ConfigItemParser slave) { io::LineReader in(filename); @@ -80,30 +182,66 @@ bool load_config_file(ZString filename, ConfigItemParser slave) PRINTF("Unable to open file: %s\n"_fmt, filename); return false; } - io::Line line; + io::Line line_; bool rv = true; - while (in.read_line(line)) + bool good_version = true; + while (in.read_line(line_)) { - if (is_comment(line.text)) + if (is_comment(line_.text)) continue; - XString key; - ZString value; - if (!config_split(line.text, &key, &value)) + auto line = io::respan(line_.to_span(), ZString(line_.text)); + io::Spanned<XString> key; + io::Spanned<ZString> value; + if (!config_split(line, &key, &value)) { - line.error("Bad config line"_s); + line.span.error("Bad config line"_s); rv = false; continue; } - if (key == "import"_s) + if (key.data == "version"_s) + { + if (value.data == "all"_s) + { + good_version = true; + } + else + { + good_version = true; + while (good_version && value.data) + { + ZString::iterator it = std::find(value.data.begin(), value.data.end(), ' '); + io::Spanned<XString> value_head; + value_head.data = value.data.xislice_h(it); + value_head.span = value.span; + value.data = value.data.xislice_t(it).lstrip(); + + value_head.span.end.column = value_head.span.begin.column + value_head.data.size() - 1; + value.span.begin.column = value.span.end.column - value.data.size() + 1; + + good_version &= check_version(value_head, borrow(rv)); + } + } + continue; + } + if (!good_version) { - rv &= load_config_file(value, slave); continue; } - else if (key == "version-lt"_s) + if (key.data == "import"_s) + { + if (!load_config_file(value.data, slave)) + { + value.span.error("Failed to include file"_s); + rv = false; + } + continue; + } + else if (key.data == "version-lt"_s) { Version vers; - if (!extract(value, &vers)) + if (!extract(value.data, &vers)) { + value.span.error("Bad value"_s); rv = false; continue; } @@ -111,47 +249,48 @@ bool load_config_file(ZString filename, ConfigItemParser slave) continue; break; } - else if (key == "version-le"_s) + else if (key.data == "version-le"_s) { Version vers; - if (!extract(value, &vers)) + if (!extract(value.data, &vers)) { rv = false; + value.span.error("Bad value"_s); continue; } if (CURRENT_VERSION <= vers) continue; break; } - else if (key == "version-gt"_s) + else if (key.data == "version-gt"_s) { Version vers; - if (!extract(value, &vers)) + if (!extract(value.data, &vers)) { rv = false; + value.span.error("Bad value"_s); continue; } if (CURRENT_VERSION > vers) continue; break; } - else if (key == "version-ge"_s) + else if (key.data == "version-ge"_s) { Version vers; - if (!extract(value, &vers)) + if (!extract(value.data, &vers)) { rv = false; + value.span.error("Bad value"_s); continue; } if (CURRENT_VERSION >= vers) continue; break; } - else if (!slave(key, value)) + else { - line.error("Bad config key or value"_s); - rv = false; - continue; + rv &= slave(key, value); } // nothing to see here, move along } diff --git a/src/mmo/config_parse.hpp b/src/mmo/config_parse.hpp index 50a115e..06432ba 100644 --- a/src/mmo/config_parse.hpp +++ b/src/mmo/config_parse.hpp @@ -20,16 +20,13 @@ #include "fwd.hpp" -#include "../strings/fwd.hpp" - namespace tmwa { -typedef bool (*ConfigItemParser)(XString key, ZString value); +using ConfigItemParser = bool(io::Spanned<XString> key, io::Spanned<ZString> value); bool is_comment(XString line); -bool config_split(ZString line, XString *key, ZString *value); -bool config_split(XString line, XString *key, XString *value); +bool config_split(io::Spanned<ZString> line, io::Spanned<XString> *key, io::Spanned<ZString> *value); /// Master config parser. This handles 'import' and 'version-ge' etc. /// Then it defers to the inferior parser for a line it does not understand. diff --git a/src/mmo/config_parse_test.cpp b/src/mmo/config_parse_test.cpp new file mode 100644 index 0000000..e1170cb --- /dev/null +++ b/src/mmo/config_parse_test.cpp @@ -0,0 +1,60 @@ +#include "config_parse.hpp" +// config_parse_test.cpp - Testsuite for config parsers +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include <gtest/gtest.h> + +#include "../strings/literal.hpp" +#include "../strings/rstring.hpp" + +#include "../io/span.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +#define EXPECT_SPAN(span, bl,bc, el,ec) \ + ({ \ + EXPECT_EQ((span).begin.line, bl); \ + EXPECT_EQ((span).begin.column, bc); \ + EXPECT_EQ((span).end.line, el); \ + EXPECT_EQ((span).end.column, ec); \ + }) +TEST(configparse, keyvalue) +{ + // 0 1 2 3 + // 123456789012345678901234567890 + RString data = " key : value "_s; + + io::Spanned<ZString> input, value; + io::Spanned<XString> key; + input.data = data; + input.span.begin.text = data; + input.span.begin.filename = "<config parse key/value test>"_s; + input.span.begin.line = 1; + input.span.begin.column = 1; + input.span.end = input.span.begin; + input.span.end.column = data.size(); + EXPECT_EQ(data.size(), 30); + ASSERT_TRUE(config_split(input, &key, &value)); + EXPECT_SPAN(key.span, 1,3, 1,5); + EXPECT_SPAN(value.span, 1,18, 1,30); +} +} // namespace tmwa diff --git a/src/mmo/consts.hpp b/src/mmo/consts.hpp index c1a7eb6..5533446 100644 --- a/src/mmo/consts.hpp +++ b/src/mmo/consts.hpp @@ -54,6 +54,11 @@ constexpr int MAX_PARTY = 12; #define MIN_CLOTH_COLOR battle_config.min_cloth_color #define MAX_CLOTH_COLOR battle_config.max_cloth_color +namespace map +{ + struct map_session_data; +} + // WTF is this doing here? struct PartyMember { @@ -61,6 +66,6 @@ struct PartyMember CharName name; MapName map; int leader, online, lv; - struct map_session_data *sd; + map::map_session_data *sd; }; } // namespace tmwa diff --git a/src/io/cxxstdio_enums.hpp b/src/mmo/cxxstdio_enums.hpp index 05cdcae..28a8a14 100644 --- a/src/io/cxxstdio_enums.hpp +++ b/src/mmo/cxxstdio_enums.hpp @@ -29,49 +29,58 @@ namespace tmwa { namespace e { -enum class BF : uint16_t; enum class EPOS : uint16_t; -enum class MapCell : uint8_t; -enum class Option : uint16_t; +enum class Opt0 : uint16_t; inline -auto decay_for_printf(BF v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline auto decay_for_printf(EPOS v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } inline -auto decay_for_printf(MapCell v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } +auto decay_for_printf(Opt0 v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } +} // namespace e + +enum class ItemLook : uint16_t; +enum class SP : uint16_t; +enum class SkillID : uint16_t; +enum class StatusChange : uint16_t; + +inline +auto decay_for_printf(ItemLook v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } +inline +auto decay_for_printf(SP v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } inline -auto decay_for_printf(Option v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -} +auto decay_for_printf(SkillID v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } +inline +auto decay_for_printf(StatusChange v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } + +namespace map +{ +namespace e +{ +enum class BF : uint16_t; +enum class MapCell : uint8_t; +inline +auto decay_for_printf(BF v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } +inline +auto decay_for_printf(MapCell v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } +} // namespace map::e namespace magic { enum class SPELLARG : uint8_t; inline auto decay_for_printf(SPELLARG v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -} +} // namespace map::magic enum class BL : uint8_t; enum class ByteCode : uint8_t; -enum class ItemLook : uint16_t; enum class MS : uint8_t; -enum class SP : uint16_t; -enum class SkillID : uint16_t; -enum class StatusChange : uint16_t; inline auto decay_for_printf(BL v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } inline auto decay_for_printf(ByteCode v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } inline -auto decay_for_printf(ItemLook v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline auto decay_for_printf(MS v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(SP v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(SkillID v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } -inline -auto decay_for_printf(StatusChange v) -> typename remove_enum<decltype(v)>::type { return typename remove_enum<decltype(v)>::type(v); } +} // namespace map } // namespace tmwa diff --git a/src/mmo/enums.cpp b/src/mmo/enums.cpp deleted file mode 100644 index d05be91..0000000 --- a/src/mmo/enums.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "enums.hpp" -// enums.cpp - Common enumerated types -// -// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/mmo/enums.hpp b/src/mmo/enums.hpp index bf8a75c..caecc13 100644 --- a/src/mmo/enums.hpp +++ b/src/mmo/enums.hpp @@ -66,14 +66,14 @@ enum class SkillFlags : uint16_t; } using e::SkillFlags; -// Option and Opt1..3 in map.hpp +// Opt0 and Opt1..3 in map.hpp namespace e { -enum class Option : uint16_t; -constexpr Option get_enum_min_value(Option) { return Option(0x0000); } -constexpr Option get_enum_max_value(Option) { return Option(0xffff); } +enum class Opt0 : uint16_t; +constexpr Opt0 get_enum_min_value(Opt0) { return Opt0(0x0000); } +constexpr Opt0 get_enum_max_value(Opt0) { return Opt0(0xffff); } } -using e::Option; +using e::Opt0; enum class ATTR { @@ -101,22 +101,10 @@ enum class ItemLook : uint16_t { NONE = 0, BLADE = 1, // or some other common weapons - _2, SETZER_AND_SCYTHE = 3, - _6, STAFF = 10, BOW = 11, - _13 = 13, - _14 = 14, - _16 = 16, - SINGLE_HANDED_COUNT = 17, - - DUAL_BLADE = 0x11, - DUAL_2 = 0x12, - DUAL_6 = 0x13, - DUAL_12 = 0x14, - DUAL_16 = 0x15, - DUAL_26 = 0x16, + COUNT = 17, }; enum class SEX : uint8_t @@ -124,6 +112,7 @@ enum class SEX : uint8_t FEMALE = 0, MALE = 1, // For items. This is also used as error, sometime. + // TODO switch to Option<SEX> where appropriate. NEUTRAL = 2, }; inline diff --git a/src/mmo/extract_enums.cpp b/src/mmo/extract_enums.cpp index f906179..efafa39 100644 --- a/src/mmo/extract_enums.cpp +++ b/src/mmo/extract_enums.cpp @@ -23,4 +23,37 @@ namespace tmwa { +bool impl_extract(XString str, DIR *d) +{ + unsigned di; + if (extract(str, &di) && di < 8) + { + *d = static_cast<DIR>(di); + return true; + } + const struct + { + LString str; + DIR d; + } dirs[] = + { + {"S"_s, DIR::S}, + {"SW"_s, DIR::SW}, + {"W"_s, DIR::W}, + {"NW"_s, DIR::NW}, + {"N"_s, DIR::N}, + {"NE"_s, DIR::NE}, + {"E"_s, DIR::E}, + {"SE"_s, DIR::SE}, + }; + for (auto& pair : dirs) + { + if (str == pair.str) + { + *d = pair.d; + return true; + } + } + return false; +} } // namespace tmwa diff --git a/src/mmo/extract_enums.hpp b/src/mmo/extract_enums.hpp index 613fae9..0e8ac4c 100644 --- a/src/mmo/extract_enums.hpp +++ b/src/mmo/extract_enums.hpp @@ -22,7 +22,9 @@ #include <cstdint> -#include "extract.hpp" +#include "../io/extract.hpp" + +#include "clif.t.hpp" namespace tmwa @@ -30,40 +32,54 @@ namespace tmwa namespace e { enum class EPOS : uint16_t; -enum class MobMode : uint16_t; enum class Opt1 : uint16_t; enum class Opt2 : uint16_t; -enum class Option : uint16_t; +enum class Opt0 : uint16_t; inline -bool extract(XString str, EPOS *iv) { return extract_as_int(str, iv); } +bool impl_extract(XString str, EPOS *iv) { return extract_as_int(str, iv); } inline -bool extract(XString str, MobMode *iv) { return extract_as_int(str, iv); } +bool impl_extract(XString str, Opt1 *iv) { return extract_as_int(str, iv); } inline -bool extract(XString str, Opt1 *iv) { return extract_as_int(str, iv); } +bool impl_extract(XString str, Opt2 *iv) { return extract_as_int(str, iv); } inline -bool extract(XString str, Opt2 *iv) { return extract_as_int(str, iv); } -inline -bool extract(XString str, Option *iv) { return extract_as_int(str, iv); } -} +bool impl_extract(XString str, Opt0 *iv) { return extract_as_int(str, iv); } +} // namespace e enum class ItemLook : uint16_t; enum class ItemType : uint8_t; -enum class Race : uint8_t; enum class SEX : uint8_t; enum class SkillID : uint16_t; enum class StatusChange : uint16_t; inline -bool extract(XString str, ItemLook *iv) { return extract_as_int(str, iv); } +bool impl_extract(XString str, ItemLook *iv) { return extract_as_int(str, iv); } inline -bool extract(XString str, ItemType *iv) { return extract_as_int(str, iv); } +bool impl_extract(XString str, ItemType *iv) { return extract_as_int(str, iv); } inline -bool extract(XString str, Race *iv) { return extract_as_int(str, iv); } +bool impl_extract(XString str, SEX *iv) { return extract_as_int(str, iv); } inline -bool extract(XString str, SEX *iv) { return extract_as_int(str, iv); } +bool impl_extract(XString str, SkillID *iv) { return extract_as_int(str, iv); } +inline +bool impl_extract(XString str, StatusChange *iv) { return extract_as_int(str, iv); } + +bool impl_extract(XString, DIR *); + +namespace map +{ +namespace e +{ +enum class MobMode : uint16_t; + +inline +bool impl_extract(XString str, MobMode *iv) { return extract_as_int(str, iv); } +} // namespace map::e +enum class Race : uint8_t; +enum class ATK; + inline -bool extract(XString str, SkillID *iv) { return extract_as_int(str, iv); } +bool impl_extract(XString str, Race *iv) { return extract_as_int(str, iv); } inline -bool extract(XString str, StatusChange *iv) { return extract_as_int(str, iv); } +bool impl_extract(XString str, ATK *iv) { return extract_as_int(str, iv); } +} // namespace map } // namespace tmwa diff --git a/src/mmo/fwd.hpp b/src/mmo/fwd.hpp index 3b56bfb..434885e 100644 --- a/src/mmo/fwd.hpp +++ b/src/mmo/fwd.hpp @@ -20,21 +20,29 @@ #include "../sanity.hpp" +#include "../ints/fwd.hpp" // rank 1 +#include "../strings/fwd.hpp" // rank 1 +#include "../compat/fwd.hpp" // rank 2 +#include "../generic/fwd.hpp" // rank 3 +#include "../io/fwd.hpp" // rank 4 +#include "../net/fwd.hpp" // rank 5 +// mmo/fwd.hpp is rank 6 + namespace tmwa { // meh, add more when I feel like it class MapName; class CharName; -class CharPair; class HumanTimeDiff; +class Species; class AccountId; class CharId; class PartyId; -class ItemUnkId; class ItemNameId; +class BlockId; class GmLevel; class AccountName; @@ -43,26 +51,14 @@ class AccountCrypt; class AccountEmail; class ServerName; class PartyName; +class QuestId; class VarName; class MapName; class CharName; -class Item; -#if 0 -class Point; -class SkillValue; -#endif -class GlobalReg; -#if 0 -class CharKey; -class CharData; -class CharPair; -#endif -class Storage; -#if 0 -class GM_Account; -class PartyMember; -#endif -class PartyMost; -class PartyPair; +struct MobName; +struct NpcName; +struct ScriptLabel; +struct ItemName; +struct NpcEvent; } // namespace tmwa diff --git a/src/mmo/human_time_diff.cpp b/src/mmo/human_time_diff.cpp index 49a7664..aaa7928 100644 --- a/src/mmo/human_time_diff.cpp +++ b/src/mmo/human_time_diff.cpp @@ -18,9 +18,51 @@ // 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 "../poison.hpp" namespace tmwa { +bool impl_extract(XString str, HumanTimeDiff *iv) +{ + // str is a sequence of [-+]?[0-9]+([ay]|m|[jd]|h|mn|s) + // there are NO spaces here + // parse by counting the number starts + auto is_num = [](char c) + { return c == '-' || c == '+' || ('0' <= c && c <= '9'); }; + if (!str || !is_num(str.front())) + return false; + *iv = HumanTimeDiff{}; + while (str) + { + auto it = std::find_if_not(str.begin(), str.end(), is_num); + auto it2 = std::find_if(it, str.end(), is_num); + XString number = str.xislice_h(it); + XString suffix = str.xislice(it, it2); + str = str.xislice_t(it2); + + short *ptr = nullptr; + if (suffix == "y"_s || suffix == "a"_s) + ptr = &iv->year; + else if (suffix == "m"_s) + ptr = &iv->month; + else if (suffix == "j"_s || suffix == "d"_s) + ptr = &iv->day; + else if (suffix == "h"_s) + ptr = &iv->hour; + else if (suffix == "mn"_s) + ptr = &iv->minute; + else if (suffix == "s"_s) + ptr = &iv->second; + else + return false; + if (number.startswith('+') && !number.startswith("+-"_s)) + number = number.xslice_t(1); + if (*ptr || !extract(number, ptr)) + return false; + } + return true; +} } // namespace tmwa diff --git a/src/mmo/human_time_diff.hpp b/src/mmo/human_time_diff.hpp index b5c19fb..92b3288 100644 --- a/src/mmo/human_time_diff.hpp +++ b/src/mmo/human_time_diff.hpp @@ -24,8 +24,6 @@ #include "../strings/xstring.hpp" -#include "extract.hpp" - namespace tmwa { @@ -44,46 +42,5 @@ struct HumanTimeDiff return !bool(*this); } }; - -inline -bool extract(XString str, HumanTimeDiff *iv) -{ - // str is a sequence of [-+]?[0-9]+([ay]|m|[jd]|h|mn|s) - // there are NO spaces here - // parse by counting the number starts - auto is_num = [](char c) - { return c == '-' || c == '+' || ('0' <= c && c <= '9'); }; - if (!str || !is_num(str.front())) - return false; - *iv = HumanTimeDiff{}; - while (str) - { - auto it = std::find_if_not(str.begin(), str.end(), is_num); - auto it2 = std::find_if(it, str.end(), is_num); - XString number = str.xislice_h(it); - XString suffix = str.xislice(it, it2); - str = str.xislice_t(it2); - - short *ptr = nullptr; - if (suffix == "y"_s || suffix == "a"_s) - ptr = &iv->year; - else if (suffix == "m"_s) - ptr = &iv->month; - else if (suffix == "j"_s || suffix == "d"_s) - ptr = &iv->day; - else if (suffix == "h"_s) - ptr = &iv->hour; - else if (suffix == "mn"_s) - ptr = &iv->minute; - else if (suffix == "s"_s) - ptr = &iv->second; - else - return false; - if (number.startswith('+') && !number.startswith("+-"_s)) - number = number.xslice_t(1); - if (*ptr || !extract(number, ptr)) - return false; - } - return true; -} +bool impl_extract(XString str, HumanTimeDiff *iv); } // namespace tmwa diff --git a/src/mmo/human_time_diff_test.cpp b/src/mmo/human_time_diff_test.cpp index c18599d..08a75bf 100644 --- a/src/mmo/human_time_diff_test.cpp +++ b/src/mmo/human_time_diff_test.cpp @@ -20,6 +20,8 @@ #include <gtest/gtest.h> +#include "../io/extract.hpp" + #include "../poison.hpp" diff --git a/src/mmo/ids.cpp b/src/mmo/ids.cpp index d40d5c3..65c470b 100644 --- a/src/mmo/ids.cpp +++ b/src/mmo/ids.cpp @@ -18,9 +18,25 @@ // 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 "../poison.hpp" namespace tmwa { +bool impl_extract(XString str, Species *w) +{ + // lots of data files use this + if (str == "-1"_s) + { + *w = NEGATIVE_SPECIES; + return true; + } + return extract(str, &w->_value); +} +bool impl_extract(XString str, GmLevel *lvl) +{ + return extract(str, &lvl->bits); +} } // namespace tmwa diff --git a/src/mmo/ids.hpp b/src/mmo/ids.hpp index 4e2b97c..28b146a 100644 --- a/src/mmo/ids.hpp +++ b/src/mmo/ids.hpp @@ -23,8 +23,6 @@ #include "../ints/little.hpp" #include "../ints/wrap.hpp" -#include "extract.hpp" - namespace tmwa { @@ -32,17 +30,7 @@ class Species : public Wrapped<uint16_t> { public: explicit operator bool() cons constexpr Species NEGATIVE_SPECIES = Species(); -inline -bool extract(XString str, Species *w) -{ - // lots of data files use this - if (str == "-1"_s) - { - *w = NEGATIVE_SPECIES; - return true; - } - return extract(str, &w->_value); -} +bool impl_extract(XString str, Species *w); class AccountId : public Wrapped<uint32_t> { public: constexpr AccountId() : Wrapped<uint32_t>() {} protected: constexpr explicit AccountId(uint32_t a) : Wrapped<uint32_t>(a) {} }; @@ -52,12 +40,14 @@ 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 { uint32_t bits; - friend bool extract(XString str, GmLevel *lvl) { return extract(str, &lvl->bits); } + friend bool impl_extract(XString str, GmLevel *lvl); constexpr explicit GmLevel(uint32_t b) : bits(b) {} constexpr explicit 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/src/mmo/login.t.hpp b/src/mmo/login.t.hpp new file mode 100644 index 0000000..f2c775a --- /dev/null +++ b/src/mmo/login.t.hpp @@ -0,0 +1,44 @@ +#pragma once +// login.t.hpp - externally useful types from login +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include <cstdint> + +#include "../generic/enum.hpp" + + +namespace tmwa +{ +namespace e +{ +enum class VERSION_2 : uint8_t +{ + /// client supports updatehost + UPDATEHOST = 0x01, + /// send servers in forward order + SERVERORDER = 0x02, +}; +ENUM_BITWISE_OPERATORS(VERSION_2) +} +using e::VERSION_2; +} // namespace tmwa diff --git a/src/mmo/mmo.cpp b/src/mmo/mmo.cpp deleted file mode 100644 index aafa431..0000000 --- a/src/mmo/mmo.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "mmo.hpp" -// mmo.cpp - dummy file to make Make dependencies work -// -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/map/skill.t.hpp b/src/mmo/skill.t.hpp index d0e3926..21e4059 100644 --- a/src/map/skill.t.hpp +++ b/src/mmo/skill.t.hpp @@ -77,6 +77,9 @@ enum class SkillID : uint16_t // TODO: Remove these! NEGATIVE = 0xffff, ZERO = 0x0000, + // this is probably the remains of the 'basic' skill, + // which has since been partially split into emote, trade, and party, + // but the confusion is caused by the fact that it also covered attacks. ONE = 0x0001, // Basic skills. diff --git a/src/mmo/strs.cpp b/src/mmo/strs.cpp index 71dceec..d780702 100644 --- a/src/mmo/strs.cpp +++ b/src/mmo/strs.cpp @@ -18,9 +18,15 @@ // 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/cxxstdio.hpp" + #include "../poison.hpp" namespace tmwa { +VString<49> convert_for_printf(NpcEvent ev) +{ + return STRNPRINTF(50, "%s::%s"_fmt, ev.npc, ev.label); +} } // namespace tmwa diff --git a/src/mmo/strs.hpp b/src/mmo/strs.hpp index fea0c98..6a132c2 100644 --- a/src/mmo/strs.hpp +++ b/src/mmo/strs.hpp @@ -123,4 +123,37 @@ CharName stringish<CharName>(VString<23> iv) { return CharName(iv); } + +struct MobName : VString<23> {}; +struct NpcName : VString<23> {}; +struct ScriptLabel : VString<23> {}; +struct ItemName : VString<23> {}; + +// formerly VString<49>, as name::label +struct NpcEvent +{ + NpcName npc; + ScriptLabel label; + + explicit operator bool() + { + return npc || label; + } + bool operator !() + { + return !bool(*this); + } + + friend bool operator == (const NpcEvent& l, const NpcEvent& r) + { + return l.npc == r.npc && l.label == r.label; + } + + friend bool operator < (const NpcEvent& l, const NpcEvent& r) + { + return l.npc < r.npc || (l.npc == r.npc && l.label < r.label); + } + + friend VString<49> convert_for_printf(NpcEvent ev); +}; } // namespace tmwa diff --git a/src/mmo/utils.hpp b/src/mmo/utils.hpp deleted file mode 100644 index fc3ea74..0000000 --- a/src/mmo/utils.hpp +++ /dev/null @@ -1,167 +0,0 @@ -#pragma once -// utils.hpp - Useful stuff that hasn't been categorized. -// -// Copyright © ????-2004 Athena Dev Teams -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "fwd.hpp" - -#include <cstring> -#include <ctime> - -#include <type_traits> - -#include "../ints/little.hpp" - -#include "../strings/fwd.hpp" -#include "../strings/vstring.hpp" - -#include "../generic/operators.hpp" - -#include "../io/fwd.hpp" - - -namespace tmwa -{ -template<class T> -struct is_trivially_copyable -: std::integral_constant<bool, - // come back when GCC actually implements the public traits properly - __has_trivial_copy(T) - && __has_trivial_assign(T) - && __has_trivial_destructor(T)> -{}; - -bool e_mail_check(XString email); -int config_switch (ZString str); - -template<class T> -void really_memzero_this(T *v) -{ - static_assert(is_trivially_copyable<T>::value, "only for mostly-pod types"); - static_assert(std::is_class<T>::value || std::is_union<T>::value, "Only for user-defined structures (for now)"); - memset(v, '\0', sizeof(*v)); -} -template<class T, size_t n> -void really_memzero_this(T (&)[n]) = delete; - -// Exists in place of time_t, to give it a predictable printf-format. -// (on x86 and amd64, time_t == long, but not on x32) -static_assert(sizeof(long long) >= sizeof(time_t), "long long >= time_t"); -struct TimeT : Comparable -{ - long long value; - - // conversion - TimeT(time_t t=0) : value(t) {} - TimeT(struct tm t) : value(timegm(&t)) {} - operator time_t() const { return value; } - operator struct tm() const { time_t v = value; return *gmtime(&v); } - - explicit operator bool() const { return value; } - bool operator !() const { return !value; } - - // prevent surprises - template<class T> - TimeT(T) = delete; - template<class T> - operator T() const = delete; - - static - TimeT now() - { - // poisoned, but this is still in header-land - return time(nullptr); - } - - bool error() const - { - return value == -1; - } - bool okay() const - { - return !error(); - } -}; - -inline -long long convert_for_printf(TimeT t) -{ - return t.value; -} - -// 2038 problem -inline __attribute__((warn_unused_result)) -bool native_to_network(Little32 *net, TimeT nat) -{ - time_t tmp = nat; - return native_to_network(net, static_cast<uint32_t>(tmp)); -} - -inline __attribute__((warn_unused_result)) -bool network_to_native(TimeT *nat, Little32 net) -{ - uint32_t tmp; - bool rv = network_to_native(&tmp, net); - *nat = static_cast<time_t>(tmp); - return rv; -} - -inline __attribute__((warn_unused_result)) -bool native_to_network(Little64 *net, TimeT nat) -{ - time_t tmp = nat; - return native_to_network(net, static_cast<uint64_t>(tmp)); -} - -inline __attribute__((warn_unused_result)) -bool network_to_native(TimeT *nat, Little64 net) -{ - uint64_t tmp; - bool rv = network_to_native(&tmp, net); - *nat = static_cast<time_t>(tmp); - return rv; -} - - -struct timestamp_seconds_buffer : VString<19> {}; -struct timestamp_milliseconds_buffer : VString<23> {}; -void stamp_time(timestamp_seconds_buffer&, const TimeT *t=nullptr); -void stamp_time(timestamp_milliseconds_buffer&); - -void log_with_timestamp(io::WriteFile& out, XString line); - -// TODO VString? -#define TIMESTAMP_DUMMY "YYYY-MM-DD HH:MM:SS" -static_assert(sizeof(TIMESTAMP_DUMMY) == sizeof(timestamp_seconds_buffer), - "timestamp size"); -#define WITH_TIMESTAMP(str) str TIMESTAMP_DUMMY -// str: prefix: YYYY-MM-DD HH:MM:SS -// sizeof: 01234567890123456789012345678 -// str + sizeof: ^ -// -1: ^ -// there's probably a better way to do this now -#define REPLACE_TIMESTAMP(str, t) \ - stamp_time( \ - reinterpret_cast<timestamp_seconds_buffer *>( \ - str + sizeof(str) \ - )[-1], \ - &t \ - ) -} // namespace tmwa diff --git a/src/mmo/version.cpp b/src/mmo/version.cpp index 2e337c1..f91b748 100644 --- a/src/mmo/version.cpp +++ b/src/mmo/version.cpp @@ -24,7 +24,7 @@ #include "../strings/xstring.hpp" -#include "extract.hpp" +#include "../io/extract.hpp" #include "../poison.hpp" @@ -66,11 +66,36 @@ Version CURRENT_MAP_SERVER_VERSION = LString CURRENT_VERSION_STRING = VERSION_STRING; -bool extract(XString str, Version *vers) +bool impl_extract(XString str, Version *vers) { *vers = {}; - // TODO should I try to extract dev and vend also? - // It would've been useful during the magic migration. + // versions look like: + // 1.2.3 (release) + // 1.2.3+5 (vendor patches) + // 1.2.3-4 (dev patches) + // 1.2.3-4+5 (dev patches + vendor patches) + XString a, b; + if (extract(str, record<'+'>(&a, &b))) + { + if (!extract(b, &vers->vend)) + { + return false; + } + str = a; + } + if (extract(str, record<'-'>(&a, &b))) + { + if (!extract(b, &vers->devel)) + { + return false; + } + str = a; + } return extract(str, record<'.'>(&vers->major, &vers->minor, &vers->patch)); } + +LString VERSION_INFO_HEADER = "This server code consists of Free Software under GPL3&AGPL3"_s; +LString VERSION_INFO_COMMIT = "This is commit " VERSION_HASH ", also known as " VERSION_FULL ""_s; +LString VERSION_INFO_NUMBER = "The version is " VERSION_STRING ""_s; +LString VERSION_INFO_URL = "For source, see [@@" VENDOR_SOURCE "|" VENDOR_SOURCE "@@]"_s; } // namespace tmwa diff --git a/src/mmo/version.hpp b/src/mmo/version.hpp index 440dce6..a09953f 100644 --- a/src/mmo/version.hpp +++ b/src/mmo/version.hpp @@ -24,8 +24,6 @@ #include <cstdint> -#include "../strings/fwd.hpp" - namespace tmwa { @@ -89,5 +87,10 @@ extern Version CURRENT_MAP_SERVER_VERSION; extern LString CURRENT_VERSION_STRING; -bool extract(XString str, Version *vers); +bool impl_extract(XString str, Version *vers); + +extern LString VERSION_INFO_HEADER; +extern LString VERSION_INFO_COMMIT; +extern LString VERSION_INFO_NUMBER; +extern LString VERSION_INFO_URL; } // namespace tmwa diff --git a/src/monitor/main.cpp b/src/monitor/main.cpp deleted file mode 100644 index ec1139a..0000000 --- a/src/monitor/main.cpp +++ /dev/null @@ -1,257 +0,0 @@ -// monitor/main.cpp - Old daemon to restart servers when they crashed. -// -// Copyright © ???? Bartosz Waszak <waszi@evil.org.pl> -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include <sys/wait.h> - -#include <fcntl.h> -#include <unistd.h> - -#include <csignal> -#include <cstdlib> - -#include "../strings/mstring.hpp" -#include "../strings/astring.hpp" -#include "../strings/zstring.hpp" -#include "../strings/xstring.hpp" -#include "../strings/literal.hpp" - -#include "../io/cxxstdio.hpp" -#include "../io/fd.hpp" -#include "../io/read.hpp" - -#include "../mmo/config_parse.hpp" -#include "../mmo/utils.hpp" - -#include "../poison.hpp" - -#define LOGIN_SERVER "./login-server"_s -#define MAP_SERVER "./map-server"_s -#define CHAR_SERVER "./char-server"_s -#define CONFIG "conf/eathena-monitor.conf"_s - - -namespace tmwa -{ -// initialiized to $HOME/tmwserver -static -AString workdir; -//the rest are relative to workdir -static -AString login_server = LOGIN_SERVER; -static -AString map_server = MAP_SERVER; -static -AString char_server = CHAR_SERVER; - -static -pid_t pid_login, pid_map, pid_char; - -static -AString make_path(XString base, XString path) -{ - MString m; - m += base; - m += '/'; - m += path; - return AString(m); -} - -static -bool parse_option(XString name, ZString value) -{ - if (name == "login_server"_s) - login_server = value; - else if (name == "map_server"_s) - map_server = value; - else if (name == "char_server"_s) - char_server = value; - else if (name == "workdir"_s) - workdir = value; - else - { - FPRINTF(stderr, "WARNING: ingnoring invalid option '%s' : '%s'\n"_fmt, - AString(name), value); - return false; - } - return true; -} - -static -bool read_config(ZString filename) -{ - bool rv = true; - io::ReadFile in(filename); - if (!in.is_open()) - { - FPRINTF(stderr, "Monitor config file not found: %s\n"_fmt, filename); - exit(1); - } - - AString line; - while (in.getline(line)) - { - if (is_comment(line)) - continue; - XString name; - ZString value; - if (!config_split(line, &name, &value)) - { - PRINTF("Bad line: %s\n"_fmt, line); - rv = false; - continue; - } - - if (!parse_option(name, value)) - { - PRINTF("Bad key/value: %s\n"_fmt, line); - rv = false; - continue; - } - } - return rv; -} - -static -pid_t start_process(ZString exec) -{ - const char *args[2] = {exec.c_str(), nullptr}; - pid_t pid = fork(); - if (pid == -1) - { - FPRINTF(stderr, "Failed to fork"_fmt); - return 0; - } - if (pid == 0) - { - DIAG_PUSH(); - DIAG_I(cast_qual); - execv(exec.c_str(), const_cast<char **>(args)); - DIAG_POP(); - perror("Failed to exec"); - kill(getppid(), SIGABRT); - exit(1); - } - return pid; -} - -// Kill all children with the same signal we got, then ourself. -static -void stop_process(int sig) -{ - if (pid_map) - kill(pid_map, sig); - if (pid_login) - kill(pid_login, sig); - if (pid_char) - kill(pid_char, sig); - DIAG_PUSH(); - DIAG_I(old_style_cast); - DIAG_I(zero_as_null_pointer_constant); - signal(sig, SIG_DFL); - DIAG_POP(); - raise(sig); -} -} // namespace tmwa - -int main(int argc, char *argv[]) -{ - using namespace tmwa; - // These are all the signals we are likely to get - // The shell handles stop/cont - signal(SIGTERM, stop_process); - signal(SIGINT, stop_process); - signal(SIGQUIT, stop_process); - signal(SIGABRT, stop_process); - - workdir = make_path(ZString(strings::really_construct_from_a_pointer, getenv("HOME"), nullptr), "tmwserver"_s); - - ZString config = CONFIG; - if (argc > 1) - config = ZString(strings::really_construct_from_a_pointer, argv[1], nullptr); - read_config(config); - - if (chdir(workdir.c_str()) < 0) - { - perror("Failed to change directory"); - exit(1); - } - - PRINTF("Starting:\n"_fmt); - PRINTF("* workdir: %s\n"_fmt, workdir); - PRINTF("* login_server: %s\n"_fmt, login_server); - PRINTF("* char_server: %s\n"_fmt, char_server); - PRINTF("* map_server: %s\n"_fmt, map_server); - { - //make sure all possible file descriptors are free for use by the servers - //if there are file descriptors higher than the max open from before the limit dropped, that's not our problem - io::FD fd = io::FD::sysconf_SC_OPEN_MAX(); - while ((fd = fd.prev()) > io::FD::stderr()) - { - if (fd.close() == 0) - FPRINTF(stderr, "close fd %d\n"_fmt, fd.uncast_dammit()); - } - fd = io::FD::open("/dev/null"_s, O_RDWR); - if (fd == io::FD()) - { - perror("open /dev/null"); - exit(1); - } - fd.dup2(io::FD::stdin()); - fd.dup2(io::FD::stdout()); - fd.close(); - } - while (1) - { - // write stuff to stderr - timestamp_seconds_buffer timestamp; - stamp_time(timestamp); - - if (!pid_login) - { - pid_login = start_process(login_server); - FPRINTF(stderr, "[%s] forked login server: %lu\n"_fmt, - timestamp, static_cast<unsigned long>(pid_login)); - } - if (!pid_char) - { - pid_char = start_process(char_server); - FPRINTF(stderr, "[%s] forked char server: %lu\n"_fmt, - timestamp, static_cast<unsigned long>(pid_char)); - } - if (!pid_map) - { - pid_map = start_process(map_server); - FPRINTF(stderr, "[%s] forked map server: %lu\n"_fmt, - timestamp, static_cast<unsigned long>(pid_map)); - } - pid_t dead = wait(nullptr); - if (dead == -1) - { - perror("Failed to wait for child"); - exit(1); - } - if (pid_login == dead) - pid_login = 0; - if (pid_char == dead) - pid_char = 0; - if (pid_map == dead) - pid_map = 0; - } -} diff --git a/src/net/fwd.hpp b/src/net/fwd.hpp index 2097772..5de8450 100644 --- a/src/net/fwd.hpp +++ b/src/net/fwd.hpp @@ -20,6 +20,13 @@ #include "../sanity.hpp" +#include "../ints/fwd.hpp" // rank 1 +#include "../strings/fwd.hpp" // rank 1 +#include "../compat/fwd.hpp" // rank 2 +#include "../generic/fwd.hpp" // rank 3 +#include "../io/fwd.hpp" // rank 4 +// net/fwd.hpp is rank 5 + namespace tmwa { @@ -28,6 +35,4 @@ class Session; class IP4Address; class TimerData; - -enum class RecvResult; } // namespace tmwa diff --git a/src/net/ip.cpp b/src/net/ip.cpp index bfc2028..bedbca8 100644 --- a/src/net/ip.cpp +++ b/src/net/ip.cpp @@ -22,15 +22,14 @@ #include "../strings/vstring.hpp" #include "../io/cxxstdio.hpp" - -#include "../mmo/extract.hpp" +#include "../io/extract.hpp" #include "../poison.hpp" namespace tmwa { -bool extract(XString str, IP4Address *rv) +bool impl_extract(XString str, IP4Address *rv) { if (str.endswith('.')) return false; @@ -43,7 +42,7 @@ bool extract(XString str, IP4Address *rv) return false; } -bool extract(XString str, IP4Mask *rv) +bool impl_extract(XString str, IP4Mask *rv) { IP4Address a, m; unsigned b; @@ -106,6 +105,33 @@ bool extract(XString str, IP4Mask *rv) return true; } +bool impl_extract(XString str, std::vector<IP4Mask> *iv) +{ + if (str == "all"_s) + { + iv->clear(); + iv->push_back(IP4Mask()); + return true; + } + if (str == "clear"_s) + { + iv->clear(); + return true; + } + // don't add if already 'all' + if (iv->size() == 1 && iv->front().mask() == IP4Address()) + { + return true; + } + IP4Mask mask; + if (extract(str, &mask)) + { + iv->push_back(mask); + return true; + } + return false; +} + VString<15> convert_for_printf(IP4Address a_) { const uint8_t *a = a_.bytes(); diff --git a/src/net/ip.hpp b/src/net/ip.hpp index e9e71f4..7508c08 100644 --- a/src/net/ip.hpp +++ b/src/net/ip.hpp @@ -25,7 +25,7 @@ #include <cstddef> #include <cstdint> -#include "../strings/fwd.hpp" +#include <vector> namespace tmwa @@ -160,7 +160,7 @@ IP4Address IP4_BROADCAST({255, 255, 255, 255}); VString<15> convert_for_printf(IP4Address a); VString<31> convert_for_printf(IP4Mask m); -bool extract(XString str, IP4Address *iv); - -bool extract(XString str, IP4Mask *iv); +bool impl_extract(XString str, IP4Address *iv); +bool impl_extract(XString str, IP4Mask *iv); +bool impl_extract(XString str, std::vector<IP4Mask> *iv); } // namespace tmwa diff --git a/src/net/ip_test.cpp b/src/net/ip_test.cpp index 419dc03..2b9bcad 100644 --- a/src/net/ip_test.cpp +++ b/src/net/ip_test.cpp @@ -24,6 +24,7 @@ #include "../strings/literal.hpp" #include "../io/cxxstdio.hpp" +#include "../io/extract.hpp" #include "../poison.hpp" diff --git a/src/net/socket.cpp b/src/net/socket.cpp index a01cd81..fce45fb 100644 --- a/src/net/socket.cpp +++ b/src/net/socket.cpp @@ -34,10 +34,6 @@ #include "../io/cxxstdio.hpp" -// TODO get rid of ordering violations -#include "../mmo/utils.hpp" -#include "../mmo/core.hpp" - #include "timer.hpp" #include "../poison.hpp" @@ -399,7 +395,7 @@ void realloc_fifo(Session *s, size_t rfifo_size, size_t wfifo_size) } } -void do_sendrecv(interval_t next_ms) +bool do_sendrecv(interval_t next_ms) { bool any = false; io::FD_Set rfd = readfds, wfd; @@ -419,9 +415,9 @@ void do_sendrecv(interval_t next_ms) { PRINTF("Shutting down - nothing to do\n"_fmt); // TODO hoist this - runflag = false; + return false; } - return; + return true; } struct timeval timeout; { @@ -431,7 +427,7 @@ void do_sendrecv(interval_t next_ms) timeout.tv_usec = next_us.count(); } if (io::FD_Set::select(fd_max, &rfd, &wfd, nullptr, &timeout) <= 0) - return; + return true; for (io::FD i : iter_fds()) { Session *s = get_session(i); @@ -451,9 +447,10 @@ void do_sendrecv(interval_t next_ms) s->func_recv(s); } } + return true; } -void do_parsepacket(void) +bool do_parsepacket(void) { for (io::FD i : iter_fds()) { @@ -483,5 +480,6 @@ void do_parsepacket(void) /// Reclaim buffer space for what was read RFIFOFLUSH(s); } + return true; } } // namespace tmwa diff --git a/src/net/socket.hpp b/src/net/socket.hpp index 576ef85..d6caefd 100644 --- a/src/net/socket.hpp +++ b/src/net/socket.hpp @@ -22,20 +22,19 @@ #include "fwd.hpp" -#include <algorithm> - #include <sys/select.h> +#include <algorithm> #include <memory> -#include "../compat/iter.hpp" -#include "../compat/rawmem.hpp" -#include "../compat/time_t.hpp" - #include "../strings/astring.hpp" #include "../strings/vstring.hpp" #include "../strings/xstring.hpp" +#include "../compat/iter.hpp" +#include "../compat/rawmem.hpp" +#include "../compat/time_t.hpp" + #include "../generic/dumb_ptr.hpp" #include "../io/fd.hpp" @@ -125,8 +124,8 @@ public: io::FD fd; - friend void do_sendrecv(interval_t next); - friend void do_parsepacket(void); + friend bool do_sendrecv(interval_t next); + friend bool do_parsepacket(void); friend void delete_session(Session *); }; @@ -171,7 +170,7 @@ void delete_session(Session *); /// Make a the internal queues bigger void realloc_fifo(Session *s, size_t rfifo_size, size_t wfifo_size); /// Update all sockets that can be read/written from the queues -void do_sendrecv(interval_t next); +bool do_sendrecv(interval_t next); /// Call the parser function for every socket that has read data -void do_parsepacket(void); +bool do_parsepacket(void); } // namespace tmwa diff --git a/src/net/timer.hpp b/src/net/timer.hpp index 338e339..5e7cc90 100644 --- a/src/net/timer.hpp +++ b/src/net/timer.hpp @@ -21,11 +21,8 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. #include "timer.t.hpp" - #include "fwd.hpp" -#include "../strings/fwd.hpp" - namespace tmwa { diff --git a/src/net/timer.py b/src/net/timer.py new file mode 100644 index 0000000..2ccb3bb --- /dev/null +++ b/src/net/timer.py @@ -0,0 +1,117 @@ +class duration(object): + __slots__ = ('_whole', '_frac', '_units') + name = 'std::chrono::duration' + enabled = True + + def __init__(self, value): + from decimal import Decimal + + rep = int(value['__r']) + ratio = value.type.template_argument(1) + num = int(ratio.template_argument(0)) + den = int(ratio.template_argument(1)) + # this will fail on duration<float> + value = Decimal(rep) * num / den + whole = int(value) + self._whole = whole + self._frac = value - whole + units = { + (1, 1000*1000*1000): ('nanoseconds', '_ns'), + (1, 1000*1000): ('microseconds', '_us'), + (1, 1000): ('milliseconds', '_ms'), + (1, 1): ('seconds', '_s'), + (60, 1): ('minutes', '_min'), + (60*60, 1): ('hours', '_h'), + (24*60*60, 1): ('duration<int, ratio<%d, %d>>', '_d'), + # days don't exist (probably because of leap seconds) + } + self._units = units.get((num, den)) or ('duration<???, ratio<%d, %d>>' % (num, den), '_?') + + def to_string(self): + whole = self._whole + frac = self._frac + cu, su = self._units + if not whole and not frac: + return '0%s' % su + s = whole + min = s // 60 + s %= 60 + h = min // 60 + min %= 60 + d = h // 24 + h %= 24 + msx = frac * 1000 + ms = int(msx) + usx = (msx - ms) * 1000 + us = int(usx) + nsx = (usx - us) * 1000 + ns = int(nsx) + bits = [ + '%d_d' % d if d else None, + '%d_h' % h if h else None, + '%d_min' % min if min else None, + '%d_s' % s if s else None, + '%d_ms' % ms if ms else None, + '%d_us' % us if us else None, + '%d_ns' % ns if ns else None, + ] + body = ' + '.join(b for b in bits if b is not None) + if not body.endswith(su): + body = '%s(%s)' % (cu, body) + elif '+' in body: + body = '(%s)' % body + return body + + tests = [ + ('std::chrono::nanoseconds(0)', '0_ns'), + ('std::chrono::microseconds(0)', '0_us'), + ('std::chrono::milliseconds(0)', '0_ms'), + ('std::chrono::seconds(0)', '0_s'), + ('std::chrono::minutes(0)', '0_min'), + ('std::chrono::hours(0)', '0_h'), + ('std::chrono::duration<int, std::ratio<60*60*24>>(0)', '0_d'), + + ('std::chrono::nanoseconds(1)', '1_ns'), + ('std::chrono::microseconds(1)', '1_us'), + ('std::chrono::milliseconds(1)', '1_ms'), + ('std::chrono::seconds(1)', '1_s'), + ('std::chrono::minutes(1)', '1_min'), + ('std::chrono::hours(1)', '1_h'), + ('std::chrono::duration<int, std::ratio<60*60*24>>(1)', '1_d'), + + ('std::chrono::nanoseconds(1)', '1_ns'), + ('std::chrono::microseconds(1)', '1_us'), + ('std::chrono::milliseconds(1)', '1_ms'), + ('std::chrono::seconds(1)', '1_s'), + ('std::chrono::minutes(1)', '1_min'), + ('std::chrono::hours(1)', '1_h'), + ('std::chrono::duration<int, std::ratio<60*60*24>>(1)', '1_d'), + + ('std::chrono::nanoseconds(3)', '3_ns'), + ('std::chrono::microseconds(3)', '3_us'), + ('std::chrono::milliseconds(3)', '3_ms'), + ('std::chrono::seconds(3)', '3_s'), + ('std::chrono::minutes(3)', '3_min'), + ('std::chrono::hours(3)', '3_h'), + ('std::chrono::duration<int, std::ratio<60*60*24>>(3)', '3_d'), + + ('std::chrono::nanoseconds(1000)', 'nanoseconds(1_us)'), + ('std::chrono::microseconds(1000)', 'microseconds(1_ms)'), + ('std::chrono::milliseconds(1000)', 'milliseconds(1_s)'), + ('std::chrono::seconds(60)', 'seconds(1_min)'), + ('std::chrono::minutes(60)', 'minutes(1_h)'), + ('std::chrono::hours(24)', 'hours(1_d)'), + + ('std::chrono::nanoseconds(1001)', '(1_us + 1_ns)'), + ('std::chrono::microseconds(1001)', '(1_ms + 1_us)'), + ('std::chrono::milliseconds(1001)', '(1_s + 1_ms)'), + ('std::chrono::seconds(61)', '(1_min + 1_s)'), + ('std::chrono::minutes(61)', '(1_h + 1_min)'), + ('std::chrono::hours(25)', '(1_d + 1_h)'), + + ('std::chrono::nanoseconds(1001*1000)', 'nanoseconds(1_ms + 1_us)'), + ('std::chrono::microseconds(1001*1000)', 'microseconds(1_s + 1_ms)'), + ('std::chrono::milliseconds(61*1000)', 'milliseconds(1_min + 1_s)'), + ('std::chrono::seconds(61*60)', 'seconds(1_h + 1_min)'), + ('std::chrono::minutes(25*60)', 'minutes(1_d + 1_h)'), + ] diff --git a/src/net/timestamp-utils.cpp b/src/net/timestamp-utils.cpp new file mode 100644 index 0000000..b5873ca --- /dev/null +++ b/src/net/timestamp-utils.cpp @@ -0,0 +1,72 @@ +#include "timestamp-utils.hpp" +// timestamp-utils.cpp - Useful stuff that hasn't been categorized. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include <sys/time.h> + +#include <algorithm> + +#include "../strings/xstring.hpp" + +#include "../compat/time_t.hpp" + +#include "../io/write.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +static_assert(sizeof(timestamp_seconds_buffer) == 20, "seconds buffer"); +static_assert(sizeof(timestamp_milliseconds_buffer) == 24, "millis buffer"); + +void stamp_time(timestamp_seconds_buffer& out, const TimeT *t) +{ + struct tm when = t ? *t : TimeT::now(); + char buf[20]; + strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when); + out = stringish<timestamp_seconds_buffer>(VString<19>(strings::really_construct_from_a_pointer, buf)); +} +void stamp_time(timestamp_milliseconds_buffer& out) +{ + struct timeval tv; + gettimeofday(&tv, nullptr); + struct tm when = TimeT(tv.tv_sec); + char buf[24]; + strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &when); + sprintf(buf + 19, ".%03d", static_cast<int>(tv.tv_usec / 1000)); + out = stringish<timestamp_milliseconds_buffer>(VString<23>(strings::really_construct_from_a_pointer, buf)); +} + +void log_with_timestamp(io::WriteFile& out, XString line) +{ + if (!line) + { + out.put('\n'); + return; + } + timestamp_milliseconds_buffer tmpstr; + stamp_time(tmpstr); + out.really_put(tmpstr.data(), tmpstr.size()); + out.really_put(": ", 2); + out.put_line(line); +} +} // namespace tmwa diff --git a/src/net/timestamp-utils.hpp b/src/net/timestamp-utils.hpp new file mode 100644 index 0000000..f5b5dce --- /dev/null +++ b/src/net/timestamp-utils.hpp @@ -0,0 +1,54 @@ +#pragma once +// timestamp-utils.hpp - Useful stuff that hasn't been categorized. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "fwd.hpp" + +#include "../strings/vstring.hpp" + + +namespace tmwa +{ +struct timestamp_seconds_buffer : VString<19> {}; +struct timestamp_milliseconds_buffer : VString<23> {}; +void stamp_time(timestamp_seconds_buffer&, const TimeT *t=nullptr); +void stamp_time(timestamp_milliseconds_buffer&); + +void log_with_timestamp(io::WriteFile& out, XString line); + +// TODO VString? +#define TIMESTAMP_DUMMY "YYYY-MM-DD HH:MM:SS" +static_assert(sizeof(TIMESTAMP_DUMMY) == sizeof(timestamp_seconds_buffer), + "timestamp size"); +#define WITH_TIMESTAMP(str) str TIMESTAMP_DUMMY +// str: prefix: YYYY-MM-DD HH:MM:SS +// sizeof: 01234567890123456789012345678 +// str + sizeof: ^ +// -1: ^ +// there's probably a better way to do this now +#define REPLACE_TIMESTAMP(str, t) \ + stamp_time( \ + reinterpret_cast<timestamp_seconds_buffer *>( \ + str + sizeof(str) \ + )[-1], \ + &t \ + ) +} // namespace tmwa diff --git a/src/generic/array.cpp b/src/proto-base/fwd.hpp index 3063569..1790717 100644 --- a/src/generic/array.cpp +++ b/src/proto-base/fwd.hpp @@ -1,5 +1,5 @@ -#include "array.hpp" -// array.cpp - A simple bounds-checked array. +#pragma once +// proto-base/fwd.hpp - list of type names for network protocol base // // Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> // @@ -18,9 +18,15 @@ // 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 "../poison.hpp" +#include "../sanity.hpp" + +#include "../strings/fwd.hpp" // rank 1 +#include "../generic/fwd.hpp" // rank 3 +#include "../mmo/fwd.hpp" // rank 6 +// proto-base/fwd.hpp is rank 7 namespace tmwa { +// meh, add more when I feel like it } // namespace tmwa diff --git a/src/proto-base/net-array.hpp b/src/proto-base/net-array.hpp new file mode 100644 index 0000000..814d257 --- /dev/null +++ b/src/proto-base/net-array.hpp @@ -0,0 +1,53 @@ +#pragma once +// proto2/net-array.hpp - Special logic for fixed-size arrays in packets. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU 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 "../generic/array.hpp" + + +namespace tmwa +{ +template<class T, size_t N> +struct NetArray +{ + T data[N]; +}; +template<class T, class U, class I> +bool native_to_network(NetArray<T, I::alloc_size> *network, GenericArray<U, I> native) +{ + for (size_t i = 0; i < I::alloc_size; ++i) + { + if (!native_to_network(&(*network).data[i], native[I::offset_to_index(i)])) + return false; + } + return true; +} +template<class T, class U, class I> +bool network_to_native(GenericArray<U, I> *native, NetArray<T, I::alloc_size> network) +{ + for (size_t i = 0; i < I::alloc_size; ++i) + { + if (!network_to_native(&(*native)[I::offset_to_index(i)], network.data[i])) + return false; + } + return true; +} +} // namespace tmwa diff --git a/src/proto-base/net-neutral.hpp b/src/proto-base/net-neutral.hpp new file mode 100644 index 0000000..523748c --- /dev/null +++ b/src/proto-base/net-neutral.hpp @@ -0,0 +1,38 @@ +#pragma once +// proto2/net-neutral.hpp - Convert nothing across the network +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU 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" + + +namespace tmwa +{ +template<class T> +bool native_to_network(T *network, T native) +{ + *network = native; + return true; +} +template<class T> +bool network_to_native(T *native, T network) +{ + *native = network; + return true; +} +} // namespace tmwa diff --git a/src/proto-base/net-skewed-length.hpp b/src/proto-base/net-skewed-length.hpp new file mode 100644 index 0000000..84af508 --- /dev/null +++ b/src/proto-base/net-skewed-length.hpp @@ -0,0 +1,46 @@ +#pragma once +// proto2/net-skewed-length.hpp - Deprecated logic for skewed-size packets. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU 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 <cstddef> + + +namespace tmwa +{ +template<class T, size_t N> +struct SkewedLength +{ + T data; +}; +template<class T, size_t N, class U> +bool native_to_network(SkewedLength<T, N> *network, U native) +{ + native -= N; + return native_to_network(&network->data, native); +} +template<class T, size_t N, class U> +bool network_to_native(U *native, SkewedLength<T, N> network) +{ + bool rv = network_to_native(native, network.data); + *native += N; + return rv; +} +} // namespace tmwa diff --git a/src/proto-base/net-string.hpp b/src/proto-base/net-string.hpp new file mode 100644 index 0000000..a9a120d --- /dev/null +++ b/src/proto-base/net-string.hpp @@ -0,0 +1,87 @@ +#pragma once +// proto2/net-string.hpp - Special logic for fixed-size strings in packets. +// +// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU 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 "../strings/vstring.hpp" + +#include "../mmo/strs.hpp" + + +namespace tmwa +{ +template<size_t N> +struct NetString +{ + char data[N]; +}; +template<size_t N> +bool native_to_network(NetString<N> *network, VString<N-1> native) +{ + // basically WBUF_STRING + char *const begin = network->data; + char *const end = begin + N; + char *const mid = std::copy(native.begin(), native.end(), begin); + std::fill(mid, end, '\0'); + return true; +} +template<size_t N> +bool network_to_native(VString<N-1> *native, NetString<N> network) +{ + // basically RBUF_STRING + const char *const begin = network.data; + const char *const end = begin + N; + const char *const mid = std::find(begin, end, '\0'); + *native = XString(begin, mid, nullptr); + return true; +} + +inline +bool native_to_network(NetString<24> *network, CharName native) +{ + VString<23> tmp = native.to__actual(); + bool rv = native_to_network(network, tmp); + return rv; +} +inline +bool network_to_native(CharName *native, NetString<24> network) +{ + VString<23> tmp; + bool rv = network_to_native(&tmp, network); + *native = stringish<CharName>(tmp); + return rv; +} + +inline +bool native_to_network(NetString<16> *network, MapName native) +{ + XString tmp = native; + bool rv = native_to_network(network, VString<15>(tmp)); + return rv; +} +inline +bool network_to_native(MapName *native, NetString<16> network) +{ + VString<15> tmp; + bool rv = network_to_native(&tmp, network); + *native = stringish<MapName>(tmp); + return rv; +} +} // namespace tmwa diff --git a/src/range/fwd.hpp b/src/range/fwd.hpp index 646eadb..4bad327 100644 --- a/src/range/fwd.hpp +++ b/src/range/fwd.hpp @@ -20,6 +20,8 @@ #include "../sanity.hpp" +// range/fwd.hpp is rank 1 + namespace tmwa { diff --git a/src/range/slice.cpp b/src/range/slice.cpp deleted file mode 100644 index f93c19f..0000000 --- a/src/range/slice.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "slice.hpp" -// slice.cpp - dummy file to make Make dependencies work -// -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/sanity.hpp b/src/sanity.hpp index c00d9b2..455990a 100644 --- a/src/sanity.hpp +++ b/src/sanity.hpp @@ -41,10 +41,10 @@ namespace tmwa #endif // __GNUC__ == 4 #if not defined(__i386__) and not defined(__x86_64__) -// Known platform dependencies: -// endianness for the [RW]FIFO.* macros -// possibly, some signal-handling -// some integer sizes (partially fixed for the x32 ABI) -# error "Unsupported platform, we use x86 / amd64 only" +// There are no longer any *known* platform-independent bits, +// but nothing has been tested yet. It compiles, though, so ship it! +# ifndef QUIET +# warning "Unsupported platform, upstream uses x86 / amd64 only" +# endif #endif // not __i386__ } // namespace tmwa diff --git a/src/sexpr/fwd.hpp b/src/sexpr/fwd.hpp index 580b322..b86d9fb 100644 --- a/src/sexpr/fwd.hpp +++ b/src/sexpr/fwd.hpp @@ -20,6 +20,10 @@ #include "../sanity.hpp" +#include "../strings/fwd.hpp" // rank 1 +#include "../io/fwd.hpp" // rank 4 +// sexpr/fwd.hpp is rank 5 + namespace tmwa { diff --git a/src/sexpr/lexer.hpp b/src/sexpr/lexer.hpp index 63be72d..9b198a0 100644 --- a/src/sexpr/lexer.hpp +++ b/src/sexpr/lexer.hpp @@ -22,7 +22,6 @@ #include <vector> -#include "../strings/fwd.hpp" #include "../strings/astring.hpp" #include "../strings/zstring.hpp" @@ -59,10 +58,13 @@ namespace sexpr Lexer(ZString filename) : _in(filename), _current(TOK_EOF), _span(), _depth() { adv(); } - // for unit tests - Lexer(ZString fake, io::FD fd) - : _in(fake, fd), _current(TOK_EOF), _span(), _depth() + Lexer(io::read_file_from_string, ZString name, XString str) + : _in(io::from_string, name, str), _current(TOK_EOF), _span(), _depth() { adv(); } + Lexer(io::read_file_from_string, ZString name, LString str) + : _in(io::from_string, name, str), _current(TOK_EOF), _span(), _depth() + { adv(); } + Lexeme peek() { return _current; } void adv() { _current = _adv(); } ZString val_string() { return _string; } diff --git a/src/sexpr/lexer_test.cpp b/src/sexpr/lexer_test.cpp index fdb47f2..d84312e 100644 --- a/src/sexpr/lexer_test.cpp +++ b/src/sexpr/lexer_test.cpp @@ -29,22 +29,6 @@ namespace tmwa { -static -io::FD string_pipe(ZString sz) -{ - io::FD rfd, wfd; - if (-1 == io::FD::pipe(rfd, wfd)) - return io::FD(); - if (sz.size() != wfd.write(sz.c_str(), sz.size())) - { - rfd.close(); - wfd.close(); - return io::FD(); - } - wfd.close(); - return rfd; -} - TEST(sexpr, escape) { EXPECT_EQ(sexpr::escape('\0'), "\\x00"_s); @@ -73,24 +57,24 @@ TEST(sexpr, escape) TEST(sexpr, lexer) { io::LineSpan span; - sexpr::Lexer lexer("<lexer-test1>"_s, string_pipe(" foo( ) 123\"\" \n"_s)); + sexpr::Lexer lexer(io::from_string, "<lexer-test1>"_s, " foo( ) 123\"\" \n"_s); EXPECT_EQ(lexer.peek(), sexpr::TOK_TOKEN); EXPECT_EQ(lexer.val_string(), "foo"_s); - EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s), + EXPECT_EQ(lexer.span().error_str("test"_s), "<lexer-test1>:1:2: error: test\n" " foo( ) 123\"\" \n" " ^~~\n"_s ); lexer.adv(); EXPECT_EQ(lexer.peek(), sexpr::TOK_OPEN); - EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s), + EXPECT_EQ(lexer.span().error_str("test"_s), "<lexer-test1>:1:5: error: test\n" " foo( ) 123\"\" \n" " ^\n"_s ); lexer.adv(); EXPECT_EQ(lexer.peek(), sexpr::TOK_CLOSE); - EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s), + EXPECT_EQ(lexer.span().error_str("test"_s), "<lexer-test1>:1:7: error: test\n" " foo( ) 123\"\" \n" " ^\n"_s @@ -98,7 +82,7 @@ TEST(sexpr, lexer) lexer.adv(); EXPECT_EQ(lexer.peek(), sexpr::TOK_TOKEN); EXPECT_EQ(lexer.val_string(), "123"_s); - EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s), + EXPECT_EQ(lexer.span().error_str("test"_s), "<lexer-test1>:1:9: error: test\n" " foo( ) 123\"\" \n" " ^~~\n"_s @@ -106,7 +90,7 @@ TEST(sexpr, lexer) lexer.adv(); EXPECT_EQ(lexer.peek(), sexpr::TOK_STRING); EXPECT_EQ(lexer.val_string(), ""_s); - EXPECT_EQ(lexer.span().message_str("error"_s, "test"_s), + EXPECT_EQ(lexer.span().error_str("test"_s), "<lexer-test1>:1:12: error: test\n" " foo( ) 123\"\" \n" " ^~\n"_s @@ -120,7 +104,7 @@ TEST(sexpr, lexbad) QuietFd q; { io::LineSpan span; - sexpr::Lexer lexer("<lexer-bad>"_s, string_pipe("(\n"_s)); + sexpr::Lexer lexer(io::from_string, "<lexer-bad>"_s, "(\n"_s); EXPECT_EQ(lexer.peek(), sexpr::TOK_OPEN); lexer.adv(); EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR); @@ -135,7 +119,7 @@ TEST(sexpr, lexbad) }) { io::LineSpan span; - sexpr::Lexer lexer("<lexer-bad>"_s, string_pipe(bad)); + sexpr::Lexer lexer(io::from_string, "<lexer-bad>"_s, bad); EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR); } } diff --git a/src/sexpr/parser.hpp b/src/sexpr/parser.hpp index feed636..cfe8212 100644 --- a/src/sexpr/parser.hpp +++ b/src/sexpr/parser.hpp @@ -22,8 +22,6 @@ #include <cstdlib> -#include "../strings/fwd.hpp" - #include "../io/line.hpp" #include "lexer.hpp" diff --git a/src/sexpr/parser_test.cpp b/src/sexpr/parser_test.cpp index 846d425..bbaf5eb 100644 --- a/src/sexpr/parser_test.cpp +++ b/src/sexpr/parser_test.cpp @@ -20,32 +20,18 @@ #include <gtest/gtest.h> +#include "../tests/fdhack.hpp" + #include "../poison.hpp" namespace tmwa { -static -io::FD string_pipe(ZString sz) -{ - io::FD rfd, wfd; - if (-1 == io::FD::pipe(rfd, wfd)) - return io::FD(); - if (sz.size() != wfd.write(sz.c_str(), sz.size())) - { - rfd.close(); - wfd.close(); - return io::FD(); - } - wfd.close(); - return rfd; -} - TEST(sexpr, parser) { sexpr::SExpr s; io::LineSpan span; - sexpr::Lexer lexer("<parser-test1>"_s, string_pipe(" foo( ) 123\"\" \n"_s)); + sexpr::Lexer lexer(io::from_string, "<parser-test1>"_s, " foo( ) 123\"\" \n"_s); EXPECT_TRUE(sexpr::parse(lexer, s)); EXPECT_EQ(s._type, sexpr::TOKEN); @@ -70,7 +56,7 @@ TEST(sexpr, parser) TEST(sexpr, parselist) { sexpr::SExpr s; - sexpr::Lexer lexer("<parser-test1>"_s, string_pipe("(foo)(bar)\n"_s)); + sexpr::Lexer lexer(io::from_string, "<parser-test1>"_s, "(foo)(bar)\n"_s); EXPECT_TRUE(sexpr::parse(lexer, s)); EXPECT_EQ(s._type, sexpr::LIST); @@ -90,6 +76,7 @@ TEST(sexpr, parselist) TEST(sexpr, parsebad) { + QuietFd q; for (LString bad : { "(\n"_s, ")\n"_s, @@ -105,7 +92,7 @@ TEST(sexpr, parsebad) { sexpr::SExpr s; io::LineSpan span; - sexpr::Lexer lexer("<parse-bad>"_s, string_pipe(bad)); + sexpr::Lexer lexer(io::from_string, "<parse-bad>"_s, bad); EXPECT_FALSE(sexpr::parse(lexer, s)); EXPECT_EQ(lexer.peek(), sexpr::TOK_ERROR); } diff --git a/src/sexpr/union.cpp b/src/sexpr/union_test.cpp index 6f65012..ca60b49 100644 --- a/src/sexpr/union.cpp +++ b/src/sexpr/union_test.cpp @@ -1,5 +1,5 @@ #include "union.hpp" -// union.cpp - Just include the header file and try to instantiate. +// union_test.cpp - Just include the header file and try to instantiate. // // Copyright © 2012 Ben Longbons <b.r.longbons@gmail.com> // diff --git a/src/sexpr/variant.hpp b/src/sexpr/variant.hpp index fbf9345..0eccc5a 100644 --- a/src/sexpr/variant.hpp +++ b/src/sexpr/variant.hpp @@ -33,19 +33,42 @@ namespace tmwa { namespace sexpr { -#define JOIN(a, b) a##b - -#define WITH_VAR(ty, var, expr) \ - for (bool JOIN(var, _guard) = true; JOIN(var, _guard); ) \ - for (ty var = expr; JOIN(var, _guard); JOIN(var, _guard) = false) -#define MATCH(expr) \ - WITH_VAR(auto&&, _match_var, expr) \ - switch (tmwa::sexpr::VariantFriend::get_state(_match_var)) -#define TYPED_CASE(ty, var, look) \ - break; \ - case tmwa::sexpr::VariantFriend::get_state_for<look, decltype(_match_var)>(): \ - WITH_VAR(ty, var, tmwa::sexpr::VariantFriend::unchecked_get<look>(_match_var)) -#define CASE(ty, var) TYPED_CASE(ty, var, std::remove_const<std::remove_reference<ty>::type>::type) +#define MATCH_BEGIN(expr) \ + { \ + auto&& _match_var = (expr); \ + switch (tmwa::sexpr::VariantFriend::get_state(_match_var)) \ + { \ + { \ + { \ + /* }}}} */ +#define MATCH_END() \ + /* {{{{ */ \ + } \ + } \ + } \ + (void) _match_var; \ + } + +#define MATCH_CASE(ty, v) \ + /* {{{{ */ \ + } \ + break; \ + } \ + { \ + using _match_case_type = std::remove_const<std::remove_reference<ty>::type>::type; \ + case tmwa::sexpr::VariantFriend::get_state_for<_match_case_type, decltype(_match_var)>(): \ + { \ + ty v = tmwa::sexpr::VariantFriend::unchecked_get<_match_case_type>(_match_var); + /* }}}} */ +#define MATCH_DEFAULT() \ + /* {{{{ */ \ + } \ + break; \ + } \ + { \ + default: \ + { \ + /* }}}} */ template<class... T> class Variant diff --git a/src/sexpr/variant.tcc b/src/sexpr/variant.tcc index 1f7df03..c370fa4 100644 --- a/src/sexpr/variant.tcc +++ b/src/sexpr/variant.tcc @@ -120,6 +120,9 @@ namespace sexpr } catch (...) { + // TODO switch from requiring nothrow default construct, to + // instead require nothrow moves, and offer the strong exception + // guarantee (which is actually easier that the basic one) #if GCC != 407 // apparent compiler bug, not reduced // 4.7.2 from wheezy is bad // 4.7.3 from jessie is good diff --git a/src/sexpr/variant_test.cpp b/src/sexpr/variant_test.cpp index bc378aa..c671264 100644 --- a/src/sexpr/variant_test.cpp +++ b/src/sexpr/variant_test.cpp @@ -77,42 +77,46 @@ TEST(variant, match) : sexpr::Variant<Foo, Bar>(Foo(1)) {} }; + Sub v1; - MATCH (v1) + MATCH_BEGIN (v1) { - // This is not a public API, it's just for testing. - default: - FAIL(); - - CASE(Foo, f) + MATCH_DEFAULT () + { + FAIL(); + } + MATCH_CASE (Foo, f) { (void)f; SUCCEED(); } - CASE(Bar, b) + MATCH_CASE (Bar, b) { (void)b; FAIL(); } } + MATCH_END (); + v1.emplace<Bar>(2); - MATCH (v1) + MATCH_BEGIN (v1) { - // This is not a public API, it's just for testing. - default: - FAIL(); - - CASE(Foo, f) + MATCH_DEFAULT () + { + FAIL(); + } + MATCH_CASE (Foo, f) { (void)f; FAIL(); } - CASE(Bar, b) + MATCH_CASE (Bar, b) { (void)b; SUCCEED(); } } + MATCH_END (); } TEST(variant, copymove1) diff --git a/src/sexpr/void.cpp b/src/sexpr/void.cpp deleted file mode 100644 index 9f0eeb5..0000000 --- a/src/sexpr/void.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "void.hpp" -// void.cpp - Just include the header file. -// -// Copyright © 2012 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -namespace sexpr -{ -} // namespace sexpr -} // namespace tmwa diff --git a/src/strings/astring.py b/src/strings/astring.py index f4cbf66..a3306d9 100644 --- a/src/strings/astring.py +++ b/src/strings/astring.py @@ -25,7 +25,7 @@ class AString(object): test_extra = ''' using tmwa::operator "" _s; - #include "../src/strings/zstring.hpp" + #include "../strings/zstring.hpp" ''' tests = [ diff --git a/src/strings/fwd.hpp b/src/strings/fwd.hpp index b1b8266..29762f9 100644 --- a/src/strings/fwd.hpp +++ b/src/strings/fwd.hpp @@ -23,6 +23,8 @@ #include <cstddef> #include <cstdint> +// strings/fwd.hpp is rank 1 + namespace tmwa { diff --git a/src/strings/rstring.cpp b/src/strings/rstring.cpp index e74d1d5..aaf0ba0 100644 --- a/src/strings/rstring.cpp +++ b/src/strings/rstring.cpp @@ -36,7 +36,7 @@ namespace tmwa { namespace strings { - RString::RString() + RString::RString() noexcept : u{.begin= ""}, maybe_end(u.begin) { } @@ -58,13 +58,18 @@ namespace strings } RString& RString::operator = (const RString& r) { - // order important for self-assign - if (!r.maybe_end) - r.u.owned->count++; - if (!maybe_end && !u.owned->count--) - ::operator delete(u.owned); - u = r.u; - maybe_end = r.maybe_end; + // this turns out to be a win + // certain callers end up needing to do self-assignment a *lot*, + // leading to pointless ++,--s + if (this->u.owned != r.u.owned) + { + if (!r.maybe_end) + r.u.owned->count++; + if (!maybe_end && !u.owned->count--) + ::operator delete(u.owned); + u = r.u; + maybe_end = r.maybe_end; + } return *this; } RString& RString::operator = (RString&& r) diff --git a/src/strings/rstring.hpp b/src/strings/rstring.hpp index ad44beb..62f74fa 100644 --- a/src/strings/rstring.hpp +++ b/src/strings/rstring.hpp @@ -48,7 +48,7 @@ namespace strings const char *maybe_end; public: - RString(); + RString() noexcept; RString(LString s); RString(const RString&); RString(RString&&); diff --git a/src/strings/rstring.py b/src/strings/rstring.py index 61603d8..75fe2db 100644 --- a/src/strings/rstring.py +++ b/src/strings/rstring.py @@ -1,3 +1,6 @@ +# used by other pretty-printers +rstring_disable_children = False + class RString(object): __slots__ = ('_value') name = 'tmwa::strings::RString' @@ -21,6 +24,8 @@ class RString(object): return b.lazy_string(length=d) def children(self): + if rstring_disable_children: + return v = self._value if v['maybe_end']: pass @@ -31,7 +36,7 @@ class RString(object): test_extra = ''' using tmwa::operator "" _s; - #include "../src/strings/zstring.hpp" + #include "../strings/zstring.hpp" ''' tests = [ diff --git a/src/strings/strings2_test.cpp b/src/strings/strings2_test.cpp index 8ac8482..8b91306 100644 --- a/src/strings/strings2_test.cpp +++ b/src/strings/strings2_test.cpp @@ -228,4 +228,13 @@ TEST(StringTests, rlong) EXPECT_EQ(&*r.begin(), &*r3.begin()); EXPECT_EQ(&*a.begin(), &*a3.begin()); } + +TEST(StringTest, rself) +{ + // force dynamic allocation; valgrind will check for memory errors + RString r = XString("foo bar baz"_s); + RString r2 = r; + r = r; + r = r2; +} } // namespace tmwa diff --git a/src/strings/vstring.cpp b/src/strings/vstring.cpp deleted file mode 100644 index 1cb313a..0000000 --- a/src/strings/vstring.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "vstring.hpp" -// strings/vstring.cpp - Functions for vstring.hpp -// -// Copyright © 2013 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -namespace strings -{ -} // namespace strings -} // namespace tmwa diff --git a/src/strings/xstring.py b/src/strings/xstring.py index b2e33bb..ae764df 100644 --- a/src/strings/xstring.py +++ b/src/strings/xstring.py @@ -22,10 +22,10 @@ class XString(object): ''' tests = [ - ('tmwa::XString(""_s)', '"" = {base = 0x0}'), - ('tmwa::XString("Hello"_s)', '"Hello" = {base = 0x0}'), - ('tmwa::XString("' + str256[:-2] + '"_s)', '"' + str256[:-2] + '" = {base = 0x0}'), - ('tmwa::XString("' + str256[:-1] + '"_s)', '"' + str256[:-1] + '" = {base = 0x0}'), - ('tmwa::XString("' + str256 + '"_s)', '"' + str256 + '" = {base = 0x0}'), - ('tmwa::XString("' + str256 + 'x"_s)', '"' + str256 + 'x" = {base = 0x0}'), + ('tmwa::XString(""_s)', '"" = {base = nullptr}'), + ('tmwa::XString("Hello"_s)', '"Hello" = {base = nullptr}'), + ('tmwa::XString("' + str256[:-2] + '"_s)', '"' + str256[:-2] + '" = {base = nullptr}'), + ('tmwa::XString("' + str256[:-1] + '"_s)', '"' + str256[:-1] + '" = {base = nullptr}'), + ('tmwa::XString("' + str256 + '"_s)', '"' + str256 + '" = {base = nullptr}'), + ('tmwa::XString("' + str256 + 'x"_s)', '"' + str256 + 'x" = {base = nullptr}'), ] diff --git a/src/strings/zstring.py b/src/strings/zstring.py index f57252f..570c8f1 100644 --- a/src/strings/zstring.py +++ b/src/strings/zstring.py @@ -22,10 +22,10 @@ class ZString(object): ''' tests = [ - ('tmwa::ZString(""_s)', '"" = {base = 0x0}'), - ('tmwa::ZString("Hello"_s)', '"Hello" = {base = 0x0}'), - ('tmwa::ZString("' + str256[:-2] + '"_s)', '"' + str256[:-2] + '" = {base = 0x0}'), - ('tmwa::ZString("' + str256[:-1] + '"_s)', '"' + str256[:-1] + '" = {base = 0x0}'), - ('tmwa::ZString("' + str256 + '"_s)', '"' + str256 + '" = {base = 0x0}'), - ('tmwa::ZString("' + str256 + 'x"_s)', '"' + str256 + 'x" = {base = 0x0}'), + ('tmwa::ZString(""_s)', '"" = {base = nullptr}'), + ('tmwa::ZString("Hello"_s)', '"Hello" = {base = nullptr}'), + ('tmwa::ZString("' + str256[:-2] + '"_s)', '"' + str256[:-2] + '" = {base = nullptr}'), + ('tmwa::ZString("' + str256[:-1] + '"_s)', '"' + str256[:-1] + '" = {base = nullptr}'), + ('tmwa::ZString("' + str256 + '"_s)', '"' + str256 + '" = {base = nullptr}'), + ('tmwa::ZString("' + str256 + 'x"_s)', '"' + str256 + 'x" = {base = nullptr}'), ] diff --git a/src/tests/fdhack.cpp b/src/tests/fdhack.cpp deleted file mode 100644 index 7a95431..0000000 --- a/src/tests/fdhack.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "fdhack.hpp" -// fdhack.cpp - Move file descriptors around. -// -// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "../poison.hpp" - - -namespace tmwa -{ -} // namespace tmwa diff --git a/src/tests/fwd.hpp b/src/tests/fwd.hpp index 48627da..3ee52ff 100644 --- a/src/tests/fwd.hpp +++ b/src/tests/fwd.hpp @@ -20,6 +20,10 @@ #include "../sanity.hpp" +#include "../strings/fwd.hpp" // rank 1 +#include "../io/fwd.hpp" // rank 4 +// tests/fwd.hpp is rank 5, but gtests do not require rank + namespace tmwa { diff --git a/src/warnings.hpp b/src/warnings.hpp index 9389766..884857f 100644 --- a/src/warnings.hpp +++ b/src/warnings.hpp @@ -161,7 +161,10 @@ DIAG_E(trigraphs); DIAG_E(type_limits); DIAG_E(undef); DIAG_E(uninitialized); +// clang bug, fixed in 3.5 +#ifndef GENERATING_DEPENDENCIES DIAG_E(unknown_pragmas); +#endif DIAG_W(unreachable_code); DIAG_X(unsafe_loop_optimizations); DIAG_E(unused_but_set_parameter); diff --git a/src/generic/enum.cpp b/src/wire/fwd.hpp index 49402e9..83d5b20 100644 --- a/src/generic/enum.cpp +++ b/src/wire/fwd.hpp @@ -1,5 +1,5 @@ -#include "enum.hpp" -// enum.cpp - Safe building blocks for enumerated types. +#pragma once +// wire/fwd.hpp - list of type names for network packets // // Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> // @@ -18,9 +18,17 @@ // 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 "../poison.hpp" +#include "../sanity.hpp" +#include "../ints/fwd.hpp" // rank 1 +#include "../compat/fwd.hpp" // rank 2 +#include "../io/fwd.hpp" // rank 4 +#include "../net/fwd.hpp" // rank 5 +#include "../proto2/fwd.hpp" // rank 8 +// wire/fwd.hpp is rank 9 namespace tmwa { +enum class RecvResult; +// meh, add more when I feel like it } // namespace tmwa diff --git a/src/net/packets.cpp b/src/wire/packets.cpp index 3cba856..be06283 100644 --- a/src/net/packets.cpp +++ b/src/wire/packets.cpp @@ -65,29 +65,29 @@ bool packet_send(Session *s, const Byte *data, size_t sz) return true; } -void packet_dump(io::WriteFile& logfp, Session *s) +void packet_dump(Session *s) { - FPRINTF(logfp, + FPRINTF(stderr, "---- 00-01-02-03-04-05-06-07 08-09-0A-0B-0C-0D-0E-0F\n"_fmt); char tmpstr[16 + 1] {}; int i; for (i = 0; i < packet_avail(s); i++) { if ((i & 15) == 0) - FPRINTF(logfp, "%04X "_fmt, i); + FPRINTF(stderr, "%04X "_fmt, i); Byte rfifob_ib; packet_fetch(s, i, &rfifob_ib, 1); uint8_t rfifob_i = rfifob_ib.value; - FPRINTF(logfp, "%02x "_fmt, rfifob_i); + FPRINTF(stderr, "%02x "_fmt, rfifob_i); if (rfifob_i > 0x1f) tmpstr[i % 16] = rfifob_i; else tmpstr[i % 16] = '.'; if ((i - 7) % 16 == 0) // -8 + 1 - FPRINTF(logfp, " "_fmt); + FPRINTF(stderr, " "_fmt); else if ((i + 1) % 16 == 0) { - FPRINTF(logfp, " %s\n"_fmt, tmpstr); + FPRINTF(stderr, " %s\n"_fmt, tmpstr); std::fill(tmpstr + 0, tmpstr + 17, '\0'); } } @@ -95,12 +95,12 @@ void packet_dump(io::WriteFile& logfp, Session *s) { for (int j = i; j % 16 != 0; j++) { - FPRINTF(logfp, " "_fmt); + FPRINTF(stderr, " "_fmt); if ((j - 7) % 16 == 0) // -8 + 1 - FPRINTF(logfp, " "_fmt); + FPRINTF(stderr, " "_fmt); } - FPRINTF(logfp, " %s\n"_fmt, tmpstr); + FPRINTF(stderr, " %s\n"_fmt, tmpstr); } - FPRINTF(logfp, "\n"_fmt); + FPRINTF(stderr, "\n"_fmt); } } // namespace tmwa diff --git a/src/net/packets.hpp b/src/wire/packets.hpp index 5cc377c..82cc919 100644 --- a/src/net/packets.hpp +++ b/src/wire/packets.hpp @@ -22,16 +22,13 @@ #include <vector> -#include "../compat/cast.hpp" - #include "../ints/little.hpp" -#include "../io/fwd.hpp" +#include "../compat/cast.hpp" -// TODO ordering violation, should invert #include "../proto2/fwd.hpp" -#include "socket.hpp" +#include "../net/socket.hpp" namespace tmwa @@ -56,7 +53,7 @@ enum class SendResult size_t packet_avail(Session *s); -void packet_dump(io::WriteFile& out, Session *s); +void packet_dump(Session *s); bool packet_fetch(Session *s, size_t offset, Byte *data, size_t sz); void packet_discard(Session *s, size_t sz); |