From 4763e230ab02bcf3e7db20dee23d42a02815bdb3 Mon Sep 17 00:00:00 2001 From: Ben Longbons Date: Tue, 7 Oct 2014 01:56:05 -0700 Subject: Split script.cpp --- src/generic/fwd.hpp | 2 + src/generic/random.hpp | 4 +- src/generic/random2.hpp | 4 +- src/login/login.hpp | 4 +- src/map/battle.hpp | 4 +- src/map/clif.hpp | 4 +- src/map/fwd.hpp | 3 + src/map/itemdb.cpp | 2 + src/map/itemdb.hpp | 2 +- src/map/magic-expr.cpp | 1 + src/map/magic-interpreter.hpp | 6 +- src/map/magic-stmt.cpp | 1 + src/map/magic-v2.cpp | 1 + src/map/map.cpp | 2 +- src/map/map.hpp | 7 +- src/map/mob.hpp | 4 +- src/map/npc.cpp | 3 +- src/map/npc.hpp | 1 + src/map/pc.cpp | 2 +- src/map/pc.hpp | 4 +- src/map/script-buffer.cpp | 30 + src/map/script-buffer.hpp | 42 + src/map/script-call-internal.hpp | 97 + src/map/script-call-internal.tcc | 76 + src/map/script-call.cpp | 1042 +++++++ src/map/script-call.hpp | 67 + src/map/script-call.t.hpp | 45 + src/map/script-fun.cpp | 3003 ++++++++++++++++++++ src/map/script-fun.hpp | 38 + src/map/script-parse-internal.hpp | 69 + src/map/script-parse.cpp | 840 ++++++ src/map/script-parse.hpp | 42 + src/map/script-persist.cpp | 30 + src/map/script-persist.hpp | 119 + src/map/script-persist.py | 25 + src/map/script-startup-internal.hpp | 44 + src/map/script-startup.cpp | 265 ++ src/map/script-startup.hpp | 35 + src/map/script.cpp | 5191 ----------------------------------- src/map/script.hpp | 209 -- src/map/script.py | 25 - src/map/skill.hpp | 3 +- 42 files changed, 5948 insertions(+), 5450 deletions(-) create mode 100644 src/map/script-buffer.cpp create mode 100644 src/map/script-buffer.hpp create mode 100644 src/map/script-call-internal.hpp create mode 100644 src/map/script-call-internal.tcc create mode 100644 src/map/script-call.cpp create mode 100644 src/map/script-call.hpp create mode 100644 src/map/script-call.t.hpp create mode 100644 src/map/script-fun.cpp create mode 100644 src/map/script-fun.hpp create mode 100644 src/map/script-parse-internal.hpp create mode 100644 src/map/script-parse.cpp create mode 100644 src/map/script-parse.hpp create mode 100644 src/map/script-persist.cpp create mode 100644 src/map/script-persist.hpp create mode 100644 src/map/script-persist.py create mode 100644 src/map/script-startup-internal.hpp create mode 100644 src/map/script-startup.cpp create mode 100644 src/map/script-startup.hpp delete mode 100644 src/map/script.cpp delete mode 100644 src/map/script.hpp delete mode 100644 src/map/script.py (limited to 'src') diff --git a/src/generic/fwd.hpp b/src/generic/fwd.hpp index cec6bd4..9c389b1 100644 --- a/src/generic/fwd.hpp +++ b/src/generic/fwd.hpp @@ -33,4 +33,6 @@ template class DMap; template class UPMap; + +class InternPool; } // namespace tmwa diff --git a/src/generic/random.hpp b/src/generic/random.hpp index 5d67236..897ad43 100644 --- a/src/generic/random.hpp +++ b/src/generic/random.hpp @@ -18,10 +18,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "fwd.hpp" - #include "random.t.hpp" +#include "fwd.hpp" + #include diff --git a/src/generic/random2.hpp b/src/generic/random2.hpp index 23d165c..3d481f4 100644 --- a/src/generic/random2.hpp +++ b/src/generic/random2.hpp @@ -18,10 +18,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "fwd.hpp" - #include "random.hpp" +#include "fwd.hpp" + #include #include "../compat/iter.hpp" diff --git a/src/login/login.hpp b/src/login/login.hpp index 92f3c76..5900440 100644 --- a/src/login/login.hpp +++ b/src/login/login.hpp @@ -18,10 +18,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "fwd.hpp" - #include "login.t.hpp" +#include "fwd.hpp" + namespace tmwa { diff --git a/src/map/battle.hpp b/src/map/battle.hpp index 83d94fc..5f47b70 100644 --- a/src/map/battle.hpp +++ b/src/map/battle.hpp @@ -20,10 +20,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "fwd.hpp" - #include "battle.t.hpp" +#include "fwd.hpp" + #include "../strings/fwd.hpp" #include "../generic/fwd.hpp" diff --git a/src/map/clif.hpp b/src/map/clif.hpp index ae23b7b..da8aa20 100644 --- a/src/map/clif.hpp +++ b/src/map/clif.hpp @@ -20,10 +20,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "fwd.hpp" - #include "clif.t.hpp" +#include "fwd.hpp" + #include #include "../strings/fwd.hpp" diff --git a/src/map/fwd.hpp b/src/map/fwd.hpp index b73d36a..da5763d 100644 --- a/src/map/fwd.hpp +++ b/src/map/fwd.hpp @@ -43,7 +43,10 @@ struct NpcEvent; struct item_data; enum class SP : uint16_t; + +struct ScriptBuffer; struct ScriptLabel; +struct ScriptState; namespace magic { diff --git a/src/map/itemdb.cpp b/src/map/itemdb.cpp index 50cc5c4..b0c8c02 100644 --- a/src/map/itemdb.cpp +++ b/src/map/itemdb.cpp @@ -35,6 +35,8 @@ #include "../mmo/extract.hpp" #include "../mmo/extract_enums.hpp" +#include "script-parse.hpp" + #include "../poison.hpp" diff --git a/src/map/itemdb.hpp b/src/map/itemdb.hpp index 25b4dad..1fdca2e 100644 --- a/src/map/itemdb.hpp +++ b/src/map/itemdb.hpp @@ -26,7 +26,7 @@ #include "../mmo/mmo.hpp" #include "map.t.hpp" -#include "script.hpp" +#include "script-buffer.hpp" namespace tmwa diff --git a/src/map/magic-expr.cpp b/src/map/magic-expr.cpp index 4ea83b6..a1196fa 100644 --- a/src/map/magic-expr.cpp +++ b/src/map/magic-expr.cpp @@ -42,6 +42,7 @@ #include "magic-interpreter-base.hpp" #include "npc.hpp" #include "pc.hpp" +#include "script-call.hpp" #include "../poison.hpp" diff --git a/src/map/magic-interpreter.hpp b/src/map/magic-interpreter.hpp index 3bb600c..7cdf7bf 100644 --- a/src/map/magic-interpreter.hpp +++ b/src/map/magic-interpreter.hpp @@ -19,10 +19,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "fwd.hpp" - #include "magic-interpreter.t.hpp" +#include "fwd.hpp" + #include #include @@ -40,7 +40,7 @@ #include "../mmo/utils.hpp" #include "map.hpp" -#include "script.hpp" +#include "script-buffer.hpp" #include "skill.t.hpp" diff --git a/src/map/magic-stmt.cpp b/src/map/magic-stmt.cpp index 1786839..f33f663 100644 --- a/src/map/magic-stmt.cpp +++ b/src/map/magic-stmt.cpp @@ -43,6 +43,7 @@ #include "mob.hpp" #include "npc.hpp" #include "pc.hpp" +#include "script-call.hpp" #include "skill.hpp" #include "../poison.hpp" diff --git a/src/map/magic-v2.cpp b/src/map/magic-v2.cpp index b299279..8a84a2d 100644 --- a/src/map/magic-v2.cpp +++ b/src/map/magic-v2.cpp @@ -41,6 +41,7 @@ #include "magic-interpreter.hpp" #include "magic-interpreter-base.hpp" #include "magic-stmt.hpp" +#include "script-parse.hpp" #include "../poison.hpp" diff --git a/src/map/map.cpp b/src/map/map.cpp index 4a25029..5e9db39 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -72,7 +72,7 @@ #include "npc.hpp" #include "party.hpp" #include "pc.hpp" -#include "script.hpp" +#include "script-startup.hpp" #include "skill.hpp" #include "storage.hpp" #include "trade.hpp" diff --git a/src/map/map.hpp b/src/map/map.hpp index a87c0fe..aeb8821 100644 --- a/src/map/map.hpp +++ b/src/map/map.hpp @@ -20,10 +20,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "fwd.hpp" - #include "map.t.hpp" +#include "fwd.hpp" + #include #include #include @@ -48,7 +48,8 @@ #include "clif.t.hpp" #include "mapflag.hpp" #include "mob.t.hpp" -#include "script.hpp" // change to script.t.hpp +#include "script-buffer.hpp" +#include "script-persist.hpp" #include "skill.t.hpp" diff --git a/src/map/mob.hpp b/src/map/mob.hpp index d0cc07a..1f2aead 100644 --- a/src/map/mob.hpp +++ b/src/map/mob.hpp @@ -20,10 +20,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "fwd.hpp" - #include "mob.t.hpp" +#include "fwd.hpp" + #include "../generic/fwd.hpp" #include "../generic/enum.hpp" #include "../generic/random.t.hpp" diff --git a/src/map/npc.cpp b/src/map/npc.cpp index 95a157b..22aac75 100644 --- a/src/map/npc.cpp +++ b/src/map/npc.cpp @@ -54,7 +54,8 @@ #include "map.hpp" #include "mob.hpp" #include "pc.hpp" -#include "script.hpp" +#include "script-call.hpp" +#include "script-parse.hpp" #include "skill.hpp" #include "../poison.hpp" diff --git a/src/map/npc.hpp b/src/map/npc.hpp index 3870a39..30b0178 100644 --- a/src/map/npc.hpp +++ b/src/map/npc.hpp @@ -35,6 +35,7 @@ #include "../proto2/fwd.hpp" #include "map.hpp" +#include "script-call.t.hpp" namespace tmwa diff --git a/src/map/pc.cpp b/src/map/pc.cpp index 7f4c367..ac78672 100644 --- a/src/map/pc.cpp +++ b/src/map/pc.cpp @@ -57,7 +57,7 @@ #include "npc.hpp" #include "party.hpp" #include "path.hpp" -#include "script.hpp" +#include "script-call.hpp" #include "skill.hpp" #include "storage.hpp" #include "trade.hpp" diff --git a/src/map/pc.hpp b/src/map/pc.hpp index 8a06076..605915e 100644 --- a/src/map/pc.hpp +++ b/src/map/pc.hpp @@ -20,10 +20,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "fwd.hpp" - #include "pc.t.hpp" +#include "fwd.hpp" + #include "../strings/fwd.hpp" #include "../generic/dumb_ptr.hpp" diff --git a/src/map/script-buffer.cpp b/src/map/script-buffer.cpp new file mode 100644 index 0000000..f3a639a --- /dev/null +++ b/src/map/script-buffer.cpp @@ -0,0 +1,30 @@ +#include "script-buffer.hpp" +// script-buffer.cpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011 Chuck Miller +// Copyright © 2011-2014 Ben Longbons +// Copyright © 2013 wushin +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../poison.hpp" + + +namespace tmwa +{ +} // namespace tmwa diff --git a/src/map/script-buffer.hpp b/src/map/script-buffer.hpp new file mode 100644 index 0000000..def23e2 --- /dev/null +++ b/src/map/script-buffer.hpp @@ -0,0 +1,42 @@ +#pragma once +// script-buffer.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "fwd.hpp" + +#include + + +namespace tmwa +{ +class ScriptBuffer; +} // namespace tmwa + +namespace std +{ +template<> +struct default_delete +{ + default_delete() {} + default_delete(default_delete) {} + void operator()(const tmwa::ScriptBuffer *sd); +}; +} // namespace std diff --git a/src/map/script-call-internal.hpp b/src/map/script-call-internal.hpp new file mode 100644 index 0000000..72e1e46 --- /dev/null +++ b/src/map/script-call-internal.hpp @@ -0,0 +1,97 @@ +#pragma once +// script-call-internal.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "script-call.hpp" +#include "fwd.hpp" + +#include "../mmo/ids.hpp" + +#include "script-persist.hpp" + + +namespace tmwa +{ +enum class VariableCode : uint8_t +{ + PARAM, + VARIABLE, +}; + +struct script_stack +{ + std::vector stack_datav; +}; + +enum class ScriptEndState; +// future improvements coming! +class ScriptState +{ +public: + struct script_stack *stack; + int start, end; + ScriptEndState state; + BlockId rid, oid; + ScriptPointer scriptp, new_scriptp; + int defsp, new_defsp; +}; + +void run_func(ScriptState *st); + +enum class ScriptEndState +{ + ZERO, + STOP, + END, + RERUNLINE, + GOTO, + RETFUNC, +}; + +dumb_ptr script_rid2sd(ScriptState *st); +void get_val(dumb_ptr sd, struct script_data *data); +__attribute__((deprecated)) +void get_val(ScriptState *st, struct script_data *data); +struct script_data get_val2(ScriptState *st, SIR reg); +void set_reg(dumb_ptr sd, VariableCode type, SIR reg, struct script_data vd); +void set_reg(dumb_ptr sd, VariableCode type, SIR reg, int id); +void set_reg(dumb_ptr sd, VariableCode type, SIR reg, RString zd); +__attribute__((warn_unused_result)) +RString conv_str(ScriptState *st, struct script_data *data); +__attribute__((warn_unused_result)) +int conv_num(ScriptState *st, struct script_data *data); +__attribute__((warn_unused_result)) +const ScriptBuffer *conv_script(ScriptState *st, struct script_data *data); + +template +void push_int(struct script_stack *stack, int val); +template +void push_reg(struct script_stack *stack, SIR reg); +template +void push_script(struct script_stack *stack, const ScriptBuffer *code); +template +void push_str(struct script_stack *stack, RString str); + +void push_copy(struct script_stack *stack, int pos_); +void pop_stack(struct script_stack *stack, int start, int end); +} // namespace tmwa + +#include "script-call-internal.tcc" diff --git a/src/map/script-call-internal.tcc b/src/map/script-call-internal.tcc new file mode 100644 index 0000000..cc5c2a4 --- /dev/null +++ b/src/map/script-call-internal.tcc @@ -0,0 +1,76 @@ +// script-call-internal.tcc - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "script-persist.hpp" + + +namespace tmwa +{ +template +bool first_type_is_any() +{ + return false; +} + +template +constexpr +bool first_type_is_any() +{ + return std::is_same::value || first_type_is_any(); +} + + +template +void push_int(struct script_stack *stack, int val) +{ + static_assert(first_type_is_any(), "not int type"); + + script_data nsd = T{.numi= val}; + stack->stack_datav.push_back(nsd); +} + +template +void push_reg(struct script_stack *stack, SIR reg) +{ + static_assert(first_type_is_any(), "not reg type"); + + script_data nsd = T{.reg= reg}; + stack->stack_datav.push_back(nsd); +} + +template +void push_script(struct script_stack *stack, const ScriptBuffer *code) +{ + static_assert(first_type_is_any(), "not scriptbuf type"); + + script_data nsd = T{.script= code}; + stack->stack_datav.push_back(nsd); +} + +template +void push_str(struct script_stack *stack, RString str) +{ + static_assert(first_type_is_any(), "not str type"); + + script_data nsd = T{.str= str}; + stack->stack_datav.push_back(nsd); +} +} // namespace tmwa diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp new file mode 100644 index 0000000..f889e4b --- /dev/null +++ b/src/map/script-call.cpp @@ -0,0 +1,1042 @@ +#include "script-call-internal.hpp" +// script-call.cpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011 Chuck Miller +// Copyright © 2011-2014 Ben Longbons +// Copyright © 2013 wushin +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../generic/intern-pool.hpp" + +#include "../io/cxxstdio.hpp" +#include "../io/cxxstdio_enums.hpp" + +#include "battle.hpp" +#include "map.hpp" +#include "npc.hpp" +#include "pc.hpp" +#include "script-fun.hpp" +#include "script-parse-internal.hpp" +#include "script-persist.hpp" +#include "script-startup-internal.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +constexpr bool DEBUG_RUN = false; + +static +struct ScriptConfigRun +{ + static const + int check_cmdcount = 8192; + static const + int check_gotocount = 512; +} script_config; + + +/*========================================== + * ridからsdへの解決 + *------------------------------------------ + */ +dumb_ptr script_rid2sd(ScriptState *st) +{ + dumb_ptr sd = map_id2sd(st->rid); + if (!sd) + { + PRINTF("script_rid2sd: fatal error ! player not attached!\n"_fmt); + } + return sd; +} + +/*========================================== + * 変数の読み取り + *------------------------------------------ + */ +void get_val(dumb_ptr sd, struct script_data *data) +{ + MATCH (*data) + { + CASE (const ScriptDataParam&, u) + { + if (sd == nullptr) + PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp()); + int numi = 0; + if (sd) + numi = pc_readparam(sd, u.reg.sp()); + *data = ScriptDataInt{numi}; + } + CASE (const ScriptDataVariable&, u) + { + ZString name_ = variable_names.outtern(u.reg.base()); + VarName name = stringish(name_); + char prefix = name.front(); + char postfix = name.back(); + + if (prefix != '$') + { + if (sd == nullptr) + PRINTF("get_val error name?:%s\n"_fmt, name); + } + if (postfix == '$') + { + RString str; + if (prefix == '@') + { + if (sd) + str = pc_readregstr(sd, u.reg); + } + else if (prefix == '$') + { + RString *s = mapregstr_db.search(u.reg); + if (s) + str = *s; + } + else + { + PRINTF("script: get_val: illegal scope string variable.\n"_fmt); + str = "!!ERROR!!"_s; + } + *data = ScriptDataStr{str}; + } + else + { + int numi = 0; + if (prefix == '@') + { + if (sd) + numi = pc_readreg(sd, u.reg); + } + else if (prefix == '$') + { + numi = mapreg_db.get(u.reg); + } + else if (prefix == '#') + { + if (name[1] == '#') + { + if (sd) + numi = pc_readaccountreg2(sd, name); + } + else + { + if (sd) + numi = pc_readaccountreg(sd, name); + } + } + else + { + if (sd) + numi = pc_readglobalreg(sd, name); + } + *data = ScriptDataInt{numi}; + } + } + } +} + +void get_val(ScriptState *st, struct script_data *data) +{ + dumb_ptr sd = st->rid ? map_id2sd(st->rid) : nullptr; + get_val(sd, data); +} + +/*========================================== + * 変数の読み取り2 + *------------------------------------------ + */ +struct script_data get_val2(ScriptState *st, SIR reg) +{ + struct script_data dat = ScriptDataVariable{reg}; + get_val(st, &dat); + return dat; +} + +/*========================================== + * 変数設定用 + *------------------------------------------ + */ +void set_reg(dumb_ptr sd, VariableCode type, SIR reg, struct script_data vd) +{ + if (type == VariableCode::PARAM) + { + int val = vd.get_if()->numi; + pc_setparam(sd, reg.sp(), val); + return; + } + assert (type == VariableCode::VARIABLE); + + ZString name_ = variable_names.outtern(reg.base()); + VarName name = stringish(name_); + char prefix = name.front(); + char postfix = name.back(); + + if (postfix == '$') + { + RString str = vd.get_if()->str; + if (prefix == '@') + { + pc_setregstr(sd, reg, str); + } + else if (prefix == '$') + { + mapreg_setregstr(reg, str); + } + else + { + PRINTF("script: set_reg: illegal scope string variable !"_fmt); + } + } + else + { + int val = vd.get_if()->numi; + if (prefix == '@') + { + pc_setreg(sd, reg, val); + } + else if (prefix == '$') + { + mapreg_setreg(reg, val); + } + else if (prefix == '#') + { + if (name[1] == '#') + pc_setaccountreg2(sd, name, val); + else + pc_setaccountreg(sd, name, val); + } + else + { + pc_setglobalreg(sd, name, val); + } + } +} + +void set_reg(dumb_ptr sd, VariableCode type, SIR reg, int id) +{ + struct script_data vd = ScriptDataInt{id}; + set_reg(sd, type, reg, vd); +} + +void set_reg(dumb_ptr sd, VariableCode type, SIR reg, RString zd) +{ + struct script_data vd = ScriptDataStr{zd}; + set_reg(sd, type, reg, vd); +} + +/*========================================== + * 文字列への変換 + *------------------------------------------ + */ +RString conv_str(ScriptState *st, struct script_data *data) +{ + get_val(st, data); + assert (!data->is()); + if (auto *u = data->get_if()) + { + AString buf = STRPRINTF("%d"_fmt, u->numi); + *data = ScriptDataStr{buf}; + } + return data->get_if()->str; +} + +/*========================================== + * 数値へ変換 + *------------------------------------------ + */ +int conv_num(ScriptState *st, struct script_data *data) +{ + int rv = 0; + get_val(st, data); + assert (!data->is()); + MATCH (*data) + { + default: + abort(); + CASE (const ScriptDataStr&, u) + { + RString p = u.str; + rv = atoi(p.c_str()); + } + CASE (const ScriptDataInt&, u) + { + return u.numi; + } + CASE (const ScriptDataPos&, u) + { + return u.numi; + } + } + *data = ScriptDataInt{rv}; + return rv; +} + +const ScriptBuffer *conv_script(ScriptState *st, struct script_data *data) +{ + get_val(st, data); + return data->get_if()->script; +} + +void push_copy(struct script_stack *stack, int pos_) +{ + script_data csd = stack->stack_datav[pos_]; + stack->stack_datav.push_back(csd); +} + +void pop_stack(struct script_stack *stack, int start, int end) +{ + auto it = stack->stack_datav.begin(); + stack->stack_datav.erase(it + start, it + end); +} + +// +// 実行部main +// +/*========================================== + * コマンドの読み取り + *------------------------------------------ + */ +static +ByteCode get_com(ScriptPointer *script) +{ + if (static_cast(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 (auto *u = back.get_if()) + rv = u->numi; + st->stack->stack_datav.pop_back(); + return rv; +} + +static +bool isstr(struct script_data& c) +{ + return c.is(); +} + +/*========================================== + * 加算演算子 + *------------------------------------------ + */ +static +void op_add(ScriptState *st) +{ + get_val(st, &st->stack->stack_datav.back()); + script_data back = st->stack->stack_datav.back(); + st->stack->stack_datav.pop_back(); + + script_data& back1 = st->stack->stack_datav.back(); + get_val(st, &back1); + + if (!(isstr(back) || isstr(back1))) + { + back1.get_if()->numi += back.get_if()->numi; + } + else + { + RString sb = conv_str(st, &back); + RString sb1 = conv_str(st, &back1); + MString buf; + buf += sb1; + buf += sb; + back1 = ScriptDataStr{.str= AString(buf)}; + } +} + +/*========================================== + * 二項演算子(文字列) + *------------------------------------------ + */ +static +void op_2str(ScriptState *st, ByteCode op, ZString s1, ZString s2) +{ + int a = 0; + + switch (op) + { + case ByteCode::EQ: + a = s1 == s2; + break; + case ByteCode::NE: + a = s1 != s2; + break; + case ByteCode::GT: + a = s1 > s2; + break; + case ByteCode::GE: + a = s1 >= s2; + break; + case ByteCode::LT: + a = s1 < s2; + break; + case ByteCode::LE: + a = s1 <= s2; + break; + default: + PRINTF("illegal string operater\n"_fmt); + break; + } + + push_int(st->stack, a); +} + +/*========================================== + * 二項演算子(数値) + *------------------------------------------ + */ +static +void op_2num(ScriptState *st, ByteCode op, int i1, int i2) +{ + switch (op) + { + case ByteCode::SUB: + i1 -= i2; + break; + case ByteCode::MUL: + i1 *= i2; + break; + case ByteCode::DIV: + i1 /= i2; + break; + case ByteCode::MOD: + i1 %= i2; + break; + case ByteCode::AND: + i1 &= i2; + break; + case ByteCode::OR: + i1 |= i2; + break; + case ByteCode::XOR: + i1 ^= i2; + break; + case ByteCode::LAND: + i1 = i1 && i2; + break; + case ByteCode::LOR: + i1 = i1 || i2; + break; + case ByteCode::EQ: + i1 = i1 == i2; + break; + case ByteCode::NE: + i1 = i1 != i2; + break; + case ByteCode::GT: + i1 = i1 > i2; + break; + case ByteCode::GE: + i1 = i1 >= i2; + break; + case ByteCode::LT: + i1 = i1 < i2; + break; + case ByteCode::LE: + i1 = i1 <= i2; + break; + case ByteCode::R_SHIFT: + i1 = i1 >> i2; + break; + case ByteCode::L_SHIFT: + i1 = i1 << i2; + break; + } + push_int(st->stack, i1); +} + +/*========================================== + * 二項演算子 + *------------------------------------------ + */ +static +void op_2(ScriptState *st, ByteCode op) +{ + // pop_val has unfortunate implications here + script_data d2 = st->stack->stack_datav.back(); + st->stack->stack_datav.pop_back(); + get_val(st, &d2); + script_data d1 = st->stack->stack_datav.back(); + st->stack->stack_datav.pop_back(); + get_val(st, &d1); + + if (isstr(d1) && isstr(d2)) + { + // ss => op_2str + op_2str(st, op, d1.get_if()->str, d2.get_if()->str); + } + else if (!(isstr(d1) || isstr(d2))) + { + // ii => op_2num + op_2num(st, op, d1.get_if()->numi, d2.get_if()->numi); + } + else + { + // si,is => error + PRINTF("script: op_2: int&str, str&int not allow.\n"_fmt); + push_int(st->stack, 0); + } +} + +/*========================================== + * 単項演算子 + *------------------------------------------ + */ +static +void op_1num(ScriptState *st, ByteCode op) +{ + int i1; + i1 = pop_val(st); + switch (op) + { + case ByteCode::NEG: + i1 = -i1; + break; + case ByteCode::NOT: + i1 = ~i1; + break; + case ByteCode::LNOT: + i1 = !i1; + break; + } + push_int(st->stack, i1); +} + +/*========================================== + * 関数の実行 + *------------------------------------------ + */ +void run_func(ScriptState *st) +{ + size_t end_sp = st->stack->stack_datav.size(); + size_t start_sp = end_sp - 1; + while (!st->stack->stack_datav[start_sp].is()) + { + start_sp--; + if (start_sp == 0) + { + if (battle_config.error_log) + PRINTF("function not found\n"_fmt); + st->state = ScriptEndState::END; + return; + } + } + // the func is before the arg + start_sp--; + st->start = start_sp; + st->end = end_sp; + + if (!st->stack->stack_datav[st->start].is()) + { + PRINTF("run_func: not function and command! \n"_fmt); + st->state = ScriptEndState::END; + return; + } + size_t func = st->stack->stack_datav[st->start].get_if()->numi; + + if (DEBUG_RUN && battle_config.etc_log) + { + PRINTF("run_func : %s\n"_fmt, + builtin_functions[func].name); + PRINTF("stack dump :"_fmt); + for (script_data& d : st->stack->stack_datav) + { + MATCH (d) + { + CASE (const ScriptDataInt&, u) + { + PRINTF(" int(%d)"_fmt, u.numi); + } + CASE (const ScriptDataRetInfo&, u) + { + PRINTF(" retinfo(%p)"_fmt, static_cast(u.script)); + } + CASE (const ScriptDataParam&, u) + { + PRINTF(" param(%d)"_fmt, u.reg.sp()); + } + CASE (const ScriptDataVariable&, u) + { + PRINTF(" name(%s)[%d]"_fmt, variable_names.outtern(u.reg.base()), u.reg.index()); + } + CASE (const ScriptDataArg&, u) + { + (void)u; + PRINTF(" arg"_fmt); + } + CASE (const ScriptDataPos&, u) + { + (void)u; + PRINTF(" pos(%d)"_fmt, u.numi); + } + CASE (const ScriptDataStr&, u) + { + (void)u; + PRINTF(" str(%s)"_fmt, u.str); + } + CASE (const ScriptDataFuncRef&, u) + { + (void)u; + PRINTF(" func(%s)"_fmt, builtin_functions[u.numi].name); + } + } + } + PRINTF("\n"_fmt); + } + builtin_functions[func].func(st); + + pop_stack(st->stack, start_sp, end_sp); + + if (st->state == ScriptEndState::RETFUNC) + { + // ユーザー定義関数からの復帰 + int olddefsp = st->defsp; + + pop_stack(st->stack, st->defsp, start_sp); // 復帰に邪魔なスタック削除 + if (st->defsp < 4 + || !st->stack->stack_datav[st->defsp - 1].is()) + { + PRINTF("script:run_func (return) return without callfunc or callsub!\n"_fmt); + st->state = ScriptEndState::END; + return; + } + assert (olddefsp == st->defsp); // pretty sure it hasn't changed yet + st->scriptp.code = conv_script(st, &st->stack->stack_datav[olddefsp - 1]); // スクリプトを復元 + st->scriptp.pos = conv_num(st, &st->stack->stack_datav[olddefsp - 2]); // スクリプト位置の復元 + st->defsp = conv_num(st, &st->stack->stack_datav[olddefsp - 3]); // 基準スタックポインタを復元 + // Number of arguments. + int i = conv_num(st, &st->stack->stack_datav[olddefsp - 4]); // 引数の数所得 + assert (i == 0); + + pop_stack(st->stack, olddefsp - 4 - i, olddefsp); // 要らなくなったスタック(引数と復帰用データ)削除 + + st->state = ScriptEndState::GOTO; + } +} + +// pretend it's external so this can be called in the debugger +void dump_script(const ScriptBuffer *script); +void dump_script(const ScriptBuffer *script) +{ + ScriptPointer scriptp(script, 0); + while (scriptp.pos < reinterpret_cast *>(script)->size()) + { + PRINTF("%6zu: "_fmt, scriptp.pos); + switch (ByteCode c = get_com(&scriptp)) + { + case ByteCode::EOL: + PRINTF("EOL\n"_fmt); // extra newline between functions + break; + case ByteCode::INT: + // synthesized! + PRINTF("INT %d"_fmt, get_num(&scriptp)); + break; + + case ByteCode::POS: + case ByteCode::VARIABLE: + case ByteCode::FUNC_REF: + case ByteCode::PARAM: + { + int arg = 0; + arg |= static_cast(scriptp.pop()) << 0; + arg |= static_cast(scriptp.pop()) << 8; + arg |= static_cast(scriptp.pop()) << 16; + switch(c) + { + case ByteCode::POS: + PRINTF("POS %d"_fmt, arg); + break; + case ByteCode::VARIABLE: + PRINTF("VARIABLE %s"_fmt, variable_names.outtern(arg)); + break; + case ByteCode::FUNC_REF: + PRINTF("FUNC_REF %s"_fmt, builtin_functions[arg].name); + break; + case ByteCode::PARAM: + PRINTF("PARAM SP::#%d (sorry)"_fmt, arg); + break; + } + } + break; + case ByteCode::ARG: + PRINTF("ARG"_fmt); + break; + case ByteCode::STR: + PRINTF("STR \"%s\""_fmt, scriptp.pops()); + break; + case ByteCode::FUNC: + PRINTF("FUNC"_fmt); + break; + + case ByteCode::ADD: + PRINTF("ADD"_fmt); + break; + case ByteCode::SUB: + PRINTF("SUB"_fmt); + break; + case ByteCode::MUL: + PRINTF("MUL"_fmt); + break; + case ByteCode::DIV: + PRINTF("DIV"_fmt); + break; + case ByteCode::MOD: + PRINTF("MOD"_fmt); + break; + case ByteCode::EQ: + PRINTF("EQ"_fmt); + break; + case ByteCode::NE: + PRINTF("NE"_fmt); + break; + case ByteCode::GT: + PRINTF("GT"_fmt); + break; + case ByteCode::GE: + PRINTF("GE"_fmt); + break; + case ByteCode::LT: + PRINTF("LT"_fmt); + break; + case ByteCode::LE: + PRINTF("LE"_fmt); + break; + case ByteCode::AND: + PRINTF("AND"_fmt); + break; + case ByteCode::OR: + PRINTF("OR"_fmt); + break; + case ByteCode::XOR: + PRINTF("XOR"_fmt); + break; + case ByteCode::LAND: + PRINTF("LAND"_fmt); + break; + case ByteCode::LOR: + PRINTF("LOR"_fmt); + break; + case ByteCode::R_SHIFT: + PRINTF("R_SHIFT"_fmt); + break; + case ByteCode::L_SHIFT: + PRINTF("L_SHIFT"_fmt); + break; + case ByteCode::NEG: + PRINTF("NEG"_fmt); + break; + case ByteCode::NOT: + PRINTF("NOT"_fmt); + break; + case ByteCode::LNOT: + PRINTF("LNOT"_fmt); + break; + + case ByteCode::NOP: + PRINTF("NOP"_fmt); + break; + + default: + PRINTF("??? %d"_fmt, c); + break; + } + PRINTF("\n"_fmt); + } +} + +/*========================================== + * スクリプトの実行メイン部分 + *------------------------------------------ + */ +static +void run_script_main(ScriptState *st, const ScriptBuffer *rootscript) +{ + int cmdcount = script_config.check_cmdcount; + int gotocount = script_config.check_gotocount; + struct script_stack *stack = st->stack; + + st->defsp = stack->stack_datav.size(); + + int rerun_pos = st->scriptp.pos; + st->state = ScriptEndState::ZERO; + while (st->state == ScriptEndState::ZERO) + { + switch (ByteCode c = get_com(&st->scriptp)) + { + case ByteCode::EOL: + if (stack->stack_datav.size() != st->defsp) + { + if (true) + PRINTF("stack.sp (%zu) != default (%d)\n"_fmt, + stack->stack_datav.size(), + st->defsp); + abort(); + } + rerun_pos = st->scriptp.pos; + break; + case ByteCode::INT: + // synthesized! + push_int(stack, get_num(&st->scriptp)); + break; + + case ByteCode::POS: + case ByteCode::VARIABLE: + case ByteCode::FUNC_REF: + case ByteCode::PARAM: + // Note that these 3 have *very* different meanings, + // despite being encoded similarly. + { + int arg = 0; + arg |= static_cast(st->scriptp.pop()) << 0; + arg |= static_cast(st->scriptp.pop()) << 8; + arg |= static_cast(st->scriptp.pop()) << 16; + switch(c) + { + case ByteCode::POS: + push_int(stack, arg); + break; + case ByteCode::VARIABLE: + push_reg(stack, SIR::from(arg)); + break; + case ByteCode::FUNC_REF: + push_int(stack, arg); + break; + case ByteCode::PARAM: + SP arg_sp = static_cast(arg); + push_reg(stack, SIR::from(arg_sp)); + break; + } + } + break; + case ByteCode::ARG: + push_int(stack, 0); + break; + case ByteCode::STR: + push_str(stack, st->scriptp.pops()); + break; + case ByteCode::FUNC: + run_func(st); + if (st->state == ScriptEndState::GOTO) + { + rerun_pos = st->scriptp.pos; + st->state = ScriptEndState::ZERO; + if (gotocount > 0 && (--gotocount) <= 0) + { + PRINTF("run_script: infinity loop !\n"_fmt); + st->state = ScriptEndState::END; + } + } + break; + + case ByteCode::ADD: + op_add(st); + break; + + case ByteCode::SUB: + case ByteCode::MUL: + case ByteCode::DIV: + case ByteCode::MOD: + case ByteCode::EQ: + case ByteCode::NE: + case ByteCode::GT: + case ByteCode::GE: + case ByteCode::LT: + case ByteCode::LE: + case ByteCode::AND: + case ByteCode::OR: + case ByteCode::XOR: + case ByteCode::LAND: + case ByteCode::LOR: + case ByteCode::R_SHIFT: + case ByteCode::L_SHIFT: + op_2(st, c); + break; + + case ByteCode::NEG: + case ByteCode::NOT: + case ByteCode::LNOT: + op_1num(st, c); + break; + + case ByteCode::NOP: + st->state = ScriptEndState::END; + break; + + default: + if (battle_config.error_log) + PRINTF("unknown command : %d @ %zu\n"_fmt, + c, st->scriptp.pos); + st->state = ScriptEndState::END; + break; + } + if (cmdcount > 0 && (--cmdcount) <= 0) + { + PRINTF("run_script: infinity loop !\n"_fmt); + st->state = ScriptEndState::END; + } + } + switch (st->state) + { + case ScriptEndState::STOP: + break; + case ScriptEndState::END: + { + dumb_ptr 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, BlockId rid, BlockId oid) +{ + return run_script_l(sp, rid, oid, nullptr); +} + +int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid, + Slice args) +{ + struct script_stack stack; + ScriptState st; + dumb_ptr sd = map_id2sd(rid); + const ScriptBuffer *rootscript = sp.code; + int i; + if (sp.code == nullptr || sp.pos >> 24) + return -1; + + if (sd && !sd->npc_stackbuf.empty() && sd->npc_scriptroot == rootscript) + { + // 前回のスタックを復帰 + sp.code = sd->npc_script; + stack.stack_datav = std::move(sd->npc_stackbuf); + } + st.stack = &stack; + st.scriptp = sp; + st.rid = rid; + st.oid = oid; + for (i = 0; i < args.size(); i++) + { + if (args[i].name.back() == '$') + pc_setregstr(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.s); + else + pc_setreg(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.i); + } + run_script_main(&st, rootscript); + + stack.stack_datav.clear(); + return st.scriptp.pos; +} + +void set_script_var_i(dumb_ptr sd, VarName var, int e, int val) +{ + size_t k = variable_names.intern(var); + SIR reg = SIR::from(k, e); + set_reg(sd, VariableCode::VARIABLE, reg, val); +} +void set_script_var_s(dumb_ptr sd, VarName var, int e, XString val) +{ + size_t k = variable_names.intern(var); + SIR reg = SIR::from(k, e); + set_reg(sd, VariableCode::VARIABLE, reg, val); +} +int get_script_var_i(dumb_ptr sd, VarName var, int e) +{ + size_t k = variable_names.intern(var); + SIR reg = SIR::from(k, e); + struct script_data dat = ScriptDataVariable{.reg= reg}; + get_val(sd, &dat); + if (auto *u = dat.get_if()) + return u->numi; + PRINTF("Warning: you lied about the type and I'm too lazy to fix it!"_fmt); + return 0; +} +ZString get_script_var_s(dumb_ptr sd, VarName var, int e) +{ + size_t k = variable_names.intern(var); + SIR reg = SIR::from(k, e); + struct script_data dat = ScriptDataVariable{.reg= reg}; + get_val(sd, &dat); + if (auto *u = dat.get_if()) + // this is almost certainly a memory leak after CONSTSTR removal + return u->str; + PRINTF("Warning: you lied about the type and I can't fix it!"_fmt); + return ZString(); +} +} // namespace tmwa diff --git a/src/map/script-call.hpp b/src/map/script-call.hpp new file mode 100644 index 0000000..da9d03f --- /dev/null +++ b/src/map/script-call.hpp @@ -0,0 +1,67 @@ +#pragma once +// script-call.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "script-call.t.hpp" + +#include "fwd.hpp" + +#include "../range/fwd.hpp" + +#include "../generic/fwd.hpp" + +#include "../mmo/fwd.hpp" + + +namespace tmwa +{ +enum class ByteCode : uint8_t; + +// implemented in script-parse.cpp because reasons +struct ScriptPointer +{ + const ScriptBuffer *code; + size_t pos; + + ScriptPointer() + : code() + , pos() + {} + + ScriptPointer(const ScriptBuffer *c, size_t p) + : code(c) + , pos(p) + {} + + ByteCode peek() const; + ByteCode pop(); + ZString pops(); +}; + +int run_script_l(ScriptPointer, BlockId, BlockId, Slice args); +int run_script(ScriptPointer, BlockId, BlockId); + +void set_script_var_i(dumb_ptr sd, VarName var, int e, int val); +void set_script_var_s(dumb_ptr sd, VarName var, int e, XString val); + +int get_script_var_i(dumb_ptr sd, VarName var, int e); +ZString get_script_var_s(dumb_ptr sd, VarName var, int e); +} // namespace tmwa diff --git a/src/map/script-call.t.hpp b/src/map/script-call.t.hpp new file mode 100644 index 0000000..04a6a80 --- /dev/null +++ b/src/map/script-call.t.hpp @@ -0,0 +1,45 @@ +#pragma once +// script-call.t.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "fwd.hpp" + +#include "../strings/zstring.hpp" + + +namespace tmwa +{ +struct argrec_t +{ + ZString name; + union _aru + { + int i; + ZString s; + + _aru(int n) : i(n) {} + _aru(ZString z) : s(z) {} + } v; + + argrec_t(ZString n, int i) : name(n), v(i) {} + argrec_t(ZString n, ZString z) : name(n), v(z) {} +}; +} // namespace tmwa diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp new file mode 100644 index 0000000..5caf060 --- /dev/null +++ b/src/map/script-fun.cpp @@ -0,0 +1,3003 @@ +#include "script-fun.hpp" +// script-fun.cpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011 Chuck Miller +// Copyright © 2011-2014 Ben Longbons +// Copyright © 2013 wushin +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../compat/fun.hpp" + +#include "../generic/db.hpp" +#include "../generic/dumb_ptr.hpp" +#include "../generic/intern-pool.hpp" +#include "../generic/random.hpp" + +#include "../io/cxxstdio.hpp" + +#include "../net/timer.hpp" + +#include "../mmo/core.hpp" + +#include "atcommand.hpp" +#include "battle.hpp" +#include "chrif.hpp" +#include "clif.hpp" +#include "intif.hpp" +#include "itemdb.hpp" +#include "magic-interpreter-base.hpp" +#include "map.hpp" +#include "mob.hpp" +#include "npc.hpp" +#include "party.hpp" +#include "pc.hpp" +#include "script-call-internal.hpp" +#include "script-parse-internal.hpp" +#include "script-persist.hpp" +#include "skill.hpp" +#include "storage.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +static +Array pos_str //= +{{ + "Head"_s, + "Body"_s, + "Left hand"_s, + "Right hand"_s, + "Robe"_s, + "Shoes"_s, + "Accessory 1"_s, + "Accessory 2"_s, + "Head 2"_s, + "Head 3"_s, + "Not Equipped"_s, +}}; + +#define AARG(n) (st->stack->stack_datav[st->start + 2 + (n)]) +#define HARG(n) (st->end > st->start + 2 + (n)) + +// +// 埋め込み関数 +// +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_mes(ScriptState *st) +{ + RString mes = conv_str(st, &AARG(0)); + clif_scriptmes(script_rid2sd(st), st->oid, mes); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_goto(ScriptState *st) +{ + if (!AARG(0).is()) + { + PRINTF("script: goto: not label !\n"_fmt); + st->state = ScriptEndState::END; + return; + } + + st->scriptp.pos = conv_num(st, &AARG(0)); + st->state = ScriptEndState::GOTO; +} + +/*========================================== + * ユーザー定義関数の呼び出し + *------------------------------------------ + */ +static +void builtin_callfunc(ScriptState *st) +{ + RString str = conv_str(st, &AARG(0)); + const ScriptBuffer *scr = userfunc_db.get(str); + + if (scr) + { + int j = 0; + assert (st->start + 3 == st->end); +#if 0 + for (int i = st->start + 3; i < st->end; i++, j++) + push_copy(st->stack, i); +#endif + + push_int(st->stack, j); // 引数の数をプッシュ + push_int(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ + push_int(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ + push_script(st->stack, st->scriptp.code); // 現在のスクリプトをプッシュ + + st->scriptp = ScriptPointer(scr, 0); + st->defsp = st->start + 4 + j; + st->state = ScriptEndState::GOTO; + } + else + { + PRINTF("script:callfunc: function not found! [%s]\n"_fmt, str); + st->state = ScriptEndState::END; + } +} + +/*========================================== + * サブルーティンの呼び出し + *------------------------------------------ + */ +static +void builtin_callsub(ScriptState *st) +{ + int pos_ = conv_num(st, &AARG(0)); + int j = 0; + assert (st->start + 3 == st->end); +#if 0 + for (int i = st->start + 3; i < st->end; i++, j++) + push_copy(st->stack, i); +#endif + + push_int(st->stack, j); // 引数の数をプッシュ + push_int(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ + push_int(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ + push_script(st->stack, st->scriptp.code); // 現在のスクリプトをプッシュ + + st->scriptp.pos = pos_; + st->defsp = st->start + 4 + j; + st->state = ScriptEndState::GOTO; +} + +/*========================================== + * サブルーチン/ユーザー定義関数の終了 + *------------------------------------------ + */ +static +void builtin_return(ScriptState *st) +{ +#if 0 + if (HARG(0)) + { // 戻り値有り + push_copy(st->stack, st->start + 2); + } +#endif + st->state = ScriptEndState::RETFUNC; +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_next(ScriptState *st) +{ + st->state = ScriptEndState::STOP; + clif_scriptnext(script_rid2sd(st), st->oid); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_close(ScriptState *st) +{ + st->state = ScriptEndState::END; + clif_scriptclose(script_rid2sd(st), st->oid); +} + +static +void builtin_close2(ScriptState *st) +{ + st->state = ScriptEndState::STOP; + clif_scriptclose(script_rid2sd(st), st->oid); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_menu(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + + if (sd->state.menu_or_input == 0) + { + // First half: show menu. + st->state = ScriptEndState::RERUNLINE; + sd->state.menu_or_input = 1; + + MString buf; + for (int i = 0; i < (st->end - (st->start + 2)) / 2; i++) + { + RString choice_str = conv_str(st, &AARG(i * 2 + 0)); + if (!choice_str) + break; + buf += choice_str; + buf += ':'; + } + + clif_scriptmenu(script_rid2sd(st), st->oid, AString(buf)); + } + else + { + // Rerun: item is chosen from menu. + if (sd->npc_menu == 0xff) + { + // cancel + sd->state.menu_or_input = 0; + st->state = ScriptEndState::END; + return; + } + + // Actually jump to the label. + // Logic change: menu_choices is the *total* number of labels, + // not just the displayed number that ends with the "". + // (Would it be better to pop the stack before rerunning?) + int menu_choices = (st->end - (st->start + 2)) / 2; + pc_setreg(sd, SIR::from(variable_names.intern("@menu"_s)), sd->npc_menu); + sd->state.menu_or_input = 0; + if (sd->npc_menu > 0 && sd->npc_menu <= menu_choices) + { + int arg_index = (sd->npc_menu - 1) * 2 + 1; + if (!AARG(arg_index).is()) + { + st->state = ScriptEndState::END; + return; + } + st->scriptp.pos = AARG(arg_index).get_if()->numi; + st->state = ScriptEndState::GOTO; + } + } +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_rand(ScriptState *st) +{ + if (HARG(1)) + { + int min = conv_num(st, &AARG(0)); + int max = conv_num(st, &AARG(1)); + if (min > max) + std::swap(max, min); + push_int(st->stack, random_::in(min, max)); + } + else + { + int range = conv_num(st, &AARG(0)); + push_int(st->stack, range <= 0 ? 0 : random_::to(range)); + } +} + +/*========================================== + * Check whether the PC is at the specified location + *------------------------------------------ + */ +static +void builtin_isat(ScriptState *st) +{ + int x, y; + dumb_ptr sd = script_rid2sd(st); + + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + x = conv_num(st, &AARG(1)); + y = conv_num(st, &AARG(2)); + + if (!sd) + return; + + push_int(st->stack, + (x == sd->bl_x) && (y == sd->bl_y) + && (str == sd->bl_m->name_)); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_warp(ScriptState *st) +{ + int x, y; + dumb_ptr sd = script_rid2sd(st); + + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + x = conv_num(st, &AARG(1)); + y = conv_num(st, &AARG(2)); + if (str == "Random"_s) + pc_randomwarp(sd, BeingRemoveWhy::WARPED); + else if (str == "SavePoint"_s or str == "Save"_s) + { + if (sd->bl_m->flag.get(MapFlag::NORETURN)) + return; + + pc_setpos(sd, sd->status.save_point.map_, sd->status.save_point.x, sd->status.save_point.y, + BeingRemoveWhy::WARPED); + } + else + pc_setpos(sd, str, x, y, BeingRemoveWhy::GONE); +} + +/*========================================== + * エリア指定ワープ + *------------------------------------------ + */ +static +void builtin_areawarp_sub(dumb_ptr bl, MapName mapname, int x, int y) +{ + dumb_ptr sd = bl->is_player(); + if (mapname == "Random"_s) + pc_randomwarp(sd, BeingRemoveWhy::WARPED); + else + pc_setpos(sd, mapname, x, y, BeingRemoveWhy::GONE); +} + +static +void builtin_areawarp(ScriptState *st) +{ + int x, y; + int x0, y0, x1, y1; + + MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); + x0 = conv_num(st, &AARG(1)); + y0 = conv_num(st, &AARG(2)); + x1 = conv_num(st, &AARG(3)); + y1 = conv_num(st, &AARG(4)); + MapName str = stringish(ZString(conv_str(st, &AARG(5)))); + x = conv_num(st, &AARG(6)); + y = conv_num(st, &AARG(7)); + + map_local *m = map_mapname2mapid(mapname); + if (m == nullptr) + return; + + map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y), + m, + x0, y0, + x1, y1, + BL::PC); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_heal(ScriptState *st) +{ + int hp, sp; + + hp = conv_num(st, &AARG(0)); + sp = conv_num(st, &AARG(1)); + pc_heal(script_rid2sd(st), hp, sp); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_itemheal(ScriptState *st) +{ + int hp, sp; + + hp = conv_num(st, &AARG(0)); + sp = conv_num(st, &AARG(1)); + pc_itemheal(script_rid2sd(st), hp, sp); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_percentheal(ScriptState *st) +{ + int hp, sp; + + hp = conv_num(st, &AARG(0)); + sp = conv_num(st, &AARG(1)); + pc_percentheal(script_rid2sd(st), hp, sp); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_input(ScriptState *st) +{ + dumb_ptr sd = nullptr; + script_data& scrd = AARG(0); + assert (scrd.is()); + + SIR reg = scrd.get_if()->reg; + ZString name = variable_names.outtern(reg.base()); +// char prefix = name.front(); + char postfix = name.back(); + + sd = script_rid2sd(st); + if (sd->state.menu_or_input) + { + // Second time (rerun) + sd->state.menu_or_input = 0; + if (postfix == '$') + { + set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_str); + } + else + { + //commented by Lupus (check Value Number Input fix in clif.c) + //** Fix by fritz :X keeps people from abusing old input bugs + // wtf? + if (sd->npc_amount < 0) //** If input amount is less then 0 + { + clif_tradecancelled(sd); // added "Deal has been cancelled" message by Valaris + builtin_close(st); //** close + } + + set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_amount); + } + } + else + { + // First time - send prompt to client, then wait + st->state = ScriptEndState::RERUNLINE; + if (postfix == '$') + clif_scriptinputstr(sd, st->oid); + else + clif_scriptinput(sd, st->oid); + sd->state.menu_or_input = 1; + } +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_if (ScriptState *st) +{ + int sel, i; + + sel = conv_num(st, &AARG(0)); + if (!sel) + return; + + // 関数名をコピー + push_copy(st->stack, st->start + 3); + // 間に引数マーカを入れて + push_int(st->stack, 0); + // 残りの引数をコピー + for (i = st->start + 4; i < st->end; i++) + { + push_copy(st->stack, i); + } + run_func(st); +} + +/*========================================== + * 変数設定 + *------------------------------------------ + */ +static +void builtin_set(ScriptState *st) +{ + dumb_ptr sd = nullptr; + if (auto *u = AARG(0).get_if()) + { + SIR reg = u->reg; + sd = script_rid2sd(st); + + int val = conv_num(st, &AARG(1)); + set_reg(sd, VariableCode::PARAM, reg, val); + return; + } + + SIR reg = AARG(0).get_if()->reg; + + ZString name = variable_names.outtern(reg.base()); + char prefix = name.front(); + char postfix = name.back(); + + if (prefix != '$') + sd = script_rid2sd(st); + + if (postfix == '$') + { + // 文字列 + RString str = conv_str(st, &AARG(1)); + set_reg(sd, VariableCode::VARIABLE, reg, str); + } + else + { + // 数値 + int val = conv_num(st, &AARG(1)); + set_reg(sd, VariableCode::VARIABLE, reg, val); + } + +} + +/*========================================== + * 配列変数設定 + *------------------------------------------ + */ +static +void builtin_setarray(ScriptState *st) +{ + dumb_ptr sd = nullptr; + SIR reg = AARG(0).get_if()->reg; + ZString name = variable_names.outtern(reg.base()); + char prefix = name.front(); + char postfix = name.back(); + + if (prefix != '$' && prefix != '@') + { + PRINTF("builtin_setarray: illegal scope !\n"_fmt); + return; + } + if (prefix != '$') + sd = script_rid2sd(st); + + for (int j = 0, i = 1; i < st->end - st->start - 2 && j < 256; i++, j++) + { + if (postfix == '$') + set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_str(st, &AARG(i))); + else + set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_num(st, &AARG(i))); + } +} + +/*========================================== + * 配列変数クリア + *------------------------------------------ + */ +static +void builtin_cleararray(ScriptState *st) +{ + dumb_ptr sd = nullptr; + SIR reg = AARG(0).get_if()->reg; + ZString name = variable_names.outtern(reg.base()); + char prefix = name.front(); + char postfix = name.back(); + int sz = conv_num(st, &AARG(2)); + + if (prefix != '$' && prefix != '@') + { + PRINTF("builtin_cleararray: illegal scope !\n"_fmt); + return; + } + if (prefix != '$') + sd = script_rid2sd(st); + + for (int i = 0; i < sz; i++) + { + if (postfix == '$') + set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARG(1))); + else + set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_num(st, &AARG(1))); + } + +} + +/*========================================== + * 配列変数のサイズ所得 + *------------------------------------------ + */ +static +int getarraysize(ScriptState *st, SIR reg) +{ + int i = reg.index(), c = i; + for (; i < 256; i++) + { + struct script_data vd = get_val2(st, reg.iplus(i)); + MATCH (vd) + { + CASE (const ScriptDataStr&, u) + { + if (u.str[0]) + c = i; + goto continue_outer; + } + CASE (const ScriptDataInt&, u) + { + if (u.numi) + c = i; + goto continue_outer; + } + } + abort(); + continue_outer: + ; + } + return c + 1; +} + +static +void builtin_getarraysize(ScriptState *st) +{ + SIR reg = AARG(0).get_if()->reg; + ZString name = variable_names.outtern(reg.base()); + char prefix = name.front(); + + if (prefix != '$' && prefix != '@') + { + PRINTF("builtin_copyarray: illegal scope !\n"_fmt); + return; + } + + push_int(st->stack, getarraysize(st, reg)); +} + +/*========================================== + * 指定要素を表す値(キー)を所得する + *------------------------------------------ + */ +static +void builtin_getelementofarray(ScriptState *st) +{ + if (auto *u = AARG(0).get_if()) + { + int i = conv_num(st, &AARG(1)); + if (i > 255 || i < 0) + { + PRINTF("script: getelementofarray (operator[]): param2 illegal number %d\n"_fmt, + i); + push_int(st->stack, 0); + } + else + { + push_reg(st->stack, + u->reg.iplus(i)); + } + } + else + { + PRINTF("script: getelementofarray (operator[]): param1 not name !\n"_fmt); + push_int(st->stack, 0); + } +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_setlook(ScriptState *st) +{ + LOOK type = LOOK(conv_num(st, &AARG(0))); + int val = conv_num(st, &AARG(1)); + + pc_changelook(script_rid2sd(st), type, val); + +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_countitem(ScriptState *st) +{ + ItemNameId nameid; + int count = 0; + dumb_ptr sd; + + struct script_data *data; + + sd = script_rid2sd(st); + + data = &AARG(0); + get_val(st, data); + if (data->is()) + { + ZString name = ZString(conv_str(st, data)); + struct item_data *item_data = itemdb_searchname(name); + if (item_data != nullptr) + nameid = item_data->nameid; + } + else + nameid = wrap(conv_num(st, data)); + + if (nameid) + { + for (IOff0 i : IOff0::iter()) + { + if (sd->status.inventory[i].nameid == nameid) + count += sd->status.inventory[i].amount; + } + } + else + { + if (battle_config.error_log) + PRINTF("wrong item ID : countitem (%i)\n"_fmt, nameid); + } + push_int(st->stack, count); + +} + +/*========================================== + * 重量チェック + *------------------------------------------ + */ +static +void builtin_checkweight(ScriptState *st) +{ + ItemNameId nameid; + int amount; + dumb_ptr sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data = &AARG(0); + get_val(st, data); + if (data->is()) + { + ZString name = ZString(conv_str(st, data)); + struct item_data *item_data = itemdb_searchname(name); + if (item_data) + nameid = item_data->nameid; + } + else + nameid = wrap(conv_num(st, data)); + + amount = conv_num(st, &AARG(1)); + if (amount <= 0 || !nameid) + { + //if get wrong item ID or amount<=0, don't count weight of non existing items + push_int(st->stack, 0); + return; + } + + if (itemdb_weight(nameid) * amount + sd->weight > sd->max_weight) + { + push_int(st->stack, 0); + } + else + { + push_int(st->stack, 1); + } + +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_getitem(ScriptState *st) +{ + ItemNameId nameid; + int amount; + dumb_ptr sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data = &AARG(0); + get_val(st, data); + if (data->is()) + { + ZString name = ZString(conv_str(st, data)); + struct item_data *item_data = itemdb_searchname(name); + if (item_data != nullptr) + nameid = item_data->nameid; + } + else + nameid = wrap(conv_num(st, data)); + + if ((amount = + conv_num(st, &AARG(1))) <= 0) + { + return; //return if amount <=0, skip the useles iteration + } + + if (nameid) + { + Item item_tmp {}; + item_tmp.nameid = nameid; + if (HARG(3)) //アイテムを指定したIDに渡す + sd = map_id2sd(wrap(conv_num(st, &AARG(3)))); + if (sd == nullptr) //アイテムを渡す相手がいなかったらお帰り + return; + PickupFail flag; + if ((flag = pc_additem(sd, &item_tmp, amount)) != PickupFail::OKAY) + { + clif_additem(sd, IOff0::from(0), 0, flag); + map_addflooritem(&item_tmp, amount, + sd->bl_m, sd->bl_x, sd->bl_y, + nullptr, nullptr, nullptr); + } + } + +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_makeitem(ScriptState *st) +{ + ItemNameId nameid; + int amount; + int x, y; + dumb_ptr sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data = &AARG(0); + get_val(st, data); + if (data->is()) + { + ZString name = ZString(conv_str(st, data)); + struct item_data *item_data = itemdb_searchname(name); + if (item_data) + nameid = item_data->nameid; + } + else + nameid = wrap(conv_num(st, data)); + + amount = conv_num(st, &AARG(1)); + MapName mapname = stringish(ZString(conv_str(st, &AARG(2)))); + x = conv_num(st, &AARG(3)); + y = conv_num(st, &AARG(4)); + + map_local *m; + if (sd && mapname == MOB_THIS_MAP) + m = sd->bl_m; + else + m = map_mapname2mapid(mapname); + + if (nameid) + { + Item item_tmp {}; + item_tmp.nameid = nameid; + + map_addflooritem(&item_tmp, amount, m, x, y, nullptr, nullptr, nullptr); + } +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_delitem(ScriptState *st) +{ + ItemNameId nameid; + int amount; + dumb_ptr sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data = &AARG(0); + get_val(st, data); + if (data->is()) + { + ZString name = ZString(conv_str(st, data)); + struct item_data *item_data = itemdb_searchname(name); + if (item_data) + nameid = item_data->nameid; + } + else + nameid = wrap(conv_num(st, data)); + + amount = conv_num(st, &AARG(1)); + + if (!nameid || amount <= 0) + { + //by Lupus. Don't run FOR if u got wrong item ID or amount<=0 + return; + } + + for (IOff0 i : IOff0::iter()) + { + if (sd->status.inventory[i].nameid == nameid) + { + if (sd->status.inventory[i].amount >= amount) + { + pc_delitem(sd, i, amount, 0); + break; + } + else + { + amount -= sd->status.inventory[i].amount; + if (amount == 0) + amount = sd->status.inventory[i].amount; + pc_delitem(sd, i, amount, 0); + break; + } + } + } + +} + +/*========================================== + *キャラ関係のパラメータ取得 + *------------------------------------------ + */ +static +void builtin_readparam(ScriptState *st) +{ + dumb_ptr sd; + + SP type = SP(conv_num(st, &AARG(0))); + if (HARG(1)) + sd = map_nick2sd(stringish(ZString(conv_str(st, &AARG(1))))); + else + sd = script_rid2sd(st); + + if (sd == nullptr) + { + push_int(st->stack, -1); + return; + } + + push_int(st->stack, pc_readparam(sd, type)); + +} + +/*========================================== + *キャラ関係のID取得 + *------------------------------------------ + */ +static +void builtin_getcharid(ScriptState *st) +{ + int num; + dumb_ptr sd; + + num = conv_num(st, &AARG(0)); + if (HARG(1)) + sd = map_nick2sd(stringish(ZString(conv_str(st, &AARG(1))))); + else + sd = script_rid2sd(st); + if (sd == nullptr) + { + push_int(st->stack, -1); + return; + } + if (num == 0) + push_int(st->stack, unwrap(sd->status_key.char_id)); + if (num == 1) + push_int(st->stack, unwrap(sd->status.party_id)); + if (num == 2) + push_int(st->stack, 0/*guild_id*/); + if (num == 3) + push_int(st->stack, unwrap(sd->status_key.account_id)); +} + +/*========================================== + *指定IDのPT名取得 + *------------------------------------------ + */ +static +RString builtin_getpartyname_sub(PartyId party_id) +{ + PartyPair p = party_search(party_id); + + if (p) + return p->name; + + return RString(); +} + +/*========================================== + * キャラクタの名前 + *------------------------------------------ + */ +static +void builtin_strcharinfo(ScriptState *st) +{ + dumb_ptr sd; + int num; + + sd = script_rid2sd(st); + num = conv_num(st, &AARG(0)); + if (num == 0) + { + RString buf = sd->status_key.name.to__actual(); + push_str(st->stack, buf); + } + if (num == 1) + { + RString buf = builtin_getpartyname_sub(sd->status.party_id); + if (buf) + push_str(st->stack, buf); + else + push_str(st->stack, ""_s); + } + if (num == 2) + { + // was: guild name + push_str(st->stack, ""_s); + } + +} + +// indexed by the equip_* in db/const.txt +// TODO change to use EQUIP +static +Array equip //= +{{ + EPOS::HAT, + EPOS::MISC1, + EPOS::SHIELD, + EPOS::WEAPON, + EPOS::GLOVES, + EPOS::SHOES, + EPOS::CAPE, + EPOS::MISC2, + EPOS::TORSO, + EPOS::LEGS, + EPOS::ARROW, +}}; + +/*========================================== + * GetEquipID(Pos); Pos: 1-10 + *------------------------------------------ + */ +static +void builtin_getequipid(ScriptState *st) +{ + int num; + dumb_ptr sd; + struct item_data *item; + + sd = script_rid2sd(st); + if (sd == nullptr) + { + PRINTF("getequipid: sd == nullptr\n"_fmt); + return; + } + num = conv_num(st, &AARG(0)); + IOff0 i = pc_checkequip(sd, equip[num - 1]); + if (i.ok()) + { + item = sd->inventory_data[i]; + if (item) + push_int(st->stack, unwrap(item->nameid)); + else + push_int(st->stack, 0); + } + else + { + push_int(st->stack, -1); + } +} + +/*========================================== + * 装備名文字列(精錬メニュー用) + *------------------------------------------ + */ +static +void builtin_getequipname(ScriptState *st) +{ + int num; + dumb_ptr sd; + struct item_data *item; + + AString buf; + + sd = script_rid2sd(st); + num = conv_num(st, &AARG(0)); + IOff0 i = pc_checkequip(sd, equip[num - 1]); + if (i.ok()) + { + item = sd->inventory_data[i]; + if (item) + buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], item->jname); + else + buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]); + } + else + { + buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]); + } + push_str(st->stack, buf); + +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_statusup2(ScriptState *st) +{ + SP type = SP(conv_num(st, &AARG(0))); + int val = conv_num(st, &AARG(1)); + dumb_ptr sd = script_rid2sd(st); + pc_statusup2(sd, type, val); + +} + +/*========================================== + * 装備品による能力値ボーナス + *------------------------------------------ + */ +static +void builtin_bonus(ScriptState *st) +{ + SP type = SP(conv_num(st, &AARG(0))); + int val = conv_num(st, &AARG(1)); + dumb_ptr sd = script_rid2sd(st); + pc_bonus(sd, type, val); + +} + +/*========================================== + * 装備品による能力値ボーナス + *------------------------------------------ + */ +static +void builtin_bonus2(ScriptState *st) +{ + SP type = SP(conv_num(st, &AARG(0))); + int type2 = conv_num(st, &AARG(1)); + int val = conv_num(st, &AARG(2)); + dumb_ptr 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, &AARG(0))); + level = conv_num(st, &AARG(1)); + if (HARG(2)) + flag = conv_num(st, &AARG(2)); + sd = script_rid2sd(st); + pc_skill(sd, id, level, flag); + clif_skillinfoblock(sd); + +} + +/*========================================== + * [Fate] Sets the skill level permanently + *------------------------------------------ + */ +static +void builtin_setskill(ScriptState *st) +{ + int level; + dumb_ptr sd; + + SkillID id = static_cast(conv_num(st, &AARG(0))); + level = conv_num(st, &AARG(1)); + sd = script_rid2sd(st); + + level = std::min(level, MAX_SKILL_LEVEL); + level = std::max(level, 0); + sd->status.skill[id].lv = level; + clif_skillinfoblock(sd); +} + +/*========================================== + * スキルレベル所得 + *------------------------------------------ + */ +static +void builtin_getskilllv(ScriptState *st) +{ + SkillID id = SkillID(conv_num(st, &AARG(0))); + push_int(st->stack, pc_checkskill(script_rid2sd(st), id)); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_getgmlevel(ScriptState *st) +{ + push_int(st->stack, pc_isGM(script_rid2sd(st)).get_all_bits()); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_end(ScriptState *st) +{ + st->state = ScriptEndState::END; +} + +/*========================================== + * [Freeyorp] Return the current opt2 + *------------------------------------------ + */ + +static +void builtin_getopt2(ScriptState *st) +{ + dumb_ptr sd; + + sd = script_rid2sd(st); + + push_int(st->stack, static_cast(sd->opt2)); + +} + +/*========================================== + * [Freeyorp] Sets opt2 + *------------------------------------------ + */ + +static +void builtin_setopt2(ScriptState *st) +{ + dumb_ptr sd; + + Opt2 new_opt2 = Opt2(conv_num(st, &AARG(0))); + sd = script_rid2sd(st); + if (new_opt2 == sd->opt2) + return; + sd->opt2 = new_opt2; + clif_changeoption(sd); + pc_calcstatus(sd, 0); + +} + +/*========================================== + * セーブポイントの保存 + *------------------------------------------ + */ +static +void builtin_savepoint(ScriptState *st) +{ + int x, y; + + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + x = conv_num(st, &AARG(1)); + y = conv_num(st, &AARG(2)); + pc_setsavepoint(script_rid2sd(st), str, x, y); +} + +/*========================================== + * gettimetick(type) + * + * type The type of time measurement. + * Specify 0 for the system tick, 1 for + * seconds elapsed today, or 2 for seconds + * since Unix epoch. Defaults to 0 for any + * other value. + *------------------------------------------ + */ +static +void builtin_gettimetick(ScriptState *st) /* Asgard Version */ +{ + int type; + type = conv_num(st, &AARG(0)); + + switch (type) + { + /* Number of seconds elapsed today(0-86399, 00:00:00-23:59:59). */ + case 1: + { + struct tm t = TimeT::now(); + push_int(st->stack, + t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec); + break; + } + /* Seconds since Unix epoch. */ + case 2: + push_int(st->stack, static_cast(TimeT::now())); + break; + /* System tick(unsigned int, and yes, it will wrap). */ + case 0: + default: + push_int(st->stack, gettick().time_since_epoch().count()); + break; + } +} + +/*========================================== + * GetTime(Type); + * 1: Sec 2: Min 3: Hour + * 4: WeekDay 5: MonthDay 6: Month + * 7: Year + *------------------------------------------ + */ +static +void builtin_gettime(ScriptState *st) /* Asgard Version */ +{ + int type = conv_num(st, &AARG(0)); + + struct tm t = TimeT::now(); + + switch (type) + { + case 1: //Sec(0~59) + push_int(st->stack, t.tm_sec); + break; + case 2: //Min(0~59) + push_int(st->stack, t.tm_min); + break; + case 3: //Hour(0~23) + push_int(st->stack, t.tm_hour); + break; + case 4: //WeekDay(0~6) + push_int(st->stack, t.tm_wday); + break; + case 5: //MonthDay(01~31) + push_int(st->stack, t.tm_mday); + break; + case 6: //Month(01~12) + push_int(st->stack, t.tm_mon + 1); + break; + case 7: //Year(20xx) + push_int(st->stack, t.tm_year + 1900); + break; + default: //(format error) + push_int(st->stack, -1); + break; + } +} + +/*========================================== + * カプラ倉庫を開く + *------------------------------------------ + */ +static +void builtin_openstorage(ScriptState *st) +{ +// int sync = 0; +// if (st->end >= 3) sync = conv_num(st,& (st->stack->stack_data[st->start+2])); + dumb_ptr 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, &AARG(0)); + job = conv_num(st, &AARG(1)); + if (base < 0 || job < 0) + return; + if (sd) + pc_gainexp_reason(sd, base, job, PC_GAINEXP_REASON::SCRIPT); + +} + +/*========================================== + * モンスター発生 + *------------------------------------------ + */ +static +void builtin_monster(ScriptState *st) +{ + Species mob_class; + int amount, x, y; + NpcEvent event; + + MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); + x = conv_num(st, &AARG(1)); + y = conv_num(st, &AARG(2)); + MobName str = stringish(ZString(conv_str(st, &AARG(3)))); + mob_class = wrap(conv_num(st, &AARG(4))); + amount = conv_num(st, &AARG(5)); + if (HARG(6)) + extract(ZString(conv_str(st, &AARG(6))), &event); + + mob_once_spawn(map_id2sd(st->rid), mapname, x, y, str, mob_class, amount, + event); +} + +/*========================================== + * モンスター発生 + *------------------------------------------ + */ +static +void builtin_areamonster(ScriptState *st) +{ + Species mob_class; + int amount, x0, y0, x1, y1; + NpcEvent event; + + MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); + x0 = conv_num(st, &AARG(1)); + y0 = conv_num(st, &AARG(2)); + x1 = conv_num(st, &AARG(3)); + y1 = conv_num(st, &AARG(4)); + MobName str = stringish(ZString(conv_str(st, &AARG(5)))); + mob_class = wrap(conv_num(st, &AARG(6))); + amount = conv_num(st, &AARG(7)); + if (HARG(8)) + extract(ZString(conv_str(st, &AARG(8))), &event); + + mob_once_spawn_area(map_id2sd(st->rid), mapname, x0, y0, x1, y1, str, mob_class, + amount, event); +} + +/*========================================== + * モンスター削除 + *------------------------------------------ + */ +static +void builtin_killmonster_sub(dumb_ptr bl, NpcEvent event) +{ + dumb_ptr md = bl->is_mob(); + if (event) + { + if (event == md->npc_event) + mob_delete(md); + return; + } + else if (!event) + { + if (md->spawn.delay1 == static_cast(-1) + && md->spawn.delay2 == static_cast(-1)) + mob_delete(md); + return; + } +} + +static +void builtin_killmonster(ScriptState *st) +{ + MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); + ZString event_ = ZString(conv_str(st, &AARG(1))); + NpcEvent event; + if (event_ != "All"_s) + extract(event_, &event); + + map_local *m = map_mapname2mapid(mapname); + if (m == nullptr) + return; + map_foreachinarea(std::bind(builtin_killmonster_sub, ph::_1, event), + m, + 0, 0, + m->xs, m->ys, + BL::MOB); +} + +static +void builtin_killmonsterall_sub(dumb_ptr bl) +{ + mob_delete(bl->is_mob()); +} + +static +void builtin_killmonsterall(ScriptState *st) +{ + MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); + + map_local *m = map_mapname2mapid(mapname); + if (m == nullptr) + return; + map_foreachinarea(builtin_killmonsterall_sub, + m, + 0, 0, + m->xs, m->ys, + BL::MOB); +} + +/*========================================== + * NPC主体イベント実行 + *------------------------------------------ + */ +static +void builtin_donpcevent(ScriptState *st) +{ + ZString event_ = ZString(conv_str(st, &AARG(0))); + NpcEvent event; + extract(event_, &event); + npc_event_do(event); +} + +/*========================================== + * イベントタイマー追加 + *------------------------------------------ + */ +static +void builtin_addtimer(ScriptState *st) +{ + interval_t tick = static_cast(conv_num(st, &AARG(0))); + ZString event_ = ZString(conv_str(st, &AARG(1))); + NpcEvent event; + extract(event_, &event); + pc_addeventtimer(script_rid2sd(st), tick, event); +} + +/*========================================== + * NPCタイマー初期化 + *------------------------------------------ + */ +static +void builtin_initnpctimer(ScriptState *st) +{ + dumb_ptr nd_; + if (HARG(0)) + nd_ = npc_name2id(stringish(ZString(conv_str(st, &AARG(0))))); + else + nd_ = map_id_is_npc(st->oid); + assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); + dumb_ptr nd = nd_->is_script(); + + npc_settimerevent_tick(nd, interval_t::zero()); + npc_timerevent_start(nd); +} + +/*========================================== + * NPCタイマー開始 + *------------------------------------------ + */ +static +void builtin_startnpctimer(ScriptState *st) +{ + dumb_ptr nd_; + if (HARG(0)) + nd_ = npc_name2id(stringish(ZString(conv_str(st, &AARG(0))))); + else + nd_ = map_id_is_npc(st->oid); + assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); + dumb_ptr nd = nd_->is_script(); + + npc_timerevent_start(nd); +} + +/*========================================== + * NPCタイマー停止 + *------------------------------------------ + */ +static +void builtin_stopnpctimer(ScriptState *st) +{ + dumb_ptr nd_; + if (HARG(0)) + nd_ = npc_name2id(stringish(ZString(conv_str(st, &AARG(0))))); + else + nd_ = map_id_is_npc(st->oid); + assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); + dumb_ptr nd = nd_->is_script(); + + npc_timerevent_stop(nd); +} + +/*========================================== + * NPCタイマー情報所得 + *------------------------------------------ + */ +static +void builtin_getnpctimer(ScriptState *st) +{ + dumb_ptr nd_; + int type = conv_num(st, &AARG(0)); + int val = 0; + if (HARG(1)) + nd_ = npc_name2id(stringish(ZString(conv_str(st, &AARG(1))))); + else + nd_ = map_id_is_npc(st->oid); + assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); + dumb_ptr nd = nd_->is_script(); + + switch (type) + { + case 0: + val = npc_gettimerevent_tick(nd).count(); + break; + case 1: + val = nd->scr.timer_active; + break; + case 2: + val = nd->scr.timer_eventv.size(); + break; + } + push_int(st->stack, val); +} + +/*========================================== + * NPCタイマー値設定 + *------------------------------------------ + */ +static +void builtin_setnpctimer(ScriptState *st) +{ + dumb_ptr nd_; + interval_t tick = static_cast(conv_num(st, &AARG(0))); + if (HARG(1)) + nd_ = npc_name2id(stringish(ZString(conv_str(st, &AARG(1))))); + else + nd_ = map_id_is_npc(st->oid); + assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); + dumb_ptr nd = nd_->is_script(); + + npc_settimerevent_tick(nd, tick); +} + +/*========================================== + * 天の声アナウンス + *------------------------------------------ + */ +static +void builtin_announce(ScriptState *st) +{ + int flag; + ZString str = ZString(conv_str(st, &AARG(0))); + flag = conv_num(st, &AARG(1)); + + if (flag & 0x0f) + { + dumb_ptr bl; + if (flag & 0x08) + bl = map_id2bl(st->oid); + else + bl = script_rid2sd(st); + clif_GMmessage(bl, str, flag); + } + else + intif_GMmessage(str); +} + +/*========================================== + * 天の声アナウンス(特定マップ) + *------------------------------------------ + */ +static +void builtin_mapannounce_sub(dumb_ptr bl, XString str, int flag) +{ + clif_GMmessage(bl, str, flag | 3); +} + +static +void builtin_mapannounce(ScriptState *st) +{ + int flag; + + MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); + ZString str = ZString(conv_str(st, &AARG(1))); + flag = conv_num(st, &AARG(2)); + + map_local *m = map_mapname2mapid(mapname); + if (m == nullptr) + return; + map_foreachinarea(std::bind(builtin_mapannounce_sub, ph::_1, str, flag & 0x10), + m, + 0, 0, + m->xs, m->ys, + BL::PC); +} + +/*========================================== + * ユーザー数所得 + *------------------------------------------ + */ +static +void builtin_getusers(ScriptState *st) +{ + int flag = conv_num(st, &AARG(0)); + 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_int(st->stack, val); +} + +/*========================================== + * マップ指定ユーザー数所得 + *------------------------------------------ + */ +static +void builtin_getmapusers(ScriptState *st) +{ + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + map_local *m = map_mapname2mapid(str); + if (m == nullptr) + { + push_int(st->stack, -1); + return; + } + push_int(st->stack, m->users); +} + +/*========================================== + * エリア指定ユーザー数所得 + *------------------------------------------ + */ +static +void builtin_getareausers_sub(dumb_ptr bl, int *users) +{ + if (bool(bl->is_player()->status.option & Opt0::HIDE)) + return; + (*users)++; +} + +static +void builtin_getareausers_living_sub(dumb_ptr bl, int *users) +{ + if (bool(bl->is_player()->status.option & Opt0::HIDE)) + return; + if (!pc_isdead(bl->is_player())) + (*users)++; +} + +static +void builtin_getareausers(ScriptState *st) +{ + int x0, y0, x1, y1, users = 0; + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + x0 = conv_num(st, &AARG(1)); + y0 = conv_num(st, &AARG(2)); + x1 = conv_num(st, &AARG(3)); + y1 = conv_num(st, &AARG(4)); + + int living = 0; + if (HARG(5)) + { + living = conv_num(st, &AARG(5)); + } + map_local *m = map_mapname2mapid(str); + if (m == nullptr) + { + push_int(st->stack, -1); + return; + } + map_foreachinarea(std::bind(living ? builtin_getareausers_living_sub: builtin_getareausers_sub, ph::_1, &users), + m, + x0, y0, + x1, y1, + BL::PC); + push_int(st->stack, users); +} + +/*========================================== + * エリア指定ドロップアイテム数所得 + *------------------------------------------ + */ +static +void builtin_getareadropitem_sub(dumb_ptr bl, ItemNameId item, int *amount) +{ + dumb_ptr drop = bl->is_item(); + + if (drop->item_data.nameid == item) + (*amount) += drop->item_data.amount; + +} + +static +void builtin_getareadropitem_sub_anddelete(dumb_ptr bl, ItemNameId item, int *amount) +{ + dumb_ptr drop = bl->is_item(); + + if (drop->item_data.nameid == item) + { + (*amount) += drop->item_data.amount; + clif_clearflooritem(drop, nullptr); + map_delobject(drop->bl_id, drop->bl_type); + } +} + +static +void builtin_getareadropitem(ScriptState *st) +{ + ItemNameId item; + int x0, y0, x1, y1, amount = 0, delitems = 0; + struct script_data *data; + + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + x0 = conv_num(st, &AARG(1)); + y0 = conv_num(st, &AARG(2)); + x1 = conv_num(st, &AARG(3)); + y1 = conv_num(st, &AARG(4)); + + data = &AARG(5); + get_val(st, data); + if (data->is()) + { + ZString name = ZString(conv_str(st, data)); + struct item_data *item_data = itemdb_searchname(name); + if (item_data) + item = item_data->nameid; + } + else + item = wrap(conv_num(st, data)); + + if (HARG(6)) + delitems = conv_num(st, &AARG(6)); + + map_local *m = map_mapname2mapid(str); + if (m == nullptr) + { + push_int(st->stack, -1); + return; + } + if (delitems) + map_foreachinarea(std::bind(builtin_getareadropitem_sub_anddelete, ph::_1, item, &amount), + m, + x0, y0, + x1, y1, + BL::ITEM); + else + map_foreachinarea(std::bind(builtin_getareadropitem_sub, ph::_1, item, &amount), + m, + x0, y0, + x1, y1, + BL::ITEM); + + push_int(st->stack, amount); +} + +/*========================================== + * NPCの有効化 + *------------------------------------------ + */ +static +void builtin_enablenpc(ScriptState *st) +{ + NpcName str = stringish(ZString(conv_str(st, &AARG(0)))); + npc_enable(str, 1); +} + +/*========================================== + * NPCの無効化 + *------------------------------------------ + */ +static +void builtin_disablenpc(ScriptState *st) +{ + NpcName str = stringish(ZString(conv_str(st, &AARG(0)))); + npc_enable(str, 0); +} + +/*========================================== + * 状態異常にかかる + *------------------------------------------ + */ +static +void builtin_sc_start(ScriptState *st) +{ + dumb_ptr bl; + int val1; + StatusChange type = static_cast(conv_num(st, &AARG(0))); + interval_t tick = static_cast(conv_num(st, &AARG(1))); + if (tick < 1_s) + // work around old behaviour of: + // speed potion + // atk potion + // matk potion + // + // which used to use seconds + // all others used milliseconds + tick *= 1000; + val1 = conv_num(st, &AARG(2)); + if (HARG(3)) //指定したキャラを状態異常にする + bl = map_id2bl(wrap(conv_num(st, &AARG(3)))); + else + bl = map_id2bl(st->rid); + skill_status_change_start(bl, type, val1, tick); +} + +/*========================================== + * 状態異常が直る + *------------------------------------------ + */ +static +void builtin_sc_end(ScriptState *st) +{ + dumb_ptr bl; + StatusChange type = StatusChange(conv_num(st, &AARG(0))); + bl = map_id2bl(st->rid); + skill_status_change_end(bl, type, nullptr); +} + +static +void builtin_sc_check(ScriptState *st) +{ + dumb_ptr bl; + StatusChange type = StatusChange(conv_num(st, &AARG(0))); + bl = map_id2bl(st->rid); + + push_int(st->stack, skill_status_change_active(bl, type)); + +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_debugmes(ScriptState *st) +{ + RString mes = conv_str(st, &AARG(0)); + PRINTF("script debug : %d %d : %s\n"_fmt, + st->rid, st->oid, mes); +} + +/*========================================== + * ステータスリセット + *------------------------------------------ + */ +static +void builtin_resetstatus(ScriptState *st) +{ + dumb_ptr sd; + sd = script_rid2sd(st); + pc_resetstate(sd); +} + +/*========================================== + * 性別変換 + *------------------------------------------ + */ +static +void builtin_changesex(ScriptState *st) +{ + dumb_ptr sd = nullptr; + sd = script_rid2sd(st); + + chrif_char_ask_name(AccountId(), sd->status_key.name, 5, HumanTimeDiff()); // type: 5 - changesex + chrif_save(sd); +} + +/*========================================== + * RIDのアタッチ + *------------------------------------------ + */ +static +void builtin_attachrid(ScriptState *st) +{ + st->rid = wrap(conv_num(st, &AARG(0))); + push_int(st->stack, (map_id2sd(st->rid) != nullptr)); +} + +/*========================================== + * RIDのデタッチ + *------------------------------------------ + */ +static +void builtin_detachrid(ScriptState *st) +{ + st->rid = BlockId(); +} + +/*========================================== + * 存在チェック + *------------------------------------------ + */ +static +void builtin_isloggedin(ScriptState *st) +{ + push_int(st->stack, + map_id2sd(wrap(conv_num(st, &AARG(0)))) != nullptr); +} + +static +void builtin_setmapflag(ScriptState *st) +{ + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + int i = conv_num(st, &AARG(1)); + MapFlag mf = map_flag_from_int(i); + map_local *m = map_mapname2mapid(str); + if (m != nullptr) + { + m->flag.set(mf, 1); + } +} + +static +void builtin_removemapflag(ScriptState *st) +{ + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + int i = conv_num(st, &AARG(1)); + MapFlag mf = map_flag_from_int(i); + map_local *m = map_mapname2mapid(str); + if (m != nullptr) + { + m->flag.set(mf, 0); + } +} + +static +void builtin_getmapflag(ScriptState *st) +{ + int r = -1; + + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + int i = conv_num(st, &AARG(1)); + MapFlag mf = map_flag_from_int(i); + map_local *m = map_mapname2mapid(str); + if (m != nullptr) + { + r = m->flag.get(mf); + } + + push_int(st->stack, r); +} + +static +void builtin_pvpon(ScriptState *st) +{ + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + map_local *m = map_mapname2mapid(str); + if (m != nullptr && !m->flag.get(MapFlag::PVP) && !m->flag.get(MapFlag::NOPVP)) + { + m->flag.set(MapFlag::PVP, 1); + + if (battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris] + return; + + for (io::FD i : iter_fds()) + { + Session *s = get_session(i); + if (!s) + continue; + map_session_data *pl_sd = static_cast(s->session_data.get()); + if (pl_sd && pl_sd->state.auth) + { + if (m == pl_sd->bl_m && !pl_sd->pvp_timer) + { + pl_sd->pvp_timer = Timer(gettick() + 200_ms, + std::bind(pc_calc_pvprank_timer, ph::_1, ph::_2, + pl_sd->bl_id)); + pl_sd->pvp_rank = 0; + pl_sd->pvp_point = 5; + } + } + } + } + +} + +static +void builtin_pvpoff(ScriptState *st) +{ + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + map_local *m = map_mapname2mapid(str); + if (m != nullptr && m->flag.get(MapFlag::PVP) && m->flag.get(MapFlag::NOPVP)) + { + m->flag.set(MapFlag::PVP, 0); + + if (battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris] + return; + + for (io::FD i : iter_fds()) + { + Session *s = get_session(i); + if (!s) + continue; + map_session_data *pl_sd = static_cast(s->session_data.get()); + if (pl_sd && pl_sd->state.auth) + { + if (m == pl_sd->bl_m) + { + pl_sd->pvp_timer.cancel(); + } + } + } + } + +} + +/*========================================== + * NPCエモーション + *------------------------------------------ + */ + +static +void builtin_emotion(ScriptState *st) +{ + int type; + type = conv_num(st, &AARG(0)); + if (type < 0 || type > 100) + return; + clif_emotion(map_id2bl(st->oid), type); +} + +static +void builtin_mapwarp(ScriptState *st) // Added by RoVeRT +{ + int x, y; + int x0, y0, x1, y1; + + MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); + x0 = 0; + y0 = 0; + map_local *m = map_mapname2mapid(mapname); + x1 = m->xs; + y1 = m->ys; + MapName str = stringish(ZString(conv_str(st, &AARG(1)))); + x = conv_num(st, &AARG(2)); + y = conv_num(st, &AARG(3)); + + if (m == nullptr) + return; + + map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y), + m, + x0, y0, + x1, y1, + BL::PC); +} + +static +void builtin_cmdothernpc(ScriptState *st) // Added by RoVeRT +{ + NpcName npc = stringish(ZString(conv_str(st, &AARG(0)))); + ZString command = ZString(conv_str(st, &AARG(1))); + + npc_command(map_id2sd(st->rid), npc, command); +} + +static +void builtin_mobcount_sub(dumb_ptr bl, NpcEvent event, int *c) +{ + if (event == bl->is_mob()->npc_event) + (*c)++; +} + +static +void builtin_mobcount(ScriptState *st) // Added by RoVeRT +{ + int c = 0; + MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); + ZString event_ = ZString(conv_str(st, &AARG(1))); + NpcEvent event; + extract(event_, &event); + + map_local *m = map_mapname2mapid(mapname); + if (m == nullptr) + { + push_int(st->stack, -1); + return; + } + map_foreachinarea(std::bind(builtin_mobcount_sub, ph::_1, event, &c), + m, + 0, 0, + m->xs, m->ys, + BL::MOB); + + push_int(st->stack, (c - 1)); + +} + +static +void builtin_marriage(ScriptState *st) +{ + CharName partner = stringish(ZString(conv_str(st, &AARG(0)))); + dumb_ptr sd = script_rid2sd(st); + dumb_ptr p_sd = map_nick2sd(partner); + + if (sd == nullptr || p_sd == nullptr || pc_marriage(sd, p_sd) < 0) + { + push_int(st->stack, 0); + return; + } + push_int(st->stack, 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 == nullptr || pc_divorce(sd) < 0) + { + push_int(st->stack, 0); + return; + } + + push_int(st->stack, 1); +} + +/*========================================== + * IDからItem名 + *------------------------------------------ + */ +static +void builtin_getitemname(ScriptState *st) +{ + struct item_data *i_data; + struct script_data *data; + + data = &AARG(0); + get_val(st, data); + if (data->is()) + { + ZString name = ZString(conv_str(st, data)); + i_data = itemdb_searchname(name); + } + else + { + ItemNameId item_id = wrap(conv_num(st, data)); + i_data = itemdb_search(item_id); + } + + RString item_name; + if (i_data) + item_name = i_data->jname; + else + item_name = "Unknown Item"_s; + + push_str(st->stack, item_name); +} + +static +void builtin_getspellinvocation(ScriptState *st) +{ + RString name = conv_str(st, &AARG(0)); + + AString invocation = magic::magic_find_invocation(name); + if (!invocation) + invocation = "..."_s; + + push_str(st->stack, invocation); +} + +static +void builtin_getpartnerid2(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + + push_int(st->stack, unwrap(sd->status.partner_id)); +} + +/*========================================== + * PCの所持品情報読み取り + *------------------------------------------ + */ +static +void builtin_getinventorylist(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + int j = 0; + if (!sd) + return; + for (IOff0 i : IOff0::iter()) + { + if (sd->status.inventory[i].nameid + && sd->status.inventory[i].amount > 0) + { + pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_id"_s), j), + unwrap(sd->status.inventory[i].nameid)); + pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_amount"_s), j), + sd->status.inventory[i].amount); + pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_equip"_s), j), + static_cast(sd->status.inventory[i].equip)); + j++; + } + } + pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_count"_s)), j); +} + +static +void builtin_getactivatedpoolskilllist(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + SkillID pool_skills[MAX_SKILL_POOL]; + int skill_pool_size = skill_pool(sd, pool_skills); + int i, count = 0; + + if (!sd) + return; + + for (i = 0; i < skill_pool_size; i++) + { + SkillID skill_id = pool_skills[i]; + + if (sd->status.skill[skill_id].lv) + { + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count), + static_cast(skill_id)); + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count), + sd->status.skill[skill_id].lv); + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count), + static_cast(sd->status.skill[skill_id].flags)); + pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count), + skill_name(skill_id)); + ++count; + } + } + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count); + +} + +static +void builtin_getunactivatedpoolskilllist(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + int i, count = 0; + + if (!sd) + return; + + for (i = 0; i < skill_pool_skills_size; i++) + { + SkillID skill_id = skill_pool_skills[i]; + + if (sd->status.skill[skill_id].lv + && !bool(sd->status.skill[skill_id].flags & SkillFlags::POOL_ACTIVATED)) + { + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count), + static_cast(skill_id)); + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count), + sd->status.skill[skill_id].lv); + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count), + static_cast(sd->status.skill[skill_id].flags)); + pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count), + skill_name(skill_id)); + ++count; + } + } + pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count); +} + +static +void builtin_poolskill(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + SkillID skill_id = SkillID(conv_num(st, &AARG(0))); + + skill_pool_activate(sd, skill_id); + clif_skillinfoblock(sd); + +} + +static +void builtin_unpoolskill(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + SkillID skill_id = SkillID(conv_num(st, &AARG(0))); + + skill_pool_deactivate(sd, skill_id); + clif_skillinfoblock(sd); + +} + +/*========================================== + * NPCから発生するエフェクト + * misceffect(effect, [target]) + * + * effect The effect type/ID. + * target The player name or being ID on + * which to display the effect. If not + * specified, it attempts to default to + * the current NPC or invoking PC. + *------------------------------------------ + */ +static +void builtin_misceffect(ScriptState *st) +{ + int type; + BlockId id; + CharName name; + dumb_ptr bl = nullptr; + + type = conv_num(st, &AARG(0)); + + if (HARG(1)) + { + struct script_data *sdata = &AARG(1); + + get_val(st, sdata); + + if (sdata->is()) + name = stringish(ZString(conv_str(st, sdata))); + else + id = wrap(conv_num(st, sdata)); + } + + if (name.to__actual()) + { + dumb_ptr sd = map_nick2sd(name); + if (sd) + bl = sd; + } + else if (id) + bl = map_id2bl(id); + else if (st->oid) + bl = map_id2bl(st->oid); + else + { + dumb_ptr 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 == nullptr) + return; + + clif_specialeffect(bl, + conv_num(st, + &AARG(0)), + 0); + +} + +static +void builtin_specialeffect2(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + + if (sd == nullptr) + return; + + clif_specialeffect(sd, + conv_num(st, + &AARG(0)), + 0); + +} + +/*========================================== + * Nude [Valaris] + *------------------------------------------ + */ + +static +void builtin_nude(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + + if (sd == nullptr) + return; + + for (EQUIP i : EQUIPs) + { + IOff0 idx = sd->equip_index_maybe[i]; + if (idx.ok()) + pc_unequipitem(sd, idx, CalcStatus::LATER); + } + pc_calcstatus(sd, 0); + +} + +/*========================================== + * UnequipById [Freeyorp] + *------------------------------------------ + */ + +static +void builtin_unequipbyid(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + if (sd == nullptr) + return; + + EQUIP slot_id = EQUIP(conv_num(st, &AARG(0))); + + if (slot_id >= EQUIP() && slot_id < EQUIP::COUNT) + { + IOff0 idx = sd->equip_index_maybe[slot_id]; + if (idx.ok()) + pc_unequipitem(sd, idx, CalcStatus::LATER); + } + + pc_calcstatus(sd, 0); + +} + +/*========================================== + * gmcommand [MouseJstr] + * + * suggested on the forums... + *------------------------------------------ + */ + +static +void builtin_gmcommand(ScriptState *st) +{ + dumb_ptr sd; + + sd = script_rid2sd(st); + RString cmd = conv_str(st, &AARG(0)); + + is_atcommand(sd->sess, sd, cmd, GmLevel::from(-1U)); + +} + +/*========================================== + * npcwarp [remoitnane] + * Move NPC to a new position on the same map. + *------------------------------------------ + */ +static +void builtin_npcwarp(ScriptState *st) +{ + int x, y; + dumb_ptr nd = nullptr; + + x = conv_num(st, &AARG(0)); + y = conv_num(st, &AARG(1)); + NpcName npc = stringish(ZString(conv_str(st, &AARG(2)))); + nd = npc_name2id(npc); + + if (!nd) + { + PRINTF("builtin_npcwarp: no such npc: %s\n"_fmt, npc); + return; + } + + map_local *m = nd->bl_m; + + /* Crude sanity checks. */ + if (m == nullptr || !nd->bl_prev + || x < 0 || x > m->xs -1 + || y < 0 || y > m->ys - 1) + return; + + npc_enable(npc, 0); + map_delblock(nd); /* [Freeyorp] */ + nd->bl_x = x; + nd->bl_y = y; + map_addblock(nd); + npc_enable(npc, 1); + +} + +/*========================================== + * message [MouseJstr] + *------------------------------------------ + */ + +static +void builtin_message(ScriptState *st) +{ + CharName player = stringish(ZString(conv_str(st, &AARG(0)))); + ZString msg = ZString(conv_str(st, &AARG(1))); + + dumb_ptr pl_sd = map_nick2sd(player); + if (pl_sd == nullptr) + return; + clif_displaymessage(pl_sd->sess, msg); + +} + +/*========================================== + * npctalk (sends message to surrounding + * area) [Valaris] + *------------------------------------------ + */ + +static +void builtin_npctalk(ScriptState *st) +{ + dumb_ptr nd = map_id_is_npc(st->oid); + RString str = conv_str(st, &AARG(0)); + + if (nd) + { + clif_message(nd, str); + } +} + +/*========================================== + * getlook char info. getlook(arg) + *------------------------------------------ + */ +static +void builtin_getlook(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + + LOOK type = LOOK(conv_num(st, &AARG(0))); + int val = -1; + switch (type) + { + case LOOK::HAIR: //1 + val = sd->status.hair; + break; + case LOOK::WEAPON: //2 + val = static_cast(sd->status.weapon); + break; + case LOOK::HEAD_BOTTOM: //3 + val = unwrap(sd->status.head_bottom); + break; + case LOOK::HEAD_TOP: //4 + val = unwrap(sd->status.head_top); + break; + case LOOK::HEAD_MID: //5 + val = unwrap(sd->status.head_mid); + break; + case LOOK::HAIR_COLOR: //6 + val = sd->status.hair_color; + break; + case LOOK::CLOTHES_COLOR: //7 + val = sd->status.clothes_color; + break; + case LOOK::SHIELD: //8 + val = unwrap(sd->status.shield); + break; + case LOOK::SHOES: //9 + break; + } + + push_int(st->stack, val); +} + +/*========================================== + * get char save point. argument: 0- map name, 1- x, 2- y + *------------------------------------------ +*/ +static +void builtin_getsavepoint(ScriptState *st) +{ + int x, y, type; + dumb_ptr sd; + + sd = script_rid2sd(st); + + type = conv_num(st, &AARG(0)); + + x = sd->status.save_point.x; + y = sd->status.save_point.y; + switch (type) + { + case 0: + { + RString mapname = sd->status.save_point.map_; + push_str(st->stack, mapname); + } + break; + case 1: + push_int(st->stack, x); + break; + case 2: + push_int(st->stack, y); + break; + } +} + +/*========================================== + * areatimer + *------------------------------------------ + */ +static +void builtin_areatimer_sub(dumb_ptr bl, interval_t tick, NpcEvent event) +{ + pc_addeventtimer(bl->is_player(), tick, event); +} + +static +void builtin_areatimer(ScriptState *st) +{ + int x0, y0, x1, y1; + + MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); + x0 = conv_num(st, &AARG(1)); + y0 = conv_num(st, &AARG(2)); + x1 = conv_num(st, &AARG(3)); + y1 = conv_num(st, &AARG(4)); + interval_t tick = static_cast(conv_num(st, &AARG(5))); + ZString event_ = ZString(conv_str(st, &AARG(6))); + NpcEvent event; + extract(event_, &event); + + map_local *m = map_mapname2mapid(mapname); + if (m == nullptr) + return; + + map_foreachinarea(std::bind(builtin_areatimer_sub, ph::_1, tick, event), + m, + x0, y0, + x1, y1, + BL::PC); +} + +/*========================================== + * Check whether the PC is in the specified rectangle + *------------------------------------------ + */ +static +void builtin_isin(ScriptState *st) +{ + int x1, y1, x2, y2; + dumb_ptr sd = script_rid2sd(st); + + MapName str = stringish(ZString(conv_str(st, &AARG(0)))); + x1 = conv_num(st, &AARG(1)); + y1 = conv_num(st, &AARG(2)); + x2 = conv_num(st, &AARG(3)); + y2 = conv_num(st, &AARG(4)); + + if (!sd) + return; + + push_int(st->stack, + (sd->bl_x >= x1 && sd->bl_x <= x2) + && (sd->bl_y >= y1 && sd->bl_y <= y2) + && (str == sd->bl_m->name_)); +} + +// Trigger the shop on a (hopefully) nearby shop NPC +static +void builtin_shop(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + dumb_ptr nd; + + if (!sd) + return; + + NpcName name = stringish(ZString(conv_str(st, &AARG(0)))); + nd = npc_name2id(name); + if (!nd) + { + PRINTF("builtin_shop: no such npc: %s\n"_fmt, name); + return; + } + + builtin_close(st); + clif_npcbuysell(sd, nd->bl_id); +} + +/*========================================== + * Check whether the PC is dead + *------------------------------------------ + */ +static +void builtin_isdead(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + + push_int(st->stack, pc_isdead(sd)); +} + +/*======================================== + * Changes a NPC name, and sprite + *---------------------------------------- + */ +static +void builtin_fakenpcname(ScriptState *st) +{ + NpcName name = stringish(ZString(conv_str(st, &AARG(0)))); + NpcName newname = stringish(ZString(conv_str(st, &AARG(1)))); + Species newsprite = wrap(static_cast(conv_num(st, &AARG(2)))); + dumb_ptr nd = npc_name2id(name); + if (!nd) + { + PRINTF("builtin_fakenpcname: no such npc: %s\n"_fmt, name); + return; + } + nd->name = newname; + nd->npc_class = newsprite; + + // Refresh this npc + npc_enable(name, 0); + npc_enable(name, 1); + +} + +/*============================ + * Gets the PC's x pos + *---------------------------- + */ +static +void builtin_getx(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + + push_int(st->stack, sd->bl_x); +} + +/*============================ + * Gets the PC's y pos + *---------------------------- + */ +static +void builtin_gety(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + + push_int(st->stack, sd->bl_y); +} + +/* + * Get the PC's current map's name + */ +static +void builtin_getmap(ScriptState *st) +{ + dumb_ptr sd = script_rid2sd(st); + + push_str(st->stack, sd->bl_m->name_); +} + +static +void builtin_mapexit(ScriptState *) +{ + runflag = 0; +} + + +#define BUILTIN(func, args, ret) \ +{builtin_##func, #func ## _s, args, ret} + +BuiltinFunction builtin_functions[] = +{ + BUILTIN(mes, "s"_s, '\0'), + BUILTIN(goto, "L"_s, '\0'), + BUILTIN(callfunc, "F"_s, '\0'), + BUILTIN(callsub, "L"_s, '\0'), + BUILTIN(return, ""_s, '\0'), + BUILTIN(next, ""_s, '\0'), + BUILTIN(close, ""_s, '\0'), + BUILTIN(close2, ""_s, '\0'), + BUILTIN(menu, "sL**"_s, '\0'), + BUILTIN(rand, "i?"_s, 'i'), + BUILTIN(isat, "Mxy"_s, 'i'), + BUILTIN(warp, "Mxy"_s, '\0'), + BUILTIN(areawarp, "MxyxyMxy"_s, '\0'), + BUILTIN(heal, "ii"_s, '\0'), + BUILTIN(itemheal, "ii"_s, '\0'), + BUILTIN(percentheal, "ii"_s, '\0'), + BUILTIN(input, "N"_s, '\0'), + BUILTIN(if, "iF*"_s, '\0'), + BUILTIN(set, "Ne"_s, '\0'), + BUILTIN(setarray, "Ne*"_s, '\0'), + BUILTIN(cleararray, "Nei"_s, '\0'), + BUILTIN(getarraysize, "N"_s, 'i'), + BUILTIN(getelementofarray, "Ni"_s, '.'), + BUILTIN(setlook, "ii"_s, '\0'), + BUILTIN(countitem, "I"_s, 'i'), + BUILTIN(checkweight, "Ii"_s, 'i'), + BUILTIN(getitem, "Ii??"_s, '\0'), + BUILTIN(makeitem, "IiMxy"_s, '\0'), + BUILTIN(delitem, "Ii"_s, '\0'), + BUILTIN(readparam, "i?"_s, 'i'), + BUILTIN(getcharid, "i?"_s, 'i'), + BUILTIN(strcharinfo, "i"_s, 's'), + BUILTIN(getequipid, "i"_s, 'i'), + BUILTIN(getequipname, "i"_s, 's'), + BUILTIN(statusup2, "ii"_s, '\0'), + BUILTIN(bonus, "ii"_s, '\0'), + BUILTIN(bonus2, "iii"_s, '\0'), + BUILTIN(skill, "ii?"_s, '\0'), + BUILTIN(setskill, "ii"_s, '\0'), + BUILTIN(getskilllv, "i"_s, 'i'), + BUILTIN(getgmlevel, ""_s, 'i'), + BUILTIN(end, ""_s, '\0'), + BUILTIN(getopt2, ""_s, 'i'), + BUILTIN(setopt2, "i"_s, '\0'), + BUILTIN(savepoint, "Mxy"_s, '\0'), + BUILTIN(gettimetick, "i"_s, 'i'), + BUILTIN(gettime, "i"_s, 'i'), + BUILTIN(openstorage, ""_s, '\0'), + BUILTIN(getexp, "ii"_s, '\0'), + BUILTIN(monster, "Mxysmi?"_s, '\0'), + BUILTIN(areamonster, "Mxyxysmi?"_s, '\0'), + BUILTIN(killmonster, "ME"_s, '\0'), + BUILTIN(killmonsterall, "M"_s, '\0'), + BUILTIN(donpcevent, "E"_s, '\0'), + BUILTIN(addtimer, "tE"_s, '\0'), + BUILTIN(initnpctimer, ""_s, '\0'), + BUILTIN(startnpctimer, "?"_s, '\0'), + BUILTIN(stopnpctimer, ""_s, '\0'), + BUILTIN(getnpctimer, "i"_s, 'i'), + BUILTIN(setnpctimer, "i"_s, '\0'), + BUILTIN(announce, "si"_s, '\0'), + BUILTIN(mapannounce, "Msi"_s, '\0'), + BUILTIN(getusers, "i"_s, 'i'), + BUILTIN(getmapusers, "M"_s, 'i'), + BUILTIN(getareausers, "Mxyxy?"_s, 'i'), + BUILTIN(getareadropitem, "Mxyxyi?"_s, 'i'), + BUILTIN(enablenpc, "s"_s, '\0'), + BUILTIN(disablenpc, "s"_s, '\0'), + BUILTIN(sc_start, "iTi?"_s, '\0'), + BUILTIN(sc_end, "i"_s, '\0'), + BUILTIN(sc_check, "i"_s, 'i'), + BUILTIN(debugmes, "s"_s, '\0'), + BUILTIN(resetstatus, ""_s, '\0'), + BUILTIN(changesex, ""_s, '\0'), + BUILTIN(attachrid, "i"_s, 'i'), + BUILTIN(detachrid, ""_s, '\0'), + BUILTIN(isloggedin, "i"_s, 'i'), + BUILTIN(setmapflag, "Mi"_s, '\0'), + BUILTIN(removemapflag, "Mi"_s, '\0'), + BUILTIN(getmapflag, "Mi"_s, 'i'), + BUILTIN(pvpon, "M"_s, '\0'), + BUILTIN(pvpoff, "M"_s, '\0'), + BUILTIN(emotion, "i"_s, '\0'), + BUILTIN(mapwarp, "MMxy"_s, '\0'), + BUILTIN(cmdothernpc, "ss"_s, '\0'), + BUILTIN(mobcount, "ME"_s, 'i'), + BUILTIN(marriage, "P"_s, 'i'), + BUILTIN(divorce, ""_s, 'i'), + BUILTIN(getitemname, "I"_s, 's'), + BUILTIN(getspellinvocation, "s"_s, 's'), + BUILTIN(getpartnerid2, ""_s, 'i'), + BUILTIN(getinventorylist, ""_s, '\0'), + BUILTIN(getactivatedpoolskilllist, ""_s, '\0'), + BUILTIN(getunactivatedpoolskilllist, ""_s, '\0'), + BUILTIN(poolskill, "i"_s, '\0'), + BUILTIN(unpoolskill, "i"_s, '\0'), + BUILTIN(misceffect, "i?"_s, '\0'), + BUILTIN(specialeffect, "i"_s, '\0'), + BUILTIN(specialeffect2, "i"_s, '\0'), + BUILTIN(nude, ""_s, '\0'), + BUILTIN(unequipbyid, "i"_s, '\0'), + BUILTIN(gmcommand, "s"_s, '\0'), + BUILTIN(npcwarp, "xys"_s, '\0'), + BUILTIN(message, "Ps"_s, '\0'), + BUILTIN(npctalk, "s"_s, '\0'), + BUILTIN(getlook, "i"_s, 'i'), + BUILTIN(getsavepoint, "i"_s, '.'), + BUILTIN(areatimer, "MxyxytE"_s, '\0'), + BUILTIN(isin, "Mxyxy"_s, 'i'), + BUILTIN(shop, "s"_s, '\0'), + BUILTIN(isdead, ""_s, 'i'), + BUILTIN(fakenpcname, "ssi"_s, '\0'), + BUILTIN(getx, ""_s, 'i'), + BUILTIN(gety, ""_s, 'i'), + BUILTIN(getmap, ""_s, 's'), + BUILTIN(mapexit, ""_s, '\0'), + {nullptr, ""_s, ""_s, '\0'}, +}; +} // namespace tmwa diff --git a/src/map/script-fun.hpp b/src/map/script-fun.hpp new file mode 100644 index 0000000..e9f64f9 --- /dev/null +++ b/src/map/script-fun.hpp @@ -0,0 +1,38 @@ +#pragma once +// script-fun.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "fwd.hpp" + +#include "../strings/literal.hpp" + +namespace tmwa +{ +struct BuiltinFunction +{ + void (*func)(ScriptState *); + LString name; + LString arg; + char ret; +}; + +extern BuiltinFunction builtin_functions[]; +} // namespace tmwa diff --git a/src/map/script-parse-internal.hpp b/src/map/script-parse-internal.hpp new file mode 100644 index 0000000..f01317a --- /dev/null +++ b/src/map/script-parse-internal.hpp @@ -0,0 +1,69 @@ +#pragma once +// script-parse-internal.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "script-parse.hpp" +#include "fwd.hpp" + +#include "../strings/rstring.hpp" + + +namespace tmwa +{ +enum class StringCode : uint8_t +{ + NOP, POS, INT, PARAM, FUNC, + VARIABLE, +}; +enum class ByteCode : uint8_t +{ + // types and specials + // Note that 'INT' is synthetic, and does not occur in the data stream + NOP, POS, INT, PARAM, FUNC, STR, ARG, + VARIABLE, EOL, + + // unary and binary operators + LOR, LAND, LE, LT, GE, GT, EQ, NE, + XOR, OR, AND, ADD, SUB, MUL, DIV, MOD, + NEG, LNOT, NOT, R_SHIFT, L_SHIFT, + + // additions + // needed because FUNC is used for the actual call + FUNC_REF, +}; + +struct str_data_t +{ + StringCode type; + RString strs; + int backpatch; + int label_; + int val; +}; + +extern +Map str_datam; +extern +InternPool variable_names; + +str_data_t *search_strp(XString p); +str_data_t *add_strp(XString p); +} // namespace tmwa diff --git a/src/map/script-parse.cpp b/src/map/script-parse.cpp new file mode 100644 index 0000000..0c93477 --- /dev/null +++ b/src/map/script-parse.cpp @@ -0,0 +1,840 @@ +#include "script-parse-internal.hpp" +// script-parse.cpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011 Chuck Miller +// Copyright © 2011-2014 Ben Longbons +// Copyright © 2013 wushin +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include + +#include "../generic/db.hpp" +#include "../generic/intern-pool.hpp" + +#include "../strings/rstring.hpp" + +#include "../io/cxxstdio.hpp" +#include "../io/cxxstdio_enums.hpp" + +#include "map.t.hpp" +#include "script-buffer.hpp" +#include "script-call.hpp" +#include "script-fun.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +constexpr bool DEBUG_DISP = false; + +class ScriptBuffer +{ + typedef ZString::iterator ZSit; + + std::vector script_buf; +public: + // construction methods + void add_scriptc(ByteCode a); + void add_scriptb(uint8_t a); + void add_scripti(uint32_t a); + void add_scriptl(str_data_t *a); + void set_label(str_data_t *ld, int pos_); + ZSit parse_simpleexpr(ZSit p); + ZSit parse_subexpr(ZSit p, int limit); + ZSit parse_expr(ZSit p); + ZSit parse_line(ZSit p, bool *canstep); + void parse_script(ZString src, int line, bool implicit_end); + + // consumption methods + ByteCode operator[](size_t i) const { return script_buf[i]; } + ZString get_str(size_t i) const + { + return ZString(strings::really_construct_from_a_pointer, reinterpret_cast(&script_buf[i]), nullptr); + } +}; +} // namespace tmwa + +void std::default_delete::operator()(const tmwa::ScriptBuffer *sd) +{ + really_delete1 sd; +} + +namespace tmwa +{ +// implemented for script-call.hpp because reasons +ByteCode ScriptPointer::peek() const { return (*code)[pos]; } +ByteCode ScriptPointer::pop() { return (*code)[pos++]; } +ZString ScriptPointer::pops() +{ + ZString rv = code->get_str(pos); + pos += rv.size(); + ++pos; + return rv; +} + +Map str_datam; +static +str_data_t LABEL_NEXTLINE_; + +Map scriptlabel_db; +static +std::set probable_labels; +UPMap userfunc_db; + +static +struct ScriptConfigParse +{ + static const + int warn_func_no_comma = 1; + static const + int warn_cmd_no_comma = 1; + static const + int warn_func_mismatch_paramnum = 1; + static const + int warn_cmd_mismatch_paramnum = 1; +} script_config; + +static +int parse_cmd_if = 0; +static +str_data_t *parse_cmdp; + +InternPool variable_names; + +str_data_t *search_strp(XString p) +{ + return str_datam.search(p); +} + +str_data_t *add_strp(XString p) +{ + if (str_data_t *rv = search_strp(p)) + return rv; + + RString p2 = p; + str_data_t *datum = str_datam.init(p2); + datum->type = StringCode::NOP; + datum->strs = p2; + datum->backpatch = -1; + datum->label_ = -1; + return datum; +} + +/*========================================== + * スクリプトバッファに1バイト書き込む + *------------------------------------------ + */ +void ScriptBuffer::add_scriptc(ByteCode a) +{ + script_buf.push_back(a); +} + +/*========================================== + * スクリプトバッファにデータタイプを書き込む + *------------------------------------------ + */ +void ScriptBuffer::add_scriptb(uint8_t a) +{ + add_scriptc(static_cast(a)); +} + +/*========================================== + * スクリプトバッファに整数を書き込む + *------------------------------------------ + */ +void ScriptBuffer::add_scripti(uint32_t a) +{ + while (a >= 0x40) + { + add_scriptb(a | 0xc0); + a = (a - 0x40) >> 6; + } + add_scriptb(a | 0x80); +} + +/*========================================== + * スクリプトバッファにラベル/変数/関数を書き込む + *------------------------------------------ + */ +// 最大16Mまで +void ScriptBuffer::add_scriptl(str_data_t *ld) +{ + int backpatch = ld->backpatch; + + switch (ld->type) + { + case StringCode::POS: + add_scriptc(ByteCode::POS); + add_scriptb(static_cast(ld->label_)); + add_scriptb(static_cast(ld->label_ >> 8)); + add_scriptb(static_cast(ld->label_ >> 16)); + break; + case StringCode::NOP: + // need to set backpatch, because it might become a label later + add_scriptc(ByteCode::VARIABLE); + ld->backpatch = script_buf.size(); + add_scriptb(static_cast(backpatch)); + add_scriptb(static_cast(backpatch >> 8)); + add_scriptb(static_cast(backpatch >> 16)); + break; + case StringCode::INT: + add_scripti(ld->val); + break; + case StringCode::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 StringCode::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 = StringCode::POS; + ld->label_ = pos_; + for (int i = ld->backpatch; i >= 0 && i != 0x00ffffff; i = next) + { + next = 0; + // woot! no longer endian-dependent! + next |= static_cast(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 +ZString::iterator skip_space(ZString::iterator p) +{ + while (1) + { + while (isspace(*p)) + p++; + if (p[0] == '/' && p[1] == '/') + { + while (*p && *p != '\n') + p++; + } + else if (p[0] == '/' && p[1] == '*') + { + p++; + while (*p && (p[-1] != '*' || p[0] != '/')) + p++; + if (*p) + p++; + } + else + break; + } + return p; +} + +/*========================================== + * 1単語スキップ + *------------------------------------------ + */ +static +ZString::iterator skip_word(ZString::iterator p) +{ + // prefix + if (*p == '$') + p++; // MAP鯖内共有変数用 + if (*p == '@') + p++; // 一時的変数用(like weiss) + if (*p == '#') + p++; // account変数用 + if (*p == '#') + p++; // ワールドaccount変数用 + + while (isalnum(*p) || *p == '_') + p++; + + // postfix + if (*p == '$') + p++; // 文字列変数 + + return p; +} + +// TODO: replace this whole mess with some sort of input stream that works +// a line at a time. +static +ZString startptr; +static +int startline; + +int script_errors = 0; +/*========================================== + * エラーメッセージ出力 + *------------------------------------------ + */ +static +void disp_error_message(ZString mes, ZString::iterator pos_) +{ + script_errors++; + + assert (startptr.begin() <= pos_ && pos_ <= startptr.end()); + + int line; + ZString::iterator p; + + for (line = startline, p = startptr.begin(); p != startptr.end(); line++) + { + ZString::iterator linestart = p; + ZString::iterator lineend = std::find(p, startptr.end(), '\n'); + if (pos_ < lineend) + { + PRINTF("\n%s\nline %d : "_fmt, mes, line); + for (int i = 0; linestart + i != lineend; i++) + { + if (linestart + i != pos_) + PRINTF("%c"_fmt, linestart[i]); + else + PRINTF("\'%c\'"_fmt, linestart[i]); + } + PRINTF("\a\n"_fmt); + return; + } + p = lineend + 1; + } +} + +/*========================================== + * 項の解析 + *------------------------------------------ + */ +ZString::iterator ScriptBuffer::parse_simpleexpr(ZString::iterator p) +{ + p = skip_space(p); + + if (*p == ';' || *p == ',') + { + disp_error_message("unexpected expr end"_s, p); + exit(1); + } + if (*p == '(') + { + + p = parse_subexpr(p + 1, -1); + p = skip_space(p); + if ((*p++) != ')') + { + disp_error_message("unmatch ')'"_s, p); + exit(1); + } + } + else if (isdigit(*p) || ((*p == '-' || *p == '+') && isdigit(p[1]))) + { + char *np; + int i = strtoul(&*p, &np, 0); + add_scripti(i); + p += np - &*p; + } + else if (*p == '"') + { + add_scriptc(ByteCode::STR); + p++; + while (*p && *p != '"') + { + if (*p == '\\') + p++; + else if (*p == '\n') + { + disp_error_message("unexpected newline @ string"_s, p); + exit(1); + } + add_scriptb(*p++); + } + if (!*p) + { + disp_error_message("unexpected eof @ string"_s, p); + exit(1); + } + add_scriptb(0); + p++; //'"' + } + else + { + // label , register , function etc + ZString::iterator p2 = skip_word(p); + if (p2 == p) + { + disp_error_message("unexpected character"_s, p); + exit(1); + } + XString word(&*p, &*p2, nullptr); + if (word.startswith("On"_s) || word.startswith("L_"_s) || word.startswith("S_"_s)) + probable_labels.insert(stringish(word)); + if (parse_cmd_if && (word == "callsub"_s || word == "callfunc"_s || word == "return"_s)) + { + disp_error_message("Sorry, callsub/callfunc/return have never worked properly in an if statement."_s, p); + } + str_data_t *ld = add_strp(word); + + parse_cmdp = ld; // warn_*_mismatch_paramnumのために必要 + // why not just check l->str == "if"_s or std::string(p, p2) == "if"_s? + if (ld == search_strp("if"_s)) // warn_cmd_no_commaのために必要 + parse_cmd_if++; + p = p2; + + if (ld->type != StringCode::FUNC && *p == '[') + { + // array(name[i] => getelementofarray(name,i) ) + add_scriptl(search_strp("getelementofarray"_s)); + add_scriptc(ByteCode::ARG); + add_scriptl(ld); + p = parse_subexpr(p + 1, -1); + p = skip_space(p); + if (*p != ']') + { + disp_error_message("unmatch ']'"_s, p); + exit(1); + } + p++; + add_scriptc(ByteCode::FUNC); + } + else + add_scriptl(ld); + + } + + return p; +} + +/*========================================== + * 式の解析 + *------------------------------------------ + */ +ZString::iterator ScriptBuffer::parse_subexpr(ZString::iterator p, int limit) +{ + ByteCode op; + int opl, len; + + p = skip_space(p); + + if (*p == '-') + { + ZString::iterator tmpp = skip_space(p + 1); + if (*tmpp == ';' || *tmpp == ',') + { + --script_errors; disp_error_message("deprecated: implicit 'next statement' label"_s, p); + add_scriptl(&LABEL_NEXTLINE_); + p++; + return p; + } + } + ZString::iterator tmpp = p; + if ((op = ByteCode::NEG, *p == '-') || (op = ByteCode::LNOT, *p == '!') + || (op = ByteCode::NOT, *p == '~')) + { + p = parse_subexpr(p + 1, 100); + add_scriptc(op); + } + else + p = parse_simpleexpr(p); + p = skip_space(p); + while (((op = ByteCode::ADD, opl = 6, len = 1, *p == '+') || + (op = ByteCode::SUB, opl = 6, len = 1, *p == '-') || + (op = ByteCode::MUL, opl = 7, len = 1, *p == '*') || + (op = ByteCode::DIV, opl = 7, len = 1, *p == '/') || + (op = ByteCode::MOD, opl = 7, len = 1, *p == '%') || + (op = ByteCode::FUNC, opl = 8, len = 1, *p == '(') || + (op = ByteCode::LAND, opl = 1, len = 2, *p == '&' && p[1] == '&') || + (op = ByteCode::AND, opl = 5, len = 1, *p == '&') || + (op = ByteCode::LOR, opl = 0, len = 2, *p == '|' && p[1] == '|') || + (op = ByteCode::OR, opl = 4, len = 1, *p == '|') || + (op = ByteCode::XOR, opl = 3, len = 1, *p == '^') || + (op = ByteCode::EQ, opl = 2, len = 2, *p == '=' && p[1] == '=') || + (op = ByteCode::NE, opl = 2, len = 2, *p == '!' && p[1] == '=') || + (op = ByteCode::R_SHIFT, opl = 5, len = 2, *p == '>' && p[1] == '>') || + (op = ByteCode::GE, opl = 2, len = 2, *p == '>' && p[1] == '=') || + (op = ByteCode::GT, opl = 2, len = 1, *p == '>') || + (op = ByteCode::L_SHIFT, opl = 5, len = 2, *p == '<' && p[1] == '<') || + (op = ByteCode::LE, opl = 2, len = 2, *p == '<' && p[1] == '=') || + (op = ByteCode::LT, opl = 2, len = 1, *p == '<')) && opl > limit) + { + p += len; + if (op == ByteCode::FUNC) + { + int i = 0; + str_data_t *funcp = parse_cmdp; + ZString::iterator plist[128]; + + if (funcp->type != StringCode::FUNC) + { + disp_error_message("expect function"_s, tmpp); + exit(0); + } + + add_scriptc(ByteCode::ARG); + while (*p && *p != ')' && i < 128) + { + plist[i] = p; + p = parse_subexpr(p, -1); + p = skip_space(p); + if (*p == ',') + p++; + else if (*p != ')' && script_config.warn_func_no_comma) + { + disp_error_message("expect ',' or ')' at func params"_s, + p); + } + p = skip_space(p); + i++; + } + plist[i] = p; + if (*p != ')') + { + disp_error_message("func request '(' ')'"_s, p); + exit(1); + } + p++; + + if (funcp->type == StringCode::FUNC + && script_config.warn_func_mismatch_paramnum) + { + ZString arg = builtin_functions[funcp->val].arg; + int j = 0; + // TODO handle ? and multiple * correctly + for (j = 0; arg[j]; j++) + if (arg[j] == '*' || arg[j] == '?') + break; + if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j)) + { + disp_error_message("illegal number of parameters"_s, + plist[std::min(i, j)]); + } + if (!builtin_functions[funcp->val].ret) + { + disp_error_message("statement in function context"_s, tmpp); + } + } + } + else // not op == ByteCode::FUNC + { + p = parse_subexpr(p, opl); + } + add_scriptc(op); + p = skip_space(p); + } + return p; /* return first untreated operator */ +} + +/*========================================== + * 式の評価 + *------------------------------------------ + */ +ZString::iterator ScriptBuffer::parse_expr(ZString::iterator p) +{ + switch (*p) + { + case ')': + case ';': + case ':': + case '[': + case ']': + case '}': + disp_error_message("unexpected char"_s, p); + exit(1); + } + p = parse_subexpr(p, -1); + return p; +} + +/*========================================== + * 行の解析 + *------------------------------------------ + */ +ZString::iterator ScriptBuffer::parse_line(ZString::iterator p, bool *can_step) +{ + int i = 0; + ZString::iterator plist[128]; + + p = skip_space(p); + if (*p == ';') + return p; + + parse_cmd_if = 0; // warn_cmd_no_commaのために必要 + + // 最初は関数名 + ZString::iterator p2 = p; + p = parse_simpleexpr(p); + p = skip_space(p); + + str_data_t *cmd = parse_cmdp; + if (cmd->type != StringCode::FUNC) + { + disp_error_message("expect command"_s, p2); + } + + { + // TODO should be LString, but no heterogenous lookup yet + static + std::set terminators = + { + "goto"_s, + "return"_s, + "close"_s, + "menu"_s, + "end"_s, + "mapexit"_s, + "shop"_s, + }; + *can_step = terminators.count(cmd->strs) == 0; + } + + add_scriptc(ByteCode::ARG); + while (*p && *p != ';' && i < 128) + { + plist[i] = p; + + p = parse_expr(p); + p = skip_space(p); + // 引数区切りの,処理 + if (*p == ',') + p++; + else if (*p != ';' && script_config.warn_cmd_no_comma + && parse_cmd_if * 2 <= i) + { + disp_error_message("expect ',' or ';' at cmd params"_s, p); + } + p = skip_space(p); + i++; + } + plist[i] = p; + if (*(p++) != ';') + { + disp_error_message("need ';'"_s, p); + exit(1); + } + add_scriptc(ByteCode::FUNC); + + if (cmd->type == StringCode::FUNC + && script_config.warn_cmd_mismatch_paramnum) + { + ZString arg = builtin_functions[cmd->val].arg; + int j = 0; + // TODO see above + for (j = 0; arg[j]; j++) + if (arg[j] == '*' || arg[j] == '?') + break; + if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j)) + { + disp_error_message("illegal number of parameters"_s, + plist[std::min(i, j)]); + } + if (builtin_functions[cmd->val].ret) + { + disp_error_message("function in statement context"_s, p2); + } + } + + return p; +} + +/*========================================== + * 組み込み関数の追加 + *------------------------------------------ + */ +static +void add_builtin_functions(void) +{ + for (int i = 0; builtin_functions[i].func; i++) + { + str_data_t *n = add_strp(builtin_functions[i].name); + n->type = StringCode::FUNC; + n->val = i; + } +} + +std::unique_ptr parse_script(ZString src, int line, bool implicit_end) +{ + auto script_buf = make_unique(); + script_buf->parse_script(src, line, implicit_end); + return std::move(script_buf); +} + +/*========================================== + * スクリプトの解析 + *------------------------------------------ + */ +void ScriptBuffer::parse_script(ZString src, int line, bool implicit_end) +{ + static int first = 1; + + if (first) + { + add_builtin_functions(); + } + first = 0; + LABEL_NEXTLINE_.type = StringCode::NOP; + LABEL_NEXTLINE_.backpatch = -1; + LABEL_NEXTLINE_.label_ = -1; + for (auto& pair : str_datam) + { + str_data_t& dit = pair.second; + if (dit.type == StringCode::POS || dit.type == StringCode::VARIABLE) + { + dit.type = StringCode::NOP; + dit.backpatch = -1; + dit.label_ = -1; + } + } + + // 外部用label dbの初期化 + scriptlabel_db.clear(); + + // for error message + startptr = src; + startline = line; + + bool can_step = true; + + ZString::iterator p = src.begin(); + p = skip_space(p); + if (*p != '{') + { + disp_error_message("not found '{'"_s, p); + abort(); + } + for (p++; *p && *p != '}';) + { + p = skip_space(p); + if (*skip_space(skip_word(p)) == ':') + { + if (can_step) + { + --script_errors; disp_error_message("deprecated: implicit fallthrough"_s, p); + } + can_step = true; + + ZString::iterator tmpp = skip_word(p); + XString str(&*p, &*tmpp, nullptr); + str_data_t *ld = add_strp(str); + bool e1 = ld->type != StringCode::NOP; + bool e2 = ld->type == StringCode::POS; + bool e3 = ld->label_ != -1; + assert (e1 == e2 && e2 == e3); + if (e3) + { + disp_error_message("dup label "_s, p); + exit(1); + } + set_label(ld, script_buf.size()); + scriptlabel_db.insert(stringish(str), script_buf.size()); + p = tmpp + 1; + continue; + } + + if (!can_step) + { + --script_errors; disp_error_message("deprecated: unreachable statement"_s, p); + } + // 他は全部一緒くた + p = parse_line(p, &can_step); + p = skip_space(p); + add_scriptc(ByteCode::EOL); + + set_label(&LABEL_NEXTLINE_, script_buf.size()); + LABEL_NEXTLINE_.type = StringCode::NOP; + LABEL_NEXTLINE_.backpatch = -1; + LABEL_NEXTLINE_.label_ = -1; + } + + if (can_step && !implicit_end) + { + --script_errors; disp_error_message("deprecated: implicit end"_s, p); + } + add_scriptc(ByteCode::NOP); + + // resolve the unknown labels + for (auto& pair : str_datam) + { + str_data_t& sit = pair.second; + if (sit.type == StringCode::NOP) + { + sit.type = StringCode::VARIABLE; + sit.label_ = 0; // anything but -1. Shouldn't matter, but helps asserts. + size_t pool_index = variable_names.intern(sit.strs); + for (int next, j = sit.backpatch; j >= 0 && j != 0x00ffffff; j = next) + { + next = 0; + next |= static_cast(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); + } + } + } + + for (const auto& pair : scriptlabel_db) + { + ScriptLabel key = pair.first; + if (key.startswith("On"_s)) + continue; + if (!(key.startswith("L_"_s) || key.startswith("S_"_s))) + PRINTF("Warning: ugly label: %s\n"_fmt, key); + else if (!probable_labels.count(key)) + PRINTF("Warning: unused label: %s\n"_fmt, key); + } + for (ScriptLabel used : probable_labels) + { + if (!scriptlabel_db.search(used)) + PRINTF("Warning: no such label: %s\n"_fmt, used); + } + probable_labels.clear(); + + if (!DEBUG_DISP) + return; + for (size_t i = 0; i < script_buf.size(); i++) + { + if ((i & 15) == 0) + PRINTF("%04zx : "_fmt, i); + PRINTF("%02x "_fmt, script_buf[i]); + if ((i & 15) == 15) + PRINTF("\n"_fmt); + } + PRINTF("\n"_fmt); +} +} // namespace tmwa diff --git a/src/map/script-parse.hpp b/src/map/script-parse.hpp new file mode 100644 index 0000000..6f536f8 --- /dev/null +++ b/src/map/script-parse.hpp @@ -0,0 +1,42 @@ +#pragma once +// script-parse.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "fwd.hpp" + +#include + +#include "../strings/fwd.hpp" + +#include "../generic/fwd.hpp" + + +namespace tmwa +{ +std::unique_ptr parse_script(ZString, int, bool implicit_end); + +extern +Map scriptlabel_db; +extern +UPMap userfunc_db; + +extern int script_errors; +} // namespace tmwa diff --git a/src/map/script-persist.cpp b/src/map/script-persist.cpp new file mode 100644 index 0000000..9397d42 --- /dev/null +++ b/src/map/script-persist.cpp @@ -0,0 +1,30 @@ +#include "script-persist.hpp" +// script-persist.cpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011 Chuck Miller +// Copyright © 2011-2014 Ben Longbons +// Copyright © 2013 wushin +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../poison.hpp" + + +namespace tmwa +{ +} // namespace tmwa diff --git a/src/map/script-persist.hpp b/src/map/script-persist.hpp new file mode 100644 index 0000000..35cd2d0 --- /dev/null +++ b/src/map/script-persist.hpp @@ -0,0 +1,119 @@ +#pragma once +// script-persist.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "fwd.hpp" + +#include "../strings/rstring.hpp" + +#include "../sexpr/variant.hpp" + + +namespace tmwa +{ +class SIR +{ + uint32_t impl; + SIR(SP v) + : impl(static_cast(v)) + {} + SIR(unsigned v, uint8_t i) + : impl((i << 24) | v) + {} +public: + SIR() : impl() {} + + unsigned base() const { return impl & 0x00ffffff; } + uint8_t index() const { return impl >> 24; } + SIR iplus(uint8_t i) const { return SIR(base(), index() + i); } + static SIR from(unsigned v, uint8_t i=0) { return SIR(v, i); } + + SP sp() const { return static_cast(impl); } + static SIR from(SP v) { return SIR(v); } + + friend bool operator == (SIR l, SIR r) { return l.impl == r.impl; } + friend bool operator < (SIR l, SIR r) { return l.impl < r.impl; } +}; + +struct ScriptDataPos +{ + int numi; +}; +struct ScriptDataInt +{ + int numi; +}; +struct ScriptDataParam +{ + SIR reg; +}; +struct ScriptDataStr +{ + RString str; +}; +struct ScriptDataArg +{ + int numi; +}; +struct ScriptDataVariable +{ + SIR reg; +}; +struct ScriptDataRetInfo +{ + // Not a ScriptPointer - pos is stored in a separate slot, + // to avoid exploding the struct for everyone. + const ScriptBuffer *script; +}; +struct ScriptDataFuncRef +{ + int numi; +}; + +using ScriptDataVariantBase = Variant< + ScriptDataPos, + ScriptDataInt, + ScriptDataParam, + ScriptDataStr, + ScriptDataArg, + ScriptDataVariable, + ScriptDataRetInfo, + ScriptDataFuncRef +>; +struct script_data : ScriptDataVariantBase +{ + script_data() = delete; + // TODO see if I can delete the copy ctor/assign instead of defaulting + script_data(script_data&&) = default; + script_data(const script_data&) = default /*delete*/; + script_data& operator = (script_data&&) = default; + script_data& operator = (const script_data&) = default /*delete*/; + + script_data(ScriptDataPos v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataInt v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataParam v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataStr v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataArg v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataVariable v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataRetInfo v) : ScriptDataVariantBase(std::move(v)) {} + script_data(ScriptDataFuncRef v) : ScriptDataVariantBase(std::move(v)) {} +}; +} // namespace tmwa diff --git a/src/map/script-persist.py b/src/map/script-persist.py new file mode 100644 index 0000000..a5010cd --- /dev/null +++ b/src/map/script-persist.py @@ -0,0 +1,25 @@ +class script_data(object): + enabled = True + + test_extra = ''' + using tmwa::operator "" _s; + ''' + + tests = [ + ('tmwa::script_data(tmwa::ScriptDataPos{42})', + '{> = {(tmwa::ScriptDataPos) = {numi = 42}}, }'), + ('tmwa::script_data(tmwa::ScriptDataInt{123})', + '{> = {(tmwa::ScriptDataInt) = {numi = 123}}, }'), + ('tmwa::script_data(tmwa::ScriptDataParam{tmwa::SIR()})', + '{> = {(tmwa::ScriptDataParam) = {reg = {impl = 0}}}, }'), + ('tmwa::script_data(tmwa::ScriptDataStr{"Hello"_s})', + '{> = {(tmwa::ScriptDataStr) = {str = "Hello"}}, }'), + ('tmwa::script_data(tmwa::ScriptDataArg{0})', + '{> = {(tmwa::ScriptDataArg) = {numi = 0}}, }'), + ('tmwa::script_data(tmwa::ScriptDataVariable{tmwa::SIR()})', + '{> = {(tmwa::ScriptDataVariable) = {reg = {impl = 0}}}, }'), + ('tmwa::script_data(tmwa::ScriptDataRetInfo{static_cast(nullptr)})', + '{> = {(tmwa::ScriptDataRetInfo) = {script = 0x0}}, }'), + ('tmwa::script_data(tmwa::ScriptDataFuncRef{404})', + '{> = {(tmwa::ScriptDataFuncRef) = {numi = 404}}, }'), + ] diff --git a/src/map/script-startup-internal.hpp b/src/map/script-startup-internal.hpp new file mode 100644 index 0000000..e4d6a8f --- /dev/null +++ b/src/map/script-startup-internal.hpp @@ -0,0 +1,44 @@ +#pragma once +// script-startup-internal.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "script-startup.hpp" +#include "fwd.hpp" + +#include "../generic/fwd.hpp" + +#include "../strings/fwd.hpp" + +#include "script-persist.hpp" + + +namespace tmwa +{ +extern +DMap mapreg_db; +extern +Map mapregstr_db; +extern +int mapreg_dirty; + +void mapreg_setreg(SIR reg, int val); +void mapreg_setregstr(SIR reg, XString str); +} // namespace tmwa diff --git a/src/map/script-startup.cpp b/src/map/script-startup.cpp new file mode 100644 index 0000000..1aa63aa --- /dev/null +++ b/src/map/script-startup.cpp @@ -0,0 +1,265 @@ +#include "script-startup-internal.hpp" +// script-startup.cpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011 Chuck Miller +// Copyright © 2011-2014 Ben Longbons +// Copyright © 2013 wushin +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "../strings/zstring.hpp" + +#include "../generic/db.hpp" +#include "../generic/intern-pool.hpp" + +#include "../io/cxxstdio.hpp" +#include "../io/read.hpp" +#include "../io/lock.hpp" + +#include "../net/timer.hpp" + +#include "../mmo/extract.hpp" + +#include "map.hpp" +#include "script-parse-internal.hpp" +#include "script-persist.hpp" + +#include "../poison.hpp" + + +namespace tmwa +{ +DMap mapreg_db; +Map mapregstr_db; +int mapreg_dirty = -1; +AString mapreg_txt = "save/mapreg.txt"_s; +constexpr std::chrono::milliseconds MAPREG_AUTOSAVE_INTERVAL = 10_s; + +bool read_constdb(ZString filename) +{ + io::ReadFile in(filename); + if (!in.is_open()) + { + PRINTF("can't read %s\n"_fmt, filename); + return false; + } + + bool rv = true; + AString line_; + while (in.getline(line_)) + { + // is_comment only works for whole-line comments + // that could change once the Z dependency is dropped ... + LString comment = "//"_s; + XString line = line_.xislice_h(std::search(line_.begin(), line_.end(), comment.begin(), comment.end())).rstrip(); + if (!line) + continue; + // "%m[A-Za-z0-9_] %i %i" + + // TODO promote either qsplit() or asplit() + auto _it = std::find(line.begin(), line.end(), ' '); + auto name = line.xislice_h(_it); + auto _rest = line.xislice_t(_it); + while (_rest.startswith(' ')) + _rest = _rest.xslice_t(1); + auto _it2 = std::find(_rest.begin(), _rest.end(), ' '); + auto val_ = _rest.xislice_h(_it2); + auto type_ = _rest.xislice_t(_it2); + while (type_.startswith(' ')) + type_ = type_.xslice_t(1); + // yes, the above actually DTRT even for underlength input + + int val; + int type = 0; + // Note for future archeaologists: this code is indented correctly + if (std::find_if_not(name.begin(), name.end(), + [](char c) + { + return ('0' <= c && c <= '9') + || ('A' <= c && c <= 'Z') + || ('a' <= c && c <= 'z') + || (c == '_'); + }) != name.end() + || !extract(val_, &val) + || (!extract(type_, &type) && type_)) + { + PRINTF("Bad const line: %s\n"_fmt, line_); + rv = false; + continue; + } + str_data_t *n = add_strp(name); + n->type = type ? StringCode::PARAM : StringCode::INT; + n->val = val; + } + return rv; +} + +/*========================================== + * マップ変数の変更 + *------------------------------------------ + */ +void mapreg_setreg(SIR reg, int val) +{ + mapreg_db.put(reg, val); + + mapreg_dirty = 1; +} + +/*========================================== + * 文字列型マップ変数の変更 + *------------------------------------------ + */ +void mapreg_setregstr(SIR reg, XString str) +{ + if (!str) + mapregstr_db.erase(reg); + else + mapregstr_db.insert(reg, str); + + mapreg_dirty = 1; +} + +/*========================================== + * 永続的マップ変数の読み込み + *------------------------------------------ + */ +static +void script_load_mapreg(void) +{ + io::ReadFile in(mapreg_txt); + + if (!in.is_open()) + return; + + AString line; + while (in.getline(line)) + { + XString buf1, buf2; + int index = 0; + if (extract(line, + record<'\t'>( + record<','>(&buf1, &index), + &buf2)) + || extract(line, + record<'\t'>( + record<','>(&buf1), + &buf2))) + { + int s = variable_names.intern(buf1); + SIR key = SIR::from(s, index); + if (buf1.back() == '$') + { + mapregstr_db.insert(key, buf2); + } + else + { + int v; + if (!extract(buf2, &v)) + goto borken; + mapreg_db.put(key, v); + } + } + else + { + borken: + PRINTF("%s: %s broken data !\n"_fmt, mapreg_txt, AString(buf1)); + continue; + } + } + mapreg_dirty = 0; +} + +/*========================================== + * 永続的マップ変数の書き込み + *------------------------------------------ + */ +static +void script_save_mapreg_intsub(SIR key, int data, io::WriteFile& fp) +{ + int num = key.base(), i = key.index(); + ZString name = variable_names.outtern(num); + if (name[1] != '@') + { + if (i == 0) + FPRINTF(fp, "%s\t%d\n"_fmt, name, data); + else + FPRINTF(fp, "%s,%d\t%d\n"_fmt, name, i, data); + } +} + +static +void script_save_mapreg_strsub(SIR key, ZString data, io::WriteFile& fp) +{ + int num = key.base(), i = key.index(); + ZString name = variable_names.outtern(num); + if (name[1] != '@') + { + if (i == 0) + FPRINTF(fp, "%s\t%s\n"_fmt, name, data); + else + FPRINTF(fp, "%s,%d\t%s\n"_fmt, name, i, data); + } +} + +static +void script_save_mapreg(void) +{ + io::WriteLock fp(mapreg_txt); + if (!fp.is_open()) + return; + for (auto& pair : mapreg_db) + script_save_mapreg_intsub(pair.first, pair.second, fp); + for (auto& pair : mapregstr_db) + script_save_mapreg_strsub(pair.first, pair.second, fp); + mapreg_dirty = 0; +} + +static +void script_autosave_mapreg(TimerData *, tick_t) +{ + if (mapreg_dirty) + script_save_mapreg(); +} + +void do_final_script(void) +{ + if (mapreg_dirty >= 0) + script_save_mapreg(); + + mapreg_db.clear(); + mapregstr_db.clear(); + scriptlabel_db.clear(); + userfunc_db.clear(); + + str_datam.clear(); +} + +/*========================================== + * 初期化 + *------------------------------------------ + */ +void do_init_script(void) +{ + script_load_mapreg(); + + Timer(gettick() + MAPREG_AUTOSAVE_INTERVAL, + script_autosave_mapreg, + MAPREG_AUTOSAVE_INTERVAL + ).detach(); +} +} // namespace tmwa diff --git a/src/map/script-startup.hpp b/src/map/script-startup.hpp new file mode 100644 index 0000000..8a6b50d --- /dev/null +++ b/src/map/script-startup.hpp @@ -0,0 +1,35 @@ +#pragma once +// script-startup.hpp - EAthena script frontend, engine, and library. +// +// Copyright © ????-2004 Athena Dev Teams +// Copyright © 2004-2011 The Mana World Development Team +// Copyright © 2011-2014 Ben Longbons +// +// This file is part of The Mana World (Athena server) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "fwd.hpp" + +#include "../strings/fwd.hpp" + +namespace tmwa +{ +void do_init_script(void); +void do_final_script(void); + +extern AString mapreg_txt; + +bool read_constdb(ZString filename); +} // namespace tmwa diff --git a/src/map/script.cpp b/src/map/script.cpp deleted file mode 100644 index 61233b8..0000000 --- a/src/map/script.cpp +++ /dev/null @@ -1,5191 +0,0 @@ -#include "script.hpp" -// script.cpp - EAthena script frontend, engine, and library. -// -// Copyright © ????-2004 Athena Dev Teams -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011 Chuck Miller -// Copyright © 2011-2014 Ben Longbons -// Copyright © 2013 wushin -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include -#include -#include -#include - -#include -#include - -#include "../compat/fun.hpp" - -#include "../strings/mstring.hpp" -#include "../strings/rstring.hpp" -#include "../strings/astring.hpp" -#include "../strings/zstring.hpp" -#include "../strings/xstring.hpp" -#include "../strings/literal.hpp" - -#include "../generic/db.hpp" -#include "../generic/intern-pool.hpp" -#include "../generic/random.hpp" - -#include "../io/cxxstdio.hpp" -#include "../io/cxxstdio_enums.hpp" -#include "../io/lock.hpp" -#include "../io/read.hpp" -#include "../io/write.hpp" - -#include "../net/socket.hpp" -#include "../net/timer.hpp" - -#include "../mmo/core.hpp" -#include "../mmo/extract.hpp" -#include "../mmo/human_time_diff.hpp" -#include "../mmo/utils.hpp" - -#include "atcommand.hpp" -#include "battle.hpp" -#include "chrif.hpp" -#include "clif.hpp" -#include "intif.hpp" -#include "itemdb.hpp" -#include "magic-interpreter-base.hpp" -#include "map.hpp" -#include "mob.hpp" -#include "npc.hpp" -#include "party.hpp" -#include "pc.hpp" -#include "skill.hpp" -#include "storage.hpp" - -#include "../poison.hpp" - - -namespace tmwa -{ -constexpr bool DEBUG_DISP = false; -constexpr bool DEBUG_RUN = false; - -enum class VariableCode : uint8_t -{ - PARAM, - VARIABLE, -}; - -enum class StringCode : uint8_t -{ - NOP, POS, INT, PARAM, FUNC, - VARIABLE, -}; - -enum class ByteCode : uint8_t -{ - // types and specials - // Note that 'INT' is synthetic, and does not occur in the data stream - NOP, POS, INT, PARAM, FUNC, STR, ARG, - VARIABLE, EOL, - - // unary and binary operators - LOR, LAND, LE, LT, GE, GT, EQ, NE, - XOR, OR, AND, ADD, SUB, MUL, DIV, MOD, - NEG, LNOT, NOT, R_SHIFT, L_SHIFT, - - // additions - // needed because FUNC is used for the actual call - FUNC_REF, -}; - -struct str_data_t -{ - StringCode type; - RString strs; - int backpatch; - int label_; - int val; -}; - -class ScriptBuffer -{ - typedef ZString::iterator ZSit; - - std::vector script_buf; -public: - // construction methods used only by script.cpp - void add_scriptc(ByteCode a); - void add_scriptb(uint8_t a); - void add_scripti(uint32_t a); - void add_scriptl(str_data_t *a); - void set_label(str_data_t *ld, int pos_); - ZSit parse_simpleexpr(ZSit p); - ZSit parse_subexpr(ZSit p, int limit); - ZSit parse_expr(ZSit p); - ZSit parse_line(ZSit p, bool *canstep); - void parse_script(ZString src, int line, bool implicit_end); - - // consumption methods used only by script.cpp - ByteCode operator[](size_t i) const { return script_buf[i]; } - ZString get_str(size_t i) const - { - return ZString(strings::really_construct_from_a_pointer, reinterpret_cast(&script_buf[i]), nullptr); - } -}; -} // namespace tmwa - -void std::default_delete::operator()(const tmwa::ScriptBuffer *sd) -{ - really_delete1 sd; -} - -namespace tmwa -{ -ByteCode ScriptPointer::peek() const { return (*code)[pos]; } -ByteCode ScriptPointer::pop() { return (*code)[pos++]; } -ZString ScriptPointer::pops() -{ - ZString rv = code->get_str(pos); - pos += rv.size(); - ++pos; - return rv; -} - -struct script_stack -{ - std::vector stack_datav; -}; - -enum class ScriptEndState; -// future improvements coming! -class ScriptState -{ -public: - struct script_stack *stack; - int start, end; - ScriptEndState state; - BlockId rid, oid; - ScriptPointer scriptp, new_scriptp; - int defsp, new_defsp; -}; - -static -Map str_datam; -static -str_data_t LABEL_NEXTLINE_; - -static -DMap mapreg_db; -static -Map mapregstr_db; -static -int mapreg_dirty = -1; -AString mapreg_txt = "save/mapreg.txt"_s; -constexpr std::chrono::milliseconds MAPREG_AUTOSAVE_INTERVAL = 10_s; - -Map scriptlabel_db; -static -std::set probable_labels; -UPMap userfunc_db; - -static -Array pos_str //= -{{ - "Head"_s, - "Body"_s, - "Left hand"_s, - "Right hand"_s, - "Robe"_s, - "Shoes"_s, - "Accessory 1"_s, - "Accessory 2"_s, - "Head 2"_s, - "Head 3"_s, - "Not Equipped"_s, -}}; - -static -struct Script_Config -{ - static const - int warn_func_no_comma = 1; - static const - int warn_cmd_no_comma = 1; - static const - int warn_func_mismatch_paramnum = 1; - static const - int warn_cmd_mismatch_paramnum = 1; - static const - int check_cmdcount = 8192; - static const - int check_gotocount = 512; -} script_config; - -static -int parse_cmd_if = 0; -static -str_data_t *parse_cmdp; - -static -void run_func(ScriptState *st); - -static -void mapreg_setreg(SIR num, int val); -static -void mapreg_setregstr(SIR num, XString str); - -struct BuiltinFunction -{ - void (*func)(ScriptState *); - LString name; - LString arg; - char ret; -}; -// defined later -extern BuiltinFunction builtin_functions[]; - -static -InternPool variable_names; - - -template -bool first_type_is_any() -{ - return false; -} - -template -constexpr -bool first_type_is_any() -{ - return std::is_same::value || first_type_is_any(); -} - - -static -str_data_t *search_strp(XString p) -{ - return str_datam.search(p); -} - -static -str_data_t *add_strp(XString p) -{ - if (str_data_t *rv = search_strp(p)) - return rv; - - RString p2 = p; - str_data_t *datum = str_datam.init(p2); - datum->type = StringCode::NOP; - datum->strs = p2; - datum->backpatch = -1; - datum->label_ = -1; - return datum; -} - -/*========================================== - * スクリプトバッファに1バイト書き込む - *------------------------------------------ - */ -void ScriptBuffer::add_scriptc(ByteCode a) -{ - script_buf.push_back(a); -} - -/*========================================== - * スクリプトバッファにデータタイプを書き込む - *------------------------------------------ - */ -void ScriptBuffer::add_scriptb(uint8_t a) -{ - add_scriptc(static_cast(a)); -} - -/*========================================== - * スクリプトバッファに整数を書き込む - *------------------------------------------ - */ -void ScriptBuffer::add_scripti(uint32_t a) -{ - while (a >= 0x40) - { - add_scriptb(a | 0xc0); - a = (a - 0x40) >> 6; - } - add_scriptb(a | 0x80); -} - -/*========================================== - * スクリプトバッファにラベル/変数/関数を書き込む - *------------------------------------------ - */ -// 最大16Mまで -void ScriptBuffer::add_scriptl(str_data_t *ld) -{ - int backpatch = ld->backpatch; - - switch (ld->type) - { - case StringCode::POS: - add_scriptc(ByteCode::POS); - add_scriptb(static_cast(ld->label_)); - add_scriptb(static_cast(ld->label_ >> 8)); - add_scriptb(static_cast(ld->label_ >> 16)); - break; - case StringCode::NOP: - // need to set backpatch, because it might become a label later - add_scriptc(ByteCode::VARIABLE); - ld->backpatch = script_buf.size(); - add_scriptb(static_cast(backpatch)); - add_scriptb(static_cast(backpatch >> 8)); - add_scriptb(static_cast(backpatch >> 16)); - break; - case StringCode::INT: - add_scripti(ld->val); - break; - case StringCode::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 StringCode::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 = StringCode::POS; - ld->label_ = pos_; - for (int i = ld->backpatch; i >= 0 && i != 0x00ffffff; i = next) - { - next = 0; - // woot! no longer endian-dependent! - next |= static_cast(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 -ZString::iterator skip_space(ZString::iterator p) -{ - while (1) - { - while (isspace(*p)) - p++; - if (p[0] == '/' && p[1] == '/') - { - while (*p && *p != '\n') - p++; - } - else if (p[0] == '/' && p[1] == '*') - { - p++; - while (*p && (p[-1] != '*' || p[0] != '/')) - p++; - if (*p) - p++; - } - else - break; - } - return p; -} - -/*========================================== - * 1単語スキップ - *------------------------------------------ - */ -static -ZString::iterator skip_word(ZString::iterator p) -{ - // prefix - if (*p == '$') - p++; // MAP鯖内共有変数用 - if (*p == '@') - p++; // 一時的変数用(like weiss) - if (*p == '#') - p++; // account変数用 - if (*p == '#') - p++; // ワールドaccount変数用 - - while (isalnum(*p) || *p == '_') - p++; - - // postfix - if (*p == '$') - p++; // 文字列変数 - - return p; -} - -// TODO: replace this whole mess with some sort of input stream that works -// a line at a time. -static -ZString startptr; -static -int startline; - -int script_errors = 0; -/*========================================== - * エラーメッセージ出力 - *------------------------------------------ - */ -static -void disp_error_message(ZString mes, ZString::iterator pos_) -{ - script_errors++; - - assert (startptr.begin() <= pos_ && pos_ <= startptr.end()); - - int line; - ZString::iterator p; - - for (line = startline, p = startptr.begin(); p != startptr.end(); line++) - { - ZString::iterator linestart = p; - ZString::iterator lineend = std::find(p, startptr.end(), '\n'); - if (pos_ < lineend) - { - PRINTF("\n%s\nline %d : "_fmt, mes, line); - for (int i = 0; linestart + i != lineend; i++) - { - if (linestart + i != pos_) - PRINTF("%c"_fmt, linestart[i]); - else - PRINTF("\'%c\'"_fmt, linestart[i]); - } - PRINTF("\a\n"_fmt); - return; - } - p = lineend + 1; - } -} - -/*========================================== - * 項の解析 - *------------------------------------------ - */ -ZString::iterator ScriptBuffer::parse_simpleexpr(ZString::iterator p) -{ - p = skip_space(p); - - if (*p == ';' || *p == ',') - { - disp_error_message("unexpected expr end"_s, p); - exit(1); - } - if (*p == '(') - { - - p = parse_subexpr(p + 1, -1); - p = skip_space(p); - if ((*p++) != ')') - { - disp_error_message("unmatch ')'"_s, p); - exit(1); - } - } - else if (isdigit(*p) || ((*p == '-' || *p == '+') && isdigit(p[1]))) - { - char *np; - int i = strtoul(&*p, &np, 0); - add_scripti(i); - p += np - &*p; - } - else if (*p == '"') - { - add_scriptc(ByteCode::STR); - p++; - while (*p && *p != '"') - { - if (*p == '\\') - p++; - else if (*p == '\n') - { - disp_error_message("unexpected newline @ string"_s, p); - exit(1); - } - add_scriptb(*p++); - } - if (!*p) - { - disp_error_message("unexpected eof @ string"_s, p); - exit(1); - } - add_scriptb(0); - p++; //'"' - } - else - { - // label , register , function etc - ZString::iterator p2 = skip_word(p); - if (p2 == p) - { - disp_error_message("unexpected character"_s, p); - exit(1); - } - XString word(&*p, &*p2, nullptr); - if (word.startswith("On"_s) || word.startswith("L_"_s) || word.startswith("S_"_s)) - probable_labels.insert(stringish(word)); - if (parse_cmd_if && (word == "callsub"_s || word == "callfunc"_s || word == "return"_s)) - { - disp_error_message("Sorry, callsub/callfunc/return have never worked properly in an if statement."_s, p); - } - str_data_t *ld = add_strp(word); - - parse_cmdp = ld; // warn_*_mismatch_paramnumのために必要 - // why not just check l->str == "if"_s or std::string(p, p2) == "if"_s? - if (ld == search_strp("if"_s)) // warn_cmd_no_commaのために必要 - parse_cmd_if++; - p = p2; - - if (ld->type != StringCode::FUNC && *p == '[') - { - // array(name[i] => getelementofarray(name,i) ) - add_scriptl(search_strp("getelementofarray"_s)); - add_scriptc(ByteCode::ARG); - add_scriptl(ld); - p = parse_subexpr(p + 1, -1); - p = skip_space(p); - if (*p != ']') - { - disp_error_message("unmatch ']'"_s, p); - exit(1); - } - p++; - add_scriptc(ByteCode::FUNC); - } - else - add_scriptl(ld); - - } - - return p; -} - -/*========================================== - * 式の解析 - *------------------------------------------ - */ -ZString::iterator ScriptBuffer::parse_subexpr(ZString::iterator p, int limit) -{ - ByteCode op; - int opl, len; - - p = skip_space(p); - - if (*p == '-') - { - ZString::iterator tmpp = skip_space(p + 1); - if (*tmpp == ';' || *tmpp == ',') - { - --script_errors; disp_error_message("deprecated: implicit 'next statement' label"_s, p); - add_scriptl(&LABEL_NEXTLINE_); - p++; - return p; - } - } - ZString::iterator tmpp = p; - if ((op = ByteCode::NEG, *p == '-') || (op = ByteCode::LNOT, *p == '!') - || (op = ByteCode::NOT, *p == '~')) - { - p = parse_subexpr(p + 1, 100); - add_scriptc(op); - } - else - p = parse_simpleexpr(p); - p = skip_space(p); - while (((op = ByteCode::ADD, opl = 6, len = 1, *p == '+') || - (op = ByteCode::SUB, opl = 6, len = 1, *p == '-') || - (op = ByteCode::MUL, opl = 7, len = 1, *p == '*') || - (op = ByteCode::DIV, opl = 7, len = 1, *p == '/') || - (op = ByteCode::MOD, opl = 7, len = 1, *p == '%') || - (op = ByteCode::FUNC, opl = 8, len = 1, *p == '(') || - (op = ByteCode::LAND, opl = 1, len = 2, *p == '&' && p[1] == '&') || - (op = ByteCode::AND, opl = 5, len = 1, *p == '&') || - (op = ByteCode::LOR, opl = 0, len = 2, *p == '|' && p[1] == '|') || - (op = ByteCode::OR, opl = 4, len = 1, *p == '|') || - (op = ByteCode::XOR, opl = 3, len = 1, *p == '^') || - (op = ByteCode::EQ, opl = 2, len = 2, *p == '=' && p[1] == '=') || - (op = ByteCode::NE, opl = 2, len = 2, *p == '!' && p[1] == '=') || - (op = ByteCode::R_SHIFT, opl = 5, len = 2, *p == '>' && p[1] == '>') || - (op = ByteCode::GE, opl = 2, len = 2, *p == '>' && p[1] == '=') || - (op = ByteCode::GT, opl = 2, len = 1, *p == '>') || - (op = ByteCode::L_SHIFT, opl = 5, len = 2, *p == '<' && p[1] == '<') || - (op = ByteCode::LE, opl = 2, len = 2, *p == '<' && p[1] == '=') || - (op = ByteCode::LT, opl = 2, len = 1, *p == '<')) && opl > limit) - { - p += len; - if (op == ByteCode::FUNC) - { - int i = 0; - str_data_t *funcp = parse_cmdp; - ZString::iterator plist[128]; - - if (funcp->type != StringCode::FUNC) - { - disp_error_message("expect function"_s, tmpp); - exit(0); - } - - add_scriptc(ByteCode::ARG); - while (*p && *p != ')' && i < 128) - { - plist[i] = p; - p = parse_subexpr(p, -1); - p = skip_space(p); - if (*p == ',') - p++; - else if (*p != ')' && script_config.warn_func_no_comma) - { - disp_error_message("expect ',' or ')' at func params"_s, - p); - } - p = skip_space(p); - i++; - } - plist[i] = p; - if (*p != ')') - { - disp_error_message("func request '(' ')'"_s, p); - exit(1); - } - p++; - - if (funcp->type == StringCode::FUNC - && script_config.warn_func_mismatch_paramnum) - { - ZString arg = builtin_functions[funcp->val].arg; - int j = 0; - // TODO handle ? and multiple * correctly - for (j = 0; arg[j]; j++) - if (arg[j] == '*' || arg[j] == '?') - break; - if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j)) - { - disp_error_message("illegal number of parameters"_s, - plist[std::min(i, j)]); - } - if (!builtin_functions[funcp->val].ret) - { - disp_error_message("statement in function context"_s, tmpp); - } - } - } - else // not op == ByteCode::FUNC - { - p = parse_subexpr(p, opl); - } - add_scriptc(op); - p = skip_space(p); - } - return p; /* return first untreated operator */ -} - -/*========================================== - * 式の評価 - *------------------------------------------ - */ -ZString::iterator ScriptBuffer::parse_expr(ZString::iterator p) -{ - switch (*p) - { - case ')': - case ';': - case ':': - case '[': - case ']': - case '}': - disp_error_message("unexpected char"_s, p); - exit(1); - } - p = parse_subexpr(p, -1); - return p; -} - -/*========================================== - * 行の解析 - *------------------------------------------ - */ -ZString::iterator ScriptBuffer::parse_line(ZString::iterator p, bool *can_step) -{ - int i = 0; - ZString::iterator plist[128]; - - p = skip_space(p); - if (*p == ';') - return p; - - parse_cmd_if = 0; // warn_cmd_no_commaのために必要 - - // 最初は関数名 - ZString::iterator p2 = p; - p = parse_simpleexpr(p); - p = skip_space(p); - - str_data_t *cmd = parse_cmdp; - if (cmd->type != StringCode::FUNC) - { - disp_error_message("expect command"_s, p2); - } - - { - // TODO should be LString, but no heterogenous lookup yet - static - std::set terminators = - { - "goto"_s, - "return"_s, - "close"_s, - "menu"_s, - "end"_s, - "mapexit"_s, - "shop"_s, - }; - *can_step = terminators.count(cmd->strs) == 0; - } - - add_scriptc(ByteCode::ARG); - while (*p && *p != ';' && i < 128) - { - plist[i] = p; - - p = parse_expr(p); - p = skip_space(p); - // 引数区切りの,処理 - if (*p == ',') - p++; - else if (*p != ';' && script_config.warn_cmd_no_comma - && parse_cmd_if * 2 <= i) - { - disp_error_message("expect ',' or ';' at cmd params"_s, p); - } - p = skip_space(p); - i++; - } - plist[i] = p; - if (*(p++) != ';') - { - disp_error_message("need ';'"_s, p); - exit(1); - } - add_scriptc(ByteCode::FUNC); - - if (cmd->type == StringCode::FUNC - && script_config.warn_cmd_mismatch_paramnum) - { - ZString arg = builtin_functions[cmd->val].arg; - int j = 0; - // TODO see above - for (j = 0; arg[j]; j++) - if (arg[j] == '*' || arg[j] == '?') - break; - if ((arg[j] == 0 && i != j) || ((arg[j] == '*' || arg[j] == '?') && i < j)) - { - disp_error_message("illegal number of parameters"_s, - plist[std::min(i, j)]); - } - if (builtin_functions[cmd->val].ret) - { - disp_error_message("function in statement context"_s, p2); - } - } - - return p; -} - -/*========================================== - * 組み込み関数の追加 - *------------------------------------------ - */ -static -void add_builtin_functions(void) -{ - for (int i = 0; builtin_functions[i].func; i++) - { - str_data_t *n = add_strp(builtin_functions[i].name); - n->type = StringCode::FUNC; - n->val = i; - } -} - -bool read_constdb(ZString filename) -{ - io::ReadFile in(filename); - if (!in.is_open()) - { - PRINTF("can't read %s\n"_fmt, filename); - return false; - } - - bool rv = true; - AString line_; - while (in.getline(line_)) - { - // is_comment only works for whole-line comments - // that could change once the Z dependency is dropped ... - LString comment = "//"_s; - XString line = line_.xislice_h(std::search(line_.begin(), line_.end(), comment.begin(), comment.end())).rstrip(); - if (!line) - continue; - // "%m[A-Za-z0-9_] %i %i" - - // TODO promote either qsplit() or asplit() - auto _it = std::find(line.begin(), line.end(), ' '); - auto name = line.xislice_h(_it); - auto _rest = line.xislice_t(_it); - while (_rest.startswith(' ')) - _rest = _rest.xslice_t(1); - auto _it2 = std::find(_rest.begin(), _rest.end(), ' '); - auto val_ = _rest.xislice_h(_it2); - auto type_ = _rest.xislice_t(_it2); - while (type_.startswith(' ')) - type_ = type_.xslice_t(1); - // yes, the above actually DTRT even for underlength input - - int val; - int type = 0; - // Note for future archeaologists: this code is indented correctly - if (std::find_if_not(name.begin(), name.end(), - [](char c) - { - return ('0' <= c && c <= '9') - || ('A' <= c && c <= 'Z') - || ('a' <= c && c <= 'z') - || (c == '_'); - }) != name.end() - || !extract(val_, &val) - || (!extract(type_, &type) && type_)) - { - PRINTF("Bad const line: %s\n"_fmt, line_); - rv = false; - continue; - } - str_data_t *n = add_strp(name); - n->type = type ? StringCode::PARAM : StringCode::INT; - n->val = val; - } - return rv; -} - -std::unique_ptr parse_script(ZString src, int line, bool implicit_end) -{ - auto script_buf = make_unique(); - script_buf->parse_script(src, line, implicit_end); - return std::move(script_buf); -} - -/*========================================== - * スクリプトの解析 - *------------------------------------------ - */ -void ScriptBuffer::parse_script(ZString src, int line, bool implicit_end) -{ - static int first = 1; - - if (first) - { - add_builtin_functions(); - } - first = 0; - LABEL_NEXTLINE_.type = StringCode::NOP; - LABEL_NEXTLINE_.backpatch = -1; - LABEL_NEXTLINE_.label_ = -1; - for (auto& pair : str_datam) - { - str_data_t& dit = pair.second; - if (dit.type == StringCode::POS || dit.type == StringCode::VARIABLE) - { - dit.type = StringCode::NOP; - dit.backpatch = -1; - dit.label_ = -1; - } - } - - // 外部用label dbの初期化 - scriptlabel_db.clear(); - - // for error message - startptr = src; - startline = line; - - bool can_step = true; - - ZString::iterator p = src.begin(); - p = skip_space(p); - if (*p != '{') - { - disp_error_message("not found '{'"_s, p); - abort(); - } - for (p++; *p && *p != '}';) - { - p = skip_space(p); - if (*skip_space(skip_word(p)) == ':') - { - if (can_step) - { - --script_errors; disp_error_message("deprecated: implicit fallthrough"_s, p); - } - can_step = true; - - ZString::iterator tmpp = skip_word(p); - XString str(&*p, &*tmpp, nullptr); - str_data_t *ld = add_strp(str); - bool e1 = ld->type != StringCode::NOP; - bool e2 = ld->type == StringCode::POS; - bool e3 = ld->label_ != -1; - assert (e1 == e2 && e2 == e3); - if (e3) - { - disp_error_message("dup label "_s, p); - exit(1); - } - set_label(ld, script_buf.size()); - scriptlabel_db.insert(stringish(str), script_buf.size()); - p = tmpp + 1; - continue; - } - - if (!can_step) - { - --script_errors; disp_error_message("deprecated: unreachable statement"_s, p); - } - // 他は全部一緒くた - p = parse_line(p, &can_step); - p = skip_space(p); - add_scriptc(ByteCode::EOL); - - set_label(&LABEL_NEXTLINE_, script_buf.size()); - LABEL_NEXTLINE_.type = StringCode::NOP; - LABEL_NEXTLINE_.backpatch = -1; - LABEL_NEXTLINE_.label_ = -1; - } - - if (can_step && !implicit_end) - { - --script_errors; disp_error_message("deprecated: implicit end"_s, p); - } - add_scriptc(ByteCode::NOP); - - // resolve the unknown labels - for (auto& pair : str_datam) - { - str_data_t& sit = pair.second; - if (sit.type == StringCode::NOP) - { - sit.type = StringCode::VARIABLE; - sit.label_ = 0; // anything but -1. Shouldn't matter, but helps asserts. - size_t pool_index = variable_names.intern(sit.strs); - for (int next, j = sit.backpatch; j >= 0 && j != 0x00ffffff; j = next) - { - next = 0; - next |= static_cast(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); - } - } - } - - for (const auto& pair : scriptlabel_db) - { - ScriptLabel key = pair.first; - if (key.startswith("On"_s)) - continue; - if (!(key.startswith("L_"_s) || key.startswith("S_"_s))) - PRINTF("Warning: ugly label: %s\n"_fmt, key); - else if (!probable_labels.count(key)) - PRINTF("Warning: unused label: %s\n"_fmt, key); - } - for (ScriptLabel used : probable_labels) - { - if (!scriptlabel_db.search(used)) - PRINTF("Warning: no such label: %s\n"_fmt, used); - } - probable_labels.clear(); - - if (!DEBUG_DISP) - return; - for (size_t i = 0; i < script_buf.size(); i++) - { - if ((i & 15) == 0) - PRINTF("%04zx : "_fmt, i); - PRINTF("%02x "_fmt, script_buf[i]); - if ((i & 15) == 15) - PRINTF("\n"_fmt); - } - PRINTF("\n"_fmt); -} - -// -// 実行系 -// -enum class ScriptEndState -{ - ZERO, - STOP, - END, - RERUNLINE, - GOTO, - RETFUNC, -}; - -/*========================================== - * ridからsdへの解決 - *------------------------------------------ - */ -static -dumb_ptr script_rid2sd(ScriptState *st) -{ - dumb_ptr sd = map_id2sd(st->rid); - if (!sd) - { - PRINTF("script_rid2sd: fatal error ! player not attached!\n"_fmt); - } - return sd; -} - -/*========================================== - * 変数の読み取り - *------------------------------------------ - */ -static -void get_val(dumb_ptr sd, struct script_data *data) -{ - MATCH (*data) - { - CASE (const ScriptDataParam&, u) - { - if (sd == nullptr) - PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp()); - int numi = 0; - if (sd) - numi = pc_readparam(sd, u.reg.sp()); - *data = ScriptDataInt{numi}; - } - CASE (const ScriptDataVariable&, u) - { - ZString name_ = variable_names.outtern(u.reg.base()); - VarName name = stringish(name_); - char prefix = name.front(); - char postfix = name.back(); - - if (prefix != '$') - { - if (sd == nullptr) - PRINTF("get_val error name?:%s\n"_fmt, name); - } - if (postfix == '$') - { - RString str; - if (prefix == '@') - { - if (sd) - str = pc_readregstr(sd, u.reg); - } - else if (prefix == '$') - { - RString *s = mapregstr_db.search(u.reg); - if (s) - str = *s; - } - else - { - PRINTF("script: get_val: illegal scope string variable.\n"_fmt); - str = "!!ERROR!!"_s; - } - *data = ScriptDataStr{str}; - } - else - { - int numi = 0; - if (prefix == '@') - { - if (sd) - numi = pc_readreg(sd, u.reg); - } - else if (prefix == '$') - { - numi = mapreg_db.get(u.reg); - } - else if (prefix == '#') - { - if (name[1] == '#') - { - if (sd) - numi = pc_readaccountreg2(sd, name); - } - else - { - if (sd) - numi = pc_readaccountreg(sd, name); - } - } - else - { - if (sd) - numi = pc_readglobalreg(sd, name); - } - *data = ScriptDataInt{numi}; - } - } - } -} - -static __attribute__((deprecated)) -void get_val(ScriptState *st, struct script_data *data) -{ - dumb_ptr sd = st->rid ? map_id2sd(st->rid) : nullptr; - get_val(sd, data); -} - -/*========================================== - * 変数の読み取り2 - *------------------------------------------ - */ -static -struct script_data get_val2(ScriptState *st, SIR reg) -{ - struct script_data dat = ScriptDataVariable{reg}; - get_val(st, &dat); - return dat; -} - -/*========================================== - * 変数設定用 - *------------------------------------------ - */ -static -void set_reg(dumb_ptr sd, VariableCode type, SIR reg, struct script_data vd) -{ - if (type == VariableCode::PARAM) - { - int val = vd.get_if()->numi; - pc_setparam(sd, reg.sp(), val); - return; - } - assert (type == VariableCode::VARIABLE); - - ZString name_ = variable_names.outtern(reg.base()); - VarName name = stringish(name_); - char prefix = name.front(); - char postfix = name.back(); - - if (postfix == '$') - { - RString str = vd.get_if()->str; - if (prefix == '@') - { - pc_setregstr(sd, reg, str); - } - else if (prefix == '$') - { - mapreg_setregstr(reg, str); - } - else - { - PRINTF("script: set_reg: illegal scope string variable !"_fmt); - } - } - else - { - int val = vd.get_if()->numi; - if (prefix == '@') - { - pc_setreg(sd, reg, val); - } - else if (prefix == '$') - { - mapreg_setreg(reg, val); - } - else if (prefix == '#') - { - if (name[1] == '#') - pc_setaccountreg2(sd, name, val); - else - pc_setaccountreg(sd, name, val); - } - else - { - pc_setglobalreg(sd, name, val); - } - } -} - -static -void set_reg(dumb_ptr sd, VariableCode type, SIR reg, int id) -{ - struct script_data vd = ScriptDataInt{id}; - set_reg(sd, type, reg, vd); -} - -static -void set_reg(dumb_ptr sd, VariableCode type, SIR reg, RString zd) -{ - struct script_data vd = ScriptDataStr{zd}; - set_reg(sd, type, reg, vd); -} - -/*========================================== - * 文字列への変換 - *------------------------------------------ - */ -static __attribute__((warn_unused_result)) -RString conv_str(ScriptState *st, struct script_data *data) -{ - get_val(st, data); - assert (!data->is()); - if (auto *u = data->get_if()) - { - AString buf = STRPRINTF("%d"_fmt, u->numi); - *data = ScriptDataStr{buf}; - } - return data->get_if()->str; -} - -/*========================================== - * 数値へ変換 - *------------------------------------------ - */ -static __attribute__((warn_unused_result)) -int conv_num(ScriptState *st, struct script_data *data) -{ - int rv = 0; - get_val(st, data); - assert (!data->is()); - MATCH (*data) - { - default: - abort(); - CASE (const ScriptDataStr&, u) - { - RString p = u.str; - rv = atoi(p.c_str()); - } - CASE (const ScriptDataInt&, u) - { - return u.numi; - } - CASE (const ScriptDataPos&, u) - { - return u.numi; - } - } - *data = ScriptDataInt{rv}; - return rv; -} - -static __attribute__((warn_unused_result)) -const ScriptBuffer *conv_script(ScriptState *st, struct script_data *data) -{ - get_val(st, data); - return data->get_if()->script; -} - - -template -static -void push_int(struct script_stack *stack, int val) -{ - static_assert(first_type_is_any(), "not int type"); - - script_data nsd = T{.numi= val}; - stack->stack_datav.push_back(nsd); -} - -template -static -void push_reg(struct script_stack *stack, SIR reg) -{ - static_assert(first_type_is_any(), "not reg type"); - - script_data nsd = T{.reg= reg}; - stack->stack_datav.push_back(nsd); -} - -template -static -void push_script(struct script_stack *stack, const ScriptBuffer *code) -{ - static_assert(first_type_is_any(), "not scriptbuf type"); - - script_data nsd = T{.script= code}; - stack->stack_datav.push_back(nsd); -} - -template -static -void push_str(struct script_stack *stack, RString str) -{ - static_assert(first_type_is_any(), "not str type"); - - script_data nsd = T{.str= str}; - stack->stack_datav.push_back(nsd); -} - -static -void push_copy(struct script_stack *stack, int pos_) -{ - script_data csd = stack->stack_datav[pos_]; - stack->stack_datav.push_back(csd); -} - -static -void pop_stack(struct script_stack *stack, int start, int end) -{ - auto it = stack->stack_datav.begin(); - stack->stack_datav.erase(it + start, it + end); -} - - -#define AARG(n) (st->stack->stack_datav[st->start + 2 + (n)]) -#define HARG(n) (st->end > st->start + 2 + (n)) - -// -// 埋め込み関数 -// -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_mes(ScriptState *st) -{ - RString mes = conv_str(st, &AARG(0)); - clif_scriptmes(script_rid2sd(st), st->oid, mes); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_goto(ScriptState *st) -{ - if (!AARG(0).is()) - { - PRINTF("script: goto: not label !\n"_fmt); - st->state = ScriptEndState::END; - return; - } - - st->scriptp.pos = conv_num(st, &AARG(0)); - st->state = ScriptEndState::GOTO; -} - -/*========================================== - * ユーザー定義関数の呼び出し - *------------------------------------------ - */ -static -void builtin_callfunc(ScriptState *st) -{ - RString str = conv_str(st, &AARG(0)); - const ScriptBuffer *scr = userfunc_db.get(str); - - if (scr) - { - int j = 0; - assert (st->start + 3 == st->end); -#if 0 - for (int i = st->start + 3; i < st->end; i++, j++) - push_copy(st->stack, i); -#endif - - push_int(st->stack, j); // 引数の数をプッシュ - push_int(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ - push_int(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ - push_script(st->stack, st->scriptp.code); // 現在のスクリプトをプッシュ - - st->scriptp = ScriptPointer(scr, 0); - st->defsp = st->start + 4 + j; - st->state = ScriptEndState::GOTO; - } - else - { - PRINTF("script:callfunc: function not found! [%s]\n"_fmt, str); - st->state = ScriptEndState::END; - } -} - -/*========================================== - * サブルーティンの呼び出し - *------------------------------------------ - */ -static -void builtin_callsub(ScriptState *st) -{ - int pos_ = conv_num(st, &AARG(0)); - int j = 0; - assert (st->start + 3 == st->end); -#if 0 - for (int i = st->start + 3; i < st->end; i++, j++) - push_copy(st->stack, i); -#endif - - push_int(st->stack, j); // 引数の数をプッシュ - push_int(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ - push_int(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ - push_script(st->stack, st->scriptp.code); // 現在のスクリプトをプッシュ - - st->scriptp.pos = pos_; - st->defsp = st->start + 4 + j; - st->state = ScriptEndState::GOTO; -} - -/*========================================== - * サブルーチン/ユーザー定義関数の終了 - *------------------------------------------ - */ -static -void builtin_return(ScriptState *st) -{ -#if 0 - if (HARG(0)) - { // 戻り値有り - push_copy(st->stack, st->start + 2); - } -#endif - st->state = ScriptEndState::RETFUNC; -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_next(ScriptState *st) -{ - st->state = ScriptEndState::STOP; - clif_scriptnext(script_rid2sd(st), st->oid); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_close(ScriptState *st) -{ - st->state = ScriptEndState::END; - clif_scriptclose(script_rid2sd(st), st->oid); -} - -static -void builtin_close2(ScriptState *st) -{ - st->state = ScriptEndState::STOP; - clif_scriptclose(script_rid2sd(st), st->oid); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_menu(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - - if (sd->state.menu_or_input == 0) - { - // First half: show menu. - st->state = ScriptEndState::RERUNLINE; - sd->state.menu_or_input = 1; - - MString buf; - for (int i = 0; i < (st->end - (st->start + 2)) / 2; i++) - { - RString choice_str = conv_str(st, &AARG(i * 2 + 0)); - if (!choice_str) - break; - buf += choice_str; - buf += ':'; - } - - clif_scriptmenu(script_rid2sd(st), st->oid, AString(buf)); - } - else - { - // Rerun: item is chosen from menu. - if (sd->npc_menu == 0xff) - { - // cancel - sd->state.menu_or_input = 0; - st->state = ScriptEndState::END; - return; - } - - // Actually jump to the label. - // Logic change: menu_choices is the *total* number of labels, - // not just the displayed number that ends with the "". - // (Would it be better to pop the stack before rerunning?) - int menu_choices = (st->end - (st->start + 2)) / 2; - pc_setreg(sd, SIR::from(variable_names.intern("@menu"_s)), sd->npc_menu); - sd->state.menu_or_input = 0; - if (sd->npc_menu > 0 && sd->npc_menu <= menu_choices) - { - int arg_index = (sd->npc_menu - 1) * 2 + 1; - if (!AARG(arg_index).is()) - { - st->state = ScriptEndState::END; - return; - } - st->scriptp.pos = AARG(arg_index).get_if()->numi; - st->state = ScriptEndState::GOTO; - } - } -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_rand(ScriptState *st) -{ - if (HARG(1)) - { - int min = conv_num(st, &AARG(0)); - int max = conv_num(st, &AARG(1)); - if (min > max) - std::swap(max, min); - push_int(st->stack, random_::in(min, max)); - } - else - { - int range = conv_num(st, &AARG(0)); - push_int(st->stack, range <= 0 ? 0 : random_::to(range)); - } -} - -/*========================================== - * Check whether the PC is at the specified location - *------------------------------------------ - */ -static -void builtin_isat(ScriptState *st) -{ - int x, y; - dumb_ptr sd = script_rid2sd(st); - - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - x = conv_num(st, &AARG(1)); - y = conv_num(st, &AARG(2)); - - if (!sd) - return; - - push_int(st->stack, - (x == sd->bl_x) && (y == sd->bl_y) - && (str == sd->bl_m->name_)); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_warp(ScriptState *st) -{ - int x, y; - dumb_ptr sd = script_rid2sd(st); - - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - x = conv_num(st, &AARG(1)); - y = conv_num(st, &AARG(2)); - if (str == "Random"_s) - pc_randomwarp(sd, BeingRemoveWhy::WARPED); - else if (str == "SavePoint"_s or str == "Save"_s) - { - if (sd->bl_m->flag.get(MapFlag::NORETURN)) - return; - - pc_setpos(sd, sd->status.save_point.map_, sd->status.save_point.x, sd->status.save_point.y, - BeingRemoveWhy::WARPED); - } - else - pc_setpos(sd, str, x, y, BeingRemoveWhy::GONE); -} - -/*========================================== - * エリア指定ワープ - *------------------------------------------ - */ -static -void builtin_areawarp_sub(dumb_ptr bl, MapName mapname, int x, int y) -{ - dumb_ptr sd = bl->is_player(); - if (mapname == "Random"_s) - pc_randomwarp(sd, BeingRemoveWhy::WARPED); - else - pc_setpos(sd, mapname, x, y, BeingRemoveWhy::GONE); -} - -static -void builtin_areawarp(ScriptState *st) -{ - int x, y; - int x0, y0, x1, y1; - - MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); - x0 = conv_num(st, &AARG(1)); - y0 = conv_num(st, &AARG(2)); - x1 = conv_num(st, &AARG(3)); - y1 = conv_num(st, &AARG(4)); - MapName str = stringish(ZString(conv_str(st, &AARG(5)))); - x = conv_num(st, &AARG(6)); - y = conv_num(st, &AARG(7)); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return; - - map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y), - m, - x0, y0, - x1, y1, - BL::PC); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_heal(ScriptState *st) -{ - int hp, sp; - - hp = conv_num(st, &AARG(0)); - sp = conv_num(st, &AARG(1)); - pc_heal(script_rid2sd(st), hp, sp); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_itemheal(ScriptState *st) -{ - int hp, sp; - - hp = conv_num(st, &AARG(0)); - sp = conv_num(st, &AARG(1)); - pc_itemheal(script_rid2sd(st), hp, sp); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_percentheal(ScriptState *st) -{ - int hp, sp; - - hp = conv_num(st, &AARG(0)); - sp = conv_num(st, &AARG(1)); - pc_percentheal(script_rid2sd(st), hp, sp); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_input(ScriptState *st) -{ - dumb_ptr sd = nullptr; - script_data& scrd = AARG(0); - assert (scrd.is()); - - SIR reg = scrd.get_if()->reg; - ZString name = variable_names.outtern(reg.base()); -// char prefix = name.front(); - char postfix = name.back(); - - sd = script_rid2sd(st); - if (sd->state.menu_or_input) - { - // Second time (rerun) - sd->state.menu_or_input = 0; - if (postfix == '$') - { - set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_str); - } - else - { - //commented by Lupus (check Value Number Input fix in clif.c) - //** Fix by fritz :X keeps people from abusing old input bugs - // wtf? - if (sd->npc_amount < 0) //** If input amount is less then 0 - { - clif_tradecancelled(sd); // added "Deal has been cancelled" message by Valaris - builtin_close(st); //** close - } - - set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_amount); - } - } - else - { - // First time - send prompt to client, then wait - st->state = ScriptEndState::RERUNLINE; - if (postfix == '$') - clif_scriptinputstr(sd, st->oid); - else - clif_scriptinput(sd, st->oid); - sd->state.menu_or_input = 1; - } -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_if (ScriptState *st) -{ - int sel, i; - - sel = conv_num(st, &AARG(0)); - if (!sel) - return; - - // 関数名をコピー - push_copy(st->stack, st->start + 3); - // 間に引数マーカを入れて - push_int(st->stack, 0); - // 残りの引数をコピー - for (i = st->start + 4; i < st->end; i++) - { - push_copy(st->stack, i); - } - run_func(st); -} - -/*========================================== - * 変数設定 - *------------------------------------------ - */ -static -void builtin_set(ScriptState *st) -{ - dumb_ptr sd = nullptr; - if (auto *u = AARG(0).get_if()) - { - SIR reg = u->reg; - sd = script_rid2sd(st); - - int val = conv_num(st, &AARG(1)); - set_reg(sd, VariableCode::PARAM, reg, val); - return; - } - - SIR reg = AARG(0).get_if()->reg; - - ZString name = variable_names.outtern(reg.base()); - char prefix = name.front(); - char postfix = name.back(); - - if (prefix != '$') - sd = script_rid2sd(st); - - if (postfix == '$') - { - // 文字列 - RString str = conv_str(st, &AARG(1)); - set_reg(sd, VariableCode::VARIABLE, reg, str); - } - else - { - // 数値 - int val = conv_num(st, &AARG(1)); - set_reg(sd, VariableCode::VARIABLE, reg, val); - } - -} - -/*========================================== - * 配列変数設定 - *------------------------------------------ - */ -static -void builtin_setarray(ScriptState *st) -{ - dumb_ptr sd = nullptr; - SIR reg = AARG(0).get_if()->reg; - ZString name = variable_names.outtern(reg.base()); - char prefix = name.front(); - char postfix = name.back(); - - if (prefix != '$' && prefix != '@') - { - PRINTF("builtin_setarray: illegal scope !\n"_fmt); - return; - } - if (prefix != '$') - sd = script_rid2sd(st); - - for (int j = 0, i = 1; i < st->end - st->start - 2 && j < 256; i++, j++) - { - if (postfix == '$') - set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_str(st, &AARG(i))); - else - set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_num(st, &AARG(i))); - } -} - -/*========================================== - * 配列変数クリア - *------------------------------------------ - */ -static -void builtin_cleararray(ScriptState *st) -{ - dumb_ptr sd = nullptr; - SIR reg = AARG(0).get_if()->reg; - ZString name = variable_names.outtern(reg.base()); - char prefix = name.front(); - char postfix = name.back(); - int sz = conv_num(st, &AARG(2)); - - if (prefix != '$' && prefix != '@') - { - PRINTF("builtin_cleararray: illegal scope !\n"_fmt); - return; - } - if (prefix != '$') - sd = script_rid2sd(st); - - for (int i = 0; i < sz; i++) - { - if (postfix == '$') - set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARG(1))); - else - set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_num(st, &AARG(1))); - } - -} - -/*========================================== - * 配列変数のサイズ所得 - *------------------------------------------ - */ -static -int getarraysize(ScriptState *st, SIR reg) -{ - int i = reg.index(), c = i; - for (; i < 256; i++) - { - struct script_data vd = get_val2(st, reg.iplus(i)); - MATCH (vd) - { - CASE (const ScriptDataStr&, u) - { - if (u.str[0]) - c = i; - goto continue_outer; - } - CASE (const ScriptDataInt&, u) - { - if (u.numi) - c = i; - goto continue_outer; - } - } - abort(); - continue_outer: - ; - } - return c + 1; -} - -static -void builtin_getarraysize(ScriptState *st) -{ - SIR reg = AARG(0).get_if()->reg; - ZString name = variable_names.outtern(reg.base()); - char prefix = name.front(); - - if (prefix != '$' && prefix != '@') - { - PRINTF("builtin_copyarray: illegal scope !\n"_fmt); - return; - } - - push_int(st->stack, getarraysize(st, reg)); -} - -/*========================================== - * 指定要素を表す値(キー)を所得する - *------------------------------------------ - */ -static -void builtin_getelementofarray(ScriptState *st) -{ - if (auto *u = AARG(0).get_if()) - { - int i = conv_num(st, &AARG(1)); - if (i > 255 || i < 0) - { - PRINTF("script: getelementofarray (operator[]): param2 illegal number %d\n"_fmt, - i); - push_int(st->stack, 0); - } - else - { - push_reg(st->stack, - u->reg.iplus(i)); - } - } - else - { - PRINTF("script: getelementofarray (operator[]): param1 not name !\n"_fmt); - push_int(st->stack, 0); - } -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_setlook(ScriptState *st) -{ - LOOK type = LOOK(conv_num(st, &AARG(0))); - int val = conv_num(st, &AARG(1)); - - pc_changelook(script_rid2sd(st), type, val); - -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_countitem(ScriptState *st) -{ - ItemNameId nameid; - int count = 0; - dumb_ptr sd; - - struct script_data *data; - - sd = script_rid2sd(st); - - data = &AARG(0); - get_val(st, data); - if (data->is()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data != nullptr) - nameid = item_data->nameid; - } - else - nameid = wrap(conv_num(st, data)); - - if (nameid) - { - for (IOff0 i : IOff0::iter()) - { - if (sd->status.inventory[i].nameid == nameid) - count += sd->status.inventory[i].amount; - } - } - else - { - if (battle_config.error_log) - PRINTF("wrong item ID : countitem (%i)\n"_fmt, nameid); - } - push_int(st->stack, count); - -} - -/*========================================== - * 重量チェック - *------------------------------------------ - */ -static -void builtin_checkweight(ScriptState *st) -{ - ItemNameId nameid; - int amount; - dumb_ptr sd; - struct script_data *data; - - sd = script_rid2sd(st); - - data = &AARG(0); - get_val(st, data); - if (data->is()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data) - nameid = item_data->nameid; - } - else - nameid = wrap(conv_num(st, data)); - - amount = conv_num(st, &AARG(1)); - if (amount <= 0 || !nameid) - { - //if get wrong item ID or amount<=0, don't count weight of non existing items - push_int(st->stack, 0); - return; - } - - if (itemdb_weight(nameid) * amount + sd->weight > sd->max_weight) - { - push_int(st->stack, 0); - } - else - { - push_int(st->stack, 1); - } - -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_getitem(ScriptState *st) -{ - ItemNameId nameid; - int amount; - dumb_ptr sd; - struct script_data *data; - - sd = script_rid2sd(st); - - data = &AARG(0); - get_val(st, data); - if (data->is()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data != nullptr) - nameid = item_data->nameid; - } - else - nameid = wrap(conv_num(st, data)); - - if ((amount = - conv_num(st, &AARG(1))) <= 0) - { - return; //return if amount <=0, skip the useles iteration - } - - if (nameid) - { - Item item_tmp {}; - item_tmp.nameid = nameid; - if (HARG(3)) //アイテムを指定したIDに渡す - sd = map_id2sd(wrap(conv_num(st, &AARG(3)))); - if (sd == nullptr) //アイテムを渡す相手がいなかったらお帰り - return; - PickupFail flag; - if ((flag = pc_additem(sd, &item_tmp, amount)) != PickupFail::OKAY) - { - clif_additem(sd, IOff0::from(0), 0, flag); - map_addflooritem(&item_tmp, amount, - sd->bl_m, sd->bl_x, sd->bl_y, - nullptr, nullptr, nullptr); - } - } - -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_makeitem(ScriptState *st) -{ - ItemNameId nameid; - int amount; - int x, y; - dumb_ptr sd; - struct script_data *data; - - sd = script_rid2sd(st); - - data = &AARG(0); - get_val(st, data); - if (data->is()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data) - nameid = item_data->nameid; - } - else - nameid = wrap(conv_num(st, data)); - - amount = conv_num(st, &AARG(1)); - MapName mapname = stringish(ZString(conv_str(st, &AARG(2)))); - x = conv_num(st, &AARG(3)); - y = conv_num(st, &AARG(4)); - - map_local *m; - if (sd && mapname == MOB_THIS_MAP) - m = sd->bl_m; - else - m = map_mapname2mapid(mapname); - - if (nameid) - { - Item item_tmp {}; - item_tmp.nameid = nameid; - - map_addflooritem(&item_tmp, amount, m, x, y, nullptr, nullptr, nullptr); - } -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_delitem(ScriptState *st) -{ - ItemNameId nameid; - int amount; - dumb_ptr sd; - struct script_data *data; - - sd = script_rid2sd(st); - - data = &AARG(0); - get_val(st, data); - if (data->is()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data) - nameid = item_data->nameid; - } - else - nameid = wrap(conv_num(st, data)); - - amount = conv_num(st, &AARG(1)); - - if (!nameid || amount <= 0) - { - //by Lupus. Don't run FOR if u got wrong item ID or amount<=0 - return; - } - - for (IOff0 i : IOff0::iter()) - { - if (sd->status.inventory[i].nameid == nameid) - { - if (sd->status.inventory[i].amount >= amount) - { - pc_delitem(sd, i, amount, 0); - break; - } - else - { - amount -= sd->status.inventory[i].amount; - if (amount == 0) - amount = sd->status.inventory[i].amount; - pc_delitem(sd, i, amount, 0); - break; - } - } - } - -} - -/*========================================== - *キャラ関係のパラメータ取得 - *------------------------------------------ - */ -static -void builtin_readparam(ScriptState *st) -{ - dumb_ptr sd; - - SP type = SP(conv_num(st, &AARG(0))); - if (HARG(1)) - sd = map_nick2sd(stringish(ZString(conv_str(st, &AARG(1))))); - else - sd = script_rid2sd(st); - - if (sd == nullptr) - { - push_int(st->stack, -1); - return; - } - - push_int(st->stack, pc_readparam(sd, type)); - -} - -/*========================================== - *キャラ関係のID取得 - *------------------------------------------ - */ -static -void builtin_getcharid(ScriptState *st) -{ - int num; - dumb_ptr sd; - - num = conv_num(st, &AARG(0)); - if (HARG(1)) - sd = map_nick2sd(stringish(ZString(conv_str(st, &AARG(1))))); - else - sd = script_rid2sd(st); - if (sd == nullptr) - { - push_int(st->stack, -1); - return; - } - if (num == 0) - push_int(st->stack, unwrap(sd->status_key.char_id)); - if (num == 1) - push_int(st->stack, unwrap(sd->status.party_id)); - if (num == 2) - push_int(st->stack, 0/*guild_id*/); - if (num == 3) - push_int(st->stack, unwrap(sd->status_key.account_id)); -} - -/*========================================== - *指定IDのPT名取得 - *------------------------------------------ - */ -static -RString builtin_getpartyname_sub(PartyId party_id) -{ - PartyPair p = party_search(party_id); - - if (p) - return p->name; - - return RString(); -} - -/*========================================== - * キャラクタの名前 - *------------------------------------------ - */ -static -void builtin_strcharinfo(ScriptState *st) -{ - dumb_ptr sd; - int num; - - sd = script_rid2sd(st); - num = conv_num(st, &AARG(0)); - if (num == 0) - { - RString buf = sd->status_key.name.to__actual(); - push_str(st->stack, buf); - } - if (num == 1) - { - RString buf = builtin_getpartyname_sub(sd->status.party_id); - if (buf) - push_str(st->stack, buf); - else - push_str(st->stack, ""_s); - } - if (num == 2) - { - // was: guild name - push_str(st->stack, ""_s); - } - -} - -// indexed by the equip_* in db/const.txt -// TODO change to use EQUIP -static -Array equip //= -{{ - EPOS::HAT, - EPOS::MISC1, - EPOS::SHIELD, - EPOS::WEAPON, - EPOS::GLOVES, - EPOS::SHOES, - EPOS::CAPE, - EPOS::MISC2, - EPOS::TORSO, - EPOS::LEGS, - EPOS::ARROW, -}}; - -/*========================================== - * GetEquipID(Pos); Pos: 1-10 - *------------------------------------------ - */ -static -void builtin_getequipid(ScriptState *st) -{ - int num; - dumb_ptr sd; - struct item_data *item; - - sd = script_rid2sd(st); - if (sd == nullptr) - { - PRINTF("getequipid: sd == nullptr\n"_fmt); - return; - } - num = conv_num(st, &AARG(0)); - IOff0 i = pc_checkequip(sd, equip[num - 1]); - if (i.ok()) - { - item = sd->inventory_data[i]; - if (item) - push_int(st->stack, unwrap(item->nameid)); - else - push_int(st->stack, 0); - } - else - { - push_int(st->stack, -1); - } -} - -/*========================================== - * 装備名文字列(精錬メニュー用) - *------------------------------------------ - */ -static -void builtin_getequipname(ScriptState *st) -{ - int num; - dumb_ptr sd; - struct item_data *item; - - AString buf; - - sd = script_rid2sd(st); - num = conv_num(st, &AARG(0)); - IOff0 i = pc_checkequip(sd, equip[num - 1]); - if (i.ok()) - { - item = sd->inventory_data[i]; - if (item) - buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], item->jname); - else - buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]); - } - else - { - buf = STRPRINTF("%s-[%s]"_fmt, pos_str[num - 1], pos_str[10]); - } - push_str(st->stack, buf); - -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_statusup2(ScriptState *st) -{ - SP type = SP(conv_num(st, &AARG(0))); - int val = conv_num(st, &AARG(1)); - dumb_ptr sd = script_rid2sd(st); - pc_statusup2(sd, type, val); - -} - -/*========================================== - * 装備品による能力値ボーナス - *------------------------------------------ - */ -static -void builtin_bonus(ScriptState *st) -{ - SP type = SP(conv_num(st, &AARG(0))); - int val = conv_num(st, &AARG(1)); - dumb_ptr sd = script_rid2sd(st); - pc_bonus(sd, type, val); - -} - -/*========================================== - * 装備品による能力値ボーナス - *------------------------------------------ - */ -static -void builtin_bonus2(ScriptState *st) -{ - SP type = SP(conv_num(st, &AARG(0))); - int type2 = conv_num(st, &AARG(1)); - int val = conv_num(st, &AARG(2)); - dumb_ptr 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, &AARG(0))); - level = conv_num(st, &AARG(1)); - if (HARG(2)) - flag = conv_num(st, &AARG(2)); - sd = script_rid2sd(st); - pc_skill(sd, id, level, flag); - clif_skillinfoblock(sd); - -} - -/*========================================== - * [Fate] Sets the skill level permanently - *------------------------------------------ - */ -static -void builtin_setskill(ScriptState *st) -{ - int level; - dumb_ptr sd; - - SkillID id = static_cast(conv_num(st, &AARG(0))); - level = conv_num(st, &AARG(1)); - sd = script_rid2sd(st); - - level = std::min(level, MAX_SKILL_LEVEL); - level = std::max(level, 0); - sd->status.skill[id].lv = level; - clif_skillinfoblock(sd); -} - -/*========================================== - * スキルレベル所得 - *------------------------------------------ - */ -static -void builtin_getskilllv(ScriptState *st) -{ - SkillID id = SkillID(conv_num(st, &AARG(0))); - push_int(st->stack, pc_checkskill(script_rid2sd(st), id)); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_getgmlevel(ScriptState *st) -{ - push_int(st->stack, pc_isGM(script_rid2sd(st)).get_all_bits()); -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_end(ScriptState *st) -{ - st->state = ScriptEndState::END; -} - -/*========================================== - * [Freeyorp] Return the current opt2 - *------------------------------------------ - */ - -static -void builtin_getopt2(ScriptState *st) -{ - dumb_ptr sd; - - sd = script_rid2sd(st); - - push_int(st->stack, static_cast(sd->opt2)); - -} - -/*========================================== - * [Freeyorp] Sets opt2 - *------------------------------------------ - */ - -static -void builtin_setopt2(ScriptState *st) -{ - dumb_ptr sd; - - Opt2 new_opt2 = Opt2(conv_num(st, &AARG(0))); - sd = script_rid2sd(st); - if (new_opt2 == sd->opt2) - return; - sd->opt2 = new_opt2; - clif_changeoption(sd); - pc_calcstatus(sd, 0); - -} - -/*========================================== - * セーブポイントの保存 - *------------------------------------------ - */ -static -void builtin_savepoint(ScriptState *st) -{ - int x, y; - - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - x = conv_num(st, &AARG(1)); - y = conv_num(st, &AARG(2)); - pc_setsavepoint(script_rid2sd(st), str, x, y); -} - -/*========================================== - * gettimetick(type) - * - * type The type of time measurement. - * Specify 0 for the system tick, 1 for - * seconds elapsed today, or 2 for seconds - * since Unix epoch. Defaults to 0 for any - * other value. - *------------------------------------------ - */ -static -void builtin_gettimetick(ScriptState *st) /* Asgard Version */ -{ - int type; - type = conv_num(st, &AARG(0)); - - switch (type) - { - /* Number of seconds elapsed today(0-86399, 00:00:00-23:59:59). */ - case 1: - { - struct tm t = TimeT::now(); - push_int(st->stack, - t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec); - break; - } - /* Seconds since Unix epoch. */ - case 2: - push_int(st->stack, static_cast(TimeT::now())); - break; - /* System tick(unsigned int, and yes, it will wrap). */ - case 0: - default: - push_int(st->stack, gettick().time_since_epoch().count()); - break; - } -} - -/*========================================== - * GetTime(Type); - * 1: Sec 2: Min 3: Hour - * 4: WeekDay 5: MonthDay 6: Month - * 7: Year - *------------------------------------------ - */ -static -void builtin_gettime(ScriptState *st) /* Asgard Version */ -{ - int type = conv_num(st, &AARG(0)); - - struct tm t = TimeT::now(); - - switch (type) - { - case 1: //Sec(0~59) - push_int(st->stack, t.tm_sec); - break; - case 2: //Min(0~59) - push_int(st->stack, t.tm_min); - break; - case 3: //Hour(0~23) - push_int(st->stack, t.tm_hour); - break; - case 4: //WeekDay(0~6) - push_int(st->stack, t.tm_wday); - break; - case 5: //MonthDay(01~31) - push_int(st->stack, t.tm_mday); - break; - case 6: //Month(01~12) - push_int(st->stack, t.tm_mon + 1); - break; - case 7: //Year(20xx) - push_int(st->stack, t.tm_year + 1900); - break; - default: //(format error) - push_int(st->stack, -1); - break; - } -} - -/*========================================== - * カプラ倉庫を開く - *------------------------------------------ - */ -static -void builtin_openstorage(ScriptState *st) -{ -// int sync = 0; -// if (st->end >= 3) sync = conv_num(st,& (st->stack->stack_data[st->start+2])); - dumb_ptr 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, &AARG(0)); - job = conv_num(st, &AARG(1)); - if (base < 0 || job < 0) - return; - if (sd) - pc_gainexp_reason(sd, base, job, PC_GAINEXP_REASON::SCRIPT); - -} - -/*========================================== - * モンスター発生 - *------------------------------------------ - */ -static -void builtin_monster(ScriptState *st) -{ - Species mob_class; - int amount, x, y; - NpcEvent event; - - MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); - x = conv_num(st, &AARG(1)); - y = conv_num(st, &AARG(2)); - MobName str = stringish(ZString(conv_str(st, &AARG(3)))); - mob_class = wrap(conv_num(st, &AARG(4))); - amount = conv_num(st, &AARG(5)); - if (HARG(6)) - extract(ZString(conv_str(st, &AARG(6))), &event); - - mob_once_spawn(map_id2sd(st->rid), mapname, x, y, str, mob_class, amount, - event); -} - -/*========================================== - * モンスター発生 - *------------------------------------------ - */ -static -void builtin_areamonster(ScriptState *st) -{ - Species mob_class; - int amount, x0, y0, x1, y1; - NpcEvent event; - - MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); - x0 = conv_num(st, &AARG(1)); - y0 = conv_num(st, &AARG(2)); - x1 = conv_num(st, &AARG(3)); - y1 = conv_num(st, &AARG(4)); - MobName str = stringish(ZString(conv_str(st, &AARG(5)))); - mob_class = wrap(conv_num(st, &AARG(6))); - amount = conv_num(st, &AARG(7)); - if (HARG(8)) - extract(ZString(conv_str(st, &AARG(8))), &event); - - mob_once_spawn_area(map_id2sd(st->rid), mapname, x0, y0, x1, y1, str, mob_class, - amount, event); -} - -/*========================================== - * モンスター削除 - *------------------------------------------ - */ -static -void builtin_killmonster_sub(dumb_ptr bl, NpcEvent event) -{ - dumb_ptr md = bl->is_mob(); - if (event) - { - if (event == md->npc_event) - mob_delete(md); - return; - } - else if (!event) - { - if (md->spawn.delay1 == static_cast(-1) - && md->spawn.delay2 == static_cast(-1)) - mob_delete(md); - return; - } -} - -static -void builtin_killmonster(ScriptState *st) -{ - MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); - ZString event_ = ZString(conv_str(st, &AARG(1))); - NpcEvent event; - if (event_ != "All"_s) - extract(event_, &event); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return; - map_foreachinarea(std::bind(builtin_killmonster_sub, ph::_1, event), - m, - 0, 0, - m->xs, m->ys, - BL::MOB); -} - -static -void builtin_killmonsterall_sub(dumb_ptr bl) -{ - mob_delete(bl->is_mob()); -} - -static -void builtin_killmonsterall(ScriptState *st) -{ - MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return; - map_foreachinarea(builtin_killmonsterall_sub, - m, - 0, 0, - m->xs, m->ys, - BL::MOB); -} - -/*========================================== - * NPC主体イベント実行 - *------------------------------------------ - */ -static -void builtin_donpcevent(ScriptState *st) -{ - ZString event_ = ZString(conv_str(st, &AARG(0))); - NpcEvent event; - extract(event_, &event); - npc_event_do(event); -} - -/*========================================== - * イベントタイマー追加 - *------------------------------------------ - */ -static -void builtin_addtimer(ScriptState *st) -{ - interval_t tick = static_cast(conv_num(st, &AARG(0))); - ZString event_ = ZString(conv_str(st, &AARG(1))); - NpcEvent event; - extract(event_, &event); - pc_addeventtimer(script_rid2sd(st), tick, event); -} - -/*========================================== - * NPCタイマー初期化 - *------------------------------------------ - */ -static -void builtin_initnpctimer(ScriptState *st) -{ - dumb_ptr nd_; - if (HARG(0)) - nd_ = npc_name2id(stringish(ZString(conv_str(st, &AARG(0))))); - else - nd_ = map_id_is_npc(st->oid); - assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); - dumb_ptr nd = nd_->is_script(); - - npc_settimerevent_tick(nd, interval_t::zero()); - npc_timerevent_start(nd); -} - -/*========================================== - * NPCタイマー開始 - *------------------------------------------ - */ -static -void builtin_startnpctimer(ScriptState *st) -{ - dumb_ptr nd_; - if (HARG(0)) - nd_ = npc_name2id(stringish(ZString(conv_str(st, &AARG(0))))); - else - nd_ = map_id_is_npc(st->oid); - assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); - dumb_ptr nd = nd_->is_script(); - - npc_timerevent_start(nd); -} - -/*========================================== - * NPCタイマー停止 - *------------------------------------------ - */ -static -void builtin_stopnpctimer(ScriptState *st) -{ - dumb_ptr nd_; - if (HARG(0)) - nd_ = npc_name2id(stringish(ZString(conv_str(st, &AARG(0))))); - else - nd_ = map_id_is_npc(st->oid); - assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); - dumb_ptr nd = nd_->is_script(); - - npc_timerevent_stop(nd); -} - -/*========================================== - * NPCタイマー情報所得 - *------------------------------------------ - */ -static -void builtin_getnpctimer(ScriptState *st) -{ - dumb_ptr nd_; - int type = conv_num(st, &AARG(0)); - int val = 0; - if (HARG(1)) - nd_ = npc_name2id(stringish(ZString(conv_str(st, &AARG(1))))); - else - nd_ = map_id_is_npc(st->oid); - assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); - dumb_ptr nd = nd_->is_script(); - - switch (type) - { - case 0: - val = npc_gettimerevent_tick(nd).count(); - break; - case 1: - val = nd->scr.timer_active; - break; - case 2: - val = nd->scr.timer_eventv.size(); - break; - } - push_int(st->stack, val); -} - -/*========================================== - * NPCタイマー値設定 - *------------------------------------------ - */ -static -void builtin_setnpctimer(ScriptState *st) -{ - dumb_ptr nd_; - interval_t tick = static_cast(conv_num(st, &AARG(0))); - if (HARG(1)) - nd_ = npc_name2id(stringish(ZString(conv_str(st, &AARG(1))))); - else - nd_ = map_id_is_npc(st->oid); - assert (nd_ && nd_->npc_subtype == NpcSubtype::SCRIPT); - dumb_ptr nd = nd_->is_script(); - - npc_settimerevent_tick(nd, tick); -} - -/*========================================== - * 天の声アナウンス - *------------------------------------------ - */ -static -void builtin_announce(ScriptState *st) -{ - int flag; - ZString str = ZString(conv_str(st, &AARG(0))); - flag = conv_num(st, &AARG(1)); - - if (flag & 0x0f) - { - dumb_ptr bl; - if (flag & 0x08) - bl = map_id2bl(st->oid); - else - bl = script_rid2sd(st); - clif_GMmessage(bl, str, flag); - } - else - intif_GMmessage(str); -} - -/*========================================== - * 天の声アナウンス(特定マップ) - *------------------------------------------ - */ -static -void builtin_mapannounce_sub(dumb_ptr bl, XString str, int flag) -{ - clif_GMmessage(bl, str, flag | 3); -} - -static -void builtin_mapannounce(ScriptState *st) -{ - int flag; - - MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); - ZString str = ZString(conv_str(st, &AARG(1))); - flag = conv_num(st, &AARG(2)); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return; - map_foreachinarea(std::bind(builtin_mapannounce_sub, ph::_1, str, flag & 0x10), - m, - 0, 0, - m->xs, m->ys, - BL::PC); -} - -/*========================================== - * ユーザー数所得 - *------------------------------------------ - */ -static -void builtin_getusers(ScriptState *st) -{ - int flag = conv_num(st, &AARG(0)); - 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_int(st->stack, val); -} - -/*========================================== - * マップ指定ユーザー数所得 - *------------------------------------------ - */ -static -void builtin_getmapusers(ScriptState *st) -{ - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - map_local *m = map_mapname2mapid(str); - if (m == nullptr) - { - push_int(st->stack, -1); - return; - } - push_int(st->stack, m->users); -} - -/*========================================== - * エリア指定ユーザー数所得 - *------------------------------------------ - */ -static -void builtin_getareausers_sub(dumb_ptr bl, int *users) -{ - if (bool(bl->is_player()->status.option & Opt0::HIDE)) - return; - (*users)++; -} - -static -void builtin_getareausers_living_sub(dumb_ptr bl, int *users) -{ - if (bool(bl->is_player()->status.option & Opt0::HIDE)) - return; - if (!pc_isdead(bl->is_player())) - (*users)++; -} - -static -void builtin_getareausers(ScriptState *st) -{ - int x0, y0, x1, y1, users = 0; - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - x0 = conv_num(st, &AARG(1)); - y0 = conv_num(st, &AARG(2)); - x1 = conv_num(st, &AARG(3)); - y1 = conv_num(st, &AARG(4)); - - int living = 0; - if (HARG(5)) - { - living = conv_num(st, &AARG(5)); - } - map_local *m = map_mapname2mapid(str); - if (m == nullptr) - { - push_int(st->stack, -1); - return; - } - map_foreachinarea(std::bind(living ? builtin_getareausers_living_sub: builtin_getareausers_sub, ph::_1, &users), - m, - x0, y0, - x1, y1, - BL::PC); - push_int(st->stack, users); -} - -/*========================================== - * エリア指定ドロップアイテム数所得 - *------------------------------------------ - */ -static -void builtin_getareadropitem_sub(dumb_ptr bl, ItemNameId item, int *amount) -{ - dumb_ptr drop = bl->is_item(); - - if (drop->item_data.nameid == item) - (*amount) += drop->item_data.amount; - -} - -static -void builtin_getareadropitem_sub_anddelete(dumb_ptr bl, ItemNameId item, int *amount) -{ - dumb_ptr drop = bl->is_item(); - - if (drop->item_data.nameid == item) - { - (*amount) += drop->item_data.amount; - clif_clearflooritem(drop, nullptr); - map_delobject(drop->bl_id, drop->bl_type); - } -} - -static -void builtin_getareadropitem(ScriptState *st) -{ - ItemNameId item; - int x0, y0, x1, y1, amount = 0, delitems = 0; - struct script_data *data; - - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - x0 = conv_num(st, &AARG(1)); - y0 = conv_num(st, &AARG(2)); - x1 = conv_num(st, &AARG(3)); - y1 = conv_num(st, &AARG(4)); - - data = &AARG(5); - get_val(st, data); - if (data->is()) - { - ZString name = ZString(conv_str(st, data)); - struct item_data *item_data = itemdb_searchname(name); - if (item_data) - item = item_data->nameid; - } - else - item = wrap(conv_num(st, data)); - - if (HARG(6)) - delitems = conv_num(st, &AARG(6)); - - map_local *m = map_mapname2mapid(str); - if (m == nullptr) - { - push_int(st->stack, -1); - return; - } - if (delitems) - map_foreachinarea(std::bind(builtin_getareadropitem_sub_anddelete, ph::_1, item, &amount), - m, - x0, y0, - x1, y1, - BL::ITEM); - else - map_foreachinarea(std::bind(builtin_getareadropitem_sub, ph::_1, item, &amount), - m, - x0, y0, - x1, y1, - BL::ITEM); - - push_int(st->stack, amount); -} - -/*========================================== - * NPCの有効化 - *------------------------------------------ - */ -static -void builtin_enablenpc(ScriptState *st) -{ - NpcName str = stringish(ZString(conv_str(st, &AARG(0)))); - npc_enable(str, 1); -} - -/*========================================== - * NPCの無効化 - *------------------------------------------ - */ -static -void builtin_disablenpc(ScriptState *st) -{ - NpcName str = stringish(ZString(conv_str(st, &AARG(0)))); - npc_enable(str, 0); -} - -/*========================================== - * 状態異常にかかる - *------------------------------------------ - */ -static -void builtin_sc_start(ScriptState *st) -{ - dumb_ptr bl; - int val1; - StatusChange type = static_cast(conv_num(st, &AARG(0))); - interval_t tick = static_cast(conv_num(st, &AARG(1))); - if (tick < 1_s) - // work around old behaviour of: - // speed potion - // atk potion - // matk potion - // - // which used to use seconds - // all others used milliseconds - tick *= 1000; - val1 = conv_num(st, &AARG(2)); - if (HARG(3)) //指定したキャラを状態異常にする - bl = map_id2bl(wrap(conv_num(st, &AARG(3)))); - else - bl = map_id2bl(st->rid); - skill_status_change_start(bl, type, val1, tick); -} - -/*========================================== - * 状態異常が直る - *------------------------------------------ - */ -static -void builtin_sc_end(ScriptState *st) -{ - dumb_ptr bl; - StatusChange type = StatusChange(conv_num(st, &AARG(0))); - bl = map_id2bl(st->rid); - skill_status_change_end(bl, type, nullptr); -} - -static -void builtin_sc_check(ScriptState *st) -{ - dumb_ptr bl; - StatusChange type = StatusChange(conv_num(st, &AARG(0))); - bl = map_id2bl(st->rid); - - push_int(st->stack, skill_status_change_active(bl, type)); - -} - -/*========================================== - * - *------------------------------------------ - */ -static -void builtin_debugmes(ScriptState *st) -{ - RString mes = conv_str(st, &AARG(0)); - PRINTF("script debug : %d %d : %s\n"_fmt, - st->rid, st->oid, mes); -} - -/*========================================== - * ステータスリセット - *------------------------------------------ - */ -static -void builtin_resetstatus(ScriptState *st) -{ - dumb_ptr sd; - sd = script_rid2sd(st); - pc_resetstate(sd); -} - -/*========================================== - * 性別変換 - *------------------------------------------ - */ -static -void builtin_changesex(ScriptState *st) -{ - dumb_ptr sd = nullptr; - sd = script_rid2sd(st); - - chrif_char_ask_name(AccountId(), sd->status_key.name, 5, HumanTimeDiff()); // type: 5 - changesex - chrif_save(sd); -} - -/*========================================== - * RIDのアタッチ - *------------------------------------------ - */ -static -void builtin_attachrid(ScriptState *st) -{ - st->rid = wrap(conv_num(st, &AARG(0))); - push_int(st->stack, (map_id2sd(st->rid) != nullptr)); -} - -/*========================================== - * RIDのデタッチ - *------------------------------------------ - */ -static -void builtin_detachrid(ScriptState *st) -{ - st->rid = BlockId(); -} - -/*========================================== - * 存在チェック - *------------------------------------------ - */ -static -void builtin_isloggedin(ScriptState *st) -{ - push_int(st->stack, - map_id2sd(wrap(conv_num(st, &AARG(0)))) != nullptr); -} - -static -void builtin_setmapflag(ScriptState *st) -{ - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - int i = conv_num(st, &AARG(1)); - MapFlag mf = map_flag_from_int(i); - map_local *m = map_mapname2mapid(str); - if (m != nullptr) - { - m->flag.set(mf, 1); - } -} - -static -void builtin_removemapflag(ScriptState *st) -{ - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - int i = conv_num(st, &AARG(1)); - MapFlag mf = map_flag_from_int(i); - map_local *m = map_mapname2mapid(str); - if (m != nullptr) - { - m->flag.set(mf, 0); - } -} - -static -void builtin_getmapflag(ScriptState *st) -{ - int r = -1; - - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - int i = conv_num(st, &AARG(1)); - MapFlag mf = map_flag_from_int(i); - map_local *m = map_mapname2mapid(str); - if (m != nullptr) - { - r = m->flag.get(mf); - } - - push_int(st->stack, r); -} - -static -void builtin_pvpon(ScriptState *st) -{ - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - map_local *m = map_mapname2mapid(str); - if (m != nullptr && !m->flag.get(MapFlag::PVP) && !m->flag.get(MapFlag::NOPVP)) - { - m->flag.set(MapFlag::PVP, 1); - - if (battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris] - return; - - for (io::FD i : iter_fds()) - { - Session *s = get_session(i); - if (!s) - continue; - map_session_data *pl_sd = static_cast(s->session_data.get()); - if (pl_sd && pl_sd->state.auth) - { - if (m == pl_sd->bl_m && !pl_sd->pvp_timer) - { - pl_sd->pvp_timer = Timer(gettick() + 200_ms, - std::bind(pc_calc_pvprank_timer, ph::_1, ph::_2, - pl_sd->bl_id)); - pl_sd->pvp_rank = 0; - pl_sd->pvp_point = 5; - } - } - } - } - -} - -static -void builtin_pvpoff(ScriptState *st) -{ - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - map_local *m = map_mapname2mapid(str); - if (m != nullptr && m->flag.get(MapFlag::PVP) && m->flag.get(MapFlag::NOPVP)) - { - m->flag.set(MapFlag::PVP, 0); - - if (battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris] - return; - - for (io::FD i : iter_fds()) - { - Session *s = get_session(i); - if (!s) - continue; - map_session_data *pl_sd = static_cast(s->session_data.get()); - if (pl_sd && pl_sd->state.auth) - { - if (m == pl_sd->bl_m) - { - pl_sd->pvp_timer.cancel(); - } - } - } - } - -} - -/*========================================== - * NPCエモーション - *------------------------------------------ - */ - -static -void builtin_emotion(ScriptState *st) -{ - int type; - type = conv_num(st, &AARG(0)); - if (type < 0 || type > 100) - return; - clif_emotion(map_id2bl(st->oid), type); -} - -static -void builtin_mapwarp(ScriptState *st) // Added by RoVeRT -{ - int x, y; - int x0, y0, x1, y1; - - MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); - x0 = 0; - y0 = 0; - map_local *m = map_mapname2mapid(mapname); - x1 = m->xs; - y1 = m->ys; - MapName str = stringish(ZString(conv_str(st, &AARG(1)))); - x = conv_num(st, &AARG(2)); - y = conv_num(st, &AARG(3)); - - if (m == nullptr) - return; - - map_foreachinarea(std::bind(builtin_areawarp_sub, ph::_1, str, x, y), - m, - x0, y0, - x1, y1, - BL::PC); -} - -static -void builtin_cmdothernpc(ScriptState *st) // Added by RoVeRT -{ - NpcName npc = stringish(ZString(conv_str(st, &AARG(0)))); - ZString command = ZString(conv_str(st, &AARG(1))); - - npc_command(map_id2sd(st->rid), npc, command); -} - -static -void builtin_mobcount_sub(dumb_ptr bl, NpcEvent event, int *c) -{ - if (event == bl->is_mob()->npc_event) - (*c)++; -} - -static -void builtin_mobcount(ScriptState *st) // Added by RoVeRT -{ - int c = 0; - MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); - ZString event_ = ZString(conv_str(st, &AARG(1))); - NpcEvent event; - extract(event_, &event); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - { - push_int(st->stack, -1); - return; - } - map_foreachinarea(std::bind(builtin_mobcount_sub, ph::_1, event, &c), - m, - 0, 0, - m->xs, m->ys, - BL::MOB); - - push_int(st->stack, (c - 1)); - -} - -static -void builtin_marriage(ScriptState *st) -{ - CharName partner = stringish(ZString(conv_str(st, &AARG(0)))); - dumb_ptr sd = script_rid2sd(st); - dumb_ptr p_sd = map_nick2sd(partner); - - if (sd == nullptr || p_sd == nullptr || pc_marriage(sd, p_sd) < 0) - { - push_int(st->stack, 0); - return; - } - push_int(st->stack, 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 == nullptr || pc_divorce(sd) < 0) - { - push_int(st->stack, 0); - return; - } - - push_int(st->stack, 1); -} - -/*========================================== - * IDからItem名 - *------------------------------------------ - */ -static -void builtin_getitemname(ScriptState *st) -{ - struct item_data *i_data; - struct script_data *data; - - data = &AARG(0); - get_val(st, data); - if (data->is()) - { - ZString name = ZString(conv_str(st, data)); - i_data = itemdb_searchname(name); - } - else - { - ItemNameId item_id = wrap(conv_num(st, data)); - i_data = itemdb_search(item_id); - } - - RString item_name; - if (i_data) - item_name = i_data->jname; - else - item_name = "Unknown Item"_s; - - push_str(st->stack, item_name); -} - -static -void builtin_getspellinvocation(ScriptState *st) -{ - RString name = conv_str(st, &AARG(0)); - - AString invocation = magic::magic_find_invocation(name); - if (!invocation) - invocation = "..."_s; - - push_str(st->stack, invocation); -} - -static -void builtin_getpartnerid2(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - - push_int(st->stack, unwrap(sd->status.partner_id)); -} - -/*========================================== - * PCの所持品情報読み取り - *------------------------------------------ - */ -static -void builtin_getinventorylist(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - int j = 0; - if (!sd) - return; - for (IOff0 i : IOff0::iter()) - { - if (sd->status.inventory[i].nameid - && sd->status.inventory[i].amount > 0) - { - pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_id"_s), j), - unwrap(sd->status.inventory[i].nameid)); - pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_amount"_s), j), - sd->status.inventory[i].amount); - pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_equip"_s), j), - static_cast(sd->status.inventory[i].equip)); - j++; - } - } - pc_setreg(sd, SIR::from(variable_names.intern("@inventorylist_count"_s)), j); -} - -static -void builtin_getactivatedpoolskilllist(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - SkillID pool_skills[MAX_SKILL_POOL]; - int skill_pool_size = skill_pool(sd, pool_skills); - int i, count = 0; - - if (!sd) - return; - - for (i = 0; i < skill_pool_size; i++) - { - SkillID skill_id = pool_skills[i]; - - if (sd->status.skill[skill_id].lv) - { - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count), - static_cast(skill_id)); - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count), - sd->status.skill[skill_id].lv); - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count), - static_cast(sd->status.skill[skill_id].flags)); - pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count), - skill_name(skill_id)); - ++count; - } - } - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count); - -} - -static -void builtin_getunactivatedpoolskilllist(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - int i, count = 0; - - if (!sd) - return; - - for (i = 0; i < skill_pool_skills_size; i++) - { - SkillID skill_id = skill_pool_skills[i]; - - if (sd->status.skill[skill_id].lv - && !bool(sd->status.skill[skill_id].flags & SkillFlags::POOL_ACTIVATED)) - { - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_id"_s), count), - static_cast(skill_id)); - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_lv"_s), count), - sd->status.skill[skill_id].lv); - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_flag"_s), count), - static_cast(sd->status.skill[skill_id].flags)); - pc_setregstr(sd, SIR::from(variable_names.intern("@skilllist_name$"_s), count), - skill_name(skill_id)); - ++count; - } - } - pc_setreg(sd, SIR::from(variable_names.intern("@skilllist_count"_s)), count); -} - -static -void builtin_poolskill(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - SkillID skill_id = SkillID(conv_num(st, &AARG(0))); - - skill_pool_activate(sd, skill_id); - clif_skillinfoblock(sd); - -} - -static -void builtin_unpoolskill(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - SkillID skill_id = SkillID(conv_num(st, &AARG(0))); - - skill_pool_deactivate(sd, skill_id); - clif_skillinfoblock(sd); - -} - -/*========================================== - * NPCから発生するエフェクト - * misceffect(effect, [target]) - * - * effect The effect type/ID. - * target The player name or being ID on - * which to display the effect. If not - * specified, it attempts to default to - * the current NPC or invoking PC. - *------------------------------------------ - */ -static -void builtin_misceffect(ScriptState *st) -{ - int type; - BlockId id; - CharName name; - dumb_ptr bl = nullptr; - - type = conv_num(st, &AARG(0)); - - if (HARG(1)) - { - struct script_data *sdata = &AARG(1); - - get_val(st, sdata); - - if (sdata->is()) - name = stringish(ZString(conv_str(st, sdata))); - else - id = wrap(conv_num(st, sdata)); - } - - if (name.to__actual()) - { - dumb_ptr sd = map_nick2sd(name); - if (sd) - bl = sd; - } - else if (id) - bl = map_id2bl(id); - else if (st->oid) - bl = map_id2bl(st->oid); - else - { - dumb_ptr 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 == nullptr) - return; - - clif_specialeffect(bl, - conv_num(st, - &AARG(0)), - 0); - -} - -static -void builtin_specialeffect2(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - - if (sd == nullptr) - return; - - clif_specialeffect(sd, - conv_num(st, - &AARG(0)), - 0); - -} - -/*========================================== - * Nude [Valaris] - *------------------------------------------ - */ - -static -void builtin_nude(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - - if (sd == nullptr) - return; - - for (EQUIP i : EQUIPs) - { - IOff0 idx = sd->equip_index_maybe[i]; - if (idx.ok()) - pc_unequipitem(sd, idx, CalcStatus::LATER); - } - pc_calcstatus(sd, 0); - -} - -/*========================================== - * UnequipById [Freeyorp] - *------------------------------------------ - */ - -static -void builtin_unequipbyid(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - if (sd == nullptr) - return; - - EQUIP slot_id = EQUIP(conv_num(st, &AARG(0))); - - if (slot_id >= EQUIP() && slot_id < EQUIP::COUNT) - { - IOff0 idx = sd->equip_index_maybe[slot_id]; - if (idx.ok()) - pc_unequipitem(sd, idx, CalcStatus::LATER); - } - - pc_calcstatus(sd, 0); - -} - -/*========================================== - * gmcommand [MouseJstr] - * - * suggested on the forums... - *------------------------------------------ - */ - -static -void builtin_gmcommand(ScriptState *st) -{ - dumb_ptr sd; - - sd = script_rid2sd(st); - RString cmd = conv_str(st, &AARG(0)); - - is_atcommand(sd->sess, sd, cmd, GmLevel::from(-1U)); - -} - -/*========================================== - * npcwarp [remoitnane] - * Move NPC to a new position on the same map. - *------------------------------------------ - */ -static -void builtin_npcwarp(ScriptState *st) -{ - int x, y; - dumb_ptr nd = nullptr; - - x = conv_num(st, &AARG(0)); - y = conv_num(st, &AARG(1)); - NpcName npc = stringish(ZString(conv_str(st, &AARG(2)))); - nd = npc_name2id(npc); - - if (!nd) - { - PRINTF("builtin_npcwarp: no such npc: %s\n"_fmt, npc); - return; - } - - map_local *m = nd->bl_m; - - /* Crude sanity checks. */ - if (m == nullptr || !nd->bl_prev - || x < 0 || x > m->xs -1 - || y < 0 || y > m->ys - 1) - return; - - npc_enable(npc, 0); - map_delblock(nd); /* [Freeyorp] */ - nd->bl_x = x; - nd->bl_y = y; - map_addblock(nd); - npc_enable(npc, 1); - -} - -/*========================================== - * message [MouseJstr] - *------------------------------------------ - */ - -static -void builtin_message(ScriptState *st) -{ - CharName player = stringish(ZString(conv_str(st, &AARG(0)))); - ZString msg = ZString(conv_str(st, &AARG(1))); - - dumb_ptr pl_sd = map_nick2sd(player); - if (pl_sd == nullptr) - return; - clif_displaymessage(pl_sd->sess, msg); - -} - -/*========================================== - * npctalk (sends message to surrounding - * area) [Valaris] - *------------------------------------------ - */ - -static -void builtin_npctalk(ScriptState *st) -{ - dumb_ptr nd = map_id_is_npc(st->oid); - RString str = conv_str(st, &AARG(0)); - - if (nd) - { - clif_message(nd, str); - } -} - -/*========================================== - * getlook char info. getlook(arg) - *------------------------------------------ - */ -static -void builtin_getlook(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - - LOOK type = LOOK(conv_num(st, &AARG(0))); - int val = -1; - switch (type) - { - case LOOK::HAIR: //1 - val = sd->status.hair; - break; - case LOOK::WEAPON: //2 - val = static_cast(sd->status.weapon); - break; - case LOOK::HEAD_BOTTOM: //3 - val = unwrap(sd->status.head_bottom); - break; - case LOOK::HEAD_TOP: //4 - val = unwrap(sd->status.head_top); - break; - case LOOK::HEAD_MID: //5 - val = unwrap(sd->status.head_mid); - break; - case LOOK::HAIR_COLOR: //6 - val = sd->status.hair_color; - break; - case LOOK::CLOTHES_COLOR: //7 - val = sd->status.clothes_color; - break; - case LOOK::SHIELD: //8 - val = unwrap(sd->status.shield); - break; - case LOOK::SHOES: //9 - break; - } - - push_int(st->stack, val); -} - -/*========================================== - * get char save point. argument: 0- map name, 1- x, 2- y - *------------------------------------------ -*/ -static -void builtin_getsavepoint(ScriptState *st) -{ - int x, y, type; - dumb_ptr sd; - - sd = script_rid2sd(st); - - type = conv_num(st, &AARG(0)); - - x = sd->status.save_point.x; - y = sd->status.save_point.y; - switch (type) - { - case 0: - { - RString mapname = sd->status.save_point.map_; - push_str(st->stack, mapname); - } - break; - case 1: - push_int(st->stack, x); - break; - case 2: - push_int(st->stack, y); - break; - } -} - -/*========================================== - * areatimer - *------------------------------------------ - */ -static -void builtin_areatimer_sub(dumb_ptr bl, interval_t tick, NpcEvent event) -{ - pc_addeventtimer(bl->is_player(), tick, event); -} - -static -void builtin_areatimer(ScriptState *st) -{ - int x0, y0, x1, y1; - - MapName mapname = stringish(ZString(conv_str(st, &AARG(0)))); - x0 = conv_num(st, &AARG(1)); - y0 = conv_num(st, &AARG(2)); - x1 = conv_num(st, &AARG(3)); - y1 = conv_num(st, &AARG(4)); - interval_t tick = static_cast(conv_num(st, &AARG(5))); - ZString event_ = ZString(conv_str(st, &AARG(6))); - NpcEvent event; - extract(event_, &event); - - map_local *m = map_mapname2mapid(mapname); - if (m == nullptr) - return; - - map_foreachinarea(std::bind(builtin_areatimer_sub, ph::_1, tick, event), - m, - x0, y0, - x1, y1, - BL::PC); -} - -/*========================================== - * Check whether the PC is in the specified rectangle - *------------------------------------------ - */ -static -void builtin_isin(ScriptState *st) -{ - int x1, y1, x2, y2; - dumb_ptr sd = script_rid2sd(st); - - MapName str = stringish(ZString(conv_str(st, &AARG(0)))); - x1 = conv_num(st, &AARG(1)); - y1 = conv_num(st, &AARG(2)); - x2 = conv_num(st, &AARG(3)); - y2 = conv_num(st, &AARG(4)); - - if (!sd) - return; - - push_int(st->stack, - (sd->bl_x >= x1 && sd->bl_x <= x2) - && (sd->bl_y >= y1 && sd->bl_y <= y2) - && (str == sd->bl_m->name_)); -} - -// Trigger the shop on a (hopefully) nearby shop NPC -static -void builtin_shop(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - dumb_ptr nd; - - if (!sd) - return; - - NpcName name = stringish(ZString(conv_str(st, &AARG(0)))); - nd = npc_name2id(name); - if (!nd) - { - PRINTF("builtin_shop: no such npc: %s\n"_fmt, name); - return; - } - - builtin_close(st); - clif_npcbuysell(sd, nd->bl_id); -} - -/*========================================== - * Check whether the PC is dead - *------------------------------------------ - */ -static -void builtin_isdead(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - - push_int(st->stack, pc_isdead(sd)); -} - -/*======================================== - * Changes a NPC name, and sprite - *---------------------------------------- - */ -static -void builtin_fakenpcname(ScriptState *st) -{ - NpcName name = stringish(ZString(conv_str(st, &AARG(0)))); - NpcName newname = stringish(ZString(conv_str(st, &AARG(1)))); - Species newsprite = wrap(static_cast(conv_num(st, &AARG(2)))); - dumb_ptr nd = npc_name2id(name); - if (!nd) - { - PRINTF("builtin_fakenpcname: no such npc: %s\n"_fmt, name); - return; - } - nd->name = newname; - nd->npc_class = newsprite; - - // Refresh this npc - npc_enable(name, 0); - npc_enable(name, 1); - -} - -/*============================ - * Gets the PC's x pos - *---------------------------- - */ -static -void builtin_getx(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - - push_int(st->stack, sd->bl_x); -} - -/*============================ - * Gets the PC's y pos - *---------------------------- - */ -static -void builtin_gety(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - - push_int(st->stack, sd->bl_y); -} - -/* - * Get the PC's current map's name - */ -static -void builtin_getmap(ScriptState *st) -{ - dumb_ptr sd = script_rid2sd(st); - - push_str(st->stack, sd->bl_m->name_); -} - -static -void builtin_mapexit(ScriptState *) -{ - runflag = 0; -} - - -// -// 実行部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 (auto *u = back.get_if()) - rv = u->numi; - st->stack->stack_datav.pop_back(); - return rv; -} - -static -bool isstr(struct script_data& c) -{ - return c.is(); -} - -/*========================================== - * 加算演算子 - *------------------------------------------ - */ -static -void op_add(ScriptState *st) -{ - get_val(st, &st->stack->stack_datav.back()); - script_data back = st->stack->stack_datav.back(); - st->stack->stack_datav.pop_back(); - - script_data& back1 = st->stack->stack_datav.back(); - get_val(st, &back1); - - if (!(isstr(back) || isstr(back1))) - { - back1.get_if()->numi += back.get_if()->numi; - } - else - { - RString sb = conv_str(st, &back); - RString sb1 = conv_str(st, &back1); - MString buf; - buf += sb1; - buf += sb; - back1 = ScriptDataStr{.str= AString(buf)}; - } -} - -/*========================================== - * 二項演算子(文字列) - *------------------------------------------ - */ -static -void op_2str(ScriptState *st, ByteCode op, ZString s1, ZString s2) -{ - int a = 0; - - switch (op) - { - case ByteCode::EQ: - a = s1 == s2; - break; - case ByteCode::NE: - a = s1 != s2; - break; - case ByteCode::GT: - a = s1 > s2; - break; - case ByteCode::GE: - a = s1 >= s2; - break; - case ByteCode::LT: - a = s1 < s2; - break; - case ByteCode::LE: - a = s1 <= s2; - break; - default: - PRINTF("illegal string operater\n"_fmt); - break; - } - - push_int(st->stack, a); -} - -/*========================================== - * 二項演算子(数値) - *------------------------------------------ - */ -static -void op_2num(ScriptState *st, ByteCode op, int i1, int i2) -{ - switch (op) - { - case ByteCode::SUB: - i1 -= i2; - break; - case ByteCode::MUL: - i1 *= i2; - break; - case ByteCode::DIV: - i1 /= i2; - break; - case ByteCode::MOD: - i1 %= i2; - break; - case ByteCode::AND: - i1 &= i2; - break; - case ByteCode::OR: - i1 |= i2; - break; - case ByteCode::XOR: - i1 ^= i2; - break; - case ByteCode::LAND: - i1 = i1 && i2; - break; - case ByteCode::LOR: - i1 = i1 || i2; - break; - case ByteCode::EQ: - i1 = i1 == i2; - break; - case ByteCode::NE: - i1 = i1 != i2; - break; - case ByteCode::GT: - i1 = i1 > i2; - break; - case ByteCode::GE: - i1 = i1 >= i2; - break; - case ByteCode::LT: - i1 = i1 < i2; - break; - case ByteCode::LE: - i1 = i1 <= i2; - break; - case ByteCode::R_SHIFT: - i1 = i1 >> i2; - break; - case ByteCode::L_SHIFT: - i1 = i1 << i2; - break; - } - push_int(st->stack, i1); -} - -/*========================================== - * 二項演算子 - *------------------------------------------ - */ -static -void op_2(ScriptState *st, ByteCode op) -{ - // pop_val has unfortunate implications here - script_data d2 = st->stack->stack_datav.back(); - st->stack->stack_datav.pop_back(); - get_val(st, &d2); - script_data d1 = st->stack->stack_datav.back(); - st->stack->stack_datav.pop_back(); - get_val(st, &d1); - - if (isstr(d1) && isstr(d2)) - { - // ss => op_2str - op_2str(st, op, d1.get_if()->str, d2.get_if()->str); - } - else if (!(isstr(d1) || isstr(d2))) - { - // ii => op_2num - op_2num(st, op, d1.get_if()->numi, d2.get_if()->numi); - } - else - { - // si,is => error - PRINTF("script: op_2: int&str, str&int not allow.\n"_fmt); - push_int(st->stack, 0); - } -} - -/*========================================== - * 単項演算子 - *------------------------------------------ - */ -static -void op_1num(ScriptState *st, ByteCode op) -{ - int i1; - i1 = pop_val(st); - switch (op) - { - case ByteCode::NEG: - i1 = -i1; - break; - case ByteCode::NOT: - i1 = ~i1; - break; - case ByteCode::LNOT: - i1 = !i1; - break; - } - push_int(st->stack, i1); -} - -/*========================================== - * 関数の実行 - *------------------------------------------ - */ -void run_func(ScriptState *st) -{ - size_t end_sp = st->stack->stack_datav.size(); - size_t start_sp = end_sp - 1; - while (!st->stack->stack_datav[start_sp].is()) - { - start_sp--; - if (start_sp == 0) - { - if (battle_config.error_log) - PRINTF("function not found\n"_fmt); - st->state = ScriptEndState::END; - return; - } - } - // the func is before the arg - start_sp--; - st->start = start_sp; - st->end = end_sp; - - if (!st->stack->stack_datav[st->start].is()) - { - PRINTF("run_func: not function and command! \n"_fmt); - st->state = ScriptEndState::END; - return; - } - size_t func = st->stack->stack_datav[st->start].get_if()->numi; - - if (DEBUG_RUN && battle_config.etc_log) - { - PRINTF("run_func : %s\n"_fmt, - builtin_functions[func].name); - PRINTF("stack dump :"_fmt); - for (script_data& d : st->stack->stack_datav) - { - MATCH (d) - { - CASE (const ScriptDataInt&, u) - { - PRINTF(" int(%d)"_fmt, u.numi); - } - CASE (const ScriptDataRetInfo&, u) - { - PRINTF(" retinfo(%p)"_fmt, static_cast(u.script)); - } - CASE (const ScriptDataParam&, u) - { - PRINTF(" param(%d)"_fmt, u.reg.sp()); - } - CASE (const ScriptDataVariable&, u) - { - PRINTF(" name(%s)[%d]"_fmt, variable_names.outtern(u.reg.base()), u.reg.index()); - } - CASE (const ScriptDataArg&, u) - { - (void)u; - PRINTF(" arg"_fmt); - } - CASE (const ScriptDataPos&, u) - { - (void)u; - PRINTF(" pos(%d)"_fmt, u.numi); - } - CASE (const ScriptDataStr&, u) - { - (void)u; - PRINTF(" str(%s)"_fmt, u.str); - } - CASE (const ScriptDataFuncRef&, u) - { - (void)u; - PRINTF(" func(%s)"_fmt, builtin_functions[u.numi].name); - } - } - } - PRINTF("\n"_fmt); - } - builtin_functions[func].func(st); - - pop_stack(st->stack, start_sp, end_sp); - - if (st->state == ScriptEndState::RETFUNC) - { - // ユーザー定義関数からの復帰 - int olddefsp = st->defsp; - - pop_stack(st->stack, st->defsp, start_sp); // 復帰に邪魔なスタック削除 - if (st->defsp < 4 - || !st->stack->stack_datav[st->defsp - 1].is()) - { - PRINTF("script:run_func (return) return without callfunc or callsub!\n"_fmt); - st->state = ScriptEndState::END; - return; - } - assert (olddefsp == st->defsp); // pretty sure it hasn't changed yet - st->scriptp.code = conv_script(st, &st->stack->stack_datav[olddefsp - 1]); // スクリプトを復元 - st->scriptp.pos = conv_num(st, &st->stack->stack_datav[olddefsp - 2]); // スクリプト位置の復元 - st->defsp = conv_num(st, &st->stack->stack_datav[olddefsp - 3]); // 基準スタックポインタを復元 - // Number of arguments. - int i = conv_num(st, &st->stack->stack_datav[olddefsp - 4]); // 引数の数所得 - assert (i == 0); - - pop_stack(st->stack, olddefsp - 4 - i, olddefsp); // 要らなくなったスタック(引数と復帰用データ)削除 - - st->state = ScriptEndState::GOTO; - } -} - -// pretend it's external so this can be called in the debugger -void dump_script(const ScriptBuffer *script); -void dump_script(const ScriptBuffer *script) -{ - ScriptPointer scriptp(script, 0); - while (scriptp.pos < reinterpret_cast *>(script)->size()) - { - PRINTF("%6zu: "_fmt, scriptp.pos); - switch (ByteCode c = get_com(&scriptp)) - { - case ByteCode::EOL: - PRINTF("EOL\n"_fmt); // extra newline between functions - break; - case ByteCode::INT: - // synthesized! - PRINTF("INT %d"_fmt, get_num(&scriptp)); - break; - - case ByteCode::POS: - case ByteCode::VARIABLE: - case ByteCode::FUNC_REF: - case ByteCode::PARAM: - { - int arg = 0; - arg |= static_cast(scriptp.pop()) << 0; - arg |= static_cast(scriptp.pop()) << 8; - arg |= static_cast(scriptp.pop()) << 16; - switch(c) - { - case ByteCode::POS: - PRINTF("POS %d"_fmt, arg); - break; - case ByteCode::VARIABLE: - PRINTF("VARIABLE %s"_fmt, variable_names.outtern(arg)); - break; - case ByteCode::FUNC_REF: - PRINTF("FUNC_REF %s"_fmt, builtin_functions[arg].name); - break; - case ByteCode::PARAM: - PRINTF("PARAM SP::#%d (sorry)"_fmt, arg); - break; - } - } - break; - case ByteCode::ARG: - PRINTF("ARG"_fmt); - break; - case ByteCode::STR: - PRINTF("STR \"%s\""_fmt, scriptp.pops()); - break; - case ByteCode::FUNC: - PRINTF("FUNC"_fmt); - break; - - case ByteCode::ADD: - PRINTF("ADD"_fmt); - break; - case ByteCode::SUB: - PRINTF("SUB"_fmt); - break; - case ByteCode::MUL: - PRINTF("MUL"_fmt); - break; - case ByteCode::DIV: - PRINTF("DIV"_fmt); - break; - case ByteCode::MOD: - PRINTF("MOD"_fmt); - break; - case ByteCode::EQ: - PRINTF("EQ"_fmt); - break; - case ByteCode::NE: - PRINTF("NE"_fmt); - break; - case ByteCode::GT: - PRINTF("GT"_fmt); - break; - case ByteCode::GE: - PRINTF("GE"_fmt); - break; - case ByteCode::LT: - PRINTF("LT"_fmt); - break; - case ByteCode::LE: - PRINTF("LE"_fmt); - break; - case ByteCode::AND: - PRINTF("AND"_fmt); - break; - case ByteCode::OR: - PRINTF("OR"_fmt); - break; - case ByteCode::XOR: - PRINTF("XOR"_fmt); - break; - case ByteCode::LAND: - PRINTF("LAND"_fmt); - break; - case ByteCode::LOR: - PRINTF("LOR"_fmt); - break; - case ByteCode::R_SHIFT: - PRINTF("R_SHIFT"_fmt); - break; - case ByteCode::L_SHIFT: - PRINTF("L_SHIFT"_fmt); - break; - case ByteCode::NEG: - PRINTF("NEG"_fmt); - break; - case ByteCode::NOT: - PRINTF("NOT"_fmt); - break; - case ByteCode::LNOT: - PRINTF("LNOT"_fmt); - break; - - case ByteCode::NOP: - PRINTF("NOP"_fmt); - break; - - default: - PRINTF("??? %d"_fmt, c); - break; - } - PRINTF("\n"_fmt); - } -} - -/*========================================== - * スクリプトの実行メイン部分 - *------------------------------------------ - */ -static -void run_script_main(ScriptState *st, const ScriptBuffer *rootscript) -{ - int cmdcount = script_config.check_cmdcount; - int gotocount = script_config.check_gotocount; - struct script_stack *stack = st->stack; - - st->defsp = stack->stack_datav.size(); - - int rerun_pos = st->scriptp.pos; - st->state = ScriptEndState::ZERO; - while (st->state == ScriptEndState::ZERO) - { - switch (ByteCode c = get_com(&st->scriptp)) - { - case ByteCode::EOL: - if (stack->stack_datav.size() != st->defsp) - { - if (true) - PRINTF("stack.sp (%zu) != default (%d)\n"_fmt, - stack->stack_datav.size(), - st->defsp); - abort(); - } - rerun_pos = st->scriptp.pos; - break; - case ByteCode::INT: - // synthesized! - push_int(stack, get_num(&st->scriptp)); - break; - - case ByteCode::POS: - case ByteCode::VARIABLE: - case ByteCode::FUNC_REF: - case ByteCode::PARAM: - // Note that these 3 have *very* different meanings, - // despite being encoded similarly. - { - int arg = 0; - arg |= static_cast(st->scriptp.pop()) << 0; - arg |= static_cast(st->scriptp.pop()) << 8; - arg |= static_cast(st->scriptp.pop()) << 16; - switch(c) - { - case ByteCode::POS: - push_int(stack, arg); - break; - case ByteCode::VARIABLE: - push_reg(stack, SIR::from(arg)); - break; - case ByteCode::FUNC_REF: - push_int(stack, arg); - break; - case ByteCode::PARAM: - SP arg_sp = static_cast(arg); - push_reg(stack, SIR::from(arg_sp)); - break; - } - } - break; - case ByteCode::ARG: - push_int(stack, 0); - break; - case ByteCode::STR: - push_str(stack, st->scriptp.pops()); - break; - case ByteCode::FUNC: - run_func(st); - if (st->state == ScriptEndState::GOTO) - { - rerun_pos = st->scriptp.pos; - st->state = ScriptEndState::ZERO; - if (gotocount > 0 && (--gotocount) <= 0) - { - PRINTF("run_script: infinity loop !\n"_fmt); - st->state = ScriptEndState::END; - } - } - break; - - case ByteCode::ADD: - op_add(st); - break; - - case ByteCode::SUB: - case ByteCode::MUL: - case ByteCode::DIV: - case ByteCode::MOD: - case ByteCode::EQ: - case ByteCode::NE: - case ByteCode::GT: - case ByteCode::GE: - case ByteCode::LT: - case ByteCode::LE: - case ByteCode::AND: - case ByteCode::OR: - case ByteCode::XOR: - case ByteCode::LAND: - case ByteCode::LOR: - case ByteCode::R_SHIFT: - case ByteCode::L_SHIFT: - op_2(st, c); - break; - - case ByteCode::NEG: - case ByteCode::NOT: - case ByteCode::LNOT: - op_1num(st, c); - break; - - case ByteCode::NOP: - st->state = ScriptEndState::END; - break; - - default: - if (battle_config.error_log) - PRINTF("unknown command : %d @ %zu\n"_fmt, - c, st->scriptp.pos); - st->state = ScriptEndState::END; - break; - } - if (cmdcount > 0 && (--cmdcount) <= 0) - { - PRINTF("run_script: infinity loop !\n"_fmt); - st->state = ScriptEndState::END; - } - } - switch (st->state) - { - case ScriptEndState::STOP: - break; - case ScriptEndState::END: - { - dumb_ptr 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, BlockId rid, BlockId oid) -{ - return run_script_l(sp, rid, oid, nullptr); -} - -int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid, - Slice args) -{ - struct script_stack stack; - ScriptState st; - dumb_ptr sd = map_id2sd(rid); - const ScriptBuffer *rootscript = sp.code; - int i; - if (sp.code == nullptr || sp.pos >> 24) - return -1; - - if (sd && !sd->npc_stackbuf.empty() && sd->npc_scriptroot == rootscript) - { - // 前回のスタックを復帰 - sp.code = sd->npc_script; - stack.stack_datav = std::move(sd->npc_stackbuf); - } - st.stack = &stack; - st.scriptp = sp; - st.rid = rid; - st.oid = oid; - for (i = 0; i < args.size(); i++) - { - if (args[i].name.back() == '$') - pc_setregstr(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.s); - else - pc_setreg(sd, SIR::from(variable_names.intern(args[i].name)), args[i].v.i); - } - run_script_main(&st, rootscript); - - stack.stack_datav.clear(); - return st.scriptp.pos; -} - -/*========================================== - * マップ変数の変更 - *------------------------------------------ - */ -void mapreg_setreg(SIR reg, int val) -{ - mapreg_db.put(reg, val); - - mapreg_dirty = 1; -} - -/*========================================== - * 文字列型マップ変数の変更 - *------------------------------------------ - */ -void mapreg_setregstr(SIR reg, XString str) -{ - if (!str) - mapregstr_db.erase(reg); - else - mapregstr_db.insert(reg, str); - - mapreg_dirty = 1; -} - -/*========================================== - * 永続的マップ変数の読み込み - *------------------------------------------ - */ -static -void script_load_mapreg(void) -{ - io::ReadFile in(mapreg_txt); - - if (!in.is_open()) - return; - - AString line; - while (in.getline(line)) - { - XString buf1, buf2; - int index = 0; - if (extract(line, - record<'\t'>( - record<','>(&buf1, &index), - &buf2)) - || extract(line, - record<'\t'>( - record<','>(&buf1), - &buf2))) - { - int s = variable_names.intern(buf1); - SIR key = SIR::from(s, index); - if (buf1.back() == '$') - { - mapregstr_db.insert(key, buf2); - } - else - { - int v; - if (!extract(buf2, &v)) - goto borken; - mapreg_db.put(key, v); - } - } - else - { - borken: - PRINTF("%s: %s broken data !\n"_fmt, mapreg_txt, AString(buf1)); - continue; - } - } - mapreg_dirty = 0; -} - -/*========================================== - * 永続的マップ変数の書き込み - *------------------------------------------ - */ -static -void script_save_mapreg_intsub(SIR key, int data, io::WriteFile& fp) -{ - int num = key.base(), i = key.index(); - ZString name = variable_names.outtern(num); - if (name[1] != '@') - { - if (i == 0) - FPRINTF(fp, "%s\t%d\n"_fmt, name, data); - else - FPRINTF(fp, "%s,%d\t%d\n"_fmt, name, i, data); - } -} - -static -void script_save_mapreg_strsub(SIR key, ZString data, io::WriteFile& fp) -{ - int num = key.base(), i = key.index(); - ZString name = variable_names.outtern(num); - if (name[1] != '@') - { - if (i == 0) - FPRINTF(fp, "%s\t%s\n"_fmt, name, data); - else - FPRINTF(fp, "%s,%d\t%s\n"_fmt, name, i, data); - } -} - -static -void script_save_mapreg(void) -{ - io::WriteLock fp(mapreg_txt); - if (!fp.is_open()) - return; - for (auto& pair : mapreg_db) - script_save_mapreg_intsub(pair.first, pair.second, fp); - for (auto& pair : mapregstr_db) - script_save_mapreg_strsub(pair.first, pair.second, fp); - mapreg_dirty = 0; -} - -static -void script_autosave_mapreg(TimerData *, tick_t) -{ - if (mapreg_dirty) - script_save_mapreg(); -} - -void do_final_script(void) -{ - if (mapreg_dirty >= 0) - script_save_mapreg(); - - mapreg_db.clear(); - mapregstr_db.clear(); - scriptlabel_db.clear(); - userfunc_db.clear(); - - str_datam.clear(); -} - -/*========================================== - * 初期化 - *------------------------------------------ - */ -void do_init_script(void) -{ - script_load_mapreg(); - - Timer(gettick() + MAPREG_AUTOSAVE_INTERVAL, - script_autosave_mapreg, - MAPREG_AUTOSAVE_INTERVAL - ).detach(); -} - -#define BUILTIN(func, args, ret) \ -{builtin_##func, #func ## _s, args, ret} - -BuiltinFunction builtin_functions[] = -{ - BUILTIN(mes, "s"_s, '\0'), - BUILTIN(goto, "L"_s, '\0'), - BUILTIN(callfunc, "F"_s, '\0'), - BUILTIN(callsub, "L"_s, '\0'), - BUILTIN(return, ""_s, '\0'), - BUILTIN(next, ""_s, '\0'), - BUILTIN(close, ""_s, '\0'), - BUILTIN(close2, ""_s, '\0'), - BUILTIN(menu, "sL**"_s, '\0'), - BUILTIN(rand, "i?"_s, 'i'), - BUILTIN(isat, "Mxy"_s, 'i'), - BUILTIN(warp, "Mxy"_s, '\0'), - BUILTIN(areawarp, "MxyxyMxy"_s, '\0'), - BUILTIN(heal, "ii"_s, '\0'), - BUILTIN(itemheal, "ii"_s, '\0'), - BUILTIN(percentheal, "ii"_s, '\0'), - BUILTIN(input, "N"_s, '\0'), - BUILTIN(if, "iF*"_s, '\0'), - BUILTIN(set, "Ne"_s, '\0'), - BUILTIN(setarray, "Ne*"_s, '\0'), - BUILTIN(cleararray, "Nei"_s, '\0'), - BUILTIN(getarraysize, "N"_s, 'i'), - BUILTIN(getelementofarray, "Ni"_s, '.'), - BUILTIN(setlook, "ii"_s, '\0'), - BUILTIN(countitem, "I"_s, 'i'), - BUILTIN(checkweight, "Ii"_s, 'i'), - BUILTIN(getitem, "Ii??"_s, '\0'), - BUILTIN(makeitem, "IiMxy"_s, '\0'), - BUILTIN(delitem, "Ii"_s, '\0'), - BUILTIN(readparam, "i?"_s, 'i'), - BUILTIN(getcharid, "i?"_s, 'i'), - BUILTIN(strcharinfo, "i"_s, 's'), - BUILTIN(getequipid, "i"_s, 'i'), - BUILTIN(getequipname, "i"_s, 's'), - BUILTIN(statusup2, "ii"_s, '\0'), - BUILTIN(bonus, "ii"_s, '\0'), - BUILTIN(bonus2, "iii"_s, '\0'), - BUILTIN(skill, "ii?"_s, '\0'), - BUILTIN(setskill, "ii"_s, '\0'), - BUILTIN(getskilllv, "i"_s, 'i'), - BUILTIN(getgmlevel, ""_s, 'i'), - BUILTIN(end, ""_s, '\0'), - BUILTIN(getopt2, ""_s, 'i'), - BUILTIN(setopt2, "i"_s, '\0'), - BUILTIN(savepoint, "Mxy"_s, '\0'), - BUILTIN(gettimetick, "i"_s, 'i'), - BUILTIN(gettime, "i"_s, 'i'), - BUILTIN(openstorage, ""_s, '\0'), - BUILTIN(getexp, "ii"_s, '\0'), - BUILTIN(monster, "Mxysmi?"_s, '\0'), - BUILTIN(areamonster, "Mxyxysmi?"_s, '\0'), - BUILTIN(killmonster, "ME"_s, '\0'), - BUILTIN(killmonsterall, "M"_s, '\0'), - BUILTIN(donpcevent, "E"_s, '\0'), - BUILTIN(addtimer, "tE"_s, '\0'), - BUILTIN(initnpctimer, ""_s, '\0'), - BUILTIN(startnpctimer, "?"_s, '\0'), - BUILTIN(stopnpctimer, ""_s, '\0'), - BUILTIN(getnpctimer, "i"_s, 'i'), - BUILTIN(setnpctimer, "i"_s, '\0'), - BUILTIN(announce, "si"_s, '\0'), - BUILTIN(mapannounce, "Msi"_s, '\0'), - BUILTIN(getusers, "i"_s, 'i'), - BUILTIN(getmapusers, "M"_s, 'i'), - BUILTIN(getareausers, "Mxyxy?"_s, 'i'), - BUILTIN(getareadropitem, "Mxyxyi?"_s, 'i'), - BUILTIN(enablenpc, "s"_s, '\0'), - BUILTIN(disablenpc, "s"_s, '\0'), - BUILTIN(sc_start, "iTi?"_s, '\0'), - BUILTIN(sc_end, "i"_s, '\0'), - BUILTIN(sc_check, "i"_s, 'i'), - BUILTIN(debugmes, "s"_s, '\0'), - BUILTIN(resetstatus, ""_s, '\0'), - BUILTIN(changesex, ""_s, '\0'), - BUILTIN(attachrid, "i"_s, 'i'), - BUILTIN(detachrid, ""_s, '\0'), - BUILTIN(isloggedin, "i"_s, 'i'), - BUILTIN(setmapflag, "Mi"_s, '\0'), - BUILTIN(removemapflag, "Mi"_s, '\0'), - BUILTIN(getmapflag, "Mi"_s, 'i'), - BUILTIN(pvpon, "M"_s, '\0'), - BUILTIN(pvpoff, "M"_s, '\0'), - BUILTIN(emotion, "i"_s, '\0'), - BUILTIN(mapwarp, "MMxy"_s, '\0'), - BUILTIN(cmdothernpc, "ss"_s, '\0'), - BUILTIN(mobcount, "ME"_s, 'i'), - BUILTIN(marriage, "P"_s, 'i'), - BUILTIN(divorce, ""_s, 'i'), - BUILTIN(getitemname, "I"_s, 's'), - BUILTIN(getspellinvocation, "s"_s, 's'), - BUILTIN(getpartnerid2, ""_s, 'i'), - BUILTIN(getinventorylist, ""_s, '\0'), - BUILTIN(getactivatedpoolskilllist, ""_s, '\0'), - BUILTIN(getunactivatedpoolskilllist, ""_s, '\0'), - BUILTIN(poolskill, "i"_s, '\0'), - BUILTIN(unpoolskill, "i"_s, '\0'), - BUILTIN(misceffect, "i?"_s, '\0'), - BUILTIN(specialeffect, "i"_s, '\0'), - BUILTIN(specialeffect2, "i"_s, '\0'), - BUILTIN(nude, ""_s, '\0'), - BUILTIN(unequipbyid, "i"_s, '\0'), - BUILTIN(gmcommand, "s"_s, '\0'), - BUILTIN(npcwarp, "xys"_s, '\0'), - BUILTIN(message, "Ps"_s, '\0'), - BUILTIN(npctalk, "s"_s, '\0'), - BUILTIN(getlook, "i"_s, 'i'), - BUILTIN(getsavepoint, "i"_s, '.'), - BUILTIN(areatimer, "MxyxytE"_s, '\0'), - BUILTIN(isin, "Mxyxy"_s, 'i'), - BUILTIN(shop, "s"_s, '\0'), - BUILTIN(isdead, ""_s, 'i'), - BUILTIN(fakenpcname, "ssi"_s, '\0'), - BUILTIN(getx, ""_s, 'i'), - BUILTIN(gety, ""_s, 'i'), - BUILTIN(getmap, ""_s, 's'), - BUILTIN(mapexit, ""_s, '\0'), - {nullptr, ""_s, ""_s, '\0'}, -}; - -void set_script_var_i(dumb_ptr sd, VarName var, int e, int val) -{ - size_t k = variable_names.intern(var); - SIR reg = SIR::from(k, e); - set_reg(sd, VariableCode::VARIABLE, reg, val); -} -void set_script_var_s(dumb_ptr sd, VarName var, int e, XString val) -{ - size_t k = variable_names.intern(var); - SIR reg = SIR::from(k, e); - set_reg(sd, VariableCode::VARIABLE, reg, val); -} -int get_script_var_i(dumb_ptr sd, VarName var, int e) -{ - size_t k = variable_names.intern(var); - SIR reg = SIR::from(k, e); - struct script_data dat = ScriptDataVariable{.reg= reg}; - get_val(sd, &dat); - if (auto *u = dat.get_if()) - return u->numi; - PRINTF("Warning: you lied about the type and I'm too lazy to fix it!"_fmt); - return 0; -} -ZString get_script_var_s(dumb_ptr sd, VarName var, int e) -{ - size_t k = variable_names.intern(var); - SIR reg = SIR::from(k, e); - struct script_data dat = ScriptDataVariable{.reg= reg}; - get_val(sd, &dat); - if (auto *u = dat.get_if()) - // this is almost certainly a memory leak after CONSTSTR removal - return u->str; - PRINTF("Warning: you lied about the type and I can't fix it!"_fmt); - return ZString(); -} -} // namespace tmwa diff --git a/src/map/script.hpp b/src/map/script.hpp deleted file mode 100644 index 19dbcd0..0000000 --- a/src/map/script.hpp +++ /dev/null @@ -1,209 +0,0 @@ -#pragma once -// script.hpp - EAthena script frontend, engine, and library. -// -// Copyright © ????-2004 Athena Dev Teams -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -#include "fwd.hpp" - -#include - -#include -#include - -#include "../range/fwd.hpp" - -#include "../strings/zstring.hpp" - -#include "../generic/fwd.hpp" - -#include "../sexpr/variant.hpp" - -#include "../mmo/ids.hpp" - - -namespace tmwa -{ -enum class ByteCode : uint8_t; - -class ScriptBuffer; -} // namespace tmwa - -namespace std -{ -template<> -struct default_delete -{ - default_delete() {} - default_delete(default_delete) {} - void operator()(const tmwa::ScriptBuffer *sd); -}; -} // namespace std - -namespace tmwa -{ -struct ScriptPointer -{ - const ScriptBuffer *code; - size_t pos; - - ScriptPointer() - : code() - , pos() - {} - - ScriptPointer(const ScriptBuffer *c, size_t p) - : code(c) - , pos(p) - {} - - ByteCode peek() const; - ByteCode pop(); - ZString pops(); -}; - -// internal -class SIR -{ - uint32_t impl; - SIR(SP v) - : impl(static_cast(v)) - {} - SIR(unsigned v, uint8_t i) - : impl((i << 24) | v) - {} -public: - SIR() : impl() {} - - unsigned base() const { return impl & 0x00ffffff; } - uint8_t index() const { return impl >> 24; } - SIR iplus(uint8_t i) const { return SIR(base(), index() + i); } - static SIR from(unsigned v, uint8_t i=0) { return SIR(v, i); } - - SP sp() const { return static_cast(impl); } - static SIR from(SP v) { return SIR(v); } - - friend bool operator == (SIR l, SIR r) { return l.impl == r.impl; } - friend bool operator < (SIR l, SIR r) { return l.impl < r.impl; } -}; - -struct ScriptDataPos -{ - int numi; -}; -struct ScriptDataInt -{ - int numi; -}; -struct ScriptDataParam -{ - SIR reg; -}; -struct ScriptDataStr -{ - RString str; -}; -struct ScriptDataArg -{ - int numi; -}; -struct ScriptDataVariable -{ - SIR reg; -}; -struct ScriptDataRetInfo -{ - // Not a ScriptPointer - pos is stored in a separate slot, - // to avoid exploding the struct for everyone. - const ScriptBuffer *script; -}; -struct ScriptDataFuncRef -{ - int numi; -}; - -using ScriptDataVariantBase = Variant< - ScriptDataPos, - ScriptDataInt, - ScriptDataParam, - ScriptDataStr, - ScriptDataArg, - ScriptDataVariable, - ScriptDataRetInfo, - ScriptDataFuncRef ->; -struct script_data : ScriptDataVariantBase -{ - script_data() = delete; - // TODO see if I can delete the copy ctor/assign instead of defaulting - script_data(script_data&&) = default; - script_data(const script_data&) = default /*delete*/; - script_data& operator = (script_data&&) = default; - script_data& operator = (const script_data&) = default /*delete*/; - - script_data(ScriptDataPos v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataInt v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataParam v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataStr v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataArg v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataVariable v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataRetInfo v) : ScriptDataVariantBase(std::move(v)) {} - script_data(ScriptDataFuncRef v) : ScriptDataVariantBase(std::move(v)) {} -}; - -std::unique_ptr parse_script(ZString, int, bool implicit_end); - -struct argrec_t -{ - ZString name; - union _aru - { - int i; - ZString s; - - _aru(int n) : i(n) {} - _aru(ZString z) : s(z) {} - } v; - - argrec_t(ZString n, int i) : name(n), v(i) {} - argrec_t(ZString n, ZString z) : name(n), v(z) {} -}; -int run_script_l(ScriptPointer, BlockId, BlockId, Slice args); -int run_script(ScriptPointer, BlockId, BlockId); - -extern -Map scriptlabel_db; -extern -UPMap userfunc_db; - -void do_init_script(void); -void do_final_script(void); - -extern AString mapreg_txt; - -extern int script_errors; - -bool read_constdb(ZString filename); - -void set_script_var_i(dumb_ptr sd, VarName var, int e, int val); -void set_script_var_s(dumb_ptr sd, VarName var, int e, XString val); - -int get_script_var_i(dumb_ptr sd, VarName var, int e); -ZString get_script_var_s(dumb_ptr sd, VarName var, int e); -} // namespace tmwa diff --git a/src/map/script.py b/src/map/script.py deleted file mode 100644 index a5010cd..0000000 --- a/src/map/script.py +++ /dev/null @@ -1,25 +0,0 @@ -class script_data(object): - enabled = True - - test_extra = ''' - using tmwa::operator "" _s; - ''' - - tests = [ - ('tmwa::script_data(tmwa::ScriptDataPos{42})', - '{> = {(tmwa::ScriptDataPos) = {numi = 42}}, }'), - ('tmwa::script_data(tmwa::ScriptDataInt{123})', - '{> = {(tmwa::ScriptDataInt) = {numi = 123}}, }'), - ('tmwa::script_data(tmwa::ScriptDataParam{tmwa::SIR()})', - '{> = {(tmwa::ScriptDataParam) = {reg = {impl = 0}}}, }'), - ('tmwa::script_data(tmwa::ScriptDataStr{"Hello"_s})', - '{> = {(tmwa::ScriptDataStr) = {str = "Hello"}}, }'), - ('tmwa::script_data(tmwa::ScriptDataArg{0})', - '{> = {(tmwa::ScriptDataArg) = {numi = 0}}, }'), - ('tmwa::script_data(tmwa::ScriptDataVariable{tmwa::SIR()})', - '{> = {(tmwa::ScriptDataVariable) = {reg = {impl = 0}}}, }'), - ('tmwa::script_data(tmwa::ScriptDataRetInfo{static_cast(nullptr)})', - '{> = {(tmwa::ScriptDataRetInfo) = {script = 0x0}}, }'), - ('tmwa::script_data(tmwa::ScriptDataFuncRef{404})', - '{> = {(tmwa::ScriptDataFuncRef) = {numi = 404}}, }'), - ] diff --git a/src/map/skill.hpp b/src/map/skill.hpp index ec353ce..5d23bae 100644 --- a/src/map/skill.hpp +++ b/src/map/skill.hpp @@ -20,9 +20,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include "skill.t.hpp" + #include "fwd.hpp" -#include "skill.t.hpp" #include "skill-pools.hpp" #include "../strings/fwd.hpp" -- cgit v1.2.3-60-g2f50