#include "script.hpp" #include #include #include #include #include #include #include #include "../common/cxxstdio.hpp" #include "../common/db.hpp" #include "../common/extract.hpp" #include "../common/intern-pool.hpp" #include "../common/lock.hpp" #include "../common/random.hpp" #include "../common/socket.hpp" #include "../common/utils.hpp" #include "../common/timer.hpp" #include "atcommand.hpp" #include "battle.hpp" #include "chrif.hpp" #include "clif.hpp" #include "intif.hpp" #include "itemdb.hpp" #include "magic.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" constexpr bool DEBUG_DISP = false; constexpr bool DEBUG_RUN = false; struct str_data_t { ByteCode type; std::string strs; int backpatch; int label_; int val; }; static Map str_datam; static str_data_t LABEL_NEXTLINE_; static DMap mapreg_db; static Map mapregstr_db; static int mapreg_dirty = -1; char mapreg_txt[256] = "save/mapreg.txt"; constexpr std::chrono::milliseconds MAPREG_AUTOSAVE_INTERVAL = std::chrono::seconds(10); Map scriptlabel_db; UPMap userfunc_db; static const char *pos[11] = { "Head", "Body", "Left hand", "Right hand", "Robe", "Shoes", "Accessory 1", "Accessory 2", "Head 2", "Head 3", "Not Equipped", }; static struct Script_Config { int warn_func_no_comma; int warn_cmd_no_comma; int warn_func_mismatch_paramnum; int warn_cmd_mismatch_paramnum; int check_cmdcount; int check_gotocount; } script_config; static int parse_cmd_if = 0; static str_data_t *parse_cmdp; static void run_func(ScriptState *st); static void mapreg_setreg(int num, int val); static void mapreg_setregstr(int num, const char *str); struct BuiltinFunction { void (*func)(ScriptState *); const char *name; const char *arg; }; // defined later extern BuiltinFunction builtin_functions[]; static InternPool variable_names; enum class ByteCode : uint8_t { // types and specials NOP, POS, INT, PARAM_, FUNC_, STR, CONSTSTR, ARG, VARIABLE, EOL, RETINFO, // 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, }; static str_data_t *search_strp(const std::string& p) { return str_datam.search(p); } static str_data_t *add_strp(const std::string& p) { // TODO remove lowcase std::string lowcase = p; for (char& c : lowcase) c = tolower(c); if (str_data_t *rv = search_strp(lowcase)) return rv; // ? if (str_data_t *rv = search_strp(p)) return rv; str_data_t *datum = str_datam.init(p); datum->type = ByteCode::NOP; datum->strs = p; 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(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 ByteCode::POS: add_scriptc(ByteCode::POS); add_scriptb(static_cast(ld->label_)); add_scriptb(static_cast(ld->label_ >> 8)); add_scriptb(static_cast(ld->label_ >> 16)); break; case ByteCode::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(backpatch)); add_scriptb(static_cast(backpatch >> 8)); add_scriptb(static_cast(backpatch >> 16)); break; case ByteCode::INT: add_scripti(ld->val); break; case ByteCode::FUNC_: add_scriptc(ByteCode::FUNC_REF); add_scriptb(static_cast(ld->val)); add_scriptb(static_cast(ld->val >> 8)); add_scriptb(static_cast(ld->val >> 16)); break; case ByteCode::PARAM_: add_scriptc(ByteCode::PARAM_); add_scriptb(static_cast(ld->val)); add_scriptb(static_cast(ld->val >> 8)); add_scriptb(static_cast(ld->val >> 16)); break; default: abort(); } } /*========================================== * ラベルを解決する *------------------------------------------ */ void ScriptBuffer::set_label(str_data_t *ld, int pos_) { int next; ld->type = ByteCode::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(script_buf[i + 0]) << 0; next |= static_cast(script_buf[i + 1]) << 8; next |= static_cast(script_buf[i + 2]) << 16; script_buf[i - 1] = ByteCode::POS; script_buf[i] = static_cast(pos_); script_buf[i + 1] = static_cast(pos_ >> 8); script_buf[i + 2] = static_cast(pos_ >> 16); } } /*========================================== * スペース/コメント読み飛ばし *------------------------------------------ */ static const char *skip_space(const char *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 const char *skip_word(const char *p) { // prefix if (*p == '$') p++; // MAP鯖内共有変数用 if (*p == '@') p++; // 一時的変数用(like weiss) if (*p == '#') p++; // account変数用 if (*p == '#') p++; // ワールドaccount変数用 if (*p == 'l') p++; // 一時的変数用(like weiss) while (isalnum(*p) || *p == '_') p++; // postfix if (*p == '$') p++; // 文字列変数 return p; } static const char *startptr; static int startline; /*========================================== * エラーメッセージ出力 *------------------------------------------ */ static void disp_error_message(const char *mes, const char *pos_) { int line; const char *p; for (line = startline, p = startptr; p && *p; line++) { const char *linestart = p; char *lineend = const_cast(strchr(p, '\n')); // always initialized, but clang is not smart enough char c = '\0'; if (lineend) { c = *lineend; *lineend = 0; } if (lineend == NULL || pos_ < lineend) { PRINTF("%s line %d : ", mes, line); for (int i = 0; (linestart[i] != '\r') && (linestart[i] != '\n') && linestart[i]; i++) { if (linestart + i != pos_) PRINTF("%c", linestart[i]); else PRINTF("\'%c\'", linestart[i]); } PRINTF("\a\n"); if (lineend) *lineend = c; return; } *lineend = c; p = lineend + 1; } } /*========================================== * 項の解析 *------------------------------------------ */ const char *ScriptBuffer::parse_simpleexpr(const char *p) { int i; p = skip_space(p); if (*p == ';' || *p == ',') { disp_error_message("unexpected expr end", p); exit(1); } if (*p == '(') { p = parse_subexpr(p + 1, -1); p = skip_space(p); if ((*p++) != ')') { disp_error_message("unmatch ')'", p); exit(1); } } else if (isdigit(*p) || ((*p == '-' || *p == '+') && isdigit(p[1]))) { char *np; i = strtoul(p, &np, 0); add_scripti(i); p = np; } else if (*p == '"') { add_scriptc(ByteCode::STR); p++; while (*p && *p != '"') { if (p[-1] <= 0x7e && *p == '\\') p++; else if (*p == '\n') { disp_error_message("unexpected newline @ string", p); exit(1); } add_scriptb(*p++); } if (!*p) { disp_error_message("unexpected eof @ string", p); exit(1); } add_scriptb(0); p++; //'"' } else { // label , register , function etc const char *p2 = skip_word(p); if (p2 == p) { disp_error_message("unexpected character", p); exit(1); } str_data_t *ld = add_strp(std::string(p, p2)); parse_cmdp = ld; // warn_*_mismatch_paramnumのために必要 // why not just check l->str == "if" or std::string(p, p2) == "if"? if (ld == search_strp("if")) // warn_cmd_no_commaのために必要 parse_cmd_if++; p = p2; if (ld->type != ByteCode::FUNC_ && *p == '[') { // array(name[i] => getelementofarray(name,i) ) add_scriptl(search_strp("getelementofarray")); add_scriptc(ByteCode::ARG); add_scriptl(ld); p = parse_subexpr(p + 1, -1); p = skip_space(p); if (*p != ']') { disp_error_message("unmatch ']'", p); exit(1); } p++; add_scriptc(ByteCode::FUNC_); } else add_scriptl(ld); } return p; } /*========================================== * 式の解析 *------------------------------------------ */ const char *ScriptBuffer::parse_subexpr(const char *p, int limit) { ByteCode op; int opl, len; p = skip_space(p); if (*p == '-') { const char *tmpp = skip_space(p + 1); if (*tmpp == ';' || *tmpp == ',') { add_scriptl(&LABEL_NEXTLINE_); p++; return p; } } const char *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; const char *plist[128]; if (funcp->type != ByteCode::FUNC_) { disp_error_message("expect function", 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", p); } p = skip_space(p); i++; } plist[i] = p; if (*p != ')') { disp_error_message("func request '(' ')'", p); exit(1); } p++; if (funcp->type == ByteCode::FUNC_ && script_config.warn_func_mismatch_paramnum) { const char *arg = builtin_functions[funcp->val].arg; int j = 0; for (j = 0; arg[j]; j++) if (arg[j] == '*') break; if ((arg[j] == 0 && i != j) || (arg[j] == '*' && i < j)) { disp_error_message("illegal number of parameters", plist[(i < j) ? i : j]); } } } else // not op == ByteCode::FUNC { p = parse_subexpr(p, opl); } add_scriptc(op); p = skip_space(p); } return p; /* return first untreated operator */ } /*========================================== * 式の評価 *------------------------------------------ */ const char *ScriptBuffer::parse_expr(const char *p) { switch (*p) { case ')': case ';': case ':': case '[': case ']': case '}': disp_error_message("unexpected char", p); exit(1); } p = parse_subexpr(p, -1); return p; } /*========================================== * 行の解析 *------------------------------------------ */ const char *ScriptBuffer::parse_line(const char *p) { int i = 0; const char *plist[128]; p = skip_space(p); if (*p == ';') return p; parse_cmd_if = 0; // warn_cmd_no_commaのために必要 // 最初は関数名 const char *p2 = p; p = parse_simpleexpr(p); p = skip_space(p); str_data_t *cmd = parse_cmdp; if (cmd->type != ByteCode::FUNC_) { disp_error_message("expect command", p2); // exit(0); } add_scriptc(ByteCode::ARG); while (p && *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", p); } p = skip_space(p); i++; } plist[i] = p; if (!p || *(p++) != ';') { disp_error_message("need ';'", p); exit(1); } add_scriptc(ByteCode::FUNC_); if (cmd->type == ByteCode::FUNC_ && script_config.warn_cmd_mismatch_paramnum) { const char *arg = builtin_functions[cmd->val].arg; int j = 0; for (j = 0; arg[j]; j++) if (arg[j] == '*') break; if ((arg[j] == 0 && i != j) || (arg[j] == '*' && i < j)) { disp_error_message("illegal number of parameters", plist[(i < j) ? i : j]); } } 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 = ByteCode::FUNC_; n->val = i; } } /*========================================== * 定数データベースの読み込み *------------------------------------------ */ static void read_constdb(void) { std::ifstream in("db/const.txt"); if (!in.is_open()) { PRINTF("can't read db/const.txt\n"); return; } std::string line; while (std::getline(in, line)) { if (line[0] == '/' && line[1] == '/') continue; std::string name; int val; int type = 0; // if not provided if (SSCANF(line, "%m[A-Za-z0-9_] %i %i", &name, &val, &type) < 2) continue; for (char& c : name) c = tolower(c); str_data_t *n = add_strp(name); n->type = type ? ByteCode::PARAM_ : ByteCode::INT; n->val = val; } } std::unique_ptr parse_script(const char *src, int line) { auto script_buf = make_unique(); script_buf->parse_script(src, line); return std::move(script_buf); } /*========================================== * スクリプトの解析 *------------------------------------------ */ void ScriptBuffer::parse_script(const char *src, int line) { const char *p; static int first = 1; if (first) { add_builtin_functions(); read_constdb(); } first = 0; LABEL_NEXTLINE_.type = ByteCode::NOP; LABEL_NEXTLINE_.backpatch = -1; LABEL_NEXTLINE_.label_ = -1; for (auto& pair : str_datam) { str_data_t& dit = pair.second; if (dit.type == ByteCode::POS || dit.type == ByteCode::VARIABLE) { dit.type = ByteCode::NOP; dit.backpatch = -1; dit.label_ = -1; } } // 外部用label dbの初期化 scriptlabel_db.clear(); // for error message startptr = src; startline = line; p = src; p = skip_space(p); if (*p != '{') { disp_error_message("not found '{'", p); abort(); } for (p++; p && *p && *p != '}';) { p = skip_space(p); // labelだけ特殊処理 if (*skip_space(skip_word(p)) == ':') { const char *tmpp = skip_word(p); std::string str(p, tmpp); str_data_t *ld = add_strp(str); bool e1 = ld->type != ByteCode::NOP; bool e2 = ld->type == ByteCode::POS; bool e3 = ld->label_ != -1; assert (e1 == e2 && e2 == e3); if (e3) { disp_error_message("dup label ", p); exit(1); } set_label(ld, script_buf.size()); scriptlabel_db.insert(str, script_buf.size()); p = tmpp + 1; continue; } // 他は全部一緒くた p = parse_line(p); p = skip_space(p); add_scriptc(ByteCode::EOL); set_label(&LABEL_NEXTLINE_, script_buf.size()); LABEL_NEXTLINE_.type = ByteCode::NOP; LABEL_NEXTLINE_.backpatch = -1; LABEL_NEXTLINE_.label_ = -1; } add_scriptc(ByteCode::NOP); // resolve the unknown labels for (auto& pair : str_datam) { str_data_t& sit = pair.second; if (sit.type == ByteCode::NOP) { sit.type = ByteCode::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(script_buf[j + 0]) << 0; next |= static_cast(script_buf[j + 1]) << 8; next |= static_cast(script_buf[j + 2]) << 16; script_buf[j] = static_cast(pool_index); script_buf[j + 1] = static_cast(pool_index >> 8); script_buf[j + 2] = static_cast(pool_index >> 16); } } } if (!DEBUG_DISP) return; for (size_t i = 0; i < script_buf.size(); i++) { if ((i & 15) == 0) PRINTF("%04zx : ", i); PRINTF("%02x ", script_buf[i]); if ((i & 15) == 15) PRINTF("\n"); } PRINTF("\n"); } // // 実行系 // enum class ScriptEndState { ZERO, STOP, END, RERUNLINE, GOTO, RETFUNC, }; /*========================================== * ridからsdへの解決 *------------------------------------------ */ static dumb_ptr script_rid2sd(ScriptState *st) { dumb_ptr sd = map_id2sd(st->rid); if (!sd) { PRINTF("script_rid2sd: fatal error ! player not attached!\n"); } return sd; } /*========================================== * 変数の読み取り *------------------------------------------ */ static void get_val(ScriptState *st, struct script_data *data) { dumb_ptr sd = NULL; if (data->type == ByteCode::PARAM_) { if ((sd = script_rid2sd(st)) == NULL) PRINTF("get_val error param SP::%d\n", data->u.num); data->type = ByteCode::INT; if (sd) data->u.num = pc_readparam(sd, static_cast(data->u.num)); } else if (data->type == ByteCode::VARIABLE) { const std::string& name = variable_names.outtern(data->u.num & 0x00ffffff); char prefix = name.front(); char postfix = name.back(); if (prefix != '$') { if ((sd = script_rid2sd(st)) == NULL) PRINTF("get_val error name?:%s\n", name); } if (postfix == '$') { data->type = ByteCode::CONSTSTR; if (prefix == '@' || prefix == 'l') { if (sd) data->u.str = dumb_string::fake(pc_readregstr(sd, data->u.num)); } else if (prefix == '$') { std::string *s = mapregstr_db.search(data->u.num); data->u.str = s ? dumb_string::fake(s->c_str()) : dumb_string(); } else { PRINTF("script: get_val: illegal scope string variable.\n"); data->u.str = dumb_string::fake("!!ERROR!!"); } if (!data->u.str) data->u.str = dumb_string::fake(""); } else { data->type = ByteCode::INT; if (prefix == '@' || prefix == 'l') { if (sd) data->u.num = pc_readreg(sd, data->u.num); } else if (prefix == '$') { data->u.num = mapreg_db.get(data->u.num); } else if (prefix == '#') { if (name[1] == '#') { if (sd) data->u.num = pc_readaccountreg2(sd, name.c_str()); } else { if (sd) data->u.num = pc_readaccountreg(sd, name.c_str()); } } else { if (sd) data->u.num = pc_readglobalreg(sd, name.c_str()); } } } } /*========================================== * 変数の読み取り2 *------------------------------------------ */ static struct script_data get_val2(ScriptState *st, int num) { struct script_data dat; dat.type = ByteCode::VARIABLE; dat.u.num = num; get_val(st, &dat); return dat; } /*========================================== * 変数設定用 *------------------------------------------ */ static void set_reg(dumb_ptr sd, ByteCode type, size_t num, struct script_data vd) { if (type == ByteCode::PARAM_) { int val = vd.u.num; pc_setparam(sd, static_cast(num), val); return; } assert (type == ByteCode::VARIABLE); const std::string& name = variable_names.outtern(num); char prefix = name.front(); char postfix = name.back(); if (postfix == '$') { dumb_string str = vd.u.str; if (prefix == '@' || prefix == 'l') { pc_setregstr(sd, num, str.c_str()); } else if (prefix == '$') { mapreg_setregstr(num, str.c_str()); } else { PRINTF("script: set_reg: illegal scope string variable !"); } } else { // 数値 int val = vd.u.num; if (prefix == '@' || prefix == 'l') { pc_setreg(sd, num, val); } else if (prefix == '$') { mapreg_setreg(num, val); } else if (prefix == '#') { if (name[1] == '#') pc_setaccountreg2(sd, name.c_str(), val); else pc_setaccountreg(sd, name.c_str(), val); } else { pc_setglobalreg(sd, name.c_str(), val); } } } static void set_reg(dumb_ptr sd, ByteCode type, size_t num, int id) { struct script_data vd; vd.u.num = id; set_reg(sd, type, num, vd); } static void set_reg(dumb_ptr sd, ByteCode type, size_t num, dumb_string zd) { struct script_data vd; vd.u.str = zd; set_reg(sd, type, num, vd); } /*========================================== * 文字列への変換 *------------------------------------------ */ static dumb_string conv_str(ScriptState *st, struct script_data *data) { get_val(st, data); assert (data->type != ByteCode::RETINFO); if (data->type == ByteCode::INT) { std::string buf = STRPRINTF("%d", data->u.num); data->type = ByteCode::STR; data->u.str = dumb_string::copys(buf); } return data->u.str; } /*========================================== * 数値へ変換 *------------------------------------------ */ static int conv_num(ScriptState *st, struct script_data *data) { get_val(st, data); assert (data->type != ByteCode::RETINFO); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { dumb_string p = data->u.str; data->u.num = atoi(p.c_str()); if (data->type == ByteCode::STR) p.delete_(); data->type = ByteCode::INT; } return data->u.num; } static const ScriptBuffer *conv_script(ScriptState *st, struct script_data *data) { get_val(st, data); assert (data->type == ByteCode::RETINFO); return data->u.script; } /*========================================== * スタックへ数値をプッシュ *------------------------------------------ */ static void push_val(struct script_stack *stack, ByteCode type, int val) { assert (type != ByteCode::RETINFO); assert (type != ByteCode::STR); assert (type != ByteCode::CONSTSTR); script_data nsd {}; nsd.type = type; nsd.u.num = val; stack->stack_datav.push_back(nsd); } static void push_script(struct script_stack *stack, ByteCode type, const ScriptBuffer *code) { assert (type == ByteCode::RETINFO); script_data nsd {}; nsd.type = type; nsd.u.script = code; stack->stack_datav.push_back(nsd); } /*========================================== * スタックへ文字列をプッシュ *------------------------------------------ */ static void push_str(struct script_stack *stack, ByteCode type, dumb_string str) { assert (type == ByteCode::STR || type == ByteCode::CONSTSTR); script_data nsd {}; nsd.type = type; nsd.u.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_]; if (csd.type == ByteCode::STR) csd.u.str = csd.u.str.dup(); stack->stack_datav.push_back(csd); } /*========================================== * スタックからポップ *------------------------------------------ */ static void pop_stack(struct script_stack *stack, int start, int end) { for (int i = start; i < end; i++) { if (stack->stack_datav[i].type == ByteCode::STR) stack->stack_datav[i].u.str.delete_(); } 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) { dumb_string mes = conv_str(st, &AARGO2(2)); clif_scriptmes(script_rid2sd(st), st->oid, mes.c_str()); } /*========================================== * *------------------------------------------ */ static void builtin_goto(ScriptState *st) { if (AARGO2(2).type != ByteCode::POS) { PRINTF("script: goto: not label !\n"); st->state = ScriptEndState::END; return; } st->scriptp.pos = conv_num(st, &AARGO2(2)); st->state = ScriptEndState::GOTO; } /*========================================== * ユーザー定義関数の呼び出し *------------------------------------------ */ static void builtin_callfunc(ScriptState *st) { dumb_string str = conv_str(st, &AARGO2(2)); const ScriptBuffer *scr = userfunc_db.get(str.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_val(st->stack, ByteCode::INT, j); // 引数の数をプッシュ push_val(st->stack, ByteCode::INT, st->defsp); // 現在の基準スタックポインタをプッシュ push_val(st->stack, ByteCode::INT, st->scriptp.pos); // 現在のスクリプト位置をプッシュ push_script(st->stack, ByteCode::RETINFO, 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", 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_val(st->stack, ByteCode::INT, j); // 引数の数をプッシュ push_val(st->stack, ByteCode::INT, st->defsp); // 現在の基準スタックポインタをプッシュ push_val(st->stack, ByteCode::INT, st->scriptp.pos); // 現在のスクリプト位置をプッシュ push_script(st->stack, ByteCode::RETINFO, 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 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; std::string buf; for (int i = st->start + 2; i < st->end; i += 2) { dumb_string choice_str = conv_str(st, &AARGO2(i - st->start)); if (!choice_str[0]) break; buf += choice_str.c_str(); buf += ':'; } clif_scriptmenu(script_rid2sd(st), st->oid, buf.c_str()); } 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, variable_names.intern("@menu"), 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).type != ByteCode::POS) { st->state = ScriptEndState::END; return; } st->scriptp.pos = conv_num(st, &AARGO2(arg_index + 2)); 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_val(st->stack, ByteCode::INT, random_::in(min, max)); } else { int range = conv_num(st, &AARGO2(2)); push_val(st->stack, ByteCode::INT, range <= 0 ? 0 : random_::to(range)); } } /*========================================== * *------------------------------------------ */ static void builtin_pow(ScriptState *st) { int a, b; a = conv_num(st, &AARGO2(2)); b = conv_num(st, &AARGO2(3)); #warning "This is silly" push_val(st->stack, ByteCode::INT, static_cast(pow(a * 0.001, b))); } /*========================================== * Check whether the PC is at the specified location *------------------------------------------ */ static void builtin_isat(ScriptState *st) { int x, y; dumb_ptr sd = script_rid2sd(st); dumb_string str = conv_str(st, &AARGO2(2)); x = conv_num(st, &AARGO2(3)); y = conv_num(st, &AARGO2(4)); if (!sd) return; using namespace operators; push_val(st->stack, ByteCode::INT, (x == sd->bl_x) && (y == sd->bl_y) && (str == sd->bl_m->name)); } /*========================================== * *------------------------------------------ */ static void builtin_warp(ScriptState *st) { int x, y; dumb_ptr sd = script_rid2sd(st); dumb_string str = conv_str(st, &AARGO2(2)); x = conv_num(st, &AARGO2(3)); y = conv_num(st, &AARGO2(4)); using namespace operators; if (str == "Random") pc_randomwarp(sd, BeingRemoveWhy::WARPED); else if (str == "SavePoint") { if (sd->bl_m->flag.noreturn) // 蝶禁止 return; pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, BeingRemoveWhy::WARPED); } else if (str == "Save") { if (sd->bl_m->flag.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.c_str(), x, y, BeingRemoveWhy::GONE); } /*========================================== * エリア指定ワープ *------------------------------------------ */ static void builtin_areawarp_sub(dumb_ptr bl, dumb_string mapname, int x, int y) { dumb_ptr sd = bl->as_player(); using namespace operators; if (mapname == "Random") pc_randomwarp(sd, BeingRemoveWhy::WARPED); else pc_setpos(sd, mapname.c_str(), x, y, BeingRemoveWhy::GONE); } static void builtin_areawarp(ScriptState *st) { int x, y; int x0, y0, x1, y1; dumb_string mapname = 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)); dumb_string str = conv_str(st, &AARGO2(7)); x = conv_num(st, &AARGO2(8)); y = conv_num(st, &AARGO2(9)); map_local *m = map_mapname2mapid(mapname.c_str()); 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 sd = NULL; script_data& scrd = AARGO2(2); ByteCode type = scrd.type; assert (type == ByteCode::VARIABLE); int num = scrd.u.num; const std::string& name = variable_names.outtern(num & 0x00ffffff); // 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, type, num, dumb_string::fake(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, type, num, 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_val(st->stack, ByteCode::ARG, 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 sd = NULL; int num = AARGO2(2).u.num; if (AARGO2(2).type == ByteCode::PARAM_) { sd = script_rid2sd(st); int val = conv_num(st, &AARGO2(3)); set_reg(sd, ByteCode::PARAM_, num, val); return; } const std::string& name = variable_names.outtern(num & 0x00ffffff); char prefix = name.front(); char postfix = name.back(); assert (AARGO2(2).type == ByteCode::VARIABLE); if (prefix != '$') sd = script_rid2sd(st); if (postfix == '$') { // 文字列 dumb_string str = conv_str(st, &AARGO2(3)); set_reg(sd, ByteCode::VARIABLE, num, str); } else { // 数値 int val = conv_num(st, &AARGO2(3)); set_reg(sd, ByteCode::VARIABLE, num, val); } } /*========================================== * 配列変数設定 *------------------------------------------ */ static void builtin_setarray(ScriptState *st) { dumb_ptr sd = NULL; assert (AARGO2(2).type == ByteCode::VARIABLE); int num = AARGO2(2).u.num; const std::string& name = variable_names.outtern(num & 0x00ffffff); char prefix = name.front(); char postfix = name.back(); if (prefix != '$' && prefix != '@') { PRINTF("builtin_setarray: illegal scope !\n"); return; } if (prefix != '$') sd = script_rid2sd(st); for (int j = 0, i = st->start + 3; i < st->end && j < 128; i++, j++) { if (postfix == '$') set_reg(sd, ByteCode::VARIABLE, num + (j << 24), conv_str(st, &AARGO2(i - st->start))); else set_reg(sd, ByteCode::VARIABLE, num + (j << 24), conv_num(st, &AARGO2(i - st->start))); } } /*========================================== * 配列変数クリア *------------------------------------------ */ static void builtin_cleararray(ScriptState *st) { dumb_ptr sd = NULL; assert (AARGO2(2).type == ByteCode::VARIABLE); int num = AARGO2(2).u.num; const std::string& name = variable_names.outtern(num & 0x00ffffff); char prefix = name.front(); char postfix = name.back(); int sz = conv_num(st, &AARGO2(4)); if (prefix != '$' && prefix != '@') { PRINTF("builtin_cleararray: illegal scope !\n"); return; } if (prefix != '$') sd = script_rid2sd(st); if (postfix == '$') for (int i = 0; i < sz; i++) set_reg(sd, ByteCode::VARIABLE, num + (i << 24), conv_str(st, &AARGO2(3))); else for (int i = 0; i < sz; i++) set_reg(sd, ByteCode::VARIABLE, num + (i << 24), conv_num(st, &AARGO2(3))); } /*========================================== * 配列変数のサイズ所得 *------------------------------------------ */ static int getarraysize(ScriptState *st, int num, int postfix) { int i = (num >> 24), c = i; for (; i < 128; i++) { struct script_data vd = get_val2(st, num + (i << 24)); if (postfix == '$' ? bool(vd.u.str[0]) : bool(vd.u.num)) c = i; } return c + 1; } static void builtin_getarraysize(ScriptState *st) { assert (AARGO2(2).type == ByteCode::VARIABLE); int num = AARGO2(2).u.num; const std::string& name = variable_names.outtern(num & 0x00ffffff); char prefix = name.front(); char postfix = name.back(); if (prefix != '$' && prefix != '@') { PRINTF("builtin_copyarray: illegal scope !\n"); return; } push_val(st->stack, ByteCode::INT, getarraysize(st, num, postfix)); } /*========================================== * 指定要素を表す値(キー)を所得する *------------------------------------------ */ static void builtin_getelementofarray(ScriptState *st) { if (AARGO2(2).type == ByteCode::VARIABLE) { int i = conv_num(st, &AARGO2(3)); if (i > 127 || i < 0) { PRINTF("script: getelementofarray (operator[]): param2 illegal number %d\n", i); push_val(st->stack, ByteCode::INT, 0); } else { push_val(st->stack, ByteCode::VARIABLE, (i << 24) | AARGO2(2).u.num); } } else { PRINTF("script: getelementofarray (operator[]): param1 not name !\n"); push_val(st->stack, ByteCode::INT, 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) { int nameid = 0, count = 0, i; dumb_ptr sd; struct script_data *data; sd = script_rid2sd(st); data = &AARGO2(2); get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { dumb_string name = conv_str(st, data); struct item_data *item_data = itemdb_searchname(name.c_str()); if (item_data != NULL) nameid = item_data->nameid; } else nameid = conv_num(st, data); if (nameid >= 500) //if no such ID then skip this iteration for (i = 0; i < MAX_INVENTORY; i++) { 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", nameid); } push_val(st->stack, ByteCode::INT, count); } /*========================================== * 重量チェック *------------------------------------------ */ static void builtin_checkweight(ScriptState *st) { int nameid = 0, amount; dumb_ptr sd; struct script_data *data; sd = script_rid2sd(st); data = &AARGO2(2); get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { dumb_string name = conv_str(st, data); struct item_data *item_data = itemdb_searchname(name.c_str()); if (item_data) nameid = item_data->nameid; } else nameid = conv_num(st, data); amount = conv_num(st, &AARGO2(3)); if (amount <= 0 || nameid < 500) { //if get wrong item ID or amount<=0, don't count weight of non existing items push_val(st->stack, ByteCode::INT, 0); return; } if (itemdb_weight(nameid) * amount + sd->weight > sd->max_weight) { push_val(st->stack, ByteCode::INT, 0); } else { push_val(st->stack, ByteCode::INT, 1); } } /*========================================== * *------------------------------------------ */ static void builtin_getitem(ScriptState *st) { int nameid, amount; struct item item_tmp; dumb_ptr sd; struct script_data *data; sd = script_rid2sd(st); data = &AARGO2(2); get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { dumb_string name = conv_str(st, data); struct item_data *item_data = itemdb_searchname(name.c_str()); nameid = 727; //Default to iten if (item_data != NULL) nameid = item_data->nameid; } else nameid = conv_num(st, data); if ((amount = conv_num(st, &AARGO2(3))) <= 0) { return; //return if amount <=0, skip the useles iteration } if (nameid > 0) { memset(&item_tmp, 0, sizeof(item_tmp)); item_tmp.nameid = nameid; item_tmp.identify = 1; if (HARGO2(5)) //アイテムを指定したIDに渡す sd = map_id2sd(conv_num(st, &AARGO2(5))); if (sd == NULL) //アイテムを渡す相手がいなかったらお帰り return; PickupFail flag; if ((flag = pc_additem(sd, &item_tmp, amount)) != PickupFail::OKAY) { clif_additem(sd, 0, 0, flag); map_addflooritem(&item_tmp, amount, sd->bl_m, sd->bl_x, sd->bl_y, NULL, NULL, NULL); } } } /*========================================== * *------------------------------------------ */ static void builtin_makeitem(ScriptState *st) { int nameid, amount, flag = 0; int x, y; struct item item_tmp; dumb_ptr sd; struct script_data *data; sd = script_rid2sd(st); data = &AARGO2(2); get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { dumb_string name = conv_str(st, data); struct item_data *item_data = itemdb_searchname(name.c_str()); nameid = 512; //Apple Item ID if (item_data) nameid = item_data->nameid; } else nameid = conv_num(st, data); amount = conv_num(st, &AARGO2(3)); dumb_string mapname = conv_str(st, &AARGO2(4)); x = conv_num(st, &AARGO2(5)); y = conv_num(st, &AARGO2(6)); map_local *m; using namespace operators; if (sd && mapname == "this") m = sd->bl_m; else m = map_mapname2mapid(mapname.c_str()); if (nameid > 0) { memset(&item_tmp, 0, sizeof(item_tmp)); item_tmp.nameid = nameid; if (!flag) item_tmp.identify = 1; else item_tmp.identify = !itemdb_isequip3(nameid); map_addflooritem(&item_tmp, amount, m, x, y, NULL, NULL, NULL); } } /*========================================== * *------------------------------------------ */ static void builtin_delitem(ScriptState *st) { int nameid = 0, amount, i; dumb_ptr sd; struct script_data *data; sd = script_rid2sd(st); data = &AARGO2(2); get_val(st, data); if (data->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { dumb_string name = conv_str(st, data); struct item_data *item_data = itemdb_searchname(name.c_str()); //nameid=512; if (item_data) nameid = item_data->nameid; } else nameid = conv_num(st, data); amount = conv_num(st, &AARGO2(3)); if (nameid < 500 || amount <= 0) { //by Lupus. Don't run FOR if u got wrong item ID or amount<=0 //PRINTF("wrong item ID or amount<=0 : delitem %i,\n",nameid,amount); return; } for (i = 0; i < MAX_INVENTORY; i++) { if (sd->status.inventory[i].nameid <= 0 || sd->inventory_data[i] == NULL || sd->inventory_data[i]->type != ItemType::_7 || sd->status.inventory[i].amount <= 0) continue; } for (i = 0; i < MAX_INVENTORY; i++) { 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 sd; SP type = SP(conv_num(st, &AARGO2(2))); if (HARGO2(3)) sd = map_nick2sd(conv_str(st, &AARGO2(3)).c_str()); else sd = script_rid2sd(st); if (sd == NULL) { push_val(st->stack, ByteCode::INT, -1); return; } push_val(st->stack, ByteCode::INT, pc_readparam(sd, type)); } /*========================================== *キャラ関係のID取得 *------------------------------------------ */ static void builtin_getcharid(ScriptState *st) { int num; dumb_ptr sd; num = conv_num(st, &AARGO2(2)); if (HARGO2(3)) sd = map_nick2sd(conv_str(st, &AARGO2(3)).c_str()); else sd = script_rid2sd(st); if (sd == NULL) { push_val(st->stack, ByteCode::INT, -1); return; } if (num == 0) push_val(st->stack, ByteCode::INT, sd->status.char_id); if (num == 1) push_val(st->stack, ByteCode::INT, sd->status.party_id); if (num == 2) push_val(st->stack, ByteCode::INT, 0/*guild_id*/); if (num == 3) push_val(st->stack, ByteCode::INT, sd->status.account_id); } /*========================================== *指定IDのPT名取得 *------------------------------------------ */ static dumb_string builtin_getpartyname_sub(int party_id) { struct party *p = party_search(party_id); if (p) return dumb_string::copy(p->name); return dumb_string(); } /*========================================== * キャラクタの名前 *------------------------------------------ */ static void builtin_strcharinfo(ScriptState *st) { dumb_ptr sd; int num; sd = script_rid2sd(st); num = conv_num(st, &AARGO2(2)); if (num == 0) { dumb_string buf = dumb_string::copy(sd->status.name); push_str(st->stack, ByteCode::STR, buf); } if (num == 1) { dumb_string buf = builtin_getpartyname_sub(sd->status.party_id); if (buf) push_str(st->stack, ByteCode::STR, buf); else push_str(st->stack, ByteCode::CONSTSTR, dumb_string::fake("")); } if (num == 2) { // was: guild name push_str(st->stack, ByteCode::CONSTSTR, dumb_string::fake("")); } } // indexed by the equip_* in db/const.txt // TODO change to use EQUIP static EPOS equip[10] = { EPOS::HAT, EPOS::MISC1, EPOS::SHIELD, EPOS::WEAPON, EPOS::GLOVES, EPOS::SHOES, EPOS::CAPE, EPOS::MISC2, EPOS::TORSO, EPOS::LEGS, }; /*========================================== * GetEquipID(Pos); Pos: 1-10 *------------------------------------------ */ static void builtin_getequipid(ScriptState *st) { int i, num; dumb_ptr sd; struct item_data *item; sd = script_rid2sd(st); if (sd == NULL) { PRINTF("getequipid: sd == NULL\n"); return; } num = conv_num(st, &AARGO2(2)); i = pc_checkequip(sd, equip[num - 1]); if (i >= 0) { item = sd->inventory_data[i]; if (item) push_val(st->stack, ByteCode::INT, item->nameid); else push_val(st->stack, ByteCode::INT, 0); } else { push_val(st->stack, ByteCode::INT, -1); } } /*========================================== * 装備名文字列(精錬メニュー用) *------------------------------------------ */ static void builtin_getequipname(ScriptState *st) { int i, num; dumb_ptr sd; struct item_data *item; std::string buf; sd = script_rid2sd(st); num = conv_num(st, &AARGO2(2)); i = pc_checkequip(sd, equip[num - 1]); if (i >= 0) { item = sd->inventory_data[i]; if (item) buf = STRPRINTF("%s-[%s]", pos[num - 1], item->jname); else buf = STRPRINTF("%s-[%s]", pos[num - 1], pos[10]); } else { buf = STRPRINTF("%s-[%s]", pos[num - 1], pos[10]); } push_str(st->stack, ByteCode::STR, dumb_string::copys(buf)); } /*========================================== * *------------------------------------------ */ static void builtin_statusup2(ScriptState *st) { SP type = SP(conv_num(st, &AARGO2(2))); int val = conv_num(st, &AARGO2(3)); dumb_ptr 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 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 sd = script_rid2sd(st); pc_bonus2(sd, type, type2, val); } /*========================================== * スキル所得 *------------------------------------------ */ static void builtin_skill(ScriptState *st) { int level, flag = 1; dumb_ptr 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 sd; SkillID id = static_cast(conv_num(st, &AARGO2(2))); level = conv_num(st, &AARGO2(3)); sd = script_rid2sd(st); sd->status.skill[id].lv = level; clif_skillinfoblock(sd); } /*========================================== * スキルレベル所得 *------------------------------------------ */ static void builtin_getskilllv(ScriptState *st) { SkillID id = SkillID(conv_num(st, &AARGO2(2))); push_val(st->stack, ByteCode::INT, pc_checkskill(script_rid2sd(st), id)); } /*========================================== * *------------------------------------------ */ static void builtin_getgmlevel(ScriptState *st) { push_val(st->stack, ByteCode::INT, pc_isGM(script_rid2sd(st))); } /*========================================== * *------------------------------------------ */ static void builtin_end(ScriptState *st) { st->state = ScriptEndState::END; } /*========================================== * [Freeyorp] Return the current opt2 *------------------------------------------ */ static void builtin_getopt2(ScriptState *st) { dumb_ptr sd; sd = script_rid2sd(st); push_val(st->stack, ByteCode::INT, uint16_t(sd->opt2)); } /*========================================== * [Freeyorp] Sets opt2 *------------------------------------------ */ static void builtin_setopt2(ScriptState *st) { dumb_ptr 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; dumb_string str = conv_str(st, &AARGO2(2)); x = conv_num(st, &AARGO2(3)); y = conv_num(st, &AARGO2(4)); pc_setsavepoint(script_rid2sd(st), str.c_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_val(st->stack, ByteCode::INT, t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec); break; } /* Seconds since Unix epoch. */ case 2: push_val(st->stack, ByteCode::INT, static_cast(TimeT::now())); break; /* System tick(unsigned int, and yes, it will wrap). */ case 0: default: push_val(st->stack, ByteCode::INT, 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_val(st->stack, ByteCode::INT, t.tm_sec); break; case 2: //Min(0~59) push_val(st->stack, ByteCode::INT, t.tm_min); break; case 3: //Hour(0~23) push_val(st->stack, ByteCode::INT, t.tm_hour); break; case 4: //WeekDay(0~6) push_val(st->stack, ByteCode::INT, t.tm_wday); break; case 5: //MonthDay(01~31) push_val(st->stack, ByteCode::INT, t.tm_mday); break; case 6: //Month(01~12) push_val(st->stack, ByteCode::INT, t.tm_mon + 1); break; case 7: //Year(20xx) push_val(st->stack, ByteCode::INT, t.tm_year + 1900); break; default: //(format error) push_val(st->stack, ByteCode::INT, -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 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 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) { int mob_class, amount, x, y; const char *event = ""; dumb_string mapname = conv_str(st, &AARGO2(2)); x = conv_num(st, &AARGO2(3)); y = conv_num(st, &AARGO2(4)); dumb_string str = conv_str(st, &AARGO2(5)); mob_class = conv_num(st, &AARGO2(6)); amount = conv_num(st, &AARGO2(7)); if (HARGO2(8)) event = conv_str(st, &AARGO2(8)).c_str(); mob_once_spawn(map_id2sd(st->rid), mapname.c_str(), x, y, str.c_str(), mob_class, amount, event); } /*========================================== * モンスター発生 *------------------------------------------ */ static void builtin_areamonster(ScriptState *st) { int mob_class, amount, x0, y0, x1, y1; const char *event = ""; dumb_string mapname = 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)); dumb_string str = conv_str(st, &AARGO2(7)); mob_class = conv_num(st, &AARGO2(8)); amount = conv_num(st, &AARGO2(9)); if (HARGO2(10)) event = conv_str(st, &AARGO2(10)).c_str(); mob_once_spawn_area(map_id2sd(st->rid), mapname.c_str(), x0, y0, x1, y1, str.c_str(), mob_class, amount, event); } /*========================================== * モンスター削除 *------------------------------------------ */ static void builtin_killmonster_sub(dumb_ptr bl, dumb_string event, int allflag) { dumb_ptr md = bl->as_mob(); if (!allflag) { using namespace operators; if (event == md->npc_event) mob_delete(md); return; } else if (allflag) { if (md->spawn.delay1 == static_cast(-1) && md->spawn.delay2 == static_cast(-1)) mob_delete(md); return; } } static void builtin_killmonster(ScriptState *st) { int allflag = 0; dumb_string mapname = conv_str(st, &AARGO2(2)); dumb_string event = conv_str(st, &AARGO2(3)); using namespace operators; if (event == "All") allflag = 1; map_local *m = map_mapname2mapid(mapname.c_str()); if (m == nullptr) return; map_foreachinarea(std::bind(builtin_killmonster_sub, ph::_1, event, allflag), m, 0, 0, m->xs, m->ys, BL::MOB); } static void builtin_killmonsterall_sub(dumb_ptr bl) { mob_delete(bl->as_mob()); } static void builtin_killmonsterall(ScriptState *st) { dumb_string mapname = conv_str(st, &AARGO2(2)); map_local *m = map_mapname2mapid(mapname.c_str()); 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) { dumb_string event = conv_str(st, &AARGO2(2)); npc_event_do(event.c_str()); } /*========================================== * イベントタイマー追加 *------------------------------------------ */ static void builtin_addtimer(ScriptState *st) { interval_t tick = static_cast(conv_num(st, &AARGO2(2))); dumb_string event = conv_str(st, &AARGO2(3)); pc_addeventtimer(script_rid2sd(st), tick, event.c_str()); } /*========================================== * NPCタイマー初期化 *------------------------------------------ */ static void builtin_initnpctimer(ScriptState *st) { dumb_ptr nd_; if (HARGO2(2)) nd_ = npc_name2id(conv_str(st, &AARGO2(2)).c_str()); else nd_ = map_id_as_npc(st->oid); assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); dumb_ptr nd = nd_->as_script(); npc_settimerevent_tick(nd, interval_t::zero()); npc_timerevent_start(nd); } /*========================================== * NPCタイマー開始 *------------------------------------------ */ static void builtin_startnpctimer(ScriptState *st) { dumb_ptr nd_; if (HARGO2(2)) nd_ = npc_name2id(conv_str(st, &AARGO2(2)).c_str()); else nd_ = map_id_as_npc(st->oid); assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); dumb_ptr nd = nd_->as_script(); npc_timerevent_start(nd); } /*========================================== * NPCタイマー停止 *------------------------------------------ */ static void builtin_stopnpctimer(ScriptState *st) { dumb_ptr nd_; if (HARGO2(2)) nd_ = npc_name2id(conv_str(st, &AARGO2(2)).c_str()); else nd_ = map_id_as_npc(st->oid); assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); dumb_ptr nd = nd_->as_script(); npc_timerevent_stop(nd); } /*========================================== * NPCタイマー情報所得 *------------------------------------------ */ static void builtin_getnpctimer(ScriptState *st) { dumb_ptr nd_; int type = conv_num(st, &AARGO2(2)); int val = 0; if (HARGO2(3)) nd_ = npc_name2id(conv_str(st, &AARGO2(3)).c_str()); else nd_ = map_id_as_npc(st->oid); assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); dumb_ptr nd = nd_->as_script(); switch (type) { case 0: val = npc_gettimerevent_tick(nd).count(); break; case 1: val = nd->scr.nexttimer != nd->scr.timer_eventv.end(); break; case 2: val = nd->scr.timer_eventv.size(); break; } push_val(st->stack, ByteCode::INT, val); } /*========================================== * NPCタイマー値設定 *------------------------------------------ */ static void builtin_setnpctimer(ScriptState *st) { dumb_ptr nd_; interval_t tick = static_cast(conv_num(st, &AARGO2(2))); if (HARGO2(3)) nd_ = npc_name2id(conv_str(st, &AARGO2(3)).c_str()); else nd_ = map_id_as_npc(st->oid); assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); dumb_ptr nd = nd_->as_script(); npc_settimerevent_tick(nd, tick); } /*========================================== * 天の声アナウンス *------------------------------------------ */ static void builtin_announce(ScriptState *st) { int flag; dumb_string str = conv_str(st, &AARGO2(2)); flag = conv_num(st, &AARGO2(3)); if (flag & 0x0f) { dumb_ptr bl; if (flag & 0x08) bl = map_id2bl(st->oid); else bl = script_rid2sd(st); clif_GMmessage(bl, str, flag); } else intif_GMmessage(str, flag); } /*========================================== * 天の声アナウンス(特定マップ) *------------------------------------------ */ static void builtin_mapannounce_sub(dumb_ptr bl, dumb_string str, int flag) { clif_GMmessage(bl, str.c_str(), flag | 3); } static void builtin_mapannounce(ScriptState *st) { int flag; dumb_string mapname = conv_str(st, &AARGO2(2)); dumb_string str = conv_str(st, &AARGO2(3)); flag = conv_num(st, &AARGO2(4)); map_local *m = map_mapname2mapid(mapname.c_str()); 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 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_val(st->stack, ByteCode::INT, val); } /*========================================== * マップ指定ユーザー数所得 *------------------------------------------ */ static void builtin_getmapusers(ScriptState *st) { dumb_string str = conv_str(st, &AARGO2(2)); map_local *m = map_mapname2mapid(str.c_str()); if (m == nullptr) { push_val(st->stack, ByteCode::INT, -1); return; } push_val(st->stack, ByteCode::INT, m->users); } /*========================================== * エリア指定ユーザー数所得 *------------------------------------------ */ static void builtin_getareausers_sub(dumb_ptr, int *users) { (*users)++; } static void builtin_getareausers_living_sub(dumb_ptr bl, int *users) { if (!pc_isdead(bl->as_player())) (*users)++; } static void builtin_getareausers(ScriptState *st) { int x0, y0, x1, y1, users = 0; dumb_string str = 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.c_str()); if (m == nullptr) { push_val(st->stack, ByteCode::INT, -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_val(st->stack, ByteCode::INT, users); } /*========================================== * エリア指定ドロップアイテム数所得 *------------------------------------------ */ static void builtin_getareadropitem_sub(dumb_ptr bl, int item, int *amount) { dumb_ptr drop = bl->as_item(); if (drop->item_data.nameid == item) (*amount) += drop->item_data.amount; } static void builtin_getareadropitem_sub_anddelete(dumb_ptr bl, int item, int *amount) { dumb_ptr drop = bl->as_item(); if (drop->item_data.nameid == item) { (*amount) += drop->item_data.amount; clif_clearflooritem(drop, 0); map_delobject(drop->bl_id, drop->bl_type); } } static void builtin_getareadropitem(ScriptState *st) { int x0, y0, x1, y1, item, amount = 0, delitems = 0; struct script_data *data; dumb_string str = 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->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { dumb_string name = conv_str(st, data); struct item_data *item_data = itemdb_searchname(name.c_str()); item = 512; if (item_data) item = item_data->nameid; } else item = conv_num(st, data); if (HARGO2(8)) delitems = conv_num(st, &AARGO2(8)); map_local *m = map_mapname2mapid(str.c_str()); if (m == nullptr) { push_val(st->stack, ByteCode::INT, -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_val(st->stack, ByteCode::INT, amount); } /*========================================== * NPCの有効化 *------------------------------------------ */ static void builtin_enablenpc(ScriptState *st) { dumb_string str = conv_str(st, &AARGO2(2)); npc_enable(str.c_str(), 1); } /*========================================== * NPCの無効化 *------------------------------------------ */ static void builtin_disablenpc(ScriptState *st) { dumb_string str = conv_str(st, &AARGO2(2)); npc_enable(str.c_str(), 0); } /*========================================== * 状態異常にかかる *------------------------------------------ */ static void builtin_sc_start(ScriptState *st) { dumb_ptr bl; int val1; StatusChange type = static_cast(conv_num(st, &AARGO2(2))); interval_t tick = static_cast(conv_num(st, &AARGO2(3))); if (tick < std::chrono::seconds(1)) // 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(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 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 bl; StatusChange type = StatusChange(conv_num(st, &AARGO2(2))); bl = map_id2bl(st->rid); push_val(st->stack, ByteCode::INT, skill_status_change_active(bl, type)); } /*========================================== * *------------------------------------------ */ static void builtin_debugmes(ScriptState *st) { dumb_string mes = conv_str(st, &AARGO2(2)); PRINTF("script debug : %d %d : %s\n", st->rid, st->oid, mes); } /*========================================== * ステータスリセット *------------------------------------------ */ static void builtin_resetstatus(ScriptState *st) { dumb_ptr sd; sd = script_rid2sd(st); pc_resetstate(sd); } /*========================================== * 性別変換 *------------------------------------------ */ static void builtin_changesex(ScriptState *st) { dumb_ptr sd = NULL; sd = script_rid2sd(st); if (sd->status.sex == 0) { sd->status.sex = 1; sd->sex = 1; } else if (sd->status.sex == 1) { sd->status.sex = 0; sd->sex = 0; } chrif_char_ask_name(-1, sd->status.name, 5, 0, 0, 0, 0, 0, 0); // type: 5 - changesex chrif_save(sd); } /*========================================== * RIDのアタッチ *------------------------------------------ */ static void builtin_attachrid(ScriptState *st) { st->rid = conv_num(st, &AARGO2(2)); push_val(st->stack, ByteCode::INT, (map_id2sd(st->rid) != NULL)); } /*========================================== * RIDのデタッチ *------------------------------------------ */ static void builtin_detachrid(ScriptState *st) { st->rid = 0; } /*========================================== * 存在チェック *------------------------------------------ */ static void builtin_isloggedin(ScriptState *st) { push_val(st->stack, ByteCode::INT, map_id2sd(conv_num(st, &AARGO2(2))) != NULL); } /*========================================== * *------------------------------------------ */ enum { MF_NOMEMO = 0, MF_NOTELEPORT = 1, MF_NOSAVE = 2, MF_NOBRANCH = 3, MF_NOPENALTY = 4, MF_NOZENYPENALTY = 5, MF_PVP = 6, MF_PVP_NOPARTY = 7, //MF_PVP_NOGUILD = 8, //MF_GVG = 9, //MF_GVG_NOPARTY = 10, MF_NOTRADE = 11, //MF_NOSKILL = 12, MF_NOWARP = 13, MF_NOPVP = 14, MF_NOICEWALL = 15, MF_SNOW = 16, MF_FOG = 17, MF_SAKURA = 18, MF_LEAVES = 19, MF_RAIN = 20, }; static void builtin_setmapflag(ScriptState *st) { dumb_string str = conv_str(st, &AARGO2(2)); int i = conv_num(st, &AARGO2(3)); map_local *m = map_mapname2mapid(str.c_str()); if (m != nullptr) { switch (i) { case MF_NOMEMO: m->flag.nomemo = 1; break; case MF_NOTELEPORT: m->flag.noteleport = 1; break; case MF_NOBRANCH: m->flag.nobranch = 1; break; case MF_NOPENALTY: m->flag.nopenalty = 1; break; case MF_PVP_NOPARTY: m->flag.pvp_noparty = 1; break; case MF_NOZENYPENALTY: m->flag.nozenypenalty = 1; break; case MF_NOTRADE: m->flag.notrade = 1; break; case MF_NOWARP: m->flag.nowarp = 1; break; case MF_NOPVP: m->flag.nopvp = 1; break; case MF_NOICEWALL: // [Valaris] m->flag.noicewall = 1; break; case MF_SNOW: // [Valaris] m->flag.snow = 1; break; case MF_FOG: // [Valaris] m->flag.fog = 1; break; case MF_SAKURA: // [Valaris] m->flag.sakura = 1; break; case MF_LEAVES: // [Valaris] m->flag.leaves = 1; break; case MF_RAIN: // [Valaris] m->flag.rain = 1; break; } } } static void builtin_removemapflag(ScriptState *st) { dumb_string str = conv_str(st, &AARGO2(2)); int i = conv_num(st, &AARGO2(3)); map_local *m = map_mapname2mapid(str.c_str()); if (m != nullptr) { switch (i) { case MF_NOMEMO: m->flag.nomemo = 0; break; case MF_NOTELEPORT: m->flag.noteleport = 0; break; case MF_NOSAVE: m->flag.nosave = 0; break; case MF_NOBRANCH: m->flag.nobranch = 0; break; case MF_NOPENALTY: m->flag.nopenalty = 0; break; case MF_PVP_NOPARTY: m->flag.pvp_noparty = 0; break; case MF_NOZENYPENALTY: m->flag.nozenypenalty = 0; break; case MF_NOWARP: m->flag.nowarp = 0; break; case MF_NOPVP: m->flag.nopvp = 0; break; case MF_NOICEWALL: // [Valaris] m->flag.noicewall = 0; break; case MF_SNOW: // [Valaris] m->flag.snow = 0; break; case MF_FOG: // [Valaris] m->flag.fog = 0; break; case MF_SAKURA: // [Valaris] m->flag.sakura = 0; break; case MF_LEAVES: // [Valaris] m->flag.leaves = 0; break; case MF_RAIN: // [Valaris] m->flag.rain = 0; break; } } } static void builtin_getmapflag(ScriptState *st) { int r = -1; dumb_string str = conv_str(st, &AARGO2(2)); int i = conv_num(st, &AARGO2(3)); map_local *m = map_mapname2mapid(str.c_str()); if (m != nullptr) { switch (i) { case MF_NOMEMO: r = m->flag.nomemo; break; case MF_NOTELEPORT: r = m->flag.noteleport; break; case MF_NOSAVE: r = m->flag.nosave; break; case MF_NOBRANCH: r = m->flag.nobranch; break; case MF_NOPENALTY: r = m->flag.nopenalty; break; case MF_PVP_NOPARTY: r = m->flag.pvp_noparty; break; case MF_NOZENYPENALTY: r = m->flag.nozenypenalty; break; case MF_NOWARP: r = m->flag.nowarp; break; case MF_NOPVP: r = m->flag.nopvp; break; case MF_NOICEWALL: // [Valaris] r = m->flag.noicewall; break; case MF_SNOW: // [Valaris] r = m->flag.snow; break; case MF_FOG: // [Valaris] r = m->flag.fog; break; case MF_SAKURA: // [Valaris] r = m->flag.sakura; break; case MF_LEAVES: // [Valaris] r = m->flag.leaves; break; case MF_RAIN: // [Valaris] r = m->flag.rain; break; } } push_val(st->stack, ByteCode::INT, r); } static void builtin_pvpon(ScriptState *st) { dumb_string str = conv_str(st, &AARGO2(2)); map_local *m = map_mapname2mapid(str.c_str()); if (m != nullptr && !m->flag.pvp && !m->flag.nopvp) { m->flag.pvp = 1; if (battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris] return; for (int i = 0; i < fd_max; i++) { if (!session[i]) continue; map_session_data *pl_sd = static_cast(session[i]->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() + std::chrono::milliseconds(200), 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) { dumb_string str = conv_str(st, &AARGO2(2)); map_local *m = map_mapname2mapid(str.c_str()); if (m != nullptr && m->flag.pvp && m->flag.nopvp) { m->flag.pvp = 0; if (battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris] return; for (int i = 0; i < fd_max; i++) { if (!session[i]) continue; map_session_data *pl_sd = static_cast(session[i]->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; dumb_string mapname = conv_str(st, &AARGO2(2)); x0 = 0; y0 = 0; map_local *m = map_mapname2mapid(mapname.c_str()); x1 = m->xs; y1 = m->ys; dumb_string str = 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 { dumb_string npc = conv_str(st, &AARGO2(2)); dumb_string command = conv_str(st, &AARGO2(3)); npc_command(map_id2sd(st->rid), npc.c_str(), command.c_str()); } static void builtin_mobcount_sub(dumb_ptr bl, dumb_string event, int *c) { using namespace operators; if (event == bl->as_mob()->npc_event) (*c)++; } static void builtin_mobcount(ScriptState *st) // Added by RoVeRT { int c = 0; dumb_string mapname = conv_str(st, &AARGO2(2)); dumb_string event = conv_str(st, &AARGO2(3)); map_local *m = map_mapname2mapid(mapname.c_str()); if (m == nullptr) { push_val(st->stack, ByteCode::INT, -1); return; } map_foreachinarea(std::bind(builtin_mobcount_sub, ph::_1, event, &c), m, 0, 0, m->xs, m->ys, BL::MOB); push_val(st->stack, ByteCode::INT, (c - 1)); } static void builtin_marriage(ScriptState *st) { dumb_string partner = conv_str(st, &AARGO2(2)); dumb_ptr sd = script_rid2sd(st); dumb_ptr p_sd = map_nick2sd(partner.c_str()); if (sd == NULL || p_sd == NULL || pc_marriage(sd, p_sd) < 0) { push_val(st->stack, ByteCode::INT, 0); return; } push_val(st->stack, ByteCode::INT, 1); } static void builtin_divorce(ScriptState *st) { dumb_ptr sd = script_rid2sd(st); st->state = ScriptEndState::STOP; // rely on pc_divorce to restart sd->npc_flags.divorce = 1; if (sd == NULL || pc_divorce(sd) < 0) { push_val(st->stack, ByteCode::INT, 0); return; } push_val(st->stack, ByteCode::INT, 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->type == ByteCode::STR || data->type == ByteCode::CONSTSTR) { dumb_string name = conv_str(st, data); i_data = itemdb_searchname(name.c_str()); } else { int item_id = conv_num(st, data); i_data = itemdb_search(item_id); } dumb_string item_name; if (i_data) item_name = dumb_string::copy(i_data->jname); else item_name = dumb_string::copy("Unknown Item"); push_str(st->stack, ByteCode::STR, item_name); } static void builtin_getspellinvocation(ScriptState *st) { dumb_string name = conv_str(st, &AARGO2(2)); const char *invocation = magic_find_invocation(name.str()); if (!invocation) invocation = "..."; push_str(st->stack, ByteCode::STR, dumb_string::copy(invocation)); } static void builtin_getpartnerid2(ScriptState *st) { dumb_ptr sd = script_rid2sd(st); push_val(st->stack, ByteCode::INT, sd->status.partner_id); } /*========================================== * PCの所持品情報読み取り *------------------------------------------ */ static void builtin_getinventorylist(ScriptState *st) { dumb_ptr sd = script_rid2sd(st); int i, j = 0; if (!sd) return; for (i = 0; i < MAX_INVENTORY; i++) { if (sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].amount > 0) { pc_setreg(sd, variable_names.intern("@inventorylist_id") + (j << 24), sd->status.inventory[i].nameid); pc_setreg(sd, variable_names.intern("@inventorylist_amount") + (j << 24), sd->status.inventory[i].amount); pc_setreg(sd, variable_names.intern("@inventorylist_equip") + (j << 24), uint16_t(sd->status.inventory[i].equip)); pc_setreg(sd, variable_names.intern("@inventorylist_refine") + (j << 24), sd->status.inventory[i].refine); pc_setreg(sd, variable_names.intern("@inventorylist_identify") + (j << 24), sd->status.inventory[i].identify); pc_setreg(sd, variable_names.intern("@inventorylist_attribute") + (j << 24), sd->status.inventory[i].attribute); pc_setreg(sd, variable_names.intern("@inventorylist_card1") + (j << 24), sd->status.inventory[i].card[0]); pc_setreg(sd, variable_names.intern("@inventorylist_card2") + (j << 24), sd->status.inventory[i].card[1]); pc_setreg(sd, variable_names.intern("@inventorylist_card3") + (j << 24), sd->status.inventory[i].card[2]); pc_setreg(sd, variable_names.intern("@inventorylist_card4") + (j << 24), sd->status.inventory[i].card[3]); j++; } } pc_setreg(sd, variable_names.intern("@inventorylist_count"), j); } static void builtin_getactivatedpoolskilllist(ScriptState *st) { dumb_ptr 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, variable_names.intern("@skilllist_id") + (count << 24), static_cast(skill_id)); pc_setreg(sd, variable_names.intern("@skilllist_lv") + (count << 24), sd->status.skill[skill_id].lv); pc_setreg(sd, variable_names.intern("@skilllist_flag") + (count << 24), static_cast(sd->status.skill[skill_id].flags)); pc_setregstr(sd, variable_names.intern("@skilllist_name$") + (count << 24), skill_name(skill_id).c_str()); ++count; } } pc_setreg(sd, variable_names.intern("@skilllist_count"), count); } static void builtin_getunactivatedpoolskilllist(ScriptState *st) { dumb_ptr 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, variable_names.intern("@skilllist_id") + (count << 24), static_cast(skill_id)); pc_setreg(sd, variable_names.intern("@skilllist_lv") + (count << 24), sd->status.skill[skill_id].lv); pc_setreg(sd, variable_names.intern("@skilllist_flag") + (count << 24), static_cast(sd->status.skill[skill_id].flags)); pc_setregstr(sd, variable_names.intern("@skilllist_name$") + (count << 24), skill_name(skill_id).c_str()); ++count; } } pc_setreg(sd, variable_names.intern("@skilllist_count"), count); } static void builtin_poolskill(ScriptState *st) { dumb_ptr 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 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; int id = 0; dumb_string name; dumb_ptr bl = NULL; type = conv_num(st, &AARGO2(2)); if (HARGO2(3)) { struct script_data *sdata = &AARGO2(3); get_val(st, sdata); if (sdata->type == ByteCode::STR || sdata->type == ByteCode::CONSTSTR) name = conv_str(st, sdata); else id = conv_num(st, sdata); } if (name) { dumb_ptr sd = map_nick2sd(name.c_str()); if (sd) bl = sd; } else if (id) bl = map_id2bl(id); else if (st->oid) bl = map_id2bl(st->oid); else { dumb_ptr 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 bl = map_id2bl(st->oid); if (bl == NULL) return; clif_specialeffect(bl, conv_num(st, &AARGO2(2)), 0); } static void builtin_specialeffect2(ScriptState *st) { dumb_ptr sd = script_rid2sd(st); if (sd == NULL) return; clif_specialeffect(sd, conv_num(st, &AARGO2(2)), 0); } /*========================================== * Nude [Valaris] *------------------------------------------ */ static void builtin_nude(ScriptState *st) { dumb_ptr sd = script_rid2sd(st); if (sd == NULL) return; for (EQUIP i : EQUIPs) if (sd->equip_index[i] >= 0) pc_unequipitem(sd, sd->equip_index[i], CalcStatus::LATER); pc_calcstatus(sd, 0); } /*========================================== * UnequipById [Freeyorp] *------------------------------------------ */ static void builtin_unequipbyid(ScriptState *st) { dumb_ptr sd = script_rid2sd(st); if (sd == NULL) return; EQUIP slot_id = EQUIP(conv_num(st, &AARGO2(2))); if (slot_id >= EQUIP() && slot_id < EQUIP::COUNT && sd->equip_index[slot_id] >= 0) pc_unequipitem(sd, sd->equip_index[slot_id], CalcStatus::LATER); pc_calcstatus(sd, 0); } /*========================================== * gmcommand [MouseJstr] * * suggested on the forums... *------------------------------------------ */ static void builtin_gmcommand(ScriptState *st) { dumb_ptr sd; sd = script_rid2sd(st); dumb_string cmd = conv_str(st, &AARGO2(2)); is_atcommand(sd->fd, sd, cmd.c_str(), 99); } /*========================================== * npcwarp [remoitnane] * Move NPC to a new position on the same map. *------------------------------------------ */ static void builtin_npcwarp(ScriptState *st) { int x, y; dumb_ptr nd = NULL; x = conv_num(st, &AARGO2(2)); y = conv_num(st, &AARGO2(3)); dumb_string npc = conv_str(st, &AARGO2(4)); nd = npc_name2id(npc.c_str()); if (!nd) 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.c_str(), 0); map_delblock(nd); /* [Freeyorp] */ nd->bl_x = x; nd->bl_y = y; map_addblock(nd); npc_enable(npc.c_str(), 1); } /*========================================== * message [MouseJstr] *------------------------------------------ */ static void builtin_message(ScriptState *st) { dumb_string player = conv_str(st, &AARGO2(2)); dumb_string msg = conv_str(st, &AARGO2(3)); dumb_ptr pl_sd = map_nick2sd(player.c_str()); if (pl_sd == NULL) return; clif_displaymessage(pl_sd->fd, msg); } /*========================================== * npctalk (sends message to surrounding * area) [Valaris] *------------------------------------------ */ static void builtin_npctalk(ScriptState *st) { dumb_ptr nd = map_id_as_npc(st->oid); dumb_string str = conv_str(st, &AARGO2(2)); if (nd) { std::string message = std::string(nd->name) + " : " + str.c_str(); clif_message(nd, message.c_str()); } } /*========================================== * getlook char info. getlook(arg) *------------------------------------------ */ static void builtin_getlook(ScriptState *st) { dumb_ptr 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(sd->status.weapon); break; case LOOK::HEAD_BOTTOM: //3 val = sd->status.head_bottom; break; case LOOK::HEAD_TOP: //4 val = sd->status.head_top; break; case LOOK::HEAD_MID: //5 val = 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 = sd->status.shield; break; case LOOK::SHOES: //9 break; } push_val(st->stack, ByteCode::INT, 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 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: { dumb_string mapname = dumb_string::copy(sd->status.save_point.map); push_str(st->stack, ByteCode::STR, mapname); } break; case 1: push_val(st->stack, ByteCode::INT, x); break; case 2: push_val(st->stack, ByteCode::INT, y); break; } } /*========================================== * areatimer *------------------------------------------ */ static void builtin_areatimer_sub(dumb_ptr bl, interval_t tick, dumb_string event) { pc_addeventtimer(bl->as_player(), tick, event.c_str()); } static void builtin_areatimer(ScriptState *st) { int x0, y0, x1, y1; dumb_string mapname = 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(conv_num(st, &AARGO2(7))); dumb_string event = conv_str(st, &AARGO2(8)); map_local *m = map_mapname2mapid(mapname.c_str()); 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 sd = script_rid2sd(st); dumb_string str = 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; using namespace operators; push_val(st->stack, ByteCode::INT, (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 sd = script_rid2sd(st); dumb_ptr nd; if (!sd) return; nd = npc_name2id(conv_str(st, &AARGO2(2)).c_str()); if (!nd) return; builtin_close(st); clif_npcbuysell(sd, nd->bl_id); } /*========================================== * Check whether the PC is dead *------------------------------------------ */ static void builtin_isdead(ScriptState *st) { dumb_ptr sd = script_rid2sd(st); push_val(st->stack, ByteCode::INT, pc_isdead(sd)); } /*======================================== * Changes a NPC name, and sprite *---------------------------------------- */ static void builtin_fakenpcname(ScriptState *st) { dumb_string name = conv_str(st, &AARGO2(2)); dumb_string newname = conv_str(st, &AARGO2(3)); int newsprite = conv_num(st, &AARGO2(4)); dumb_ptr nd = npc_name2id(name.c_str()); if (!nd) return; strzcpy(nd->name, newname.c_str(), sizeof(nd->name)); nd->npc_class = newsprite; // Refresh this npc npc_enable(name.c_str(), 0); npc_enable(name.c_str(), 1); } /*============================ * Gets the PC's x pos *---------------------------- */ static void builtin_getx(ScriptState *st) { dumb_ptr sd = script_rid2sd(st); push_val(st->stack, ByteCode::INT, sd->bl_x); } /*============================ * Gets the PC's y pos *---------------------------- */ static void builtin_gety(ScriptState *st) { dumb_ptr sd = script_rid2sd(st); push_val(st->stack, ByteCode::INT, sd->bl_y); } /* * Get the PC's current map's name */ static void builtin_getmap(ScriptState *st) { dumb_ptr sd = script_rid2sd(st); // A map_data lives essentially forever. push_str(st->stack, ByteCode::CONSTSTR, dumb_string::fake(sd->bl_m->name)); } // // 実行部main // /*========================================== * コマンドの読み取り *------------------------------------------ */ static ByteCode get_com(ScriptPointer *script) { if (static_cast(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(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 (back.type == ByteCode::INT) rv = back.u.num; st->stack->stack_datav.pop_back(); return rv; } static bool isstr(struct script_data& c) { return c.type == ByteCode::STR || c.type == ByteCode::CONSTSTR; } /*========================================== * 加算演算子 *------------------------------------------ */ 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(); get_val(st, &st->stack->stack_datav.back()); script_data& back1 = st->stack->stack_datav.back(); st->stack->stack_datav.pop_back(); if (!(isstr(back) || isstr(back1))) { back1.u.num += back.u.num; } else { dumb_string sb = conv_str(st, &back); dumb_string sb1 = conv_str(st, &back1); // ssの予定 std::string buf = sb1.str() + sb.str(); if (back1.type == ByteCode::STR) back1.u.str.delete_(); if (back.type == ByteCode::STR) back.u.str.delete_(); back1.type = ByteCode::STR; back1.u.str = dumb_string::copys(buf); } } /*========================================== * 二項演算子(文字列) *------------------------------------------ */ static void op_2str(ScriptState *st, ByteCode op, dumb_string s1, dumb_string s2) { int a = 0; using namespace operators; 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"); break; } push_val(st->stack, ByteCode::INT, 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_val(st->stack, ByteCode::INT, 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.u.str, d2.u.str); if (d1.type == ByteCode::STR) d1.u.str.delete_(); if (d2.type == ByteCode::STR) d2.u.str.delete_(); } else if (!(isstr(d1) || isstr(d2))) { // ii => op_2num op_2num(st, op, d1.u.num, d2.u.num); } else { // si,is => error PRINTF("script: op_2: int&str, str&int not allow.\n"); push_val(st->stack, ByteCode::INT, 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_val(st->stack, ByteCode::INT, 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].type != ByteCode::ARG) { start_sp--; if (start_sp == 0) { if (battle_config.error_log) PRINTF("function not found\n"); st->state = ScriptEndState::END; return; } } // the func is before the arg start_sp--; st->start = start_sp; st->end = end_sp; size_t func = st->stack->stack_datav[st->start].u.num; if (st->stack->stack_datav[st->start].type != ByteCode::FUNC_REF) { PRINTF("run_func: not function and command! \n"); st->state = ScriptEndState::END; return; } if (DEBUG_RUN && battle_config.etc_log) { PRINTF("run_func : %s\n", builtin_functions[func].name); PRINTF("stack dump :"); for (script_data& d : st->stack->stack_datav) { // this is not *nearly* complete enough to be useful ... switch (d.type) { case ByteCode::INT: PRINTF(" int(%d)", d.u.num); break; case ByteCode::RETINFO: PRINTF(" retinfo(%p)", static_cast(d.u.script)); break; case ByteCode::PARAM_: PRINTF(" param(%d)", d.u.num); break; case ByteCode::VARIABLE: PRINTF(" name(%s)", variable_names.outtern(d.u.num)); break; case ByteCode::ARG: PRINTF(" arg"); break; case ByteCode::POS: PRINTF(" pos(%d)", d.u.num); break; default: PRINTF(" %d,%d", d.type, d.u.num); } } PRINTF("\n"); } 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].type != ByteCode::RETINFO) { PRINTF("script:run_func (return) return without callfunc or callsub!\n"); 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; } } /*========================================== * スクリプトの実行メイン部分 *------------------------------------------ */ 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 (battle_config.error_log) PRINTF("stack.sp (%zu) != default (%d)\n", stack->stack_datav.size(), st->defsp); stack->stack_datav.resize(st->defsp); } rerun_pos = st->scriptp.pos; break; case ByteCode::INT: // synthesized! push_val(stack, ByteCode::INT, get_num(&st->scriptp)); break; case ByteCode::POS: case ByteCode::VARIABLE: case ByteCode::FUNC_REF: // Note that these 3 have *very* different meanings, // despite being encoded similarly. { int arg = 0; arg |= static_cast(st->scriptp.pop()) << 0; arg |= static_cast(st->scriptp.pop()) << 8; arg |= static_cast(st->scriptp.pop()) << 16; push_val(stack, c, arg); } break; case ByteCode::ARG: push_val(stack, c, 0); break; case ByteCode::STR: // ScriptPointer.pops() leaves it .pos pointing to the \0. push_str(stack, ByteCode::CONSTSTR, dumb_string::fake(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"); 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", c, st->scriptp.pos); st->state = ScriptEndState::END; break; } if (cmdcount > 0 && (--cmdcount) <= 0) { PRINTF("run_script: infinity loop !\n"); st->state = ScriptEndState::END; } } switch (st->state) { case ScriptEndState::STOP: break; case ScriptEndState::END: { dumb_ptr 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 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, int rid, int oid) { return run_script_l(sp, rid, oid, 0, NULL); } int run_script_l(ScriptPointer sp, int rid, int oid, int args_nr, argrec_t *args) { struct script_stack stack; ScriptState st; dumb_ptr sd = map_id2sd(rid); const ScriptBuffer *rootscript = sp.code; int i; if (sp.code == NULL || 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_nr; i++) { if (args[i].name[strlen(args[i].name) - 1] == '$') pc_setregstr(sd, variable_names.intern(args[i].name), args[i].v.s); else pc_setreg(sd, 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(int num, int val) { mapreg_db.put(num, val); mapreg_dirty = 1; } /*========================================== * 文字列型マップ変数の変更 *------------------------------------------ */ void mapreg_setregstr(int num, const char *str) { if (!str || !*str) mapregstr_db.erase(num); else mapregstr_db.insert(num, str); mapreg_dirty = 1; } /*========================================== * 永続的マップ変数の読み込み *------------------------------------------ */ static void script_load_mapreg(void) { std::ifstream in(mapreg_txt); if (!in.is_open()) return; std::string line; while (std::getline(in, line)) { std::string 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); int key = (index << 24) | s; 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", mapreg_txt, buf1); continue; } } mapreg_dirty = 0; } /*========================================== * 永続的マップ変数の書き込み *------------------------------------------ */ static void script_save_mapreg_intsub(int key, int data, FILE *fp) { int num = key & 0x00ffffff, i = key >> 24; const std::string& name = variable_names.outtern(num); if (name[1] != '@') { if (i == 0) FPRINTF(fp, "%s\t%d\n", name, data); else FPRINTF(fp, "%s,%d\t%d\n", name, i, data); } } static void script_save_mapreg_strsub(int key, const std::string& data, FILE *fp) { int num = key & 0x00ffffff, i = key >> 24; const std::string& name = variable_names.outtern(num); if (name[1] != '@') { if (i == 0) FPRINTF(fp, "%s\t%s\n", name, data); else FPRINTF(fp, "%s,%d\t%s\n", name, i, data); } } static void script_save_mapreg(void) { FILE *fp; int lock; if ((fp = lock_fopen(mapreg_txt, &lock)) == NULL) 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); lock_fclose(fp, mapreg_txt, &lock); mapreg_dirty = 0; } static void script_autosave_mapreg(TimerData *, tick_t) { if (mapreg_dirty) script_save_mapreg(); } void script_config_read() { script_config.warn_func_no_comma = 1; script_config.warn_cmd_no_comma = 1; script_config.warn_func_mismatch_paramnum = 1; script_config.warn_cmd_mismatch_paramnum = 1; script_config.check_cmdcount = 8192; script_config.check_gotocount = 512; } void do_final_script(void) { if (mapreg_dirty >= 0) script_save_mapreg(); mapreg_db.clear(); for (auto& pair : mapregstr_db) pair.second.clear(); mapregstr_db.clear(); scriptlabel_db.clear(); for (auto& pair : userfunc_db) pair.second.reset(); 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) \ {builtin_##func, #func, args} BuiltinFunction builtin_functions[] = { BUILTIN(mes, "s"), BUILTIN(next, ""), BUILTIN(close, ""), BUILTIN(close2, ""), BUILTIN(menu, "sL*"), BUILTIN(goto, "L"), BUILTIN(callsub, "L"), BUILTIN(callfunc, "F"), BUILTIN(return, ""), BUILTIN(input, "N"), BUILTIN(warp, "Mxy"), BUILTIN(isat, "Mxy"), BUILTIN(areawarp, "MxyxyMxy"), BUILTIN(setlook, "ii"), BUILTIN(set, "Ne"), BUILTIN(setarray, "Ne*"), BUILTIN(cleararray, "Nei"), BUILTIN(getarraysize, "N"), BUILTIN(getelementofarray, "Ni"), BUILTIN(if, "iF*"), BUILTIN(getitem, "Ii**"), BUILTIN(makeitem, "IiMxy"), BUILTIN(delitem, "Ii"), BUILTIN(heal, "ii"), BUILTIN(itemheal, "ii"), BUILTIN(percentheal, "ii"), BUILTIN(rand, "i*"), BUILTIN(pow, "ii"), BUILTIN(countitem, "I"), BUILTIN(checkweight, "Ii"), BUILTIN(readparam, "i*"), BUILTIN(getcharid, "i*"), BUILTIN(strcharinfo, "i"), BUILTIN(getequipid, "i"), BUILTIN(getequipname, "i"), BUILTIN(statusup2, "ii"), BUILTIN(bonus, "ii"), BUILTIN(bonus2, "iii"), BUILTIN(skill, "ii*"), BUILTIN(setskill, "ii"), BUILTIN(getskilllv, "i"), BUILTIN(getgmlevel, ""), BUILTIN(end, ""), BUILTIN(getopt2, ""), BUILTIN(setopt2, "i"), BUILTIN(savepoint, "Mxy"), BUILTIN(gettimetick, "i"), BUILTIN(gettime, "i"), BUILTIN(openstorage, "*"), BUILTIN(monster, "Mxysmi*"), BUILTIN(areamonster, "Mxyxysmi*"), BUILTIN(killmonster, "ME"), BUILTIN(killmonsterall, "M"), BUILTIN(donpcevent, "E"), BUILTIN(addtimer, "tE"), BUILTIN(initnpctimer, ""), BUILTIN(stopnpctimer, ""), BUILTIN(startnpctimer, "*"), BUILTIN(setnpctimer, "i"), BUILTIN(getnpctimer, "i"), BUILTIN(announce, "si"), BUILTIN(mapannounce, "Msi"), BUILTIN(getusers, "i"), BUILTIN(getmapusers, "M"), BUILTIN(getareausers, "Mxyxy*"), BUILTIN(getareadropitem, "Mxyxyi*"), BUILTIN(enablenpc, "s"), BUILTIN(disablenpc, "s"), BUILTIN(sc_start, "iTi*"), BUILTIN(sc_end, "i"), BUILTIN(sc_check, "i"), BUILTIN(debugmes, "s"), BUILTIN(resetstatus, ""), BUILTIN(changesex, ""), BUILTIN(attachrid, "i"), BUILTIN(detachrid, ""), BUILTIN(isloggedin, "i"), BUILTIN(setmapflag, "Mi"), BUILTIN(removemapflag, "Mi"), BUILTIN(getmapflag, "Mi"), BUILTIN(pvpon, "M"), BUILTIN(pvpoff, "M"), BUILTIN(emotion, "i"), BUILTIN(marriage, "P"), BUILTIN(divorce, ""), BUILTIN(getitemname, "I"), BUILTIN(getspellinvocation, "s"), BUILTIN(getpartnerid2, ""), BUILTIN(getexp, "ii"), BUILTIN(getinventorylist, ""), BUILTIN(getactivatedpoolskilllist, ""), BUILTIN(getunactivatedpoolskilllist, ""), BUILTIN(poolskill, "i"), BUILTIN(unpoolskill, "i"), BUILTIN(misceffect, "i*"), BUILTIN(specialeffect, "i"), BUILTIN(specialeffect2, "i"), BUILTIN(nude, ""), BUILTIN(mapwarp, "MMxy"), BUILTIN(cmdothernpc, "ss"), BUILTIN(gmcommand, "s"), BUILTIN(npcwarp, "xys"), BUILTIN(message, "Ps"), BUILTIN(npctalk, "s"), BUILTIN(mobcount, "ME"), BUILTIN(getlook, "i"), BUILTIN(getsavepoint, "i"), BUILTIN(areatimer, "MxyxytE"), BUILTIN(isin, "Mxyxy"), BUILTIN(shop, "s"), BUILTIN(isdead, ""), BUILTIN(unequipbyid, "i"), BUILTIN(fakenpcname, "ssi"), BUILTIN(getx, ""), BUILTIN(gety, ""), BUILTIN(getmap, ""), {NULL, NULL, NULL}, };