diff options
author | mekolat <mekolat@users.noreply.github.com> | 2016-04-19 09:41:31 -0400 |
---|---|---|
committer | mekolat <mekolat@users.noreply.github.com> | 2016-04-19 09:41:31 -0400 |
commit | 1ba24673e7064e39406e6faf11d790c2dcc2ac00 (patch) | |
tree | 7e2645f6fdec1dcc63ae56366371246f62865dcd /src/map | |
parent | c3e06ffe6437d27a2a7c6ddb2dc487ff2f007adf (diff) | |
parent | c786a93e91adaf68780a5fd7585f51d0528f92ed (diff) | |
download | tmwa-1ba24673e7064e39406e6faf11d790c2dcc2ac00.tar.gz tmwa-1ba24673e7064e39406e6faf11d790c2dcc2ac00.tar.bz2 tmwa-1ba24673e7064e39406e6faf11d790c2dcc2ac00.tar.xz tmwa-1ba24673e7064e39406e6faf11d790c2dcc2ac00.zip |
Merge self-fork from mekolat/magic-v3
Magic v3
Diffstat (limited to 'src/map')
40 files changed, 2038 insertions, 7397 deletions
diff --git a/src/map/battle.cpp b/src/map/battle.cpp index 991a489..031b79d 100644 --- a/src/map/battle.cpp +++ b/src/map/battle.cpp @@ -85,6 +85,8 @@ Species battle_get_class(dumb_ptr<block_list> bl) nullpo_retr(Species(), bl); if (bl->bl_type == BL::MOB) return bl->is_mob()->mob_class; + else if (bl->bl_type == BL::NPC) + return bl->is_npc()->npc_class; else if (bl->bl_type == BL::PC) return bl->is_player()->status.species; else @@ -134,7 +136,9 @@ int battle_get_range(dumb_ptr<block_list> bl) if (bl->bl_type == BL::MOB) return get_mob_db(bl->is_mob()->mob_class).range; else if (bl->bl_type == BL::PC) - return bl->is_player()->attackrange; + return (bl->is_player()->attack_spell_override + ? bl->is_player()->attack_spell_range + : bl->is_player()->attackrange); else return 0; } @@ -341,7 +345,6 @@ int battle_get_luk(dumb_ptr<block_list> bl) * 戻りは整数で1以上 *------------------------------------------ */ -static int battle_get_flee(dumb_ptr<block_list> bl) { int flee = 1; @@ -370,7 +373,6 @@ int battle_get_flee(dumb_ptr<block_list> bl) * 戻りは整数で1以上 *------------------------------------------ */ -static int battle_get_hit(dumb_ptr<block_list> bl) { int hit = 1; @@ -398,7 +400,6 @@ int battle_get_hit(dumb_ptr<block_list> bl) * 戻りは整数で1以上 *------------------------------------------ */ -static int battle_get_flee2(dumb_ptr<block_list> bl) { int flee2 = 1; @@ -430,7 +431,6 @@ int battle_get_flee2(dumb_ptr<block_list> bl) * 戻りは整数で1以上 *------------------------------------------ */ -static int battle_get_critical(dumb_ptr<block_list> bl) { int critical = 1; @@ -457,7 +457,6 @@ int battle_get_critical(dumb_ptr<block_list> bl) * 戻りは整数で1以上 *------------------------------------------ */ -static int battle_get_baseatk(dumb_ptr<block_list> bl) { eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; @@ -484,7 +483,6 @@ int battle_get_baseatk(dumb_ptr<block_list> bl) * 戻りは整数で0以上 *------------------------------------------ */ -static int battle_get_atk(dumb_ptr<block_list> bl) { eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; @@ -507,7 +505,6 @@ int battle_get_atk(dumb_ptr<block_list> bl) * 戻りは整数で0以上 *------------------------------------------ */ -static int battle_get_atk2(dumb_ptr<block_list> bl) { nullpo_retz(bl); @@ -530,7 +527,6 @@ int battle_get_atk2(dumb_ptr<block_list> bl) * 戻りは整数で0以上 *------------------------------------------ */ -static int battle_get_matk1(dumb_ptr<block_list> bl) { eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; @@ -554,7 +550,6 @@ int battle_get_matk1(dumb_ptr<block_list> bl) * 戻りは整数で0以上 *------------------------------------------ */ -static int battle_get_matk2(dumb_ptr<block_list> bl) { nullpo_retz(bl); diff --git a/src/map/battle.hpp b/src/map/battle.hpp index 8f31fe0..2925b58 100644 --- a/src/map/battle.hpp +++ b/src/map/battle.hpp @@ -83,6 +83,15 @@ int battle_get_def(dumb_ptr<block_list> bl); int battle_get_mdef(dumb_ptr<block_list> bl); int battle_get_def2(dumb_ptr<block_list> bl); int battle_get_mdef2(dumb_ptr<block_list> bl); +int battle_get_atk(dumb_ptr<block_list> bl); +int battle_get_atk2(dumb_ptr<block_list> bl); +int battle_get_matk1(dumb_ptr<block_list> bl); +int battle_get_matk2(dumb_ptr<block_list> bl); +int battle_get_hit(dumb_ptr<block_list> bl); +int battle_get_flee(dumb_ptr<block_list> bl); +int battle_get_flee2(dumb_ptr<block_list> bl); +int battle_get_critical(dumb_ptr<block_list> bl); +int battle_get_baseatk(dumb_ptr<block_list> bl); interval_t battle_get_speed(dumb_ptr<block_list> bl); interval_t battle_get_adelay(dumb_ptr<block_list> bl); interval_t battle_get_amotion(dumb_ptr<block_list> bl); diff --git a/src/map/clif.cpp b/src/map/clif.cpp index df21a88..31ecaf8 100644 --- a/src/map/clif.cpp +++ b/src/map/clif.cpp @@ -62,8 +62,6 @@ #include "globals.hpp" #include "intif.hpp" #include "itemdb.hpp" -#include "magic.hpp" -#include "magic-stmt.hpp" #include "map.hpp" #include "map_conf.hpp" #include "npc.hpp" @@ -231,6 +229,8 @@ void clif_send_sub(dumb_ptr<block_list> bl, const Buffer& buf, break; case SendWho::AREA_CHAT_WOC: + if (bl && bl == src_bl) + break; if (is_deaf(bl) && !(bl->bl_type == BL::PC && pc_isGM(src_bl->is_player()))) @@ -238,8 +238,6 @@ void clif_send_sub(dumb_ptr<block_list> bl, const Buffer& buf, clif_emotion_towards(src_bl, bl, EMOTE_IGNORED); return; } - if (bl && bl == src_bl) - return; break; } @@ -2552,7 +2550,9 @@ int clif_damage(dumb_ptr<block_list> src, dumb_ptr<block_list> dst, nullpo_retz(src); nullpo_retz(dst); - sc_data = battle_get_sc_data(dst); + int target_hp = battle_get_hp(dst); + if (target_hp < damage) + damage = target_hp; // limit damage to hp Packet_Fixed<0x008a> fixed_8a; fixed_8a.src_id = src->bl_id; @@ -2641,12 +2641,6 @@ void clif_getareachar(dumb_ptr<block_list> bl, dumb_ptr<map_session_data> sd) case BL::ITEM: clif_getareachar_item(sd, bl->is_item()); break; - case BL::SPELL: - // spell objects are not visible - // (at least, I *think* that's what this code is for) - // in any case, this is not a behavior change, just silencing - // the below warning - break; default: if (battle_config.error_log) PRINTF("get area char ??? %d\n"_fmt, @@ -3510,21 +3504,10 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd) //clif_authok(); if (sd->npc_id) npc_event_dequeue(sd); - clif_skillinfoblock(sd); - pc_checkitem(sd); //guild_info(); // loadendack時 // next exp - clif_updatestatus(sd, SP::NEXTBASEEXP); - clif_updatestatus(sd, SP::NEXTJOBEXP); - // skill point - clif_updatestatus(sd, SP::SKILLPOINT); - // item - clif_itemlist(sd); - clif_equiplist(sd); - // param all - clif_initialstatus(sd); // party party_send_movemap(sd); // 119 @@ -3538,14 +3521,11 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd) map_addblock(sd); // ブロック登録 clif_spawnpc(sd); // spawn - clif_map_pvp(sd); // send map pvp status - - // weight max , now - clif_updatestatus(sd, SP::MAXWEIGHT); - clif_updatestatus(sd, SP::WEIGHT); + if (sd->bl_m->flag.get(MapFlag::PVP)) + clif_map_pvp(sd); // send map pvp status // pvp - if (!battle_config.pk_mode) + /*if (!battle_config.pk_mode) sd->pvp_timer.cancel(); if (sd->bl_m->flag.get(MapFlag::PVP)) @@ -3563,18 +3543,13 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd) else { // sd->pvp_timer = nullptr; - } - - sd->state.connect_new = 0; + }*/ // view equipment item - clif_changelook(sd, LOOK::WEAPON, static_cast<uint16_t>(ItemLook::NONE)); - if (battle_config.save_clothcolor == 1 && sd->status.clothes_color > 0) - clif_changelook(sd, LOOK::CLOTHES_COLOR, - sd->status.clothes_color); + //if (battle_config.save_clothcolor == 1 && sd->status.clothes_color > 0) + // clif_changelook(sd, LOOK::CLOTHES_COLOR, + // sd->status.clothes_color); - // option - clif_changeoption(sd); // broken equipment // clif_changelook_accessories(sd, nullptr); @@ -3585,8 +3560,25 @@ RecvResult clif_parse_LoadEndAck(Session *s, dumb_ptr<map_session_data> sd) sd->bl_x + AREA_SIZE, sd->bl_y + AREA_SIZE, BL::NUL); - if (!sd->state.seen_motd) - pc_show_motd(sd); + if (!sd->state.connect_new) + return rv; + + // all the code below is only executed once, on login + pc_show_motd(sd); + clif_skillinfoblock(sd); + pc_checkitem(sd); + clif_updatestatus(sd, SP::NEXTBASEEXP); + clif_updatestatus(sd, SP::NEXTJOBEXP); + clif_updatestatus(sd, SP::SKILLPOINT); + clif_itemlist(sd); + clif_equiplist(sd); + clif_initialstatus(sd); + clif_changeoption(sd); + clif_changelook(sd, LOOK::WEAPON, static_cast<uint16_t>(ItemLook::NONE)); + clif_updatestatus(sd, SP::MAXWEIGHT); + clif_updatestatus(sd, SP::WEIGHT); + npc_event_doall_l(stringish<ScriptLabel>("OnPCLoginEvent"_s), sd->bl_id, nullptr); + sd->state.connect_new = 0; return rv; } @@ -3792,7 +3784,6 @@ RecvResult clif_parse_GetCharNameRequest(Session *s, dumb_ptr<map_session_data> send_fpacket<0x0095, 30>(s, fixed_95); } break; - // case BL::SPELL default: if (battle_config.error_log) PRINTF("clif_parse_GetCharNameRequest : bad type %d (%d)\n"_fmt, @@ -3839,35 +3830,31 @@ RecvResult clif_parse_GlobalMessage(Session *s, dumb_ptr<map_session_data> sd) return rv; } + if (magic_message(sd, mbuf)) + return rv; + if (is_atcommand(s, sd, mbuf, GmLevel())) return rv; - if (!magic::magic_message(sd, mbuf)) + /* Don't send chat that results in an automatic ban. */ + if (tmw_CheckChatSpam(sd, mbuf)) { - /* Don't send chat that results in an automatic ban. */ - if (tmw_CheckChatSpam(sd, mbuf)) - { - clif_displaymessage(s, "Your message could not be sent."_s); - return rv; - } - - /* It's not a spell/magic message, so send the message to others. */ + clif_displaymessage(s, "Your message could not be sent."_s); + return rv; + } - Buffer sendbuf; - clif_message_sub(sendbuf, sd, mbuf); - Buffer filteredBuf; // ManaPlus remote execution exploit prevention - XString filtered = mbuf; - if (mbuf.contains_seq("@@="_s) && mbuf.contains('|')) - filtered = "##B##3[##1Impossible to see this message. Please update your client.##3]"_s; - clif_message_sub(filteredBuf, sd, filtered); + Buffer sendbuf; + clif_message_sub(sendbuf, sd, mbuf); - clif_send(sendbuf, sd, SendWho::AREA_CHAT_WOC, - wrap<ClientVersion>(6), filteredBuf); - } + Buffer filteredBuf; // ManaPlus remote execution exploit prevention + XString filtered = mbuf; + if (mbuf.contains_seq("@@="_s) && mbuf.contains('|')) + filtered = "##B##3[##1Impossible to see this message. Please update your client.##3]"_s; + clif_message_sub(filteredBuf, sd, filtered); - /* Send the message back to the speaker. */ - send_packet_repeatonly<0x008e, 4, 1>(s, STRPRINTF("%s : %s"_fmt, battle_get_name(sd), mbuf)); + clif_send(sendbuf, sd, SendWho::AREA_CHAT_WOC, + wrap<ClientVersion>(6), filteredBuf); return rv; } @@ -4273,8 +4260,8 @@ RecvResult clif_parse_TakeItem(Session *s, dumb_ptr<map_session_data> sd) || abs(sd->bl_y - fitem->bl_y) >= 2) return rv; // too far away to pick up - if (sd->state.shroud_active && sd->state.shroud_disappears_on_pickup) - magic::magic_unshroud(sd); +// if (sd->state.shroud_active && sd->state.shroud_disappears_on_pickup) +// magic_unshroud(sd); pc_takeitem(sd, fitem); @@ -4436,6 +4423,8 @@ RecvResult clif_parse_NpcClicked(Session *s, dumb_ptr<map_session_data> sd) } if (sd->npc_id) return rv; + if (battle_get_class(map_id2bl(fixed.block_id)) == INVISIBLE_CLASS) + return rv; npc_click(sd, fixed.block_id); return rv; diff --git a/src/map/fwd.hpp b/src/map/fwd.hpp index 911d566..74e0ccf 100644 --- a/src/map/fwd.hpp +++ b/src/map/fwd.hpp @@ -57,12 +57,10 @@ struct map_session_data; struct npc_data; struct mob_data; struct flooritem_data; -//struct magic::invocation; struct map_local; class npc_data_script; class npc_data_shop; class npc_data_warp; -class npc_data_message; struct item_data; struct quest_data; @@ -71,22 +69,5 @@ struct ScriptState; struct str_data_t; class SIR; -namespace magic -{ -struct fun_t; -struct op_t; -struct expr_t; -struct val_t; -struct location_t; -struct area_t; -struct spell_t; -struct invocation; -struct teleport_anchor_t; -struct env_t; -struct magic_conf_t; -struct component_t; -struct effect_set_t; -struct proc_t; -} // namespace magic } // namespace map } // namespace tmwa diff --git a/src/map/globals.cpp b/src/map/globals.cpp index dce3906..49d6074 100644 --- a/src/map/globals.cpp +++ b/src/map/globals.cpp @@ -27,7 +27,6 @@ #include "battle_conf.hpp" #include "itemdb.hpp" #include "quest.hpp" -#include "magic-interpreter.hpp" #include "map_conf.hpp" #include "mob.hpp" #include "npc-internal.hpp" @@ -51,17 +50,6 @@ namespace tmwa std::map<MapName, RString> resnametable; Map<ItemNameId, item_data> item_db; Map<QuestId, quest_data> quest_db; - namespace magic - { - // Global magic conf - magic_conf_t magic_conf; - env_t magic_default_env = { &magic_conf, nullptr }; - namespace magic_v2 - { - std::map<RString, proc_t> procs; - std::map<RString, val_t> const_defm; - } // namespace magic_v2 - } // namespace magic DMap<BlockId, dumb_ptr<block_list>> id_db; UPMap<MapName, map_abstract> maps_db; @@ -85,6 +73,7 @@ namespace tmwa BlockId npc_id = START_NPC_NUM; Map<NpcEvent, struct event_data> ev_db; DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name; + DMap<RString, NpcEvent> spells_by_events; // used for clock-based event triggers // only tm_min, tm_hour, and tm_mday are used tm ev_tm_b = @@ -141,9 +130,5 @@ namespace tmwa // BuiltinFunction builtin_functions[]; // src/map/clif.cpp: // func_table clif_parse_func_table[0x0220]; - // src/map/magic-expr.cpp: - // std::map<ZString, fun_t> functions; - // src/map/magic-stmt.cpp: - // std::map<ZString, op_t> operations; } // namespace map } // namespace tmwa diff --git a/src/map/globals.hpp b/src/map/globals.hpp index b457b4e..1c8e70d 100644 --- a/src/map/globals.hpp +++ b/src/map/globals.hpp @@ -49,17 +49,6 @@ namespace tmwa extern std::map<MapName, RString> resnametable; extern Map<ItemNameId, item_data> item_db; extern Map<QuestId, quest_data> quest_db; - namespace magic - { - // Global magic conf - extern magic_conf_t magic_conf; - extern env_t magic_default_env; - namespace magic_v2 - { - extern std::map<RString, proc_t> procs; - extern std::map<RString, val_t> const_defm; - } // namespace magic_v2 - } // namespace magic extern DMap<BlockId, dumb_ptr<block_list>> id_db; extern UPMap<MapName, map_abstract> maps_db; extern DMap<CharName, dumb_ptr<map_session_data>> nick_db; @@ -79,6 +68,7 @@ namespace tmwa extern BlockId npc_id; extern Map<NpcEvent, event_data> ev_db; extern DMap<NpcName, dumb_ptr<npc_data>> npcs_by_name; + extern DMap<RString, NpcEvent> spells_by_events; extern tm ev_tm_b; extern Map<PartyId, PartyMost> party_db; extern std::map<AccountId, GmLevel> gm_accountm; diff --git a/src/map/magic-expr-eval.hpp b/src/map/magic-expr-eval.hpp deleted file mode 100644 index e8ed4aa..0000000 --- a/src/map/magic-expr-eval.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once -// magic-expr-eval.hpp - Utilities for evaluating magic. -// -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "fwd.hpp" - -#include "../strings/zstring.hpp" - -#include "magic-interpreter.t.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -// TODO soon kill this unlike I killed VAR -#define ARGINT(x) args[x].get_if<ValInt>()->v_int -#define ARGDIR(x) args[x].get_if<ValDir>()->v_dir -#define ARGSTR(x) ZString(args[x].get_if<ValString>()->v_string) -#define ARGENTITY(x) args[x].get_if<ValEntityPtr>()->v_entity -#define ARGLOCATION(x) args[x].get_if<ValLocation>()->v_location -#define ARGAREA(x) args[x].get_if<ValArea>()->v_area -#define ARGSPELL(x) args[x].get_if<ValSpell>()->v_spell -#define ARGINVOCATION(x) args[x].get_if<ValInvocationPtr>()->v_invocation - -#define ENTITY_TYPE(x) ARGENTITY(x)->bl_type - -#define ARGPC(x) (ARGENTITY(x)->is_player()) -#define ARGNPC(x) (ARGENTITY(x)->is_npc()) -#define ARGMOB(x) (ARGENTITY(x)->is_mob()) - -#define ARG_MAY_BE_AREA(x) (args[x].is<ValArea>() || args[x].is<ValArea>()) -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic-expr.cpp b/src/map/magic-expr.cpp deleted file mode 100644 index 197727e..0000000 --- a/src/map/magic-expr.cpp +++ /dev/null @@ -1,1874 +0,0 @@ -#include "magic-expr.hpp" -// magic-expr.cpp - Pure functions for the old magic backend. -// -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include <cassert> - -#include <algorithm> - -#include "../strings/mstring.hpp" -#include "../strings/astring.hpp" -#include "../strings/zstring.hpp" -#include "../strings/vstring.hpp" -#include "../strings/literal.hpp" - -#include "../generic/dumb_ptr.hpp" -#include "../generic/random.hpp" - -#include "../io/cxxstdio.hpp" - -#include "../mmo/cxxstdio_enums.hpp" - -#include "battle.hpp" -#include "itemdb.hpp" -#include "magic-expr-eval.hpp" -#include "magic-interpreter.hpp" -#include "magic-interpreter-base.hpp" -#include "npc.hpp" -#include "pc.hpp" -#include "script-call.hpp" - -#include "../poison.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -static -void free_area(dumb_ptr<area_t> area) -{ - if (!area) - return; - - MATCH_BEGIN (*area) - { - MATCH_CASE (const AreaUnion&, a) - { - free_area(a.a_union[0]); - free_area(a.a_union[1]); - } - } - MATCH_END (); - - area.delete_(); -} - -static -dumb_ptr<area_t> dup_area(dumb_ptr<area_t> area) -{ - MATCH_BEGIN (*area) - { - MATCH_CASE (const location_t&, loc) - { - return dumb_ptr<area_t>::make(loc); - } - MATCH_CASE (const AreaUnion&, a) - { - AreaUnion u; - u.a_union[0] = dup_area(a.a_union[0]); - u.a_union[1] = dup_area(a.a_union[1]); - return dumb_ptr<area_t>::make(u); - } - MATCH_CASE (const AreaRect&, rect) - { - return dumb_ptr<area_t>::make(rect); - } - MATCH_CASE (const AreaBar&, bar) - { - return dumb_ptr<area_t>::make(bar); - } - } - MATCH_END (); - - abort(); -} - -void magic_copy_var(val_t *dest, const val_t *src) -{ - MATCH_BEGIN (*src) - { - MATCH_DEFAULT () - { - abort(); - } - MATCH_CASE (const ValUndef&, s) - { - *dest = s; - } - MATCH_CASE (const ValInt&, s) - { - *dest = s; - } - MATCH_CASE (const ValDir&, s) - { - *dest = s; - } - MATCH_CASE (const ValString&, s) - { - *dest = ValString{s.v_string}; - } - MATCH_CASE (const ValEntityInt&, s) - { - *dest = s; - } - MATCH_CASE (const ValEntityPtr&, s) - { - *dest = s; - } - MATCH_CASE (const ValLocation&, s) - { - *dest = s; - } - MATCH_CASE (const ValArea&, s) - { - *dest = ValArea{dup_area(s.v_area)}; - } - MATCH_CASE (const ValSpell&, s) - { - *dest = s; - } - MATCH_CASE (const ValInvocationInt&, s) - { - *dest = s; - } - MATCH_CASE (const ValInvocationPtr&, s) - { - *dest = s; - } - MATCH_CASE (const ValFail&, s) - { - *dest = s; - } - MATCH_CASE (const ValNegative1&, s) - { - *dest = s; - } - } - MATCH_END (); -} - -void magic_clear_var(val_t *v) -{ - MATCH_BEGIN (*v) - { - MATCH_CASE (ValString&, s) - { - (void)s; - } - MATCH_CASE (const ValArea&, a) - { - free_area(a.v_area); - } - } - MATCH_END (); -} - -static -AString show_entity(dumb_ptr<block_list> entity) -{ - switch (entity->bl_type) - { - case BL::PC: - return entity->is_player()->status_key.name.to__actual(); - case BL::NPC: - return entity->is_npc()->name; - case BL::MOB: - return entity->is_mob()->name; - case BL::ITEM: - assert (0 && "There is no way this code did what it was supposed to do!"_s); - /* Sorry about this one... */ - // WTF? item_data is a Item, not a struct item_data - // return ((struct item_data *) (&entity->is_item()->item_data))->name; - abort(); - case BL::SPELL: - return "%invocation(ERROR:this-should-not-be-an-entity)"_s; - default: - return "%unknown-entity"_s; - } -} - -static -void stringify(val_t *v) -{ - static earray<LString, DIR, DIR::COUNT> dirs //= - {{ - "south"_s, "south-west"_s, - "west"_s, "north-west"_s, - "north"_s, "north-east"_s, - "east"_s, "south-east"_s, - }}; - AString buf; - - MATCH_BEGIN (*v) - { - MATCH_DEFAULT () - { - abort(); - } - MATCH_CASE (const ValUndef&, x) - { - (void)x; - buf = "UNDEF"_s; - } - MATCH_CASE (const ValInt&, x) - { - buf = STRPRINTF("%i"_fmt, x.v_int); - } - MATCH_CASE (const ValString&, x) - { - (void)x; - return; - } - MATCH_CASE (const ValDir&, x) - { - buf = dirs[x.v_dir]; - } - MATCH_CASE (const ValEntityPtr&, x) - { - buf = show_entity(x.v_entity); - } - MATCH_CASE (const ValLocation&, x) - { - buf = STRPRINTF("<\"%s\", %d, %d>"_fmt, - x.v_location.m->name_, - x.v_location.x, - x.v_location.y); - } - MATCH_CASE (const ValArea&, x) - { - buf = "%area"_s; - free_area(x.v_area); - } - MATCH_CASE (const ValSpell&, x) - { - buf = x.v_spell->name; - } - MATCH_CASE (const ValInvocationInt&, x) - { - dumb_ptr<invocation> invocation_ = - map_id2bl(x.v_iid)->is_spell(); - buf = invocation_->spell->name; - } - MATCH_CASE (const ValInvocationPtr&, x) - { - dumb_ptr<invocation> invocation_ = - x.v_invocation; - buf = invocation_->spell->name; - } - } - MATCH_END (); - - *v = ValString{buf}; -} - -static -void intify(val_t *v) -{ - if (v->is<ValInt>()) - return; - - magic_clear_var(v); - *v = ValInt{1}; -} - -static -dumb_ptr<area_t> area_union(dumb_ptr<area_t> area, dumb_ptr<area_t> other_area) -{ - AreaUnion a; - a.a_union[0] = area; - a.a_union[1] = other_area; - return dumb_ptr<area_t>::make(a); -} - -/** - * Turns location into area, leaves other types untouched - */ -static -void make_area(val_t *v) -{ - if (ValLocation *l = v->get_if<ValLocation>()) - { - auto a = dumb_ptr<area_t>::make(l->v_location); - *v = ValArea{a}; - } -} - -static -void make_location(val_t *v) -{ - if (ValArea *a = v->get_if<ValArea>()) - { - MATCH_BEGIN (*a->v_area) - { - MATCH_CASE (const location_t&, location) - { - free_area(a->v_area); - *v = ValLocation{location}; - } - } - MATCH_END (); - } -} - -static -void make_spell(val_t *v) -{ - assert(!v->is<ValInvocationInt>()); - if (ValInvocationPtr *p = v->get_if<ValInvocationPtr>()) - { - dumb_ptr<invocation> invoc = p->v_invocation; - if (!invoc) - { - *v = ValFail{}; - } - else - { - *v = ValSpell{invoc->spell}; - } - } -} - -static -int fun_add(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (args[0].is<ValInt>() && args[1].is<ValInt>()) - { - /* Integer addition */ - *result = ValInt{ARGINT(0) + ARGINT(1)}; - } - else if (ARG_MAY_BE_AREA(0) && ARG_MAY_BE_AREA(1)) - { - /* Area union */ - make_area(&args[0]); - make_area(&args[1]); - *result = ValArea{area_union(ARGAREA(0), ARGAREA(1))}; - ARGAREA(0) = nullptr; args[0] = ValUndef{}; - ARGAREA(1) = nullptr; args[1] = ValUndef{}; - } - else - { - /* Anything else -> string concatenation */ - stringify(&args[0]); - stringify(&args[1]); - /* Yes, we could speed this up. */ - // ugh - MString m; - m += ARGSTR(0); - m += ARGSTR(1); - *result = ValString{AString(m)}; - } - return 0; -} - -static -int fun_sub(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{ARGINT(0) - ARGINT(1)}; - return 0; -} - -static -int fun_mul(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{ARGINT(0) * ARGINT(1)}; - return 0; -} - -static -int fun_div(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (!ARGINT(1)) - return 1; /* division by zero */ - *result = ValInt{ARGINT(0) / ARGINT(1)}; - return 0; -} - -static -int fun_mod(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (!ARGINT(1)) - return 1; /* division by zero */ - *result = ValInt{ARGINT(0) % ARGINT(1)}; - return 0; -} - -static -int fun_or(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{ARGINT(0) || ARGINT(1)}; - return 0; -} - -static -int fun_and(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{ARGINT(0) && ARGINT(1)}; - return 0; -} - -static -int fun_not(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{!ARGINT(0)}; - return 0; -} - -static -int fun_neg(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{~ARGINT(0)}; - return 0; -} - -static -int fun_gte(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (args[0].is<ValString>() || args[1].is<ValString>()) - { - stringify(&args[0]); - stringify(&args[1]); - *result = ValInt{ARGSTR(0) >= ARGSTR(1)}; - } - else - { - intify(&args[0]); - intify(&args[1]); - *result = ValInt{ARGINT(0) >= ARGINT(1)}; - } - return 0; -} - -static -int fun_lt(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args) -{ - fun_gte(env, result, args); - result->get_if<ValInt>()->v_int ^= 1; - return 0; -} - -static -int fun_gt(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (args[0].is<ValString>() || args[1].is<ValString>()) - { - stringify(&args[0]); - stringify(&args[1]); - *result = ValInt{ARGSTR(0) > ARGSTR(1)}; - } - else - { - intify(&args[0]); - intify(&args[1]); - *result = ValInt{ARGINT(0) > ARGINT(1)}; - } - return 0; -} - -static -int fun_lte(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args) -{ - fun_gt(env, result, args); - result->get_if<ValInt>()->v_int ^= 1; - return 0; -} - -static -int fun_eq(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (args[0].is<ValString>() || args[1].is<ValString>()) - { - stringify(&args[0]); - stringify(&args[1]); - *result = ValInt{ARGSTR(0) == ARGSTR(1)}; - } - else if (args[0].is<ValDir>() && args[1].is<ValDir>()) - *result = ValInt{ARGDIR(0) == ARGDIR(1)}; - else if (args[0].is<ValEntityPtr>() && args[1].is<ValEntityPtr>()) - *result = ValInt{ARGENTITY(0) == ARGENTITY(1)}; - else if (args[0].is<ValLocation>() && args[1].is<ValLocation>()) - *result = ValInt{(ARGLOCATION(0).x == ARGLOCATION(1).x - && ARGLOCATION(0).y == ARGLOCATION(1).y - && ARGLOCATION(0).m == ARGLOCATION(1).m)}; - else if (args[0].is<ValArea>() && args[1].is<ValArea>()) - *result = ValInt{ARGAREA(0) == ARGAREA(1)}; /* Probably not that great an idea... */ - else if (args[0].is<ValSpell>() && args[1].is<ValSpell>()) - *result = ValInt{ARGSPELL(0) == ARGSPELL(1)}; - else if (args[0].is<ValInvocationPtr>() && args[1].is<ValInvocationPtr>()) - *result = ValInt{ARGINVOCATION(0) == ARGINVOCATION(1)}; - else - { - intify(&args[0]); - intify(&args[1]); - *result = ValInt{ARGINT(0) == ARGINT(1)}; - } - return 0; -} - -static -int fun_ne(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args) -{ - fun_eq(env, result, args); - result->get_if<ValInt>()->v_int ^= 1; - return 0; -} - -static -int fun_bitand(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{ARGINT(0) & ARGINT(1)}; - return 0; -} - -static -int fun_bitor(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{ARGINT(0) | ARGINT(1)}; - return 0; -} - -static -int fun_bitxor(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{ARGINT(0) ^ ARGINT(1)}; - return 0; -} - -static -int fun_bitshl(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{ARGINT(0) << ARGINT(1)}; - return 0; -} - -static -int fun_bitshr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{ARGINT(0) >> ARGINT(1)}; - return 0; -} - -static -int fun_max(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{std::max(ARGINT(0), ARGINT(1))}; - return 0; -} - -static -int fun_min(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{std::min(ARGINT(0), ARGINT(1))}; - return 0; -} - -static -int fun_if_then_else(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (ARGINT(0)) - magic_copy_var(result, &args[1]); - else - magic_copy_var(result, &args[2]); - return 0; -} - -Borrowed<map_local> magic_area_rect(int *x, int *y, int *width, int *height, - area_t& area_) -{ - MATCH_BEGIN (area_) - { - MATCH_CASE (const AreaUnion&, a) - { - (void)a; - abort(); - } - MATCH_CASE (const location_t&, a_loc) - { - P<map_local> m = a_loc.m; - *x = a_loc.x; - *y = a_loc.y; - *width = 1; - *height = 1; - return m; - } - MATCH_CASE (const AreaRect&, a_rect) - { - P<map_local> m = a_rect.loc.m; - *x = a_rect.loc.x; - *y = a_rect.loc.y; - *width = a_rect.width; - *height = a_rect.height; - return m; - } - MATCH_CASE (const AreaBar&, a_bar) - { - int tx = a_bar.loc.x; - int ty = a_bar.loc.y; - int twidth = a_bar.width; - int tdepth = a_bar.width; - P<map_local> m = a_bar.loc.m; - - switch (a_bar.dir) - { - case DIR::S: - *x = tx - twidth; - *y = ty; - *width = twidth * 2 + 1; - *height = tdepth; - break; - - case DIR::W: - *x = tx - tdepth; - *y = ty - twidth; - *width = tdepth; - *height = twidth * 2 + 1; - break; - - case DIR::N: - *x = tx - twidth; - *y = ty - tdepth; - *width = twidth * 2 + 1; - *height = tdepth; - break; - - case DIR::E: - *x = tx; - *y = ty - twidth; - *width = tdepth; - *height = twidth * 2 + 1; - break; - - default: - FPRINTF(stderr, - "Error: Trying to compute area of NE/SE/NW/SW-facing bar"_fmt); - *x = tx; - *y = ty; - *width = *height = 1; - } - return m; - } - } - MATCH_END (); - abort(); -} - -int magic_location_in_area(Borrowed<map_local> m, int x, int y, dumb_ptr<area_t> area) -{ - MATCH_BEGIN (*area) - { - MATCH_CASE (const AreaUnion&, a) - { - return magic_location_in_area(m, x, y, a.a_union[0]) - || magic_location_in_area(m, x, y, a.a_union[1]); - } - MATCH_CASE (const location_t&, a_loc) - { - (void)a_loc; - // TODO this can be simplified - int ax, ay, awidth, aheight; - P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area); - return (am == m - && (x >= ax) && (y >= ay) - && (x < ax + awidth) && (y < ay + aheight)); - } - MATCH_CASE (const AreaRect&, a_rect) - { - (void)a_rect; - // TODO this is too complicated - int ax, ay, awidth, aheight; - P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area); - return (am == m - && (x >= ax) && (y >= ay) - && (x < ax + awidth) && (y < ay + aheight)); - } - MATCH_CASE (const AreaBar&, a_bar) - { - (void)a_bar; - // TODO this is wrong - int ax, ay, awidth, aheight; - P<map_local> am = magic_area_rect(&ax, &ay, &awidth, &aheight, *area); - return (am == m - && (x >= ax) && (y >= ay) - && (x < ax + awidth) && (y < ay + aheight)); - } - } - MATCH_END (); - abort(); -} - -static -int fun_is_in(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{magic_location_in_area(ARGLOCATION(0).m, - ARGLOCATION(0).x, - ARGLOCATION(0).y, ARGAREA(1))}; - return 0; -} - -static -int fun_skill(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (ENTITY_TYPE(0) != BL::PC - // don't convert to enum until after the range check - // (actually it would be okay, I checked) - || ARGINT(1) < 0 - || ARGINT(1) >= static_cast<uint16_t>(MAX_SKILL)) - { - *result = ValInt{0}; - } - else - { - SkillID id = static_cast<SkillID>(ARGINT(1)); - *result = ValInt{ARGPC(0)->status.skill[id].lv}; - } - return 0; -} - -static -int fun_his_shroud(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{(ENTITY_TYPE(0) == BL::PC && ARGPC(0)->state.shroud_active)}; - return 0; -} - -#define BATTLE_GETTER(name) \ -static \ -int fun_get_##name(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) \ -{ \ - *result = ValInt{battle_get_##name(ARGENTITY(0))}; \ - return 0; \ -} - -BATTLE_GETTER(str) -BATTLE_GETTER(agi) -BATTLE_GETTER(vit) -BATTLE_GETTER(dex) -BATTLE_GETTER(luk) -BATTLE_GETTER(int) -BATTLE_GETTER(lv) -BATTLE_GETTER(hp) -BATTLE_GETTER(mdef) -BATTLE_GETTER(def) -BATTLE_GETTER(max_hp) -static -int fun_get_dir(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValDir{battle_get_dir(ARGENTITY(0))}; - return 0; -} - -#define MMO_GETTER(name) \ -static \ -int fun_get_##name(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) \ -{ \ - if (ENTITY_TYPE(0) == BL::PC) \ - *result = ValInt{ARGPC(0)->status.name}; \ - else \ - *result = ValInt{0}; \ - return 0; \ -} - -MMO_GETTER(sp) -MMO_GETTER(max_sp) - -static -int fun_name_of(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (args[0].is<ValEntityPtr>()) - { - *result = ValString{show_entity(ARGENTITY(0))}; - return 0; - } - else if (args[0].is<ValSpell>()) - { - *result = ValString{ARGSPELL(0)->name}; - return 0; - } - else if (args[0].is<ValInvocationPtr>()) - { - *result = ValString{ARGINVOCATION(0)->spell->name}; - return 0; - } - return 1; -} - -/* [Freeyorp] I'm putting this one in as name_of seems to have issues with summoned or spawned mobs. */ -static -int fun_mob_id(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (ENTITY_TYPE(0) != BL::MOB) - return 1; - *result = ValInt{unwrap<Species>(ARGMOB(0)->mob_class)}; - return 0; -} - -inline -void COPY_LOCATION(block_list& dest, location_t& src) -{ - dest.bl_x = src.x; - dest.bl_y = src.y; - dest.bl_m = src.m; -} - -inline -void COPY_LOCATION(location_t& dest, block_list& src) -{ - dest.x = src.bl_x; - dest.y = src.bl_y; - dest.m = src.bl_m; -} - -static -int fun_location(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - location_t loc; - COPY_LOCATION(loc, *(ARGENTITY(0))); - *result = ValLocation{loc}; - return 0; -} - -static -int fun_random(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - int delta = ARGINT(0); - if (delta < 0) - delta = -delta; - if (delta == 0) - { - *result = ValInt{0}; - return 0; - } - *result = ValInt{random_::to(delta)}; - - if (ARGINT(0) < 0) - result->get_if<ValInt>()->v_int *= -1; - return 0; -} - -static -int fun_random_dir(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (ARGINT(0)) - *result = ValDir{random_::choice({DIR::S, DIR::SW, DIR::W, DIR::NW, DIR::N, DIR::NE, DIR::E, DIR::SE})}; - else - *result = ValDir{random_::choice({DIR::S, DIR::W, DIR::N, DIR::E})}; - return 0; -} - -static -int fun_hash_entity(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{static_cast<int32_t>(unwrap<BlockId>(ARGENTITY(0)->bl_id))}; - return 0; -} - -// ret -1: not a string, ret 1: no such item, ret 0: OK -int magic_find_item(Slice<val_t> args, int index, Item *item_, int *stackable) -{ - Option<P<struct item_data>> item_data_ = None; - int must_add_sequentially; - - if (args[index].is<ValInt>()) - item_data_ = itemdb_exists(wrap<ItemNameId>(static_cast<uint16_t>(ARGINT(index)))); - else if (args[index].is<ValString>()) - item_data_ = itemdb_searchname(ARGSTR(index)); - else - return -1; - - P<struct item_data> item_data = TRY_UNWRAP(item_data_, return 1); - - // Very elegant. - must_add_sequentially = ( - item_data->type == ItemType::WEAPON - || item_data->type == ItemType::ARMOR - || item_data->type == ItemType::_7 - || item_data->type == ItemType::_8); - - if (stackable) - *stackable = !must_add_sequentially; - - *item_ = Item(); - item_->nameid = item_data->nameid; - - return 0; -} - -static -int fun_count_item(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - dumb_ptr<map_session_data> chr = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr; - int stackable; - Item item; - - GET_ARG_ITEM(1, item, stackable); - - if (!chr) - return 1; - - *result = ValInt{pc_count_all_items(chr, item.nameid)}; - return 0; -} - -static -int fun_is_equipped(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - dumb_ptr<map_session_data> chr = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr; - int stackable; - Item item; - bool retval = false; - - GET_ARG_ITEM(1, item, stackable); - - if (!chr) - return 1; - - for (EQUIP i : EQUIPs) - { - IOff0 idx = chr->equip_index_maybe[i]; - if (idx.ok() && chr->status.inventory[idx].nameid == item.nameid) - { - retval = true; - break; - } - } - - *result = ValInt{retval}; - return 0; -} - -static -int fun_is_married(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{(ENTITY_TYPE(0) == BL::PC && ARGPC(0)->status.partner_id)}; - return 0; -} - -static -int fun_is_dead(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{(ENTITY_TYPE(0) == BL::PC && pc_isdead(ARGPC(0)))}; - return 0; -} - -static -int fun_is_pc(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{(ENTITY_TYPE(0) == BL::PC)}; - return 0; -} - -static -int fun_partner(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (ENTITY_TYPE(0) == BL::PC && ARGPC(0)->status.partner_id) - { - *result = - ValEntityPtr{map_nick2sd(map_charid2nick(ARGPC(0)->status.partner_id))}; - return 0; - } - else - return 1; -} - -static -int fun_awayfrom(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - location_t *loc = &ARGLOCATION(0); - int dx = dirx[ARGDIR(1)]; - int dy = diry[ARGDIR(1)]; - int distance = ARGINT(2); - while (distance-- - && !bool(read_gatp(loc->m, loc->x + dx, loc->y + dy) - & MapCell::UNWALKABLE)) - { - loc->x += dx; - loc->y += dy; - } - - *result = ValLocation{*loc}; - return 0; -} - -static -int fun_failed(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{args[0].is<ValFail>()}; - return 0; -} - -static -int fun_npc(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - NpcName name = stringish<NpcName>(ARGSTR(0)); - dumb_ptr<npc_data> npc = npc_name2id(name); - *result = ValEntityPtr{npc}; - return npc == nullptr; -} - -static -int fun_pc(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - CharName name = stringish<CharName>(ARGSTR(0)); - dumb_ptr<map_session_data> chr = map_nick2sd(name); - *result = ValEntityPtr{chr}; - return chr == nullptr; -} - -static -int fun_distance(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (ARGLOCATION(0).m != ARGLOCATION(1).m) - *result = ValInt{0x7fffffff}; - else - *result = ValInt{std::max(abs(ARGLOCATION(0).x - ARGLOCATION(1).x), - abs(ARGLOCATION(0).y - ARGLOCATION(1).y))}; - return 0; -} - -static -int fun_rdistance(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (ARGLOCATION(0).m != ARGLOCATION(1).m) - *result = ValInt{0x7fffffff}; - else - { - int dx = ARGLOCATION(0).x - ARGLOCATION(1).x; - int dy = ARGLOCATION(0).y - ARGLOCATION(1).y; - *result = ValInt{static_cast<int>(sqrt((dx * dx) + (dy * dy)))}; - } - return 0; -} - -static -int fun_anchor(dumb_ptr<env_t> env, val_t *result, Slice<val_t> args) -{ - dumb_ptr<teleport_anchor_t> anchor = magic_find_anchor(ARGSTR(0)); - - if (!anchor) - return 1; - - magic_eval(env, result, anchor->location); - - make_area(result); - if (!result->is<ValArea>()) - { - magic_clear_var(result); - return 1; - } - - return 0; -} - -static -int fun_line_of_sight(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - block_list e1, e2; - - COPY_LOCATION(e1, ARGLOCATION(0)); - COPY_LOCATION(e2, ARGLOCATION(1)); - - *result = ValInt{battle_check_range(dumb_ptr<block_list>(&e1), dumb_ptr<block_list>(&e2), 0)}; - - return 0; -} - -void magic_random_location(location_t *dest, dumb_ptr<area_t> area) -{ - MATCH_BEGIN (*area) - { - MATCH_CASE (const AreaUnion&, a) - { - if (random_::chance({a.a_union[0]->size, area->size})) - magic_random_location(dest, a.a_union[0]); - else - magic_random_location(dest, a.a_union[1]); - } - MATCH_CASE (const location_t&, a_loc) - { - (void)a_loc; - // TODO this can be simplified - int x, y, w, h; - P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area); - - if (w <= 1) - w = 1; - - if (h <= 1) - h = 1; - - // This is not exactly the same as the old logic, - // but it's better. - auto pair = map_randfreecell(m, x, y, w, h); - - dest->m = m; - dest->x = pair.first; - dest->y = pair.second; - } - MATCH_CASE (const AreaRect&, a_rect) - { - (void)a_rect; - // TODO this can be simplified - int x, y, w, h; - P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area); - - if (w <= 1) - w = 1; - - if (h <= 1) - h = 1; - - // This is not exactly the same as the old logic, - // but it's better. - auto pair = map_randfreecell(m, x, y, w, h); - - dest->m = m; - dest->x = pair.first; - dest->y = pair.second; - } - MATCH_CASE (const AreaBar&, a_bar) - { - (void)a_bar; - // TODO this is wrong - int x, y, w, h; - P<map_local> m = magic_area_rect(&x, &y, &w, &h, *area); - - if (w <= 1) - w = 1; - - if (h <= 1) - h = 1; - - // This is not exactly the same as the old logic, - // but it's better. - auto pair = map_randfreecell(m, x, y, w, h); - - dest->m = m; - dest->x = pair.first; - dest->y = pair.second; - } - } - MATCH_END (); -} - -static -int fun_pick_location(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - location_t loc; - magic_random_location(&loc, ARGAREA(0)); - *result = ValLocation{loc}; - return 0; -} - -static -int fun_read_script_int(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - dumb_ptr<block_list> subject_p = ARGENTITY(0); - VarName var_name = stringish<VarName>(ARGSTR(1)); - int array_index = 0; - - if (subject_p->bl_type != BL::PC) - return 1; - - *result = ValInt{get_script_var_i(subject_p->is_player(), var_name, array_index)}; - return 0; -} - -static -int fun_read_script_str(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - dumb_ptr<block_list> subject_p = ARGENTITY(0); - VarName var_name = stringish<VarName>(ARGSTR(1)); - int array_index = 0; - - if (subject_p->bl_type != BL::PC) - return 1; - - *result = ValString{get_script_var_s(subject_p->is_player(), var_name, array_index)}; - return 0; -} - -static -int fun_rbox(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - location_t loc = ARGLOCATION(0); - int radius = ARGINT(1); - - AreaRect a_rect; - a_rect.loc.m = loc.m; - a_rect.loc.x = loc.x - radius; - a_rect.loc.y = loc.y - radius; - a_rect.width = radius * 2 + 1; - a_rect.height = radius * 2 + 1; - *result = ValArea{dumb_ptr<area_t>::make(a_rect)}; - - return 0; -} - -static -int fun_running_status_update(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - if (ENTITY_TYPE(0) != BL::PC && ENTITY_TYPE(0) != BL::MOB) - return 1; - - StatusChange sc = static_cast<StatusChange>(ARGINT(1)); - *result = ValInt{bool(battle_get_sc_data(ARGENTITY(0))[sc].timer)}; - return 0; -} - -static -int fun_status_option(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{(bool((ARGPC(0))->status.option & static_cast<Opt0>(ARGINT(1))))}; - return 0; -} - -static -int fun_element(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{static_cast<int>(battle_get_element(ARGENTITY(0)).element)}; - return 0; -} - -static -int fun_element_level(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{battle_get_element(ARGENTITY(0)).level}; - return 0; -} - -static -int fun_is_exterior(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ -#warning "Evil assumptions!" - *result = ValInt{ARGLOCATION(0).m->name_[4] == '1'}; - return 0; -} - -static -int fun_contains_string(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{nullptr != strstr(ARGSTR(0).c_str(), ARGSTR(1).c_str())}; - return 0; -} - -static -int fun_strstr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - const char *offset = strstr(ARGSTR(0).c_str(), ARGSTR(1).c_str()); - *result = ValInt{static_cast<int32_t>(offset - ARGSTR(0).c_str())}; - return offset == nullptr; -} - -static -int fun_strlen(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{static_cast<int32_t>(strlen(ARGSTR(0).c_str()))}; - return 0; -} - -static -int fun_substr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - RString src = ARGSTR(0); - int offset = ARGINT(1); - int len = ARGINT(2); - - if (len < 0) - len = 0; - if (offset < 0) - offset = 0; - - if (offset > src.size()) - offset = src.size(); - - if (offset + len > src.size()) - len = src.size() - offset; - - auto begin = src.begin() + offset; - auto end = begin + len; - *result = ValString{RString(begin, end)}; - - return 0; -} - -static -int fun_sqrt(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - *result = ValInt{static_cast<int>(sqrt(ARGINT(0)))}; - return 0; -} - -static -int fun_map_level(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ -#warning "Evil assumptions!" - *result = ValInt{ARGLOCATION(0).m->name_[4] - '0'}; - return 0; -} - -static -int fun_map_nr(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ -#warning "Evil assumptions!" - MapName mapname = ARGLOCATION(0).m->name_; - - *result = ValInt{((mapname[0] - '0') * 100) - + ((mapname[1] - '0') * 10) + ((mapname[2] - '0'))}; - return 0; -} - -static -int fun_dir_towards(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - int dx; - int dy; - - if (ARGLOCATION(0).m != ARGLOCATION(1).m) - return 1; - - dx = ARGLOCATION(1).x - ARGLOCATION(0).x; - dy = ARGLOCATION(1).y - ARGLOCATION(0).y; - - if (ARGINT(2)) - { - /* 8-direction mode */ - if (abs(dx) > abs(dy) * 2) - { /* east or west */ - if (dx < 0) - *result = ValDir{DIR::W}; - else - *result = ValDir{DIR::E}; - } - else if (abs(dy) > abs(dx) * 2) - { /* north or south */ - if (dy > 0) - *result = ValDir{DIR::S}; - else - *result = ValDir{DIR::N}; - } - else if (dx < 0) - { /* north-west or south-west */ - if (dy < 0) - *result = ValDir{DIR::NW}; - else - *result = ValDir{DIR::SW}; - } - else - { /* north-east or south-east */ - if (dy < 0) - *result = ValDir{DIR::NE}; - else - *result = ValDir{DIR::SE}; - } - } - else - { - /* 4-direction mode */ - if (abs(dx) > abs(dy)) - { /* east or west */ - if (dx < 0) - *result = ValDir{DIR::W}; - else - *result = ValDir{DIR::E}; - } - else - { /* north or south */ - if (dy > 0) - *result = ValDir{DIR::S}; - else - *result = ValDir{DIR::N}; - } - } - - return 0; -} - -static -int fun_extract_healer_xp(dumb_ptr<env_t>, val_t *result, Slice<val_t> args) -{ - dumb_ptr<map_session_data> sd = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr; - - if (!sd) - *result = ValInt{0}; - else - *result = ValInt{pc_extract_healer_exp(sd, ARGINT(1))}; - return 0; -} - -#define MAGIC_FUNCTION(name, args, ret, impl) {name, {name, args, ret, impl}} -#define MAGIC_FUNCTION1(name, args, ret) MAGIC_FUNCTION(#name##_s, args, ret, fun_##name) -static // should be LString, but no heterogenous lookup yet -std::map<ZString, fun_t> functions = -{ - MAGIC_FUNCTION("+"_s, ".."_s, '.', fun_add), - MAGIC_FUNCTION("-"_s, "ii"_s, 'i', fun_sub), - MAGIC_FUNCTION("*"_s, "ii"_s, 'i', fun_mul), - MAGIC_FUNCTION("/"_s, "ii"_s, 'i', fun_div), - MAGIC_FUNCTION("%"_s, "ii"_s, 'i', fun_mod), - MAGIC_FUNCTION("||"_s, "ii"_s, 'i', fun_or), - MAGIC_FUNCTION("&&"_s, "ii"_s, 'i', fun_and), - MAGIC_FUNCTION("<"_s, ".."_s, 'i', fun_lt), - MAGIC_FUNCTION(">"_s, ".."_s, 'i', fun_gt), - MAGIC_FUNCTION("<="_s, ".."_s, 'i', fun_lte), - MAGIC_FUNCTION(">="_s, ".."_s, 'i', fun_gte), - MAGIC_FUNCTION("=="_s, ".."_s, 'i', fun_eq), - MAGIC_FUNCTION("!="_s, ".."_s, 'i', fun_ne), - MAGIC_FUNCTION("|"_s, ".."_s, 'i', fun_bitor), - MAGIC_FUNCTION("&"_s, "ii"_s, 'i', fun_bitand), - MAGIC_FUNCTION("^"_s, "ii"_s, 'i', fun_bitxor), - MAGIC_FUNCTION("<<"_s, "ii"_s, 'i', fun_bitshl), - MAGIC_FUNCTION(">>"_s, "ii"_s, 'i', fun_bitshr), - MAGIC_FUNCTION1(not, "i"_s, 'i'), - MAGIC_FUNCTION1(neg, "i"_s, 'i'), - MAGIC_FUNCTION1(max, "ii"_s, 'i'), - MAGIC_FUNCTION1(min, "ii"_s, 'i'), - MAGIC_FUNCTION1(is_in, "la"_s, 'i'), - MAGIC_FUNCTION1(if_then_else, "i__"_s, '_'), - MAGIC_FUNCTION1(skill, "ei"_s, 'i'), - MAGIC_FUNCTION("str"_s, "e"_s, 'i', fun_get_str), - MAGIC_FUNCTION("agi"_s, "e"_s, 'i', fun_get_agi), - MAGIC_FUNCTION("vit"_s, "e"_s, 'i', fun_get_vit), - MAGIC_FUNCTION("dex"_s, "e"_s, 'i', fun_get_dex), - MAGIC_FUNCTION("luk"_s, "e"_s, 'i', fun_get_luk), - MAGIC_FUNCTION("int"_s, "e"_s, 'i', fun_get_int), - MAGIC_FUNCTION("level"_s, "e"_s, 'i', fun_get_lv), - MAGIC_FUNCTION("mdef"_s, "e"_s, 'i', fun_get_mdef), - MAGIC_FUNCTION("def"_s, "e"_s, 'i', fun_get_def), - MAGIC_FUNCTION("hp"_s, "e"_s, 'i', fun_get_hp), - MAGIC_FUNCTION("max_hp"_s, "e"_s, 'i', fun_get_max_hp), - MAGIC_FUNCTION("sp"_s, "e"_s, 'i', fun_get_sp), - MAGIC_FUNCTION("max_sp"_s, "e"_s, 'i', fun_get_max_sp), - MAGIC_FUNCTION("dir"_s, "e"_s, 'd', fun_get_dir), - MAGIC_FUNCTION1(name_of, "."_s, 's'), - MAGIC_FUNCTION1(mob_id, "e"_s, 'i'), - MAGIC_FUNCTION1(location, "e"_s, 'l'), - MAGIC_FUNCTION1(random, "i"_s, 'i'), - MAGIC_FUNCTION1(random_dir, "i"_s, 'd'), - MAGIC_FUNCTION1(hash_entity, "e"_s, 'i'), - MAGIC_FUNCTION1(is_married, "e"_s, 'i'), - MAGIC_FUNCTION1(partner, "e"_s, 'e'), - MAGIC_FUNCTION1(awayfrom, "ldi"_s, 'l'), - MAGIC_FUNCTION1(failed, "_"_s, 'i'), - MAGIC_FUNCTION1(pc, "s"_s, 'e'), - MAGIC_FUNCTION1(npc, "s"_s, 'e'), - MAGIC_FUNCTION1(distance, "ll"_s, 'i'), - MAGIC_FUNCTION1(rdistance, "ll"_s, 'i'), - MAGIC_FUNCTION1(anchor, "s"_s, 'a'), - MAGIC_FUNCTION("random_location"_s, "a"_s, 'l', fun_pick_location), - MAGIC_FUNCTION("script_int"_s, "es"_s, 'i', fun_read_script_int), - MAGIC_FUNCTION("script_str"_s, "es"_s, 's', fun_read_script_str), - MAGIC_FUNCTION1(rbox, "li"_s, 'a'), - MAGIC_FUNCTION1(count_item, "e."_s, 'i'), - MAGIC_FUNCTION1(line_of_sight, "ll"_s, 'i'), - MAGIC_FUNCTION1(running_status_update, "ei"_s, 'i'), - MAGIC_FUNCTION1(status_option, "ei"_s, 'i'), - MAGIC_FUNCTION1(element, "e"_s, 'i'), - MAGIC_FUNCTION1(element_level, "e"_s, 'i'), - MAGIC_FUNCTION1(his_shroud, "e"_s, 'i'), - MAGIC_FUNCTION1(is_equipped, "e."_s, 'i'), - MAGIC_FUNCTION1(is_exterior, "l"_s, 'i'), - MAGIC_FUNCTION1(contains_string, "ss"_s, 'i'), - MAGIC_FUNCTION1(strstr, "ss"_s, 'i'), - MAGIC_FUNCTION1(strlen, "s"_s, 'i'), - MAGIC_FUNCTION1(substr, "sii"_s, 's'), - MAGIC_FUNCTION1(sqrt, "i"_s, 'i'), - MAGIC_FUNCTION1(map_level, "l"_s, 'i'), - MAGIC_FUNCTION1(map_nr, "l"_s, 'i'), - MAGIC_FUNCTION1(dir_towards, "lli"_s, 'd'), - MAGIC_FUNCTION1(is_dead, "e"_s, 'i'), - MAGIC_FUNCTION1(is_pc, "e"_s, 'i'), - MAGIC_FUNCTION("extract_healer_experience"_s, "ei"_s, 'i', fun_extract_healer_xp), -}; - -fun_t *magic_get_fun(ZString name) -{ - auto it = functions.find(name); - if (it == functions.end()) - return nullptr; - return &it->second; -} - -// 1 on failure -static -int eval_location(dumb_ptr<env_t> env, location_t *dest, const e_location_t *expr) -{ - val_t m, x, y; - magic_eval(env, &m, expr->m); - magic_eval(env, &x, expr->x); - magic_eval(env, &y, expr->y); - - if (m.is<ValString>() - && x.is<ValInt>() && y.is<ValInt>()) - { - MapName name = VString<15>(ZString(m.get_if<ValString>()->v_string)); - magic_clear_var(&m); - P<map_local> map_id = TRY_UNWRAP(map_mapname2mapid(name), return 1); - dest->m = map_id; - dest->x = x.get_if<ValInt>()->v_int; - dest->y = y.get_if<ValInt>()->v_int; - return 0; - } - else - { - magic_clear_var(&m); - magic_clear_var(&x); - magic_clear_var(&y); - return 1; - } -} - -static -dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, const e_area_t& expr_) -{ - MATCH_BEGIN (expr_) - { - MATCH_CASE (const e_location_t&, a_loc) - { - location_t loc; - if (eval_location(env, &loc, &a_loc)) - { - return nullptr; - } - else - { - return dumb_ptr<area_t>::make(loc); - } - } - MATCH_CASE (const ExprAreaUnion&, a) - { - AreaUnion u; - bool fail = false; - for (int i = 0; i < 2; i++) - { - u.a_union[i] = eval_area(env, *a.a_union[i]); - if (!u.a_union[i]) - fail = true; - } - - if (fail) - { - for (int i = 0; i < 2; i++) - { - if (u.a_union[i]) - free_area(u.a_union[i]); - } - return nullptr; - } - return dumb_ptr<area_t>::make(u); - } - MATCH_CASE (const ExprAreaRect&, a_rect) - { - val_t width, height; - magic_eval(env, &width, a_rect.width); - magic_eval(env, &height, a_rect.height); - - AreaRect a_rect_; - if (width.is<ValInt>() - && height.is<ValInt>() - && !eval_location(env, &(a_rect_.loc), - &a_rect.loc)) - { - a_rect_.width = width.get_if<ValInt>()->v_int; - a_rect_.height = height.get_if<ValInt>()->v_int; - - magic_clear_var(&width); - magic_clear_var(&height); - return dumb_ptr<area_t>::make(a_rect_); - } - else - { - magic_clear_var(&width); - magic_clear_var(&height); - return nullptr; - } - } - MATCH_CASE (const ExprAreaBar&, a_bar) - { - val_t width, depth, dir; - magic_eval(env, &width, a_bar.width); - magic_eval(env, &depth, a_bar.depth); - magic_eval(env, &dir, a_bar.dir); - - AreaBar a_bar_; - if (width.is<ValInt>() - && depth.is<ValInt>() - && dir.is<ValDir>() - && !eval_location(env, &a_bar_.loc, - &a_bar.loc)) - { - a_bar_.width = width.get_if<ValInt>()->v_int; - a_bar_.depth = depth.get_if<ValInt>()->v_int; - a_bar_.dir = dir.get_if<ValDir>()->v_dir; - - magic_clear_var(&width); - magic_clear_var(&depth); - magic_clear_var(&dir); - return dumb_ptr<area_t>::make(a_bar_); - } - else - { - magic_clear_var(&width); - magic_clear_var(&depth); - magic_clear_var(&dir); - return nullptr; - } - } - } - MATCH_END (); - abort(); -} - -// This is called on arguments with begin=true, -// and on the return value with begin=false. -// In both cases, the ambiguous types are in pointer mode. -static -bool type_key_matches(char ty_key, val_t *arg, bool begin) -{ - switch (ty_key) - { - case 'i': - if (begin) - intify(arg); - return arg->is<ValInt>(); - case 'd': - return arg->is<ValDir>(); - case 's': - if (begin) - stringify(arg); - return arg->is<ValString>(); - case 'e': - return arg->is<ValEntityPtr>(); - case 'l': - if (begin) - make_location(arg); - return arg->is<ValLocation>(); - case 'a': - if (begin) - make_area(arg); - return arg->is<ValArea>(); - case 'S': - if (begin) - make_spell(arg); - return arg->is<ValSpell>(); - case 'I': - return arg->is<ValInvocationPtr>(); - default: - return true; - } -} - -int magic_signature_check(ZString opname, ZString funname, ZString signature, - Slice<val_t> args, int line, int column) -{ - int i; - for (i = 0; i < args.size(); i++) - { - val_t *arg = &args[i]; - - // whoa, it turns out the second p *does* shadow this one - if (ValEntityInt *p1 = arg->get_if<ValEntityInt>()) - { - /* Dereference entities in preparation for calling function */ - dumb_ptr<block_list> ent = map_id2bl(p1->v_eid); - if (ent) - { - *arg = ValEntityPtr{ent}; - } - else - { - *arg = ValFail{}; - } - } - else if (ValInvocationInt *p2 = arg->get_if<ValInvocationInt>()) - { - dumb_ptr<invocation> invoc = map_id2bl(p2->v_iid)->is_spell(); - if (invoc) - { - *arg = ValInvocationPtr{invoc}; - } - else - { - *arg = ValFail(); - } - } - - char ty_key = signature[i]; - if (!ty_key) - { - FPRINTF(stderr, - "[magic-eval]: L%d:%d: Too many arguments (%zu) to %s `%s'\n"_fmt, - line, column, args.size(), opname, funname); - return 1; - } - - if (arg->is<ValFail>() && ty_key != '_') - return 1; /* Fail `in a sane way': This is a perfectly permissible error */ - - // this also does conversions now - if (type_key_matches(ty_key, arg, true)) - continue; - - if (arg->is<ValUndef>()) - { - FPRINTF(stderr, - "[magic-eval]: L%d:%d: Argument #%d to %s `%s' undefined\n"_fmt, - line, column, i + 1, opname, funname); - return 1; - } - - - { /* Coercion failed? */ - if (!arg->is<ValFail>()) - { - FPRINTF(stderr, - "[magic-eval]: L%d:%d: Argument #%d to %s `%s' of incorrect type (sorry, types aren't integers anymore)\n"_fmt, - line, column, i + 1, opname, funname); - } - return 1; - } - } - - return 0; -} - -void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr) -{ - MATCH_BEGIN (*expr) - { - MATCH_CASE (const val_t&, e_val) - { - magic_copy_var(dest, &e_val); - } - - MATCH_CASE (const e_location_t&, e_location) - { - location_t loc; - if (eval_location(env, &loc, &e_location)) - *dest = ValFail(); - else - *dest = ValLocation{loc}; - } - MATCH_CASE (const e_area_t&, e_area) - { - if (dumb_ptr<area_t> area = eval_area(env, e_area)) - *dest = ValArea{area}; - else - *dest = ValFail(); - } - MATCH_CASE (const ExprFunApp&, e_funapp) - { - val_t arguments[MAX_ARGS]; - int args_nr = e_funapp.args_nr; - int i; - fun_t *f = e_funapp.funp; - - for (i = 0; i < args_nr; ++i) - magic_eval(env, &arguments[i], e_funapp.args[i]); - if (magic_signature_check("function"_s, f->name, f->signature, Slice<val_t>(arguments, args_nr), - e_funapp.line_nr, e_funapp.column) - || f->fun(env, dest, Slice<val_t>(arguments, args_nr))) - *dest = ValFail(); - else - { - assert (!dest->is<ValInvocationPtr>()); - assert (!dest->is<ValInvocationInt>()); - assert (!dest->is<ValEntityInt>()); - assert (type_key_matches(f->ret_ty, dest, false)); - - /* translate entity back into persistent int */ - if (ValEntityPtr *ent = dest->get_if<ValEntityPtr>()) - { - if (ent->v_entity) - *dest = ValEntityInt{ent->v_entity->bl_id}; - else - *dest = ValFail(); - } - // what about invocation? - } - - for (i = 0; i < args_nr; ++i) - magic_clear_var(&arguments[i]); - } - MATCH_CASE (const ExprId&, e) - { - val_t& v = env->VAR(e.e_id); - magic_copy_var(dest, &v); - } - MATCH_CASE (const ExprField&, e_field) - { - val_t v; - int id = e_field.id; - magic_eval(env, &v, e_field.expr); - - assert(!v.is<ValInvocationPtr>()); - if (ValInvocationInt *ii = v.get_if<ValInvocationInt>()) - { - dumb_ptr<invocation> t = map_id2bl(ii->v_iid)->is_spell(); - - if (!t) - *dest = ValUndef(); - else - { - val_t& val = t->env->VAR(id); - magic_copy_var(dest, &val); - } - } - else - { - FPRINTF(stderr, - "[magic] Attempt to access field %s on non-spell\n"_fmt, - env->base_env->varv[id].name); - *dest = ValFail(); - } - } - } - MATCH_END (); -} - -int magic_eval_int(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr) -{ - val_t result; - magic_eval(env, &result, expr); - - if (result.is<ValFail>() || result.is<ValUndef>()) - return 0; - - intify(&result); - - return result.get_if<ValInt>()->v_int; -} - -AString magic_eval_str(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr) -{ - val_t result; - magic_eval(env, &result, expr); - - if (result.is<ValFail>() || result.is<ValUndef>()) - return "?"_s; - - stringify(&result); - - return result.get_if<ValString>()->v_string; -} -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic-expr.hpp b/src/map/magic-expr.hpp deleted file mode 100644 index 055f37b..0000000 --- a/src/map/magic-expr.hpp +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once -// magic-expr.hpp - Pure functions for the old magic backend. -// -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "fwd.hpp" - -#include "../strings/zstring.hpp" -#include "../strings/literal.hpp" - -#include "magic-interpreter.t.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -/* - * Argument types: - * i : int - * d : dir - * s : string - * e : entity - * l : location - * a : area - * S : spell - * I : invocation - * . : any, except for fail/undef - * _ : any, including fail, but not undef - */ -struct fun_t -{ - LString name; - LString signature; - char ret_ty; - int (*fun)(dumb_ptr<env_t> env, val_t *result, Slice<val_t> arga); -}; - -/** - * Retrieves a function by name - * @param name The name to look up - * @return A function of that name, or nullptr. - */ -fun_t *magic_get_fun(ZString name); - -/** - * Evaluates an expression and stores the result in `dest' - */ -void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr); - -/** - * Evaluates an expression and coerces the result into an integer - */ -int magic_eval_int(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr); - -/** - * Evaluates an expression and coerces the result into a string - */ -AString magic_eval_str(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr); - -void magic_clear_var(val_t *v); - -void magic_copy_var(val_t *dest, const val_t *src); - -void magic_random_location(location_t *dest, dumb_ptr<area_t> area); - -// ret -1: not a string, ret 1: no such item, ret 0: OK -int magic_find_item(Slice<val_t> args, int index, Item *item, int *stackable); - -#define GET_ARG_ITEM(index, dest, stackable) \ - switch (magic_find_item(args, index, &dest, &stackable)) \ - { \ - case -1: return 1; \ - case 1: return 0; \ - default: break; \ - } - -int magic_location_in_area(Borrowed<map_local> m, int x, int y, dumb_ptr<area_t> area); - -/* Helper definitions for dealing with functions and operations */ - -int magic_signature_check(ZString opname, ZString funname, ZString signature, - Slice<val_t> args, int line, int column); - -Borrowed<map_local> magic_area_rect(int *x, int *y, int *width, int *height, - area_t& area); -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic-expr.py b/src/map/magic-expr.py deleted file mode 100644 index f53ddc8..0000000 --- a/src/map/magic-expr.py +++ /dev/null @@ -1,38 +0,0 @@ -class fun_t(object): - __slots__ = ('_value') - - name = 'tmwa::map::magic::fun_t' - depth = 1 - enabled = True - - def __init__(self, value): - if not value: - value = None - self._value = value - - def to_string(self): - value = self._value - if value is None: - return '(fun_t *) nullptr' - return '(fun_t *)' - - def children(self): - value = self._value - if value is None: - return - value = value.dereference() - yield '->name', value['name'] - yield '->signature', value['signature'] - yield '->ret_ty', value['ret_ty'] - yield '->fun', value['fun'] - - test_extra = ''' - using tmwa::operator "" _s; - ''' - - tests = [ - ('static_cast<tmwa::map::magic::fun_t *>(nullptr)', - '(fun_t *) nullptr'), - ('new tmwa::map::magic::fun_t{"name"_s, "sig"_s, \'\\0\', nullptr}', - '(fun_t *) = {->name = "name", ->signature = "sig", ->ret_ty = 0 \'\\000\', ->fun = nullptr}'), - ] diff --git a/src/map/magic-interpreter-base.cpp b/src/map/magic-interpreter-base.cpp deleted file mode 100644 index c2be363..0000000 --- a/src/map/magic-interpreter-base.cpp +++ /dev/null @@ -1,553 +0,0 @@ -#include "magic-interpreter-base.hpp" -// magic-interpreter-base.cpp - Core of the old magic system. -// -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include <algorithm> - -#include "../strings/astring.hpp" -#include "../strings/xstring.hpp" - -#include "../io/cxxstdio.hpp" - -#include "../mmo/cxxstdio_enums.hpp" - -#include "../net/timer.hpp" - -#include "globals.hpp" -#include "magic.hpp" -#include "magic-expr.hpp" -#include "magic-interpreter.hpp" -#include "pc.hpp" - -#include "../poison.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -static -void set_int(val_t *v, int i) -{ - *v = ValInt{i}; -} - -static __attribute__((unused)) -void set_dir(val_t *v, DIR d) -{ - *v = ValDir{d}; -} - - -static -void set_string(val_t *v, RString x) -{ - *v = ValString{x}; -} - -static -void set_entity(val_t *v, dumb_ptr<block_list> e) -{ - *v = ValEntityInt{e->bl_id}; -} - -static -void set_invocation(val_t *v, dumb_ptr<invocation> i) -{ - *v = ValInvocationInt{i->bl_id}; -} - -static -void set_spell(val_t *v, dumb_ptr<spell_t> x) -{ - *v = ValSpell{x}; -} - -AString magic_find_invocation(XString spellname) -{ - auto it = magic_conf.spells_by_name.find(spellname); - if (it != magic_conf.spells_by_name.end()) - return it->second->invocation; - - return AString(); -} - -dumb_ptr<spell_t> magic_find_spell(XString invocation) -{ - auto it = magic_conf.spells_by_invocation.find(invocation); - if (it != magic_conf.spells_by_invocation.end()) - return it->second; - - return nullptr; -} - -/* -------------------------------------------------------------------------------- */ -/* Spell anchors */ -/* -------------------------------------------------------------------------------- */ - -AString magic_find_anchor_invocation(XString anchor_name) -{ - auto it = magic_conf.anchors_by_name.find(anchor_name); - - if (it != magic_conf.anchors_by_name.end()) - return it->second->invocation; - - return AString(); -} - -dumb_ptr<teleport_anchor_t> magic_find_anchor(XString name) -{ - auto it = magic_conf.anchors_by_invocation.find(name); - if (it != magic_conf.anchors_by_invocation.end()) - return it->second; - - return nullptr; -} - -/* -------------------------------------------------------------------------------- */ -/* Spell guard checks */ -/* -------------------------------------------------------------------------------- */ - -static -dumb_ptr<env_t> alloc_env(magic_conf_t *conf) -{ - auto env = dumb_ptr<env_t>::make(); - env->varu = make_unique<val_t[]>(conf->varv.size()); - env->base_env = conf; - return env; -} - -static -dumb_ptr<env_t> clone_env(dumb_ptr<env_t> src) -{ - dumb_ptr<env_t> retval = alloc_env(src->base_env); - - for (int i = 0; i < src->base_env->varv.size(); i++) - magic_copy_var(&retval->varu[i], &src->varu[i]); - - return retval; -} - -void magic_free_env(dumb_ptr<env_t> env) -{ - for (int i = 0; i < env->base_env->varv.size(); i++) - magic_clear_var(&env->varu[i]); - // handled by std::unique_ptr now. Was a memory leak before. - // delete[] env->vars; - env.delete_(); -} - -dumb_ptr<env_t> spell_create_env(magic_conf_t *conf, dumb_ptr<spell_t> spell, - dumb_ptr<map_session_data> caster, int spellpower, XString param) -{ - dumb_ptr<env_t> env = alloc_env(conf); - - switch (spell->spellarg_ty) - { - - case SPELLARG::STRING: - set_string(&env->varu[spell->arg], param); - break; - - case SPELLARG::PC: - { - CharName name = stringish<CharName>(param); - dumb_ptr<map_session_data> subject = map_nick2sd(name); - if (!subject) - subject = caster; - set_entity(&env->varu[spell->arg], subject); - break; - } - - case SPELLARG::NONE: - break; - - default: - FPRINTF(stderr, "Unexpected spellarg type %d\n"_fmt, - spell->spellarg_ty); - } - - set_entity(&env->varu[VAR_CASTER], caster); - set_int(&env->varu[VAR_SPELLPOWER], spellpower); - set_spell(&env->varu[VAR_SPELL], spell); - - return env; -} - -static -void free_components(dumb_ptr<component_t> *component_holder) -{ - if (*component_holder == nullptr) - return; - free_components(&(*component_holder)->next); - (*component_holder).delete_(); - *component_holder = nullptr; -} - -void magic_add_component(dumb_ptr<component_t> *component_holder, ItemNameId id, int count) -{ - if (count <= 0) - return; - - if (*component_holder == nullptr) - { - auto component = dumb_ptr<component_t>::make(); - component->next = nullptr; - component->item_id = id; - component->count = count; - *component_holder = component; - } - else - { - dumb_ptr<component_t> component = *component_holder; - if (component->item_id == id) - { - component->count += count; - return; - } - else - magic_add_component(&component->next, id, count); - /* Tail-recurse; gcc can optimise this. Not that it matters. */ - } -} - -static -void copy_components(dumb_ptr<component_t> *component_holder, dumb_ptr<component_t> component) -{ - if (component == nullptr) - return; - - magic_add_component(component_holder, component->item_id, component->count); - copy_components(component_holder, component->next); -} - -typedef struct spellguard_check -{ - dumb_ptr<component_t> catalysts, components; - int mana; - interval_t casttime; -} spellguard_check_t; - -static -int check_prerequisites(dumb_ptr<map_session_data> caster, dumb_ptr<component_t> component) -{ - while (component) - { - if (pc_count_all_items(caster, component->item_id) < component->count) - return 0; /* insufficient */ - - component = component->next; - } - - return 1; -} - -static -void consume_components(dumb_ptr<map_session_data> caster, dumb_ptr<component_t> component) -{ - while (component) - { - pc_remove_items(caster, component->item_id, component->count); - component = component->next; - } -} - -static -int spellguard_can_satisfy(spellguard_check_t *check, dumb_ptr<map_session_data> caster, - dumb_ptr<env_t> env, int *near_miss) -{ - tick_t tick = gettick(); - - int retval = check_prerequisites(caster, check->catalysts); - - if (retval && near_miss) - *near_miss = 1; // close enough! - - retval = retval && caster->cast_tick <= tick /* Hasn't cast a spell too recently */ - && check->mana <= caster->status.sp - && check_prerequisites(caster, check->components); - - if (retval) - { - interval_t casttime = check->casttime; - - if (ValInt *v = env->VAR(VAR_MIN_CASTTIME).get_if<ValInt>()) - { - casttime = std::max(casttime, static_cast<interval_t>(v->v_int)); - } - - caster->cast_tick = tick + casttime; /* Make sure not to cast too frequently */ - - consume_components(caster, check->components); - pc_heal(caster, 0, -check->mana); - } - - return retval; -} - -static -const effect_set_t *spellguard_check_sub(spellguard_check_t *check, - dumb_ptr<spellguard_t> guard, - dumb_ptr<map_session_data> caster, - dumb_ptr<env_t> env, - int *near_miss) -{ - if (guard == nullptr) - return nullptr; - - MATCH_BEGIN (*guard) - { - MATCH_CASE (const GuardCondition&, s) - { - if (!magic_eval_int(env, s.s_condition)) - return nullptr; - } - MATCH_CASE (const GuardComponents&, s) - { - copy_components(&check->components, s.s_components); - } - MATCH_CASE (const GuardCatalysts&, s) - { - copy_components(&check->catalysts, s.s_catalysts); - } - MATCH_CASE (const GuardChoice&, s) - { - spellguard_check_t altcheck = *check; - const effect_set_t *retval; - - altcheck.components = nullptr; - altcheck.catalysts = nullptr; - - copy_components(&altcheck.catalysts, check->catalysts); - copy_components(&altcheck.components, check->components); - - retval = - spellguard_check_sub(&altcheck, guard->next, caster, env, - near_miss); - free_components(&altcheck.catalysts); - free_components(&altcheck.components); - if (retval) - return retval; - else - return spellguard_check_sub(check, s.s_alt, caster, - env, near_miss); - } - MATCH_CASE (const GuardMana&, s) - { - check->mana += magic_eval_int(env, s.s_mana); - } - MATCH_CASE (const GuardCastTime&, s) - { - check->casttime += static_cast<interval_t>(magic_eval_int(env, s.s_casttime)); - } - MATCH_CASE (const effect_set_t&, s_effect) - { - if (spellguard_can_satisfy(check, caster, env, near_miss)) - return &s_effect; - else - return nullptr; - } - } - MATCH_END (); - - return spellguard_check_sub(check, guard->next, caster, env, near_miss); -} - -static -const effect_set_t *check_spellguard(dumb_ptr<spellguard_t> guard, - dumb_ptr<map_session_data> caster, dumb_ptr<env_t> env, - int *near_miss) -{ - spellguard_check_t check; - const effect_set_t *retval; - check.catalysts = nullptr; - check.components = nullptr; - check.mana = 0; - check.casttime = interval_t::zero(); - - retval = spellguard_check_sub(&check, guard, caster, env, near_miss); - - free_components(&check.catalysts); - free_components(&check.components); - - return retval; -} - -/* -------------------------------------------------------------------------------- */ -/* Public API */ -/* -------------------------------------------------------------------------------- */ - -const effect_set_t *spell_trigger(dumb_ptr<spell_t> spell, dumb_ptr<map_session_data> caster, - dumb_ptr<env_t> env, int *near_miss) -{ - dumb_ptr<spellguard_t> guard = spell->spellguard; - - if (near_miss) - *near_miss = 0; - - for (letdef_t& ld : spell->letdefv) - magic_eval(env, &env->varu[ld.id], ld.expr); - - return check_spellguard(guard, caster, env, near_miss); -} - -static -void spell_set_location(dumb_ptr<invocation> invocation, dumb_ptr<block_list> entity) -{ - magic_clear_var(&invocation->env->varu[VAR_LOCATION]); - ValLocation v; - v.v_location.m = entity->bl_m; - v.v_location.x = entity->bl_x; - v.v_location.y = entity->bl_y; - invocation->env->varu[VAR_LOCATION] = v; -} - -void spell_update_location(dumb_ptr<invocation> invocation) -{ - if (bool(invocation->spell->flags & SPELL_FLAG::LOCAL)) - return; - else - { - dumb_ptr<block_list> owner_bl = map_id2bl(invocation->subject); - if (!owner_bl) - return; - dumb_ptr<map_session_data> owner = owner_bl->is_player(); - - spell_set_location(invocation, owner); - } -} - -dumb_ptr<invocation> spell_instantiate(const effect_set_t *effect_set, dumb_ptr<env_t> env) -{ - dumb_ptr<invocation> retval; - retval.new_(); - dumb_ptr<block_list> caster; - - retval->env = env; - - retval->caster = env->VAR(VAR_CASTER).get_if<ValEntityInt>()->v_eid; - retval->spell = env->VAR(VAR_SPELL).get_if<ValSpell>()->v_spell; - retval->current_effect = effect_set->effect; - retval->trigger_effect = effect_set->at_trigger; - retval->end_effect = effect_set->at_end; - - caster = map_id2bl(retval->caster); // must still exist - retval->bl_id = map_addobject(retval); - retval->bl_type = BL::SPELL; - retval->bl_m = caster->bl_m; - retval->bl_x = caster->bl_x; - retval->bl_y = caster->bl_y; - - map_addblock(retval); - set_invocation(&env->varu[VAR_INVOCATION], retval); - - return retval; -} - -dumb_ptr<invocation> spell_clone_effect(dumb_ptr<invocation> base) -{ - dumb_ptr<invocation> retval; - retval.new_(); - - // block_list in general is not copyable - // since this is the only call site, it is expanded here - //*retval = *base; - - retval->next_invocation = nullptr; - retval->flags = INVOCATION_FLAG::ZERO; - dumb_ptr<env_t> env = retval->env = clone_env(base->env); - retval->spell = base->spell; - retval->caster = base->caster; - retval->subject = BlockId(); - // retval->timer = 0; - // retval->stack = undef; - retval->script_pos = 0; - // huh? - retval->current_effect = base->trigger_effect; - retval->trigger_effect = base->trigger_effect; - retval->end_effect = nullptr; - // retval->status_change_refs = nullptr; - - retval->bl_id = BlockId(); - retval->bl_prev = nullptr; - retval->bl_next = nullptr; - retval->bl_m = base->bl_m; - retval->bl_x = base->bl_x; - retval->bl_y = base->bl_y; - retval->bl_type = base->bl_type; - - retval->bl_id = map_addobject(retval); - set_invocation(&env->varu[VAR_INVOCATION], retval); - - return retval; -} - -void spell_bind(dumb_ptr<map_session_data> subject, dumb_ptr<invocation> invocation) -{ - /* Only bind nonlocal spells */ - - if (!bool(invocation->spell->flags & SPELL_FLAG::LOCAL)) - { - if (bool(invocation->flags & INVOCATION_FLAG::BOUND) - || invocation->subject || invocation->next_invocation) - { - int *i = nullptr; - FPRINTF(stderr, - "[magic] INTERNAL ERROR: Attempt to re-bind spell invocation `%s'\n"_fmt, - invocation->spell->name); - *i = 1; - return; - } - - invocation->next_invocation = subject->active_spells; - subject->active_spells = invocation; - invocation->flags |= INVOCATION_FLAG::BOUND; - invocation->subject = subject->bl_id; - } - - spell_set_location(invocation, subject); -} - -int spell_unbind(dumb_ptr<map_session_data> subject, dumb_ptr<invocation> invocation_) -{ - dumb_ptr<invocation> *seeker = &subject->active_spells; - - while (*seeker) - { - if (*seeker == invocation_) - { - *seeker = invocation_->next_invocation; - - invocation_->flags &= ~INVOCATION_FLAG::BOUND; - invocation_->next_invocation = nullptr; - invocation_->subject = BlockId(); - - return 0; - } - seeker = &((*seeker)->next_invocation); - } - - return 1; -} -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic-interpreter-base.hpp b/src/map/magic-interpreter-base.hpp deleted file mode 100644 index 7c00db0..0000000 --- a/src/map/magic-interpreter-base.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once -// magic-interpreter-base.hpp - Core of the old magic system. -// -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "fwd.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -/** - * Adds a component selection to a component holder (which may initially be nullptr) - */ -void magic_add_component(dumb_ptr<component_t> *component_holder, ItemNameId id, int count); - -/** - * Identifies the invocation used to trigger a spell - * - * Returns empty string if not found - */ -AString magic_find_invocation(XString spellname); - -/** - * Identifies the invocation used to denote a teleport location - * - * Returns empty string if not found - */ -AString magic_find_anchor_invocation(XString teleport_location); - -dumb_ptr<teleport_anchor_t> magic_find_anchor(XString name); - -dumb_ptr<env_t> spell_create_env(magic_conf_t *conf, dumb_ptr<spell_t> spell, - dumb_ptr<map_session_data> caster, int spellpower, XString param); - -void magic_free_env(dumb_ptr<env_t> env); - -/** - * near_miss is set to nonzero iff the spell only failed due to ephemereal issues (spell delay in effect, out of mana, out of components) - */ -const effect_set_t *spell_trigger(dumb_ptr<spell_t> spell, - dumb_ptr<map_session_data> caster, - dumb_ptr<env_t> env, int *near_miss); - -dumb_ptr<invocation> spell_instantiate(const effect_set_t *effect, dumb_ptr<env_t> env); - -/** - * Bind a spell to a subject (this is a no-op for `local' spells). - */ -void spell_bind(dumb_ptr<map_session_data> subject, dumb_ptr<invocation> invocation); - -// 1 on failure -int spell_unbind(dumb_ptr<map_session_data> subject, dumb_ptr<invocation> invocation); - -/** - * Clones a spell to run the at_effect field - */ -dumb_ptr<invocation> spell_clone_effect(dumb_ptr<invocation> source); - -dumb_ptr<spell_t> magic_find_spell(XString invocation); - -void spell_update_location(dumb_ptr<invocation> invocation); -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic-interpreter.hpp b/src/map/magic-interpreter.hpp deleted file mode 100644 index cbd92a9..0000000 --- a/src/map/magic-interpreter.hpp +++ /dev/null @@ -1,630 +0,0 @@ -#pragma once -// magic-interpreter.hpp - Old magic. -// -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "magic-interpreter.t.hpp" - -#include "fwd.hpp" - -#include <cassert> - -#include <memory> - -#include "../strings/rstring.hpp" - -#include "../sexpr/variant.hpp" - -#include "../net/timer.t.hpp" - -#include "../mmo/ids.hpp" - -#include "map.hpp" -#include "script-buffer.hpp" -#include "../mmo/skill.t.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -struct location_t -{ - Borrowed<map_local> m; - int x, y; - - // This constructor exists solely to work around the design constraints - // of sexpr::Variant<>. See comments in variant.tcc for future plans. - __attribute__((deprecated)) - location_t() noexcept : m(borrow(undefined_gat)), x(), y() {} - location_t(Borrowed<map_local> m_, int x_, int y_) : m(m_), x(x_), y(y_) {} -}; - -struct AreaUnion -{ - dumb_ptr<area_t> a_union[2]; -}; -struct AreaRect -{ - location_t loc; - int width, height; -}; -struct AreaBar -{ - location_t loc; - int width, depth; - DIR dir; -}; - -using AreaVariantBase = Variant< - location_t, - AreaUnion, - AreaRect, - AreaBar ->; - -struct area_t : AreaVariantBase -{ - int size; - - area_t() = delete; - area_t(area_t&&) = default; - area_t(const area_t&) = delete; - area_t& operator = (area_t&&) = default; - area_t& operator = (const area_t&) = delete; - - area_t(location_t v) : AreaVariantBase(std::move(v)), size(1) {} - area_t(AreaUnion v) : AreaVariantBase(std::move(v)), size(v.a_union[0]->size + v.a_union[1]->size) {} - area_t(AreaRect v) : AreaVariantBase(std::move(v)), size(v.width * v.height) {} - area_t(AreaBar v) : AreaVariantBase(std::move(v)), size((v.width * 2 + 1) * v.depth) {} -}; - -struct ValUndef -{ -}; -struct ValInt -{ - int v_int; -}; -struct ValDir -{ - DIR v_dir; -}; -struct ValString -{ - RString v_string; -}; -struct ValEntityInt -{ - BlockId v_eid; -}; -struct ValEntityPtr -{ - dumb_ptr<block_list> v_entity; -}; -struct ValLocation -{ - location_t v_location; -}; -struct ValArea -{ - dumb_ptr<area_t> v_area; -}; -struct ValSpell -{ - dumb_ptr<spell_t> v_spell; -}; -struct ValInvocationInt -{ - BlockId v_iid; -}; -struct ValInvocationPtr -{ - dumb_ptr<invocation> v_invocation; -}; -struct ValFail -{ -}; -struct ValNegative1 -{ -}; - -using ValVariantBase = Variant< - ValUndef, - ValInt, - ValDir, - ValString, - ValEntityInt, - ValEntityPtr, - ValLocation, - ValArea, - ValSpell, - ValInvocationInt, - ValInvocationPtr, - ValFail, - ValNegative1 ->; -struct val_t : ValVariantBase -{ - val_t() noexcept : ValVariantBase(ValUndef{}) {} - val_t(val_t&&) = default; - val_t(const val_t&) = delete; - val_t& operator = (val_t&&) = default; - val_t& operator = (const val_t&) = delete; - - val_t(ValUndef v) : ValVariantBase(std::move(v)) {} - val_t(ValInt v) : ValVariantBase(std::move(v)) {} - val_t(ValDir v) : ValVariantBase(std::move(v)) {} - val_t(ValString v) : ValVariantBase(std::move(v)) {} - val_t(ValEntityInt v) : ValVariantBase(std::move(v)) {} - val_t(ValEntityPtr v) : ValVariantBase(std::move(v)) {} - val_t(ValLocation v) : ValVariantBase(std::move(v)) {} - val_t(ValArea v) : ValVariantBase(std::move(v)) {} - val_t(ValSpell v) : ValVariantBase(std::move(v)) {} - val_t(ValInvocationInt v) : ValVariantBase(std::move(v)) {} - val_t(ValInvocationPtr v) : ValVariantBase(std::move(v)) {} - val_t(ValFail v) : ValVariantBase(std::move(v)) {} - val_t(ValNegative1 v) : ValVariantBase(std::move(v)) {} -}; - - -/* ----------- */ -/* Expressions */ -/* ----------- */ - -#define MAX_ARGS 7 /* Max. # of args used in builtin primitive functions */ - -struct e_area_t; - -struct e_location_t -{ - dumb_ptr<expr_t> m, x, y; - - e_location_t() noexcept : m(), x(), y() {} -}; -struct ExprAreaUnion -{ - dumb_ptr<e_area_t> a_union[2]; -}; -struct ExprAreaRect -{ - e_location_t loc; - dumb_ptr<expr_t> width, height; -}; -struct ExprAreaBar -{ - e_location_t loc; - dumb_ptr<expr_t> width, depth, dir; -}; - -using ExprAreaVariantBase = Variant< - e_location_t, - ExprAreaUnion, - ExprAreaRect, - ExprAreaBar ->; - -struct e_area_t : ExprAreaVariantBase -{ - e_area_t() = delete; - e_area_t(e_area_t&&) = default; - e_area_t(const e_area_t&) = delete; - e_area_t& operator = (e_area_t&&) = default; - e_area_t& operator = (const e_area_t&) = delete; - - e_area_t(e_location_t v) : ExprAreaVariantBase(std::move(v)) {} - e_area_t(ExprAreaUnion v) : ExprAreaVariantBase(std::move(v)) {} - e_area_t(ExprAreaRect v) : ExprAreaVariantBase(std::move(v)) {} - e_area_t(ExprAreaBar v) : ExprAreaVariantBase(std::move(v)) {} -}; - -struct ExprFunApp -{ - fun_t *funp; - int line_nr, column; - int args_nr; - dumb_ptr<expr_t> args[MAX_ARGS]; -}; -struct ExprId -{ - int e_id; -}; -struct ExprField -{ - dumb_ptr<expr_t> expr; - int id; -}; - -using ExprVariantBase = Variant< - val_t, - e_location_t, - e_area_t, - ExprFunApp, - ExprId, - ExprField ->; -struct expr_t : ExprVariantBase -{ - expr_t() = delete; - expr_t(expr_t&&) = default; - expr_t(const expr_t&) = delete; - expr_t& operator = (expr_t&&) = default; - expr_t& operator = (const expr_t&) = delete; - - expr_t(val_t v) : ExprVariantBase(std::move(v)) {} - expr_t(e_location_t v) : ExprVariantBase(std::move(v)) {} - expr_t(e_area_t v) : ExprVariantBase(std::move(v)) {} - expr_t(ExprFunApp v) : ExprVariantBase(std::move(v)) {} - expr_t(ExprId v) : ExprVariantBase(std::move(v)) {} - expr_t(ExprField v) : ExprVariantBase(std::move(v)) {} -}; - - -struct effect_t; - -struct EffectSkip -{ -}; -struct EffectAbort -{ -}; -struct EffectAssign -{ - int id; - dumb_ptr<expr_t> expr; -}; -struct EffectForEach -{ - int id; - dumb_ptr<expr_t> area; - dumb_ptr<effect_t> body; - FOREACH_FILTER filter; -}; -struct EffectFor -{ - int id; - dumb_ptr<expr_t> start, stop; - dumb_ptr<effect_t> body; -}; -struct EffectIf -{ - dumb_ptr<expr_t> cond; - dumb_ptr<effect_t> true_branch, false_branch; -}; -struct EffectSleep -{ - dumb_ptr<expr_t> e_sleep; /* sleep time */ -}; -struct EffectScript -{ - dumb_ptr<const ScriptBuffer> e_script; -}; -struct EffectBreak -{ -}; -struct EffectOp -{ - op_t *opp; - int args_nr; - int line_nr, column; - dumb_ptr<expr_t> args[MAX_ARGS]; -}; -struct EffectEnd -{ -}; -struct EffectCall -{ - std::vector<int> *formalv; - dumb_ptr<std::vector<dumb_ptr<expr_t>>> actualvp; - dumb_ptr<effect_t> body; -}; - -using EffectVariantBase = Variant< - EffectSkip, - EffectAbort, - EffectAssign, - EffectForEach, - EffectFor, - EffectIf, - EffectSleep, - EffectScript, - EffectBreak, - EffectOp, - EffectEnd, - EffectCall ->; -struct effect_t : EffectVariantBase -{ - dumb_ptr<effect_t> next; - - effect_t() = delete; - effect_t(effect_t&&) = default; - effect_t(const effect_t&) = delete; - effect_t& operator = (effect_t&&) = default; - effect_t& operator = (const effect_t&) = delete; - - effect_t(EffectSkip v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} - effect_t(EffectAbort v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} - effect_t(EffectAssign v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} - effect_t(EffectForEach v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} - effect_t(EffectFor v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} - effect_t(EffectIf v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} - effect_t(EffectSleep v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} - effect_t(EffectScript v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} - effect_t(EffectBreak v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} - effect_t(EffectOp v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} - effect_t(EffectEnd v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} - effect_t(EffectCall v, dumb_ptr<effect_t> n) : EffectVariantBase(std::move(v)), next(n) {} -}; - -/* ---------- */ -/* Components */ -/* ---------- */ - -struct component_t -{ - dumb_ptr<component_t> next; - ItemNameId item_id; - int count; -}; - - -struct spellguard_t; -struct GuardCondition -{ - dumb_ptr<expr_t> s_condition; -}; -struct GuardMana -{ - dumb_ptr<expr_t> s_mana; -}; -struct GuardCastTime -{ - dumb_ptr<expr_t> s_casttime; -}; -struct GuardComponents -{ - dumb_ptr<component_t> s_components; -}; -struct GuardCatalysts -{ - dumb_ptr<component_t> s_catalysts; -}; -struct GuardChoice -{ - dumb_ptr<spellguard_t> s_alt; /* either `next' or `s.s_alt' */ -}; -struct effect_set_t -{ - dumb_ptr<effect_t> effect, at_trigger, at_end; -}; - -using SpellGuardVariantBase = Variant< - GuardCondition, - GuardMana, - GuardCastTime, - GuardComponents, - GuardCatalysts, - GuardChoice, - effect_set_t ->; -struct spellguard_t : SpellGuardVariantBase -{ - dumb_ptr<spellguard_t> next; - - spellguard_t() = delete; - spellguard_t(spellguard_t&&) = default; - spellguard_t(const spellguard_t&) = delete; - spellguard_t& operator = (spellguard_t&&) = default; - spellguard_t& operator = (const spellguard_t&) = delete; - - spellguard_t(GuardCondition v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {} - spellguard_t(GuardMana v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {} - spellguard_t(GuardCastTime v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {} - spellguard_t(GuardComponents v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {} - spellguard_t(GuardCatalysts v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {} - spellguard_t(GuardChoice v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {} - spellguard_t(effect_set_t v, dumb_ptr<spellguard_t> n) : SpellGuardVariantBase(std::move(v)), next(n) {} -}; - -/* ------ */ -/* Spells */ -/* ------ */ - -struct letdef_t -{ - int id; - dumb_ptr<expr_t> expr; -}; - -struct spell_t -{ - RString name; - RString invocation; - SPELL_FLAG flags; - int arg; - SPELLARG spellarg_ty; - - std::vector<letdef_t> letdefv; - - dumb_ptr<spellguard_t> spellguard; -}; - -/* ------- */ -/* Anchors */ -/* ------- */ - -struct teleport_anchor_t -{ - RString name; - RString invocation; - dumb_ptr<expr_t> location; -}; - -/* ------------------- */ -/* The big config blob */ -/* ------------------- */ - -struct magic_conf_t -{ - struct mcvar - { - RString name; - val_t val; - }; - // This should probably be done by a dedicated "intern pool" class - std::vector<mcvar> varv; - - std::map<RString, dumb_ptr<spell_t>> spells_by_name, spells_by_invocation; - - std::map<RString, dumb_ptr<teleport_anchor_t>> anchors_by_name, anchors_by_invocation; -}; - -/* Execution environment */ - -// these are not an enum they're a nasty intern hack -#define VAR_MIN_CASTTIME 0 -#define VAR_OBSCURE_CHANCE 1 -#define VAR_CASTER 2 -#define VAR_SPELLPOWER 3 -#define VAR_SPELL 4 -#define VAR_INVOCATION 5 -#define VAR_TARGET 6 -#define VAR_SCRIPTTARGET 7 -#define VAR_LOCATION 8 - -struct env_t -{ - magic_conf_t *base_env; - std::unique_ptr<val_t[]> varu; - - val_t& VAR(size_t i) - { - assert (varu); - if (varu[i].is<ValUndef>()) - return base_env->varv[i].val; - else - return varu[i]; - } - -}; - -struct CarForEach -{ - int id; - bool ty_is_spell_not_entity; - dumb_ptr<effect_t> body; - dumb_ptr<std::vector<BlockId>> entities_vp; - int index; -}; -struct CarFor -{ - int id; - dumb_ptr<effect_t> body; - int current; - int stop; -}; -struct CarProc -{ - int args_nr; - int *formalap; - dumb_ptr<val_t[]> old_actualpa; -}; - -using CarVariantBase = Variant< - CarForEach, - CarFor, - CarProc ->; - -struct cont_activation_record_t : CarVariantBase -{ - dumb_ptr<effect_t> return_location; - - cont_activation_record_t() = delete; - cont_activation_record_t(cont_activation_record_t&&) = default; - cont_activation_record_t(const cont_activation_record_t&) = delete; - cont_activation_record_t& operator = (cont_activation_record_t&&) = default; - cont_activation_record_t& operator = (const cont_activation_record_t&) = delete; - - cont_activation_record_t(CarForEach v, dumb_ptr<effect_t> rl) : CarVariantBase(std::move(v)), return_location(rl) {} - cont_activation_record_t(CarFor v, dumb_ptr<effect_t> rl) : CarVariantBase(std::move(v)), return_location(rl) {} - cont_activation_record_t(CarProc v, dumb_ptr<effect_t> rl) : CarVariantBase(std::move(v)), return_location(rl) {} -}; - -struct status_change_ref_t -{ - StatusChange sc_type; - BlockId bl_id; -}; - -struct invocation : block_list -{ - dumb_ptr<invocation> next_invocation; /* used for spells directly associated with a caster: they form a singly-linked list */ - INVOCATION_FLAG flags; - - dumb_ptr<env_t> env; - dumb_ptr<spell_t> spell; - BlockId caster; /* this is the person who originally invoked the spell */ - BlockId subject; /* when this person dies, the spell dies with it */ - - Timer timer; /* spell timer, if any */ - - std::vector<cont_activation_record_t> stack; - - int script_pos; /* Script position; if nonzero, resume the script we were running. */ - dumb_ptr<effect_t> current_effect; - dumb_ptr<effect_t> trigger_effect; /* If non-nullptr, this is used to spawn a cloned effect based on the same environment */ - dumb_ptr<effect_t> end_effect; /* If non-nullptr, this is executed when the spell terminates naturally, e.g. when all status changes have run out or all delays are over. */ - - /* Status change references: for status change updates, keep track of whom we updated where */ - std::vector<status_change_ref_t> status_change_refv; - -}; -} // namespace magic - -// inlines for map.hpp -inline dumb_ptr<magic::invocation> block_list::as_spell() { return dumb_ptr<magic::invocation>(static_cast<magic::invocation *>(this)); } -inline dumb_ptr<magic::invocation> block_list::is_spell() { return bl_type == BL::SPELL ? as_spell() : nullptr; } - -namespace magic -{ -/* The following is used only by the parser: */ -struct args_rec_t -{ - dumb_ptr<std::vector<dumb_ptr<expr_t>>> argvp; -}; - -struct proc_t -{ - RString name; - std::vector<int> argv; - dumb_ptr<effect_t> body; - - proc_t() - : name() - , argv() - , body() - {} -}; -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic-interpreter.py b/src/map/magic-interpreter.py deleted file mode 100644 index 520ab37..0000000 --- a/src/map/magic-interpreter.py +++ /dev/null @@ -1,215 +0,0 @@ -class AreaUnion(object): - __slots__ = ('_value') - name = 'tmwa::map::magic::AreaUnion' - enabled = True - - def __init__(self, value): - self._value = value - - def display_hint(self): - return 'array' - - def to_string(self): - return None - - def children(self): - v = self._value - for i in [0, 1]: - yield '[%d]', v['a_union'][i]['impl'].dereference() - - tests = [] - -class area_t(object): - enabled = True - - test_extra = ''' - #include "../strings/fwd.hpp" - using tmwa::operator "" _s; - - inline - tmwa::Borrowed<tmwa::map::map_local> fake_map_local_x_dup_for_area_t(tmwa::ZString name) - { - auto *p = new tmwa::map::map_local{}; - p->name_ = tmwa::stringish<tmwa::MapName>(name); - return tmwa::borrow(*p); - } - ''' - - tests = [ - ('tmwa::map::magic::area_t(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}'), - ('tmwa::map::magic::area_t(tmwa::map::magic::AreaUnion{{tmwa::dumb_ptr<tmwa::map::magic::area_t>::make(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}), tmwa::dumb_ptr<tmwa::map::magic::area_t>::make(tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 321, 654})}})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaUnion) = {{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}}, size = 1}, {<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::location_t) = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 321, y = 654}}, size = 1}}}, size = 2}'), - ('tmwa::map::magic::area_t(tmwa::map::magic::AreaRect{tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 123, 456}, 789, 102})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaRect) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 123, y = 456}, width = 789, height = 102}}, size = 80478}'), - ('tmwa::map::magic::area_t(tmwa::map::magic::AreaBar{tmwa::map::magic::location_t{fake_map_local_x_dup_for_area_t("map"_s), 42, 43}, 123, 456, tmwa::DIR::NW})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::location_t, tmwa::map::magic::AreaUnion, tmwa::map::magic::AreaRect, tmwa::map::magic::AreaBar>> = {(tmwa::map::magic::AreaBar) = {loc = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 43}, width = 123, depth = 456, dir = tmwa::DIR::NW}}, size = 112632}'), - ] - - -class val_t(object): - enabled = True - - test_extra = ''' - #include "../strings/fwd.hpp" - using tmwa::operator "" _s; - - inline - tmwa::Borrowed<tmwa::map::map_local> fake_map_local_x_dup_for_val_t(tmwa::ZString name) - { - auto *p = new tmwa::map::map_local{}; - p->name_ = tmwa::stringish<tmwa::MapName>(name); - return tmwa::borrow(*p); - } - ''' - - tests = [ - ('tmwa::map::magic::val_t(tmwa::map::magic::ValUndef{})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValUndef) = {<No data fields>}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValInt{42})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInt) = {v_int = 42}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValDir{tmwa::DIR::NW})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValDir) = {v_dir = tmwa::DIR::NW}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValString{"Hello"_s})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValString) = {v_string = "Hello"}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValEntityInt{tmwa::wrap<tmwa::BlockId>(123)})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValEntityInt) = {v_eid = 123}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValEntityPtr{tmwa::dumb_ptr<tmwa::map::block_list>()})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValEntityPtr) = {v_entity = 0x0}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValLocation{tmwa::map::magic::location_t{fake_map_local_x_dup_for_val_t("map"_s), 42, 123}})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValLocation) = {v_location = {m = (map_local *) = {->name = "map", ->xs = 0, ->ys = 0}, x = 42, y = 123}}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValArea{tmwa::dumb_ptr<tmwa::map::magic::area_t>()})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValArea) = {v_area = 0x0}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValSpell{tmwa::dumb_ptr<tmwa::map::magic::spell_t>()})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValSpell) = {v_spell = 0x0}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValInvocationInt{tmwa::wrap<tmwa::BlockId>(123)})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInvocationInt) = {v_iid = 123}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValInvocationPtr{})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValInvocationPtr) = {v_invocation = 0x0}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValFail{})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValFail) = {<No data fields>}}, <No data fields>}'), - ('tmwa::map::magic::val_t(tmwa::map::magic::ValNegative1{})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValNegative1) = {<No data fields>}}, <No data fields>}'), - ] - - -class ExprAreaUnion(object): - __slots__ = ('_value') - name = 'tmwa::map::magic::ExprAreaUnion' - enabled = True - - def __init__(self, value): - self._value = value - - def display_hint(self): - return 'array' - - def to_string(self): - return None - - def children(self): - v = self._value - for i in [0, 1]: - yield '[%d]', v['a_union'][i]['impl'].dereference() - - tests = [] - - -class e_area_t(object): - enabled = True - - tests = [ - ('tmwa::map::magic::e_area_t(tmwa::map::magic::e_location_t())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'), - ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaUnion{{tmwa::dumb_ptr<tmwa::map::magic::e_area_t>::make(tmwa::map::magic::e_location_t()), tmwa::dumb_ptr<tmwa::map::magic::e_area_t>::make(tmwa::map::magic::e_location_t())}})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaUnion) = {{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}, {<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}}, <No data fields>}'), - ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaRect{tmwa::map::magic::e_location_t(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>()})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaRect) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, height = 0x0}}, <No data fields>}'), - ('tmwa::map::magic::e_area_t(tmwa::map::magic::ExprAreaBar{tmwa::map::magic::e_location_t(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>()})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::ExprAreaBar) = {loc = {m = 0x0, x = 0x0, y = 0x0}, width = 0x0, depth = 0x0, dir = 0x0}}, <No data fields>}'), - ] - - - -class expr_t(object): - enabled = True - - tests = [ - ('tmwa::map::magic::expr_t(tmwa::map::magic::val_t(tmwa::map::magic::ValUndef()))', - '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::val_t) = {<tmwa::sexpr::Variant<tmwa::map::magic::ValUndef, tmwa::map::magic::ValInt, tmwa::map::magic::ValDir, tmwa::map::magic::ValString, tmwa::map::magic::ValEntityInt, tmwa::map::magic::ValEntityPtr, tmwa::map::magic::ValLocation, tmwa::map::magic::ValArea, tmwa::map::magic::ValSpell, tmwa::map::magic::ValInvocationInt, tmwa::map::magic::ValInvocationPtr, tmwa::map::magic::ValFail, tmwa::map::magic::ValNegative1>> = {(tmwa::map::magic::ValUndef) = {<No data fields>}}, <No data fields>}}, <No data fields>}'), - ('tmwa::map::magic::expr_t(tmwa::map::magic::e_location_t())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}'), - ('tmwa::map::magic::expr_t(tmwa::map::magic::e_area_t(tmwa::map::magic::e_location_t()))', - '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::e_area_t) = {<tmwa::sexpr::Variant<tmwa::map::magic::e_location_t, tmwa::map::magic::ExprAreaUnion, tmwa::map::magic::ExprAreaRect, tmwa::map::magic::ExprAreaBar>> = {(tmwa::map::magic::e_location_t) = {m = 0x0, x = 0x0, y = 0x0}}, <No data fields>}}, <No data fields>}'), - ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprFunApp())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprFunApp) = {funp = (fun_t *) nullptr, line_nr = 0, column = 0, args_nr = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, <No data fields>}'), - ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprId{123})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprId) = {e_id = 123}}, <No data fields>}'), - ('tmwa::map::magic::expr_t(tmwa::map::magic::ExprField{tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), 42})', - '{<tmwa::sexpr::Variant<tmwa::map::magic::val_t, tmwa::map::magic::e_location_t, tmwa::map::magic::e_area_t, tmwa::map::magic::ExprFunApp, tmwa::map::magic::ExprId, tmwa::map::magic::ExprField>> = {(tmwa::map::magic::ExprField) = {expr = 0x0, id = 42}}, <No data fields>}'), - ] - - -class effect_t(object): - enabled = True - - tests = [ - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectSkip{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectSkip) = {<No data fields>}}, next = 0x0}'), - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectAbort{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectAbort) = {<No data fields>}}, next = 0x0}'), - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectAssign{42, tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectAssign) = {id = 42, expr = 0x0}}, next = 0x0}'), - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectForEach{123, tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::map::magic::FOREACH_FILTER::PC}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectForEach) = {id = 123, area = 0x0, body = 0x0, filter = tmwa::map::magic::FOREACH_FILTER::PC}}, next = 0x0}'), - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectFor{42, tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectFor) = {id = 42, start = 0x0, stop = 0x0, body = 0x0}}, next = 0x0}'), - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectIf{tmwa::dumb_ptr<tmwa::map::magic::expr_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectIf) = {cond = 0x0, true_branch = 0x0, false_branch = 0x0}}, next = 0x0}'), - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectSleep{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectSleep) = {e_sleep = 0x0}}, next = 0x0}'), - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectScript{tmwa::dumb_ptr<const tmwa::map::ScriptBuffer>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectScript) = {e_script = 0x0}}, next = 0x0}'), - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectBreak{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectBreak) = {<No data fields>}}, next = 0x0}'), - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectOp(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectOp) = {opp = (op_t *) nullptr, args_nr = 0, line_nr = 0, column = 0, args = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}}, next = 0x0}'), - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectEnd{}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectEnd) = {<No data fields>}}, next = 0x0}'), - ('tmwa::map::magic::effect_t(tmwa::map::magic::EffectCall{nullptr, tmwa::dumb_ptr<std::vector<tmwa::dumb_ptr<tmwa::map::magic::expr_t>>>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::EffectSkip, tmwa::map::magic::EffectAbort, tmwa::map::magic::EffectAssign, tmwa::map::magic::EffectForEach, tmwa::map::magic::EffectFor, tmwa::map::magic::EffectIf, tmwa::map::magic::EffectSleep, tmwa::map::magic::EffectScript, tmwa::map::magic::EffectBreak, tmwa::map::magic::EffectOp, tmwa::map::magic::EffectEnd, tmwa::map::magic::EffectCall>> = {(tmwa::map::magic::EffectCall) = {formalv = nullptr, actualvp = 0x0, body = 0x0}}, next = 0x0}'), - ] - - -class spellguard_t(object): - enabled = True - - tests = [ - ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCondition{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCondition) = {s_condition = 0x0}}, next = 0x0}'), - ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardMana{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardMana) = {s_mana = 0x0}}, next = 0x0}'), - ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCastTime{tmwa::dumb_ptr<tmwa::map::magic::expr_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCastTime) = {s_casttime = 0x0}}, next = 0x0}'), - ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardComponents{tmwa::dumb_ptr<tmwa::map::magic::component_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardComponents) = {s_components = 0x0}}, next = 0x0}'), - ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardCatalysts{tmwa::dumb_ptr<tmwa::map::magic::component_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardCatalysts) = {s_catalysts = 0x0}}, next = 0x0}'), - ('tmwa::map::magic::spellguard_t(tmwa::map::magic::GuardChoice{tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::GuardChoice) = {s_alt = 0x0}}, next = 0x0}'), - ('tmwa::map::magic::spellguard_t(tmwa::map::magic::effect_set_t{tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<tmwa::map::magic::effect_t>()}, tmwa::dumb_ptr<tmwa::map::magic::spellguard_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::GuardCondition, tmwa::map::magic::GuardMana, tmwa::map::magic::GuardCastTime, tmwa::map::magic::GuardComponents, tmwa::map::magic::GuardCatalysts, tmwa::map::magic::GuardChoice, tmwa::map::magic::effect_set_t>> = {(tmwa::map::magic::effect_set_t) = {effect = 0x0, at_trigger = 0x0, at_end = 0x0}}, next = 0x0}'), - ] - - -class cont_activation_record_t(object): - enabled = True - - tests = [ - ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarForEach{42, true, tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), tmwa::dumb_ptr<std::vector<tmwa::BlockId>>(), 123}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarForEach) = {id = 42, ty_is_spell_not_entity = true, body = 0x0, entities_vp = 0x0, index = 123}}, return_location = 0x0}'), - ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarFor{42, tmwa::dumb_ptr<tmwa::map::magic::effect_t>(), 123, 456}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarFor) = {id = 42, body = 0x0, current = 123, stop = 456}}, return_location = 0x0}'), - ('tmwa::map::magic::cont_activation_record_t(tmwa::map::magic::CarProc{123, nullptr, tmwa::dumb_ptr<tmwa::map::magic::val_t[]>()}, tmwa::dumb_ptr<tmwa::map::magic::effect_t>())', - '{<tmwa::sexpr::Variant<tmwa::map::magic::CarForEach, tmwa::map::magic::CarFor, tmwa::map::magic::CarProc>> = {(tmwa::map::magic::CarProc) = {args_nr = 123, formalap = nullptr, old_actualpa = 0x0 = {sz = 0}}}, return_location = 0x0}'), - ] diff --git a/src/map/magic-interpreter.t.hpp b/src/map/magic-interpreter.t.hpp deleted file mode 100644 index e302354..0000000 --- a/src/map/magic-interpreter.t.hpp +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once -// magic-interpreter.t.hpp - Old magic. -// -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "fwd.hpp" - -#include "../generic/enum.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -enum class SPELLARG : uint8_t -{ - NONE, - PC, - STRING, -}; - -enum class FOREACH_FILTER : uint8_t -{ - MOB, - PC, - ENTITY, - TARGET, - SPELL, - NPC, -}; - -namespace e -{ -enum class SPELL_FLAG : uint8_t -{ - ZERO = 0, - - // spell associated not with caster but with place - LOCAL = 1 << 0, - // spell invocation never uttered - SILENT = 1 << 1, - // `magic word' only: don't require spellcasting ability - NONMAGIC = 1 << 2, -}; -ENUM_BITWISE_OPERATORS(SPELL_FLAG) -} -using e::SPELL_FLAG; - -namespace e -{ -enum class INVOCATION_FLAG : uint8_t -{ - ZERO = 0, - - // Bound directly to the caster (i.e., ignore its location) - BOUND = 1 << 0, - // Used `abort' to terminate - ABORTED = 1 << 1, - // On magical attacks: if we run out of steam, stop attacking altogether - STOPATTACK = 1 << 2, -}; -ENUM_BITWISE_OPERATORS(INVOCATION_FLAG) -} -using e::INVOCATION_FLAG; -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic-stmt.cpp b/src/map/magic-stmt.cpp deleted file mode 100644 index 1a8085b..0000000 --- a/src/map/magic-stmt.cpp +++ /dev/null @@ -1,1547 +0,0 @@ -#include "magic-stmt.hpp" -// magic-stmt.cpp - Imperative commands for the magic backend. -// -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include <cassert> - -#include "../compat/attr.hpp" -#include "../compat/fun.hpp" - -#include "../strings/zstring.hpp" - -#include "../generic/random2.hpp" - -#include "../io/cxxstdio.hpp" - -#include "../mmo/cxxstdio_enums.hpp" - -#include "../net/timer.hpp" - -#include "battle.hpp" -#include "clif.hpp" -#include "magic.hpp" -#include "magic-expr.hpp" -#include "magic-expr-eval.hpp" -#include "magic-interpreter.hpp" -#include "magic-interpreter-base.hpp" -#include "mob.hpp" -#include "npc.hpp" -#include "npc-parse.hpp" -#include "pc.hpp" -#include "script-call.hpp" -#include "skill.hpp" - -#include "../poison.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -/* used for local spell effects */ -constexpr Species INVISIBLE_NPC = wrap<Species>(127); - -static -void clear_activation_record(cont_activation_record_t *ar) -{ - MATCH_BEGIN (*ar) - { - MATCH_CASE (CarForEach&, c_foreach) - { - c_foreach.entities_vp.delete_(); - } - MATCH_CASE (CarProc&, c_proc) - { - c_proc.old_actualpa.delete_(); - } - } - MATCH_END (); -} - -static -void invocation_timer_callback(TimerData *, tick_t, BlockId id) -{ - dumb_ptr<invocation> invocation = map_id_is_spell(id); - - assert (invocation); - { - spell_execute(invocation); - } -} - -static -void clear_stack(dumb_ptr<invocation> invocation_) -{ - int i; - - for (i = 0; i < invocation_->stack.size(); i++) - clear_activation_record(&invocation_->stack[i]); - - invocation_->stack.clear(); -} - -void spell_free_invocation(dumb_ptr<invocation> invocation_) -{ - invocation_->status_change_refv.clear(); - - if (bool(invocation_->flags & INVOCATION_FLAG::BOUND)) - { - dumb_ptr<map_session_data> e = map_id_is_player(invocation_->subject); - if (e) - spell_unbind(e, invocation_); - } - - clear_stack(invocation_); - - invocation_->timer.cancel(); - - magic_free_env(invocation_->env); - - map_delblock(invocation_); - map_delobject(invocation_->bl_id, BL::SPELL); // also frees the object -// free(invocation_); -} - -static -void char_set_weapon_icon(dumb_ptr<map_session_data> subject, int count, - StatusChange icon, ItemNameId look) -{ - const StatusChange old_icon = subject->attack_spell_icon_override; - - subject->attack_spell_icon_override = icon; - subject->attack_spell_look_override = look; - - if (old_icon != StatusChange::ZERO && old_icon != icon) - clif_status_change(subject, old_icon, 0); - - clif_fixpcpos(subject); - if (count) - { - clif_changelook(subject, LOOK::WEAPON, unwrap<ItemNameId>(look)); - if (icon != StatusChange::ZERO) - clif_status_change(subject, icon, 1); - } - else - { - /* Set it to `normal' */ - clif_changelook(subject, LOOK::WEAPON, - static_cast<uint16_t>(subject->status.weapon)); - } -} - -static -void char_set_attack_info(dumb_ptr<map_session_data> subject, interval_t speed, int range) -{ - subject->attack_spell_delay = speed; - subject->attack_spell_range = range; - - if (speed == interval_t::zero()) - { - pc_calcstatus(subject, 1); - clif_updatestatus(subject, SP::ASPD); - clif_updatestatus(subject, SP::ATTACKRANGE); - } - else - { - subject->aspd = speed; - clif_updatestatus(subject, SP::ASPD); - clif_updatestatus(subject, SP::ATTACKRANGE); - } -} - -void magic_stop_completely(dumb_ptr<map_session_data> c) -{ - // Zap all status change references to spells - for (StatusChange i : erange(StatusChange(), StatusChange::MAX_STATUSCHANGE)) - c->sc_data[i].spell_invocation = BlockId(); - - while (c->active_spells) - spell_free_invocation(c->active_spells); - - if (c->attack_spell_override) - { - dumb_ptr<invocation> attack_spell = map_id_is_spell(c->attack_spell_override); - if (attack_spell) - spell_free_invocation(attack_spell); - c->attack_spell_override = BlockId(); - char_set_weapon_icon(c, 0, StatusChange::ZERO, ItemNameId()); - char_set_attack_info(c, interval_t::zero(), 0); - } -} - -/* Spell execution has finished normally or we have been notified by a finished skill timer */ -static -void try_to_finish_invocation(dumb_ptr<invocation> invocation) -{ - if (invocation->status_change_refv.empty() && !invocation->current_effect) - { - if (invocation->end_effect) - { - clear_stack(invocation); - invocation->current_effect = invocation->end_effect; - invocation->end_effect = nullptr; - spell_execute(invocation); - } - else - spell_free_invocation(invocation); - } -} - -static -BlockId trigger_spell(BlockId subject, BlockId spell) -{ - dumb_ptr<invocation> invocation_ = map_id_is_spell(spell); - - if (!invocation_) - return BlockId(); - - invocation_ = spell_clone_effect(invocation_); - - spell_bind(map_id_is_player(subject), invocation_); - magic_clear_var(&invocation_->env->varu[VAR_CASTER]); - invocation_->env->varu[VAR_CASTER] = ValEntityInt{subject}; - - return invocation_->bl_id; -} - -static -void entity_warp(dumb_ptr<block_list> target, Borrowed<map_local> destm, int destx, int desty); - -static -void char_update(dumb_ptr<map_session_data> character) -{ - entity_warp(character, character->bl_m, character->bl_x, - character->bl_y); -} - -static -void timer_callback_effect(TimerData *, tick_t, BlockId id, int data) -{ - dumb_ptr<block_list> target = map_id2bl(id); - if (target) - clif_misceffect(target, data); -} - -static -void entity_effect(dumb_ptr<block_list> entity, int effect_nr, interval_t delay) -{ - Timer(gettick() + delay, - std::bind(&timer_callback_effect, ph::_1, ph::_2, - entity->bl_id, effect_nr) - ).detach(); -} - -void magic_unshroud(dumb_ptr<map_session_data> other_char) -{ - other_char->state.shroud_active = 0; - // Now warp the caster out of and back into here to refresh everyone's display - char_update(other_char); - clif_displaymessage(other_char->sess, "Your shroud has been dispelled!"_s); -// entity_effect(other_char, MAGIC_EFFECT_REVEAL); -} - -static -void timer_callback_effect_npc_delete(TimerData *, tick_t, BlockId npc_id) -{ - dumb_ptr<npc_data> effect_npc = map_id_is_npc(npc_id); - npc_free(effect_npc); -} - -static -dumb_ptr<npc_data> local_spell_effect(Borrowed<map_local> m, int x, int y, int effect, - interval_t tdelay) -{ - /* 1 minute should be enough for all interesting spell effects, I hope */ - std::chrono::seconds delay = 30_s; - dumb_ptr<npc_data> effect_npc = npc_spawn_text(m, x, y, - INVISIBLE_NPC, NpcName(), "?"_s); - BlockId effect_npc_id = effect_npc->bl_id; - - entity_effect(effect_npc, effect, tdelay); - Timer(gettick() + delay, - std::bind(timer_callback_effect_npc_delete, ph::_1, ph::_2, - effect_npc_id) - ).detach(); - - return effect_npc; -} - -static -int op_sfx(dumb_ptr<env_t>, Slice<val_t> args) -{ - interval_t delay = static_cast<interval_t>(ARGINT(2)); - - if (args[0].is<ValEntityPtr>()) - { - entity_effect(ARGENTITY(0), ARGINT(1), delay); - } - else if (args[0].is<ValLocation>()) - { - local_spell_effect(ARGLOCATION(0).m, - ARGLOCATION(0).x, - ARGLOCATION(0).y, ARGINT(1), delay); - } - else - return 1; - - return 0; -} - -static -int op_instaheal(dumb_ptr<env_t> env, Slice<val_t> args) -{ - assert (!env->VAR(VAR_CASTER).is<ValEntityPtr>()); - ValEntityInt *caster_id = env->VAR(VAR_CASTER).get_if<ValEntityInt>(); - dumb_ptr<block_list> caster = caster_id - ? map_id2bl(caster_id->v_eid) : nullptr; - dumb_ptr<block_list> subject = ARGENTITY(0); - if (!caster) - caster = subject; - - if (caster->bl_type == BL::PC && subject->bl_type == BL::PC) - { - dumb_ptr<map_session_data> caster_pc = caster->is_player(); - dumb_ptr<map_session_data> subject_pc = subject->is_player(); - MAP_LOG_PC(caster_pc, "SPELLHEAL-INSTA PC%d FOR %d"_fmt, - subject_pc->status_key.char_id, ARGINT(1)); - } - - battle_heal(caster, subject, ARGINT(1), ARGINT(2), 0); - return 0; -} - -static -int op_itemheal(dumb_ptr<env_t> env, Slice<val_t> args) -{ - dumb_ptr<block_list> subject = ARGENTITY(0); - if (subject->bl_type == BL::PC) - { - pc_itemheal(subject->is_player(), - ARGINT(1), ARGINT(2)); - } - else - return op_instaheal(env, args); - - return 0; -} - -namespace e -{ -enum class Shroud -{ - HIDE_NAME_TALKING_FLAG = 1 << 0, - DISAPPEAR_ON_PICKUP_FLAG = 1 << 1, - DISAPPEAR_ON_TALK_FLAG = 1 << 2, -}; -ENUM_BITWISE_OPERATORS(Shroud) -} -using e::Shroud; - -// differs from ARGPC by checking -#define ARGCHAR(n) (ARGENTITY(n)->is_player()) - -static -int op_shroud(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<map_session_data> subject = ARGCHAR(0); - Shroud arg = static_cast<Shroud>(ARGINT(1)); - - if (!subject) - return 0; - - subject->state.shroud_active = 1; - subject->state.shroud_hides_name_talking = - bool(arg & Shroud::HIDE_NAME_TALKING_FLAG); - subject->state.shroud_disappears_on_pickup = - bool(arg & Shroud::DISAPPEAR_ON_PICKUP_FLAG); - subject->state.shroud_disappears_on_talk = - bool(arg & Shroud::DISAPPEAR_ON_TALK_FLAG); - return 0; -} - -static -int op_reveal(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<map_session_data> subject = ARGCHAR(0); - - if (subject && subject->state.shroud_active) - magic_unshroud(subject); - - return 0; -} - -static -int op_message(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<map_session_data> subject = ARGCHAR(0); - - if (subject) - clif_displaymessage(subject->sess, ARGSTR(1)); - - return 0; -} - -static -void timer_callback_kill_npc(TimerData *, tick_t, BlockId npc_id) -{ - dumb_ptr<npc_data> npc = map_id_is_npc(npc_id); - if (npc) - npc_free(npc); -} - -static -int op_messenger_npc(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<npc_data> npc; - location_t *loc = &ARGLOCATION(0); - - NpcName npcname = stringish<NpcName>(ARGSTR(2)); - npc = npc_spawn_text(loc->m, loc->x, loc->y, - wrap<Species>(static_cast<uint16_t>(ARGINT(1))), npcname, ARGSTR(3)); - - Timer(gettick() + static_cast<interval_t>(ARGINT(4)), - std::bind(timer_callback_kill_npc, ph::_1, ph::_2, - npc->bl_id) - ).detach(); - - return 0; -} - -static -void entity_warp(dumb_ptr<block_list> target, Borrowed<map_local> destm, int destx, int desty) -{ - if (target->bl_type == BL::PC || target->bl_type == BL::MOB) - { - - switch (target->bl_type) - { - case BL::PC: - { - dumb_ptr<map_session_data> character = target->is_player(); - clif_clearchar(character, BeingRemoveWhy::WARPED); - map_delblock(character); - character->bl_x = destx; - character->bl_y = desty; - character->bl_m = destm; - - pc_touch_all_relevant_npcs(character); - - // Note that touching NPCs may have triggered warping and thereby updated x and y: - MapName map_name = character->bl_m->name_; - - // Warp part #1: update relevant data, interrupt trading etc.: - pc_setpos(character, map_name, character->bl_x, character->bl_y, BeingRemoveWhy::GONE); - // Warp part #2: now notify the client - clif_changemap(character, map_name, - character->bl_x, character->bl_y); - break; - } - case BL::MOB: - target->bl_x = destx; - target->bl_y = desty; - target->bl_m = destm; - clif_fixmobpos(target->is_mob()); - break; - } - } -} - -static -int op_move(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<block_list> subject = ARGENTITY(0); - DIR dir = ARGDIR(1); - - int newx = subject->bl_x + dirx[dir]; - int newy = subject->bl_y + diry[dir]; - - if (!bool(map_getcell(subject->bl_m, newx, newy) & MapCell::UNWALKABLE)) - entity_warp(subject, subject->bl_m, newx, newy); - - return 0; -} - -static -int op_warp(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<block_list> subject = ARGENTITY(0); - location_t *loc = &ARGLOCATION(1); - - entity_warp(subject, loc->m, loc->x, loc->y); - - return 0; -} - -static -int op_banish(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<block_list> subject = ARGENTITY(0); - - if (subject->bl_type == BL::MOB) - { - dumb_ptr<mob_data> mob = subject->is_mob(); - - if (bool(mob->mode & MobMode::SUMMONED)) - mob_catch_delete(mob, BeingRemoveWhy::WARPED); - } - - return 0; -} - -static -void record_status_change(dumb_ptr<invocation> invocation_, BlockId bl_id, - StatusChange sc_id) -{ - status_change_ref_t cr {}; - cr.sc_type = sc_id; - cr.bl_id = bl_id; - - invocation_->status_change_refv.push_back(cr); -} - -static -int op_status_change(dumb_ptr<env_t> env, Slice<val_t> args) -{ - dumb_ptr<block_list> subject = ARGENTITY(0); - assert (!env->VAR(VAR_INVOCATION).is<ValInvocationPtr>()); - ValInvocationInt *ii = env->VAR(VAR_INVOCATION).get_if<ValInvocationInt>(); - BlockId invocation_id = ii - ? ii->v_iid : BlockId(); - dumb_ptr<invocation> invocation_ = map_id_is_spell(invocation_id); - - assert (!ARGINT(3)); - assert (!ARGINT(4)); - assert (!ARGINT(5)); - skill_status_effect(subject, static_cast<StatusChange>(ARGINT(1)), - ARGINT(2), - static_cast<interval_t>(ARGINT(6)), invocation_id); - - if (invocation_ && subject->bl_type == BL::PC) - record_status_change(invocation_, subject->bl_id, static_cast<StatusChange>(ARGINT(1))); - - return 0; -} - -static -int op_stop_status_change(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<block_list> subject = ARGENTITY(0); - - StatusChange sc = static_cast<StatusChange>(ARGINT(1)); - skill_status_change_end(subject, sc, nullptr); - - return 0; -} - -static -int op_override_attack(dumb_ptr<env_t> env, Slice<val_t> args) -{ - dumb_ptr<block_list> psubject = ARGENTITY(0); - int charges = ARGINT(1); - interval_t attack_delay = static_cast<interval_t>(ARGINT(2)); - int attack_range = ARGINT(3); - StatusChange icon = StatusChange(ARGINT(4)); - ItemNameId look = wrap<ItemNameId>(static_cast<uint16_t>(ARGINT(5))); - int stopattack = ARGINT(6); - dumb_ptr<map_session_data> subject; - - if (psubject->bl_type != BL::PC) - return 0; - - subject = psubject->is_player(); - - if (subject->attack_spell_override) - { - dumb_ptr<invocation> old_invocation = map_id_is_spell(subject->attack_spell_override); - if (old_invocation) - spell_free_invocation(old_invocation); - } - - ValInvocationInt *ii = env->VAR(VAR_INVOCATION).get_if<ValInvocationInt>(); - subject->attack_spell_override = - trigger_spell(subject->bl_id, ii->v_iid); - subject->attack_spell_charges = charges; - - if (subject->attack_spell_override) - { - dumb_ptr<invocation> attack_spell = map_id_is_spell(subject->attack_spell_override); - if (attack_spell && stopattack) - attack_spell->flags |= INVOCATION_FLAG::STOPATTACK; - - char_set_weapon_icon(subject, charges, icon, look); - char_set_attack_info(subject, attack_delay, attack_range); - } - - return 0; -} - -static -int op_create_item(dumb_ptr<env_t>, Slice<val_t> args) -{ - Item item; - dumb_ptr<block_list> entity = ARGENTITY(0); - dumb_ptr<map_session_data> subject; - int stackable; - int count = ARGINT(2); - if (count <= 0) - return 0; - - if (entity->bl_type == BL::PC) - subject = entity->is_player(); - else - return 0; - - GET_ARG_ITEM(1, item, stackable); - - if (!stackable) - while (count--) - pc_additem(subject, &item, 1); - else - pc_additem(subject, &item, count); - - return 0; -} - -inline -bool AGGRAVATION_MODE_ATTACKS_CASTER(int n) -{ - return n == 0 || n == 2; -} -inline -bool AGGRAVATION_MODE_MAKES_AGGRESSIVE(int n) -{ - return n > 0; -} - -static -int op_aggravate(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<block_list> victim = ARGENTITY(2); - int mode = ARGINT(1); - dumb_ptr<block_list> target = ARGENTITY(0); - dumb_ptr<mob_data> other; - - if (target->bl_type == BL::MOB) - other = target->is_mob(); - else - return 0; - - mob_target(other, victim, battle_get_range(victim)); - - if (AGGRAVATION_MODE_MAKES_AGGRESSIVE(mode)) - other->mode = MobMode::war | (other->mode & MobMode::SENSIBLE_MASK); - - if (AGGRAVATION_MODE_ATTACKS_CASTER(mode)) - { - other->target_id = victim->bl_id; - other->attacked_id = victim->bl_id; - } - - return 0; -} - -enum class MonsterAttitude -{ - HOSTILE = 0, - FRIENDLY = 1, - SERVANT = 2, - FROZEN = 3, -}; - -static -int op_spawn(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<area_t> area = ARGAREA(0); - dumb_ptr<block_list> owner_e = ARGENTITY(1); - Species monster_id = wrap<Species>(ARGINT(2)); - MonsterAttitude monster_attitude = static_cast<MonsterAttitude>(ARGINT(3)); - int monster_count = ARGINT(4); - interval_t monster_lifetime = static_cast<interval_t>(ARGINT(5)); - int i; - - dumb_ptr<map_session_data> owner = nullptr; - if (monster_attitude == MonsterAttitude::SERVANT - && owner_e->bl_type == BL::PC) - owner = owner_e->is_player(); - - for (i = 0; i < monster_count; i++) - { - location_t loc; - magic_random_location(&loc, area); - - BlockId mob_id; - dumb_ptr<mob_data> mob; - - mob_id = mob_once_spawn(owner, loc.m->name_, loc.x, loc.y, JAPANESE_NAME, // Is that needed? - monster_id, 1, NpcEvent()); - - mob = map_id_is_mob(mob_id); - - if (mob) - { - mob->mode = get_mob_db(monster_id).mode; - - switch (monster_attitude) - { - case MonsterAttitude::SERVANT: - mob->state.special_mob_ai = 1; - mob->mode |= MobMode::AGGRESSIVE; - break; - - case MonsterAttitude::FRIENDLY: - mob->mode = MobMode::CAN_ATTACK | (mob->mode & MobMode::CAN_MOVE); - break; - - case MonsterAttitude::HOSTILE: - mob->mode = MobMode::CAN_ATTACK | MobMode::AGGRESSIVE | (mob->mode & MobMode::CAN_MOVE); - if (owner) - { - mob->target_id = owner->bl_id; - mob->attacked_id = owner->bl_id; - } - break; - - case MonsterAttitude::FROZEN: - mob->mode = MobMode::ZERO; - break; - } - - mob->mode |= - MobMode::SUMMONED | MobMode::TURNS_AGAINST_BAD_MASTER; - - mob->deletetimer = Timer(gettick() + monster_lifetime, - std::bind(mob_timer_delete, ph::_1, ph::_2, - mob_id)); - - if (owner) - { - mob->master_id = owner->bl_id; - mob->master_dist = 6; - } - } - } - - return 0; -} - -static -ZString get_invocation_name(dumb_ptr<env_t> env) -{ - assert (!env->VAR(VAR_INVOCATION).is<ValInvocationPtr>()); - - ValInvocationInt *ii = env->VAR(VAR_INVOCATION).get_if<ValInvocationInt>(); - if (!ii) - return "?"_s; - - dumb_ptr<invocation> invocation_; - invocation_ = map_id_is_spell(ii->v_iid); - - if (invocation_) - return invocation_->spell->name; - else - return "??"_s; -} - -static -int op_injure(dumb_ptr<env_t> env, Slice<val_t> args) -{ - dumb_ptr<block_list> caster = ARGENTITY(0); - dumb_ptr<block_list> target = ARGENTITY(1); - int damage_caused = ARGINT(2); - int mp_damage = ARGINT(3); - int target_hp = battle_get_hp(target); - int mdef = battle_get_mdef(target); - - if (target->bl_type == BL::PC // target is player - && !target->bl_m->flag.get(MapFlag::PVP) // there is no pvpon flag - && (caster->bl_type == BL::PC) // caster is player - && ((target->is_player()->state.pvpchannel == 0) - || ((caster->is_player()->state.pvpchannel > 0) - && (target->is_player()->state.pvpchannel != caster->is_player()->state.pvpchannel)))) - return 0; /* Cannot damage other players outside of pvp */ - - if (target != caster) - { - /* Not protected against own spells */ - damage_caused = (damage_caused * (100 - mdef)) / 100; - mp_damage = (mp_damage * (100 - mdef)) / 100; - } - - damage_caused = (damage_caused > target_hp) ? target_hp : damage_caused; - - if (damage_caused < 0) - damage_caused = 0; - - // display damage first, because dealing damage may deallocate the target. - clif_damage(caster, target, - gettick(), interval_t::zero(), interval_t::zero(), - damage_caused, 0, DamageType::NORMAL); - - if (caster->bl_type == BL::PC) - { - dumb_ptr<map_session_data> caster_pc = caster->is_player(); - if (target->bl_type == BL::MOB) - { - dumb_ptr<mob_data> mob = target->is_mob(); - - MAP_LOG_PC(caster_pc, "SPELLDMG MOB%d %d FOR %d BY %s"_fmt, - mob->bl_id, mob->mob_class, damage_caused, - get_invocation_name(env)); - } - } - battle_damage(caster, target, damage_caused, mp_damage); - - return 0; -} - -static -int op_emote(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<block_list> victim = ARGENTITY(0); - int emotion = ARGINT(1); - clif_emotion(victim, emotion); - - return 0; -} - -static -int op_set_script_variable(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<map_session_data> c = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr; - VarName varname = stringish<VarName>(ARGSTR(1)); - int array_index = 0; - - if (!c) - return 1; - - set_script_var_i(c, varname, array_index, ARGINT(2)); - - return 0; -} - -static -int op_set_script_str(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<map_session_data> c = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr; - VarName varname = stringish<VarName>(ARGSTR(1)); - int array_index = 0; - - if (!c) - return 1; - - set_script_var_s(c, varname, array_index, ARGSTR(2)); - - return 0; -} - -static -int op_set_hair_colour(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<map_session_data> c = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr; - - if (!c) - return 1; - - pc_changelook(c, LOOK::HAIR_COLOR, ARGINT(1)); - - return 0; -} - -static -int op_set_hair_style(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<map_session_data> c = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr; - - if (!c) - return 1; - - pc_changelook(c, LOOK::HAIR, ARGINT(1)); - - return 0; -} - -static -int op_drop_item_for (dumb_ptr<env_t>, Slice<val_t> args) -{ - Item item; - int stackable; - location_t *loc = &ARGLOCATION(0); - int count = ARGINT(2); - interval_t interval = static_cast<interval_t>(ARGINT(3)); - dumb_ptr<map_session_data> c = ((args.size() > 4) && (ENTITY_TYPE(4) == BL::PC)) ? ARGPC(4) : nullptr; - interval_t delay = (args.size() > 5) ? static_cast<interval_t>(ARGINT(5)) : interval_t::zero(); - interval_t delaytime[3] = { delay, delay, delay }; - dumb_ptr<map_session_data> owners[3] = { c, nullptr, nullptr }; - - GET_ARG_ITEM(1, item, stackable); - - if (stackable) - { - map_addflooritem_any(&item, count, loc->m, loc->x, loc->y, - owners, delaytime, interval, 0); - } - else - { - while (count-- > 0) - map_addflooritem_any(&item, 1, loc->m, loc->x, loc->y, - owners, delaytime, interval, 0); - } - - return 0; -} - -static -int op_gain_exp(dumb_ptr<env_t>, Slice<val_t> args) -{ - dumb_ptr<map_session_data> c = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : nullptr; - - if (!c) - return 1; - - pc_gainexp_reason(c, ARGINT(1), ARGINT(2), - static_cast<PC_GAINEXP_REASON>(ARGINT(3))); - return 0; -} - -#define MAGIC_OPERATION(name, args, impl) {{name}, {{name}, {args}, impl}} -#define MAGIC_OPERATION1(name, args) MAGIC_OPERATION(#name##_s, args, op_##name) -static -std::map<ZString, op_t> operations = -{ - MAGIC_OPERATION1(sfx, ".ii"_s), - MAGIC_OPERATION1(instaheal, "eii"_s), - MAGIC_OPERATION1(itemheal, "eii"_s), - MAGIC_OPERATION1(shroud, "ei"_s), - MAGIC_OPERATION("unshroud"_s, "e"_s, op_reveal), - MAGIC_OPERATION1(message, "es"_s), - MAGIC_OPERATION1(messenger_npc, "lissi"_s), - MAGIC_OPERATION1(move, "ed"_s), - MAGIC_OPERATION1(warp, "el"_s), - MAGIC_OPERATION1(banish, "e"_s), - MAGIC_OPERATION1(status_change, "eiiiiii"_s), - MAGIC_OPERATION1(stop_status_change, "ei"_s), - MAGIC_OPERATION1(override_attack, "eiiiiii"_s), - MAGIC_OPERATION1(create_item, "e.i"_s), - MAGIC_OPERATION1(aggravate, "eie"_s), - MAGIC_OPERATION1(spawn, "aeiiii"_s), - MAGIC_OPERATION1(injure, "eeii"_s), - MAGIC_OPERATION1(emote, "ei"_s), - MAGIC_OPERATION1(set_script_variable, "esi"_s), - MAGIC_OPERATION1(set_script_str, "ess"_s), - MAGIC_OPERATION1(set_hair_colour, "ei"_s), - MAGIC_OPERATION1(set_hair_style, "ei"_s), - MAGIC_OPERATION("drop_item"_s, "l.ii"_s, op_drop_item_for), - MAGIC_OPERATION1(drop_item_for, "l.iiei"_s), - MAGIC_OPERATION("gain_experience"_s, "eiii"_s, op_gain_exp), -}; - -op_t *magic_get_op(ZString name) -{ - auto it = operations.find(name); - if (it == operations.end()) - return nullptr; - return &it->second; -} - -void spell_effect_report_termination(BlockId invocation_id, BlockId bl_id, - StatusChange sc_id, int) -{ - dumb_ptr<invocation> invocation_ = map_id_is_spell(invocation_id); - - if (!invocation_ || invocation_->bl_type != BL::SPELL) - return; - - for (status_change_ref_t& cr : invocation_->status_change_refv) - { - if (cr.sc_type == sc_id && cr.bl_id == bl_id) - { - if (&cr != &invocation_->status_change_refv.back()) - std::swap(cr, invocation_->status_change_refv.back()); - invocation_->status_change_refv.pop_back(); - - try_to_finish_invocation(invocation_); - return; - } - } - - { - dumb_ptr<block_list> entity = map_id2bl(bl_id); - if (entity->bl_type == BL::PC) - FPRINTF(stderr, - "[magic] INTERNAL ERROR: spell-effect-report-termination: tried to terminate on unexpected bl %d, sc %d\n"_fmt, - bl_id, sc_id); - return; - } - -} - -static -dumb_ptr<effect_t> return_to_stack(dumb_ptr<invocation> invocation_) -{ - if (invocation_->stack.empty()) - return nullptr; - else - { - cont_activation_record_t *ar = - &invocation_->stack.back(); - MATCH_BEGIN (*ar) - { - MATCH_CASE (const CarProc&, c_proc) - { - dumb_ptr<effect_t> ret = ar->return_location; - for (int i = 0; i < c_proc.args_nr; i++) - { - val_t *var = - &invocation_->env->varu[c_proc.formalap[i]]; - magic_clear_var(var); - *var = std::move(c_proc.old_actualpa[i]); - } - - // pop the stack - clear_activation_record(ar); - invocation_->stack.pop_back(); - - return ret; - } - MATCH_CASE (CarForEach&, c_foreach) - { - BlockId entity_id; - val_t *var = &invocation_->env->varu[c_foreach.id]; - - do - { - // This >= is really supposed to be a ==, but - // I have no clue if it's actually safe to change it. - if (c_foreach.index >= c_foreach.entities_vp->size()) - { - // pop the stack - dumb_ptr<effect_t> ret = ar->return_location; - clear_activation_record(ar); - invocation_->stack.pop_back(); - return ret; - } - - entity_id = - (*c_foreach.entities_vp)[c_foreach.index++]; - } - while (!entity_id || !map_id2bl(entity_id)); - - magic_clear_var(var); - if (c_foreach.ty_is_spell_not_entity) - *var = ValInvocationInt{entity_id}; - else - *var = ValEntityInt{entity_id}; - - return c_foreach.body; - } - MATCH_CASE (CarFor&, c_for) - { - if (c_for.current > c_for.stop) - { - dumb_ptr<effect_t> ret = ar->return_location; - // pop the stack - clear_activation_record(ar); - invocation_->stack.pop_back(); - return ret; - } - - magic_clear_var(&invocation_->env->varu[c_for.id]); - invocation_->env->varu[c_for.id] = ValInt{c_for.current++}; - - return c_for.body; - } - } - MATCH_END (); - abort(); - } -} - -static -void find_entities_in_area_c(dumb_ptr<block_list> target, - std::vector<BlockId> *entities_vp, - FOREACH_FILTER filter) -{ - switch (target->bl_type) - { - - case BL::PC: - if (filter == FOREACH_FILTER::PC - || filter == FOREACH_FILTER::ENTITY - || (filter == FOREACH_FILTER::TARGET - && target->bl_m->flag.get(MapFlag::PVP))) - break; - else if (filter == FOREACH_FILTER::SPELL) - { /* Check all spells bound to the caster */ - dumb_ptr<invocation> invoc = target->is_player()->active_spells; - /* Add all spells locked onto thie PC */ - - while (invoc) - { - entities_vp->push_back(invoc->bl_id); - invoc = invoc->next_invocation; - } - } - return; - - case BL::MOB: - if (filter == FOREACH_FILTER::MOB - || filter == FOREACH_FILTER::ENTITY - || filter == FOREACH_FILTER::TARGET) - break; - else - return; - - case BL::SPELL: - if (filter == FOREACH_FILTER::SPELL) - { - dumb_ptr<invocation> invocation = target->is_spell(); - - /* Check whether the spell is `bound'-- if so, we'll consider it iff we see the caster(case BL::PC). */ - if (bool(invocation->flags & INVOCATION_FLAG::BOUND)) - return; - else - break; /* Add the spell */ - } - else - return; - - case BL::NPC: - if (filter == FOREACH_FILTER::NPC) - break; - else - return; - - default: - return; - } - - entities_vp->push_back(target->bl_id); -} - -static -void find_entities_in_area(area_t& area_, - std::vector<BlockId> *entities_vp, - FOREACH_FILTER filter) -{ - MATCH_BEGIN (area_) - { - MATCH_CASE (const AreaUnion&, a) - { - find_entities_in_area(*a.a_union[0], entities_vp, filter); - find_entities_in_area(*a.a_union[1], entities_vp, filter); - } - MATCH_CASE (const location_t&, a_loc) - { - (void)a_loc; - // TODO this can be simplified - int x, y, width, height; - Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_); - map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter), - m, - x, y, - x + width, y + height, - BL::NUL /* filter elsewhere */); - } - MATCH_CASE (const AreaRect&, a_rect) - { - (void)a_rect; - // TODO this can be simplified - int x, y, width, height; - Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_); - map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter), - m, - x, y, - x + width, y + height, - BL::NUL /* filter elsewhere */); - } - MATCH_CASE (const AreaBar&, a_bar) - { - (void)a_bar; - // TODO this is wrong - int x, y, width, height; - Borrowed<map_local> m = magic_area_rect(&x, &y, &width, &height, area_); - map_foreachinarea(std::bind(find_entities_in_area_c, ph::_1, entities_vp, filter), - m, - x, y, - x + width, y + height, - BL::NUL /* filter elsewhere */); - } - } - MATCH_END (); -} - -static -dumb_ptr<effect_t> run_foreach(dumb_ptr<invocation> invocation, - const EffectForEach *foreach, - dumb_ptr<effect_t> return_location) -{ - const EffectForEach& e_foreach = *foreach; - - val_t area; - FOREACH_FILTER filter = e_foreach.filter; - int id = e_foreach.id; - dumb_ptr<effect_t> body = e_foreach.body; - - magic_eval(invocation->env, &area, e_foreach.area); - - auto va = area.get_if<ValArea>(); - if (!va) - { - magic_clear_var(&area); - FPRINTF(stderr, - "[magic] Error in spell `%s': FOREACH loop over non-area\n"_fmt, - invocation->spell->name); - return return_location; - } - - { - std::vector<BlockId> entities_v; - find_entities_in_area(*va->v_area, - &entities_v, filter); - entities_v.shrink_to_fit(); - // iterator_pair will go away when this gets properly containerized. - random_::shuffle(entities_v); - - CarForEach c_foreach; - c_foreach.id = id; - c_foreach.body = body; - c_foreach.index = 0; - c_foreach.entities_vp.new_(std::move(entities_v)); - c_foreach.ty_is_spell_not_entity = - (filter == FOREACH_FILTER::SPELL); - invocation->stack.emplace_back(c_foreach, return_location); - - magic_clear_var(&area); - - return return_to_stack(invocation); - } -} - -static -dumb_ptr<effect_t> run_for (dumb_ptr<invocation> invocation, - const EffectFor *for_, - dumb_ptr<effect_t> return_location) -{ - const EffectFor& e_for = *for_; - - int id = e_for.id; - val_t start; - val_t stop; - - magic_eval(invocation->env, &start, e_for.start); - magic_eval(invocation->env, &stop, e_for.stop); - - if (!start.is<ValInt>() || !stop.is<ValInt>()) - { - magic_clear_var(&start); - magic_clear_var(&stop); - FPRINTF(stderr, - "[magic] Error in spell `%s': FOR loop start or stop point is not an integer\n"_fmt, - invocation->spell->name); - return return_location; - } - - CarFor c_for; - - c_for.id = id; - c_for.current = start.get_if<ValInt>()->v_int; - c_for.stop = stop.get_if<ValInt>()->v_int; - c_for.body = e_for.body; - invocation->stack.emplace_back(c_for, return_location); - - return return_to_stack(invocation); -} - -static -dumb_ptr<effect_t> run_call(dumb_ptr<invocation> invocation, - const EffectCall *call, - dumb_ptr<effect_t> return_location) -{ - const EffectCall& e_call = *call; - - int args_nr = e_call.formalv->size(); - int *formals = e_call.formalv->data(); - auto old_actuals = dumb_ptr<val_t[]>::make(args_nr); - - CarProc c_proc; - c_proc.args_nr = args_nr; - c_proc.formalap = formals; - c_proc.old_actualpa = old_actuals; - invocation->stack.emplace_back(c_proc, return_location); - - for (int i = 0; i < args_nr; i++) - { - val_t *env_val = &invocation->env->varu[formals[i]]; - magic_copy_var(&old_actuals[i], env_val); - magic_eval(invocation->env, env_val, (*e_call.actualvp)[i]); - } - - return e_call.body; -} - -/** - * Execute a spell invocation until we abort, finish, or hit the next `sleep'. - * - * Use spell_execute() to automate handling of timers - * - * Returns: 0 if finished (all memory is freed implicitly) - * >1 if we hit `sleep'; the result is the number of ticks we should sleep for. - * -1 if we paused to wait for a user action (via script interaction) - */ -static -interval_t spell_run(dumb_ptr<invocation> invocation_, int allow_delete) -{ - const BlockId invocation_id = invocation_->bl_id; -#define REFRESH_INVOCATION invocation_ = map_id_is_spell(invocation_id); if (!invocation_) return interval_t::zero(); - - while (invocation_->current_effect) - { - dumb_ptr<effect_t> e = invocation_->current_effect; - dumb_ptr<effect_t> next = e->next; - int i; - - MATCH_BEGIN (*e) - { - MATCH_CASE (const EffectSkip&, e_) - { - (void)e_; - } - MATCH_CASE (const EffectAbort&, e_) - { - (void)e_; - invocation_->flags |= INVOCATION_FLAG::ABORTED; - invocation_->end_effect = nullptr; - clear_stack(invocation_); - next = nullptr; - } - MATCH_CASE (const EffectEnd&, e_) - { - (void)e_; - clear_stack(invocation_); - next = nullptr; - } - MATCH_CASE (const EffectAssign&, e_assign) - { - magic_eval(invocation_->env, - &invocation_->env->varu[e_assign.id], - e_assign.expr); - } - MATCH_CASE (const EffectForEach&, e_foreach) - { - next = run_foreach(invocation_, &e_foreach, next); - } - MATCH_CASE (const EffectFor&, e_for) - { - next = run_for (invocation_, &e_for, next); - } - MATCH_CASE (const EffectIf&, e_if) - { - if (magic_eval_int(invocation_->env, e_if.cond)) - next = e_if.true_branch; - else - next = e_if.false_branch; - } - MATCH_CASE (const EffectSleep&, e_) - { - interval_t sleeptime = static_cast<interval_t>( - magic_eval_int(invocation_->env, e_.e_sleep)); - invocation_->current_effect = next; - if (sleeptime > interval_t::zero()) - return sleeptime; - } - MATCH_CASE (const EffectScript&, e_) - { - dumb_ptr<map_session_data> caster = map_id_is_player(invocation_->caster); - if (caster) - { - dumb_ptr<env_t> env = invocation_->env; - ZString caster_name = (caster ? caster->status_key.name : CharName()).to__actual(); - argrec_t arg[1] = - { - {"@caster_name$"_s, caster_name}, - }; - assert (!env->VAR(VAR_SCRIPTTARGET).is<ValEntityPtr>()); - ValEntityInt *ei = env->VAR(VAR_SCRIPTTARGET).get_if<ValEntityInt>(); - BlockId message_recipient = - ei - ? ei->v_eid - : invocation_->caster; - dumb_ptr<map_session_data> recipient = map_id_is_player(message_recipient); - - if (recipient->npc_id - && recipient->npc_id != invocation_->bl_id) - goto break_match; /* Don't send multiple message boxes at once */ - - if (!invocation_->script_pos) // first time running this script? - clif_spawn_fake_npc_for_player(recipient, - invocation_->bl_id); - // We have to do this or otherwise the client won't think that it's - // dealing with an NPC - - int newpos = run_script_l( - ScriptPointer(borrow(*e_.e_script), invocation_->script_pos), - message_recipient, invocation_->bl_id, - arg); - /* Returns the new script position, or -1 once the script is finished */ - if (newpos != -1) - { - /* Must set up for continuation */ - recipient->npc_id = invocation_->bl_id; - recipient->npc_pos = invocation_->script_pos = newpos; - return static_cast<interval_t>(-1); /* Signal `wait for script' */ - } - else - invocation_->script_pos = 0; - clif_clearchar_id(invocation_->bl_id, BeingRemoveWhy::DEAD, caster->sess); - } - REFRESH_INVOCATION; // Script may have killed the caster - } - MATCH_CASE (const EffectBreak&, e_) - { - (void)e_; - next = return_to_stack(invocation_); - } - MATCH_CASE (const EffectOp&, e_op) - { - op_t *op = e_op.opp; - val_t args[MAX_ARGS]; - - for (i = 0; i < e_op.args_nr; i++) - magic_eval(invocation_->env, &args[i], e_op.args[i]); - - if (!magic_signature_check("effect"_s, op->name, op->signature, - Slice<val_t>(args, e_op.args_nr), - e_op.line_nr, - e_op.column)) - op->op(invocation_->env, Slice<val_t>(args, e_op.args_nr)); - - for (i = 0; i < e_op.args_nr; i++) - magic_clear_var(&args[i]); - - REFRESH_INVOCATION; // Effect may have killed the caster - } - MATCH_CASE (const EffectCall&, e_call) - { - next = run_call(invocation_, &e_call, next); - } - } - MATCH_END (); - - break_match: - if (!next) - next = return_to_stack(invocation_); - - invocation_->current_effect = next; - } - - if (allow_delete) - try_to_finish_invocation(invocation_); - return interval_t::zero(); -#undef REFRESH_INVOCATION -} - -static -void spell_execute_d(dumb_ptr<invocation> invocation, int allow_deletion) -{ - spell_update_location(invocation); - interval_t delta = spell_run(invocation, allow_deletion); - - if (delta > interval_t::zero()) - { - assert (!invocation->timer); - invocation->timer = Timer(gettick() + delta, - std::bind(invocation_timer_callback, ph::_1, ph::_2, - invocation->bl_id)); - } - - /* If 0, the script cleaned itself. If -1(wait-for-script), we must wait for the user. */ -} - -void spell_execute(dumb_ptr<invocation> invocation) -{ - spell_execute_d(invocation, 1); -} - -void spell_execute_script(dumb_ptr<invocation> invocation) -{ - if (invocation->script_pos) - spell_execute_d(invocation, 1); - /* Otherwise the script-within-the-spell has been terminated by some other means. - * In practice this happens when the script doesn't wait for user input: the client - * may still notify the server that it's done. Without the above check, we'd be - * running the same spell twice! */ -} - -int spell_attack(BlockId caster_id, BlockId target_id) -{ - dumb_ptr<map_session_data> caster = map_id_is_player(caster_id); - dumb_ptr<invocation> invocation_; - int stop_attack = 0; - - if (!caster) - return 0; - - invocation_ = map_id_is_spell(caster->attack_spell_override); - - if (invocation_ && bool(invocation_->flags & INVOCATION_FLAG::STOPATTACK)) - stop_attack = 1; - - if (invocation_ && caster->attack_spell_charges > 0) - { - magic_clear_var(&invocation_->env->varu[VAR_TARGET]); - invocation_->env->varu[VAR_TARGET] = ValEntityInt{target_id}; - - invocation_->current_effect = invocation_->trigger_effect; - invocation_->flags &= ~INVOCATION_FLAG::ABORTED; - spell_execute_d(invocation_, - 0 /* don't delete the invocation if done */ ); - - // If the caster died, we need to refresh here: - invocation_ = map_id_is_spell(caster->attack_spell_override); - - if (invocation_ && !bool(invocation_->flags & INVOCATION_FLAG::ABORTED)) // If we didn't abort: - caster->attack_spell_charges--; - } - - if (invocation_ && caster->attack_spell_override != invocation_->bl_id) - { - /* Attack spell changed / was refreshed */ - // spell_free_invocation(invocation); // [Fate] This would be a double free. - } - else if (!invocation_ || caster->attack_spell_charges <= 0) - { - caster->attack_spell_override = BlockId(); - char_set_weapon_icon(caster, 0, StatusChange::ZERO, ItemNameId()); - char_set_attack_info(caster, interval_t::zero(), 0); - - if (stop_attack) - pc_stopattack(caster); - - if (invocation_) - spell_free_invocation(invocation_); - } - - return 1; -} -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic-stmt.hpp b/src/map/magic-stmt.hpp deleted file mode 100644 index 3b04fe3..0000000 --- a/src/map/magic-stmt.hpp +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once -// magic-stmt.hpp - Imperative commands for the magic backend. -// -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "fwd.hpp" - -#include "../strings/zstring.hpp" - -#include "../mmo/skill.t.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -struct op_t -{ - LString name; - LString signature; - int (*op)(dumb_ptr<env_t> env, Slice<val_t> arga); -}; - -/** - * Retrieves an operation by name - * @param name The name to look up - * @return An operation of that name, or nullptr, and a function index - */ -op_t *magic_get_op(ZString name); - -/** - * Removes the shroud from a character - * - * \param character The character to remove the shroud from - */ -void magic_unshroud(dumb_ptr<map_session_data> character); - -/** - * Notifies a running spell that a status_change timer triggered by the spell has expired - * - * \param invocation The invocation to notify - * \param bl_id ID of the PC for whom this happened - * \param sc_id ID of the status change entry that finished - * \param supplanted Whether the status_change finished normally (0) or was supplanted by a new status_change (1) - */ -void spell_effect_report_termination(BlockId invocation, BlockId bl_id, - StatusChange sc_id, int supplanted); - -/** - * Execute a spell invocation and sets up timers to finish - */ -void spell_execute(dumb_ptr<invocation> invocation); - -/** - * Continue an NPC script embedded in a spell - */ -void spell_execute_script(dumb_ptr<invocation> invocation); - -/** - * Stops all magic bound to the specified character - * - */ -void magic_stop_completely(dumb_ptr<map_session_data> c); - -/** - * Attacks with a magical spell charged to the character - * - * Returns 0 if there is no charged spell or the spell is depleted. - */ -int spell_attack(BlockId caster, BlockId target); - -void spell_free_invocation(dumb_ptr<invocation> invocation); -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic-stmt.py b/src/map/magic-stmt.py deleted file mode 100644 index 7cc43d0..0000000 --- a/src/map/magic-stmt.py +++ /dev/null @@ -1,37 +0,0 @@ -class op_t(object): - __slots__ = ('_value') - - name = 'tmwa::map::magic::op_t' - depth = 1 - enabled = True - - def __init__(self, value): - if not value: - value = None - self._value = value - - def to_string(self): - value = self._value - if value is None: - return '(op_t *) nullptr' - return '(op_t *)' - - def children(self): - value = self._value - if value is None: - return - value = value.dereference() - yield '->name', value['name'] - yield '->signature', value['signature'] - yield '->op', value['op'] - - test_extra = ''' - using tmwa::operator "" _s; - ''' - - tests = [ - ('static_cast<tmwa::map::magic::op_t *>(nullptr)', - '(op_t *) nullptr'), - ('new tmwa::map::magic::op_t{"name"_s, "sig"_s, nullptr}', - '(op_t *) = {->name = "name", ->signature = "sig", ->op = nullptr}'), - ] diff --git a/src/map/magic-v2.cpp b/src/map/magic-v2.cpp deleted file mode 100644 index 52b1b8f..0000000 --- a/src/map/magic-v2.cpp +++ /dev/null @@ -1,1295 +0,0 @@ -#include "magic-v2.hpp" -// magic-v2.cpp - second generation magic parser -// -// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include <cstddef> - -#include <algorithm> -#include <map> -#include <set> - -#include "../range/slice.hpp" - -#include "../strings/rstring.hpp" -#include "../strings/literal.hpp" - -#include "../generic/dumb_ptr.hpp" - -#include "../io/cxxstdio.hpp" -#include "../io/line.hpp" - -#include "../sexpr/parser.hpp" - -#include "../ast/script.hpp" - -#include "globals.hpp" -#include "itemdb.hpp" -#include "magic-expr.hpp" -#include "magic-interpreter.hpp" -#include "magic-interpreter-base.hpp" -#include "magic-stmt.hpp" -#include "script-parse.hpp" - -#include "../poison.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -namespace magic_v2 -{ - static - size_t intern_id(ZString id_name) - { - // TODO use InternPool - size_t i; - for (i = 0; i < magic_conf.varv.size(); i++) - if (id_name == magic_conf.varv[i].name) - return i; - - // i = magic_conf.varv.size(); - /* Must add new */ - magic_conf_t::mcvar new_var {}; - new_var.name = id_name; - new_var.val = ValUndef(); - magic_conf.varv.push_back(std::move(new_var)); - - return i; - } - inline - bool INTERN_ASSERT(ZString name, int id) - { - int zid = intern_id(name); - if (zid != id) - { - FPRINTF(stderr, - "[magic-conf] INTERNAL ERROR: Builtin special var %s interned to %d, not %d as it should be!\n"_fmt, - name, zid, id); - } - return zid == id; - } - - static - bool init0() - { - bool ok = true; - - ok &= INTERN_ASSERT("min_casttime"_s, VAR_MIN_CASTTIME); - ok &= INTERN_ASSERT("obscure_chance"_s, VAR_OBSCURE_CHANCE); - ok &= INTERN_ASSERT("caster"_s, VAR_CASTER); - ok &= INTERN_ASSERT("spellpower"_s, VAR_SPELLPOWER); - ok &= INTERN_ASSERT("self_spell"_s, VAR_SPELL); - ok &= INTERN_ASSERT("self_invocation"_s, VAR_INVOCATION); - ok &= INTERN_ASSERT("target"_s, VAR_TARGET); - ok &= INTERN_ASSERT("script_target"_s, VAR_SCRIPTTARGET); - ok &= INTERN_ASSERT("location"_s, VAR_LOCATION); - - return ok; - } - - - static - bool bind_constant(io::LineSpan span, RString name, val_t val) - { - if (!const_defm.insert(std::make_pair(name, std::move(val))).second) - { - span.error(STRPRINTF("Redefinition of constant '%s'"_fmt, name)); - return false; - } - return true; - } - static - const val_t *find_constant(RString name) - { - auto it = const_defm.find(name); - if (it != const_defm.end()) - return &it->second; - - return nullptr; - } - static - dumb_ptr<effect_t> set_effect_continuation(dumb_ptr<effect_t> src, dumb_ptr<effect_t> continuation) - { - dumb_ptr<effect_t> retval = src; - /* This function is completely analogous to `spellguard_implication' above; read the control flow implications above first before pondering it. */ - - if (src == continuation) - return retval; - - /* For FOR and FOREACH, we use special stack handlers and thus don't have to set - * the continuation. It's only IF that we need to handle in this fashion. */ - MATCH_BEGIN (*src) - { - MATCH_CASE (EffectIf&, e_if) - { - set_effect_continuation(e_if.true_branch, continuation); - set_effect_continuation(e_if.false_branch, continuation); - } - } - MATCH_END (); - - if (src->next) - set_effect_continuation(src->next, continuation); - else - src->next = continuation; - - return retval; - } - static - dumb_ptr<spellguard_t> spellguard_implication(dumb_ptr<spellguard_t> a, dumb_ptr<spellguard_t> b) - { - dumb_ptr<spellguard_t> retval = a; - - if (a == b) - { - /* This can happen due to reference sharing: - * e.g., - * (R0 -> (R1 | R2)) => (R3) - * yields - * (R0 -> (R1 -> R3 | R2 -> R3)) - * - * So if we now add => R4 to that, we want - * (R0 -> (R1 -> R3 -> R4 | R2 -> R3 -> R4)) - * - * but we only need to add it once, because the R3 reference is shared. - */ - return retval; - } - - /* If the premise is a disjunction, b is the continuation of _all_ branches */ - MATCH_BEGIN (*a) - { - MATCH_CASE (const GuardChoice&, s) - { - spellguard_implication(s.s_alt, b); - } - } - MATCH_END (); - - if (a->next) - spellguard_implication(a->next, b); - else - // this is the important bit - a->next = b; - - return retval; - } - - - static - bool add_spell(io::LineSpan span, dumb_ptr<spell_t> spell) - { - auto pair1 = magic_conf.spells_by_name.insert({spell->name, spell}); - if (!pair1.second) - { - span.error(STRPRINTF("Attempt to redefine spell '%s'"_fmt, spell->name)); - return false; - } - - auto pair2 = magic_conf.spells_by_invocation.insert({spell->invocation, spell}); - if (!pair2.second) - { - span.error(STRPRINTF("Attempt to redefine spell invocation '%s'"_fmt, spell->invocation)); - magic_conf.spells_by_name.erase(pair1.first); - return false; - } - return true; - } - static - bool add_teleport_anchor(io::LineSpan span, dumb_ptr<teleport_anchor_t> anchor) - { - auto pair1 = magic_conf.anchors_by_name.insert({anchor->name, anchor}); - if (!pair1.second) - { - span.error(STRPRINTF("Attempt to redefine teleport anchor '%s'"_fmt, anchor->name)); - return false; - } - - auto pair2 = magic_conf.anchors_by_invocation.insert({anchor->name, anchor}); - if (!pair2.second) - { - span.error(STRPRINTF("Attempt to redefine anchor invocation '%s'"_fmt, anchor->invocation)); - magic_conf.anchors_by_name.erase(pair1.first); - return false; - } - return true; - } - - static - bool install_proc(io::LineSpan span, dumb_ptr<proc_t> proc) - { - RString name = proc->name; - if (!procs.insert({name, std::move(*proc)}).second) - { - span.error("procedure already exists"_s); - return false; - } - return true; - } - static - bool call_proc(io::LineSpan span, ZString name, dumb_ptr<std::vector<dumb_ptr<expr_t>>> argvp, dumb_ptr<effect_t>& retval) - { - auto pi = procs.find(name); - if (pi == procs.end()) - { - span.error(STRPRINTF("Unknown procedure '%s'"_fmt, name)); - return false; - } - - proc_t *p = &pi->second; - - if (p->argv.size() != argvp->size()) - { - span.error(STRPRINTF("Procedure %s/%zu invoked with %zu parameters"_fmt, - name, p->argv.size(), argvp->size())); - return false; - } - - EffectCall e_call; - e_call.body = p->body; - e_call.formalv = &p->argv; - e_call.actualvp = argvp; - retval = dumb_ptr<effect_t>::make(e_call, nullptr); - return true; - } - static - bool op_effect(io::LineSpan span, ZString name, Slice<dumb_ptr<expr_t>> argv, dumb_ptr<effect_t>& effect) - { - op_t *op = magic_get_op(name); - if (!op) - { - span.error(STRPRINTF("Unknown operation '%s'"_fmt, name)); - return false; - } - if (op->signature.size() != argv.size()) - { - span.error(STRPRINTF("Incorrect number of arguments to operation '%s': Expected %zu, found %zu"_fmt, - name, op->signature.size(), argv.size())); - return false; - } - - EffectOp e_op; - e_op.line_nr = span.begin.line; - e_op.column = span.begin.column; - e_op.opp = op; - assert (argv.size() <= MAX_ARGS); - e_op.args_nr = argv.size(); - - std::copy(argv.begin(), argv.end(), e_op.args); - effect = dumb_ptr<effect_t>::make(e_op, nullptr); - return true; - } - - static - dumb_ptr<expr_t> dot_expr(dumb_ptr<expr_t> expr, int id) - { - ExprField e_field; - e_field.id = id; - e_field.expr = expr; - dumb_ptr<expr_t> retval = dumb_ptr<expr_t>::make(e_field); - - return retval; - } - static - bool fun_expr(io::LineSpan span, ZString name, Slice<dumb_ptr<expr_t>> argv, dumb_ptr<expr_t>& expr) - { - fun_t *fun = magic_get_fun(name); - if (!fun) - { - span.error(STRPRINTF("Unknown function '%s'"_fmt, name)); - return false; - } - if (fun->signature.size() != argv.size()) - { - span.error(STRPRINTF("Incorrect number of arguments to function '%s': Expected %zu, found %zu"_fmt, - name, fun->signature.size(), argv.size())); - return false; - } - ExprFunApp e_funapp; - e_funapp.line_nr = span.begin.line; - e_funapp.column = span.begin.column; - e_funapp.funp = fun; - - assert (argv.size() <= MAX_ARGS); - e_funapp.args_nr = argv.size(); - - std::copy(argv.begin(), argv.end(), e_funapp.args); - expr = dumb_ptr<expr_t>::make(e_funapp); - return true; - } - static - dumb_ptr<expr_t> BIN_EXPR(io::LineSpan span, ZString name, dumb_ptr<expr_t> left, dumb_ptr<expr_t> right) - { - dumb_ptr<expr_t> e[2]; - e[0] = left; - e[1] = right; - dumb_ptr<expr_t> rv; - if (!fun_expr(span, name, e, rv)) - abort(); - return rv; - } - - static - bool fail(const sexpr::SExpr& s, ZString msg) - { - s._span.error(msg); - return false; - } -} - -namespace magic_v2 -{ - using sexpr::SExpr; - - static - bool parse_expression(const SExpr& x, dumb_ptr<expr_t>& out); - static - bool parse_effect(const SExpr& s, dumb_ptr<effect_t>& out); - static - bool parse_spellguard(const SExpr& s, dumb_ptr<spellguard_t>& out); - static - bool parse_spellbody(const SExpr& s, dumb_ptr<spellguard_t>& out); - - // Note: anything with dumb_ptr leaks memory on failure - // once this is all done, we can convert it to unique_ptr - // (may require bimaps somewhere) - static - bool is_comment(const SExpr& s) - { - if (s._type == sexpr::STRING) - return true; - if (s._type != sexpr::LIST) - return false; - if (s._list.empty()) - return false; - if (s._list[0]._type != sexpr::TOKEN) - return false; - return s._list[0]._str == "DISABLED"_s; - } - - static - bool parse_loc(const SExpr& s, e_location_t& loc) - { - if (s._type != sexpr::LIST) - return fail(s, "loc not list"_s); - if (s._list.size() != 4) - return fail(s, "loc not 3 args"_s); - if (s._list[0]._type != sexpr::TOKEN) - return fail(s._list[0], "loc cmd not tok"_s); - if (s._list[0]._str != "@"_s) - return fail(s._list[0], "loc cmd not cmd"_s); - return parse_expression(s._list[1], loc.m) - && parse_expression(s._list[2], loc.x) - && parse_expression(s._list[3], loc.y); - } - - static - bool parse_expression(const SExpr& x, dumb_ptr<expr_t>& out) - { - switch (x._type) - { - case sexpr::INT: - { - val_t val; - val = ValInt{static_cast<int32_t>(x._int)}; - if (val.get_if<ValInt>()->v_int != x._int) - return fail(x, "integer too large"_s); - - out = dumb_ptr<expr_t>::make(std::move(val)); - return true; - } - case sexpr::STRING: - { - val_t val; - val = ValString{x._str}; - - out = dumb_ptr<expr_t>::make(std::move(val)); - return true; - } - case sexpr::TOKEN: - { - earray<LString, DIR, DIR::COUNT> dirs //= - {{ - "S"_s, "SW"_s, "W"_s, "NW"_s, - "N"_s, "NE"_s, "E"_s, "SE"_s, - }}; - auto begin = std::begin(dirs); - auto end = std::end(dirs); - auto it = std::find(begin, end, x._str); - if (it != end) - { - val_t val; - val = ValDir{static_cast<DIR>(it - begin)}; - - out = dumb_ptr<expr_t>::make(std::move(val)); - return true; - } - } - { - if (const val_t *val = find_constant(x._str)) - { - val_t copy; - magic_copy_var(©, val); - out = dumb_ptr<expr_t>::make(std::move(copy)); - return true; - } - else - { - ExprId e; - e.e_id = intern_id(x._str); - out = dumb_ptr<expr_t>::make(e); - return true; - } - } - break; - case sexpr::LIST: - if (x._list.empty()) - return fail(x, "empty list"_s); - { - if (x._list[0]._type != sexpr::TOKEN) - return fail(x._list[0], "op not token"_s); - ZString op = x._list[0]._str; - // area - if (op == "@"_s) - { - e_location_t loc; - if (!parse_loc(x, loc)) - return false; - out = dumb_ptr<expr_t>::make(loc); - return true; - } - if (op == "@+"_s) - { - e_location_t loc; - dumb_ptr<expr_t> width; - dumb_ptr<expr_t> height; - if (!parse_loc(x._list[1], loc)) - return false; - if (!parse_expression(x._list[2], width)) - return false; - if (!parse_expression(x._list[3], height)) - return false; - ExprAreaRect a_rect; - a_rect.loc = loc; - a_rect.width = width; - a_rect.height = height; - out = dumb_ptr<expr_t>::make(a_rect); - return true; - } - if (op == "TOWARDS"_s) - { - e_location_t loc; - dumb_ptr<expr_t> dir; - dumb_ptr<expr_t> width; - dumb_ptr<expr_t> depth; - if (!parse_loc(x._list[1], loc)) - return false; - if (!parse_expression(x._list[2], dir)) - return false; - if (!parse_expression(x._list[3], width)) - return false; - if (!parse_expression(x._list[4], depth)) - return false; - ExprAreaBar a_bar; - a_bar.loc = loc; - a_bar.dir = dir; - a_bar.width = width; - a_bar.depth = depth; - out = dumb_ptr<expr_t>::make(a_bar); - return true; - } - if (op == "."_s) - { - if (x._list.size() != 3) - return fail(x, ". not 2"_s); - dumb_ptr<expr_t> expr; - if (!parse_expression(x._list[1], expr)) - return false; - if (x._list[2]._type != sexpr::TOKEN) - return fail(x._list[2], ".elem not name"_s); - ZString elem = x._list[2]._str; - out = dot_expr(expr, intern_id(elem)); - return true; - } - static // TODO LString - std::set<ZString> ops = - { - "<"_s, ">"_s, "<="_s, ">="_s, "=="_s, "!="_s, - "+"_s, "-"_s, "*"_s, "%"_s, "/"_s, - "&"_s, "^"_s, "|"_s, "<<"_s, ">>"_s, - "&&"_s, "||"_s, - }; - // TODO implement unary operators - if (ops.count(op)) - { - // operators are n-ary and left-associative - if (x._list.size() < 3) - return fail(x, "operator not at least 2 args"_s); - auto begin = x._list.begin() + 1; - auto end = x._list.end(); - if (!parse_expression(*begin, out)) - return false; - ++begin; - for (; begin != end; ++begin) - { - dumb_ptr<expr_t> tmp; - if (!parse_expression(*begin, tmp)) - return false; - out = BIN_EXPR(x._span, op, out, tmp); - } - return true; - } - std::vector<dumb_ptr<expr_t>> argv; - for (auto it = x._list.begin() + 1, end = x._list.end(); it != end; ++it) - { - dumb_ptr<expr_t> expr; - if (!parse_expression(*it, expr)) - return false; - argv.push_back(expr); - } - return fun_expr(x._span, op, argv, out); - } - break; - } - abort(); - } - - static - bool parse_item(const SExpr& s, ItemNameId& id, int& count) - { - if (s._type == sexpr::STRING) - { - count = 1; - - Borrowed<item_data> item = TRY_UNWRAP(itemdb_searchname(s._str), - return fail(s, "no such item"_s) - ); - id = item->nameid; - return true; - } - if (s._type != sexpr::LIST) - return fail(s, "item not string or list"_s); - if (s._list.size() != 2) - return fail(s, "item list is not pair"_s); - if (s._list[0]._type != sexpr::INT) - return fail(s._list[0], "item pair first not int"_s); - count = s._list[0]._int; - if (s._list[1]._type != sexpr::STRING) - return fail(s._list[1], "item pair second not name"_s); - - Borrowed<item_data> item = TRY_UNWRAP(itemdb_searchname(s._list[1]._str), - return fail(s, "no such item"_s) - ); - id = item->nameid; - return true; - } - - static - bool parse_spellguard(const SExpr& s, dumb_ptr<spellguard_t>& out) - { - if (s._type != sexpr::LIST) - return fail(s, "not list"_s); - if (s._list.empty()) - return fail(s, "empty list"_s); - if (s._list[0]._type != sexpr::TOKEN) - return fail(s._list[0], "not token"_s); - ZString cmd = s._list[0]._str; - if (cmd == "OR"_s) - { - auto begin = s._list.begin() + 1; - auto end = s._list.end(); - if (begin == end) - return fail(s, "missing arguments"_s); - if (!parse_spellguard(*begin, out)) - return false; - ++begin; - for (; begin != end; ++begin) - { - dumb_ptr<spellguard_t> alt; - if (!parse_spellguard(*begin, alt)) - return false; - GuardChoice choice; - auto next = out; - choice.s_alt = alt; - out = dumb_ptr<spellguard_t>::make(choice, next); - } - return true; - } - if (cmd == "GUARD"_s) - { - auto begin = s._list.begin() + 1; - auto end = s._list.end(); - while (is_comment(end[-1])) - --end; - if (begin == end) - return fail(s, "missing arguments"_s); - if (!parse_spellguard(end[-1], out)) - return false; - --end; - for (; begin != end; --end) - { - if (is_comment(end[-1])) - continue; - dumb_ptr<spellguard_t> implier; - if (!parse_spellguard(end[-1], implier)) - return false; - out = spellguard_implication(implier, out); - } - return true; - } - if (cmd == "REQUIRE"_s) - { - if (s._list.size() != 2) - return fail(s, "not one argument"_s); - dumb_ptr<expr_t> condition; - if (!parse_expression(s._list[1], condition)) - return false; - GuardCondition cond; - cond.s_condition = condition; - out = dumb_ptr<spellguard_t>::make(cond, nullptr); - return true; - } - if (cmd == "MANA"_s) - { - if (s._list.size() != 2) - return fail(s, "not one argument"_s); - dumb_ptr<expr_t> mana; - if (!parse_expression(s._list[1], mana)) - return false; - GuardMana sp; - sp.s_mana = mana; - out = dumb_ptr<spellguard_t>::make(sp, nullptr); - return true; - } - if (cmd == "CASTTIME"_s) - { - if (s._list.size() != 2) - return fail(s, "not one argument"_s); - dumb_ptr<expr_t> casttime; - if (!parse_expression(s._list[1], casttime)) - return false; - GuardCastTime ct; - ct.s_casttime = casttime; - out = dumb_ptr<spellguard_t>::make(ct, nullptr); - return true; - } - if (cmd == "CATALYSTS"_s) - { - dumb_ptr<component_t> items = nullptr; - for (auto it = s._list.begin() + 1, end = s._list.end(); it != end; ++it) - { - ItemNameId id; - int count; - if (!parse_item(*it, id, count)) - return false; - magic_add_component(&items, id, count); - } - GuardCatalysts cat; - cat.s_catalysts = items; - out = dumb_ptr<spellguard_t>::make(cat, nullptr); - return true; - } - if (cmd == "COMPONENTS"_s) - { - dumb_ptr<component_t> items = nullptr; - for (auto it = s._list.begin() + 1, end = s._list.end(); it != end; ++it) - { - ItemNameId id; - int count; - if (!parse_item(*it, id, count)) - return false; - magic_add_component(&items, id, count); - } - GuardComponents comp; - comp.s_components = items; - out = dumb_ptr<spellguard_t>::make(comp, nullptr); - return true; - } - return fail(s._list[0], "unknown guard"_s); - } - - static - bool build_effect_list(std::vector<SExpr>::const_iterator begin, - std::vector<SExpr>::const_iterator end, dumb_ptr<effect_t>& out) - { - // these backward lists could be forward by keeping the reference - // I know this is true because Linus said so - out = dumb_ptr<effect_t>::make(EffectSkip{}, nullptr); - while (end != begin) - { - const SExpr& s = *--end; - if (is_comment(s)) - continue; - dumb_ptr<effect_t> chain; - if (!parse_effect(s, chain)) - return false; - out = set_effect_continuation(chain, out); - } - return true; - } - - static - bool parse_effect(const SExpr& s, dumb_ptr<effect_t>& out) - { - if (s._type != sexpr::LIST) - return fail(s, "not list"_s); - if (s._list.empty()) - return fail(s, "empty list"_s); - if (s._list[0]._type != sexpr::TOKEN) - return fail(s._list[0], "not token"_s); - ZString cmd = s._list[0]._str; - if (cmd == "BLOCK"_s) - { - return build_effect_list(s._list.begin() + 1, s._list.end(), out); - } - if (cmd == "SET"_s) - { - if (s._list.size() != 3) - return fail(s, "not 2 args"_s); - if (s._list[1]._type != sexpr::TOKEN) - return fail(s._list[1], "not token"_s); - ZString name = s._list[1]._str; - if (find_constant(name)) - return fail(s._list[1], "assigning to constant"_s); - dumb_ptr<expr_t> expr; - if (!parse_expression(s._list[2], expr)) - return false; - - EffectAssign e_assign; - e_assign.id = intern_id(name); - e_assign.expr = expr; - out = dumb_ptr<effect_t>::make(e_assign, nullptr); - return true; - } - if (cmd == "SCRIPT"_s) - { - if (s._list.size() != 2) - return fail(s, "not 1 arg"_s); - if (s._list[1]._type != sexpr::STRING) - return fail(s._list[1], "not string"_s); - ZString body = s._list[1]._str; - auto begin = s._list[1]._span.begin; - io::LineCharReader lr(io::from_string, begin.filename, body, begin.line, begin.column); - ast::script::ScriptOptions opt; - opt.implicit_start = true; - opt.implicit_end = true; - opt.no_event = true; - auto code_res = ast::script::parse_script_body(lr, opt); - if (code_res.get_failure()) - { - PRINTF("%s\n"_fmt, code_res.get_failure()); - } - auto code = TRY_UNWRAP(code_res.get_success(), - return fail(s._list[1], "script does not compile"_s)); - std::unique_ptr<const ScriptBuffer> script = compile_script(STRPRINTF("script magic %s:%d"_fmt, begin.filename, begin.line), code, true); - if (!script) - return fail(s._list[1], "script does not compile"_s); - EffectScript e; - e.e_script = dumb_ptr<const ScriptBuffer>(script.release()); - out = dumb_ptr<effect_t>::make(e, nullptr); - return true; - } - if (cmd == "SKIP"_s) - { - if (s._list.size() != 1) - return fail(s, "not 0 arg"_s); - out = dumb_ptr<effect_t>::make(EffectSkip{}, nullptr); - return true; - } - if (cmd == "ABORT"_s) - { - if (s._list.size() != 1) - return fail(s, "not 0 arg"_s); - out = dumb_ptr<effect_t>::make(EffectAbort{}, nullptr); - return true; - } - if (cmd == "END"_s) - { - if (s._list.size() != 1) - return fail(s, "not 0 arg"_s); - out = dumb_ptr<effect_t>::make(EffectEnd{}, nullptr); - return true; - } - if (cmd == "BREAK"_s) - { - if (s._list.size() != 1) - return fail(s, "not 0 arg"_s); - out = dumb_ptr<effect_t>::make(EffectBreak{}, nullptr); - return true; - } - if (cmd == "FOREACH"_s) - { - if (s._list.size() != 5) - return fail(s, "not 4 arg"_s); - if (s._list[1]._type != sexpr::TOKEN) - return fail(s._list[1], "foreach type not token"_s); - ZString type = s._list[1]._str; - FOREACH_FILTER filter; - if (type == "PC"_s) - filter = FOREACH_FILTER::PC; - else if (type == "MOB"_s) - filter = FOREACH_FILTER::MOB; - else if (type == "ENTITY"_s) - filter = FOREACH_FILTER::ENTITY; - else if (type == "SPELL"_s) - filter = FOREACH_FILTER::SPELL; - else if (type == "TARGET"_s) - filter = FOREACH_FILTER::TARGET; - else if (type == "NPC"_s) - filter = FOREACH_FILTER::NPC; - else - return fail(s._list[1], "unknown foreach filter"_s); - if (s._list[2]._type != sexpr::TOKEN) - return fail(s._list[2], "foreach var not token"_s); - ZString var = s._list[2]._str; - dumb_ptr<expr_t> area; - dumb_ptr<effect_t> effect; - if (!parse_expression(s._list[3], area)) - return false; - if (!parse_effect(s._list[4], effect)) - return false; - - EffectForEach e_foreach; - e_foreach.id = intern_id(var); - e_foreach.area = area; - e_foreach.body = effect; - e_foreach.filter = filter; - out = dumb_ptr<effect_t>::make(e_foreach, nullptr); - return true; - } - if (cmd == "FOR"_s) - { - if (s._list.size() != 5) - return fail(s, "not 4 arg"_s); - if (s._list[1]._type != sexpr::TOKEN) - return fail(s._list[1], "for var not token"_s); - ZString var = s._list[1]._str; - dumb_ptr<expr_t> low; - dumb_ptr<expr_t> high; - dumb_ptr<effect_t> effect; - if (!parse_expression(s._list[2], low)) - return false; - if (!parse_expression(s._list[3], high)) - return false; - if (!parse_effect(s._list[4], effect)) - return false; - - EffectFor e_for; - e_for.id = intern_id(var); - e_for.start = low; - e_for.stop = high; - e_for.body = effect; - out = dumb_ptr<effect_t>::make(e_for, nullptr); - return true; - } - if (cmd == "IF"_s) - { - if (s._list.size() != 3 && s._list.size() != 4) - return fail(s, "not 2 or 3 args"_s); - dumb_ptr<expr_t> cond; - dumb_ptr<effect_t> if_true; - dumb_ptr<effect_t> if_false; - if (!parse_expression(s._list[1], cond)) - return false; - if (!parse_effect(s._list[2], if_true)) - return false; - if (s._list.size() == 4) - { - if (!parse_effect(s._list[3], if_false)) - return false; - } - else - if_false = dumb_ptr<effect_t>::make(EffectSkip{}, nullptr); - - EffectIf e_if; - e_if.cond = cond; - e_if.true_branch = if_true; - e_if.false_branch = if_false; - out = dumb_ptr<effect_t>::make(e_if, nullptr); - return true; - } - if (cmd == "WAIT"_s) - { - if (s._list.size() != 2) - return fail(s, "not 1 arg"_s); - dumb_ptr<expr_t> expr; - if (!parse_expression(s._list[1], expr)) - return false; - EffectSleep e; - e.e_sleep = expr; - out = dumb_ptr<effect_t>::make(e, nullptr); - return true; - } - if (cmd == "CALL"_s) - { - if (s._list.size() < 2) - return fail(s, "call what?"_s); - if (s._list[1]._type != sexpr::TOKEN) - return fail(s._list[1], "call token please"_s); - ZString func = s._list[1]._str; - auto argvp = dumb_ptr<std::vector<dumb_ptr<expr_t>>>::make(); - for (auto it = s._list.begin() + 2, end = s._list.end(); it != end; ++it) - { - dumb_ptr<expr_t> expr; - if (!parse_expression(*it, expr)) - return false; - argvp->push_back(expr); - } - return call_proc(s._span, func, argvp, out); - } - auto argv = std::vector<dumb_ptr<expr_t>>(); - for (auto it = s._list.begin() + 1, end = s._list.end(); it != end; ++it) - { - dumb_ptr<expr_t> expr; - if (!parse_expression(*it, expr)) - return false; - argv.push_back(expr); - } - return op_effect(s._span, cmd, argv, out); - } - - static - bool parse_spellbody(const SExpr& s, dumb_ptr<spellguard_t>& out) - { - if (s._type != sexpr::LIST) - return fail(s, "not list"_s); - if (s._list.empty()) - return fail(s, "empty list"_s); - if (s._list[0]._type != sexpr::TOKEN) - return fail(s._list[0], "not token"_s); - ZString cmd = s._list[0]._str; - if (cmd == "=>"_s) - { - if (s._list.size() != 3) - return fail(s, "list does not have exactly 2 arguments"_s); - dumb_ptr<spellguard_t> guard; - if (!parse_spellguard(s._list[1], guard)) - return false; - dumb_ptr<spellguard_t> body; - if (!parse_spellbody(s._list[2], body)) - return false; - out = spellguard_implication(guard, body); - return true; - } - if (cmd == "|"_s) - { - if (s._list.size() == 1) - return fail(s, "spellbody choice empty"_s); - auto begin = s._list.begin() + 1; - auto end = s._list.end(); - if (!parse_spellbody(*begin, out)) - return false; - ++begin; - for (; begin != end; ++begin) - { - dumb_ptr<spellguard_t> alt; - if (!parse_spellbody(*begin, alt)) - return false; - auto tmp = out; - GuardChoice choice; - choice.s_alt = alt; - out = dumb_ptr<spellguard_t>::make(choice, tmp); - } - return true; - } - if (cmd == "EFFECT"_s) - { - auto begin = s._list.begin() + 1; - auto end = s._list.end(); - - dumb_ptr<effect_t> effect, attrig, atend; - - // decreasing end can never pass begin, since we know that - // begin[-1] is token EFFECT - while (is_comment(end[-1])) - --end; - if (end[-1]._type == sexpr::LIST && !end[-1]._list.empty() - && end[-1]._list[0]._type == sexpr::TOKEN - && end[-1]._list[0]._str == "ATEND"_s) - { - auto atb = end[-1]._list.begin() + 1; - auto ate = end[-1]._list.end(); - if (!build_effect_list(atb, ate, atend)) - return false; - --end; - - while (is_comment(end[-1])) - --end; - } - else - { - atend = nullptr; - } - if (end[-1]._type == sexpr::LIST && !end[-1]._list.empty() - && end[-1]._list[0]._type == sexpr::TOKEN - && end[-1]._list[0]._str == "ATTRIGGER"_s) - { - auto atb = end[-1]._list.begin() + 1; - auto ate = end[-1]._list.end(); - if (!build_effect_list(atb, ate, attrig)) - return false; - --end; - } - else - { - attrig = nullptr; - } - if (!build_effect_list(begin, end, effect)) - return false; - effect_set_t s_effect; - s_effect.effect = effect; - s_effect.at_trigger = attrig; - s_effect.at_end = atend; - out = dumb_ptr<spellguard_t>::make(s_effect, nullptr); - return true; - } - return fail(s._list[0], "unknown spellbody"_s); - } - - static - bool parse_top_set(const std::vector<SExpr>& in) - { - if (in.size() != 3) - return fail(in[0], "not 2 arguments"_s); - ZString name = in[1]._str; - dumb_ptr<expr_t> expr; - if (!parse_expression(in[2], expr)) - return false; - if (find_constant(name)) - return fail(in[1], "assign constant"_s); - size_t var_id = intern_id(name); - magic_eval(dumb_ptr<env_t>(&magic_default_env), &magic_conf.varv[var_id].val, expr); - return true; - } - static - bool parse_const(io::LineSpan span, const std::vector<SExpr>& in) - { - if (in.size() != 3) - return fail(in[0], "not 2 arguments"_s); - if (in[1]._type != sexpr::TOKEN) - return fail(in[1], "not token"_s); - ZString name = in[1]._str; - dumb_ptr<expr_t> expr; - if (!parse_expression(in[2], expr)) - return false; - val_t tmp; - magic_eval(dumb_ptr<env_t>(&magic_default_env), &tmp, expr); - return bind_constant(span, name, std::move(tmp)); - } - static - bool parse_anchor(io::LineSpan span, const std::vector<SExpr>& in) - { - if (in.size() != 4) - return fail(in[0], "not 3 arguments"_s); - auto anchor = dumb_ptr<teleport_anchor_t>::make(); - if (in[1]._type != sexpr::TOKEN) - return fail(in[1], "not token"_s); - anchor->name = in[1]._str; - if (in[2]._type != sexpr::STRING) - return fail(in[2], "not string"_s); - anchor->invocation = in[2]._str; - dumb_ptr<expr_t> expr; - if (!parse_expression(in[3], expr)) - return false; - anchor->location = expr; - return add_teleport_anchor(span, anchor); - } - static - bool parse_proc(io::LineSpan span, const std::vector<SExpr>& in) - { - if (in.size() < 4) - return fail(in[0], "not at least 3 arguments"_s); - auto proc = dumb_ptr<proc_t>::make(); - if (in[1]._type != sexpr::TOKEN) - return fail(in[1], "name not token"_s); - proc->name = in[1]._str; - if (in[2]._type != sexpr::LIST) - return fail(in[2], "args not list"_s); - for (const SExpr& arg : in[2]._list) - { - if (arg._type != sexpr::TOKEN) - return fail(arg, "arg not token"_s); - proc->argv.push_back(intern_id(arg._str)); - } - if (!build_effect_list(in.begin() + 3, in.end(), proc->body)) - return false; - return install_proc(span, proc); - } - static - bool parse_spell(io::LineSpan span, const std::vector<SExpr>& in) - { - if (in.size() < 6) - return fail(in[0], "not at least 5 arguments"_s); - if (in[1]._type != sexpr::LIST) - return fail(in[1], "flags not list"_s); - - auto spell = dumb_ptr<spell_t>::make(); - - for (const SExpr& s : in[1]._list) - { - if (s._type != sexpr::TOKEN) - return fail(s, "flag not token"_s); - SPELL_FLAG flag = SPELL_FLAG::ZERO; - if (s._str == "LOCAL"_s) - flag = SPELL_FLAG::LOCAL; - else if (s._str == "NONMAGIC"_s) - flag = SPELL_FLAG::NONMAGIC; - else if (s._str == "SILENT"_s) - flag = SPELL_FLAG::SILENT; - else - return fail(s, "unknown flag"_s); - if (bool(spell->flags & flag)) - return fail(s, "duplicate flag"_s); - spell->flags |= flag; - } - if (in[2]._type != sexpr::TOKEN) - return fail(in[2], "name not token"_s); - spell->name = in[2]._str; - if (in[3]._type != sexpr::STRING) - return fail(in[3], "invoc not string"_s); - spell->invocation = in[3]._str; - if (in[4]._type != sexpr::LIST) - return fail(in[4], "spellarg not list"_s); - if (in[4]._list.size() == 0) - { - spell->spellarg_ty = SPELLARG::NONE; - } - else - { - if (in[4]._list.size() != 2) - return fail(in[4], "spellarg not empty list or pair"_s); - if (in[4]._list[0]._type != sexpr::TOKEN) - return fail(in[4]._list[0], "spellarg type not token"_s); - if (in[4]._list[1]._type != sexpr::TOKEN) - return fail(in[4]._list[1], "spellarg name not token"_s); - ZString ty = in[4]._list[0]._str; - if (ty == "PC"_s) - spell->spellarg_ty = SPELLARG::PC; - else if (ty == "STRING"_s) - spell->spellarg_ty = SPELLARG::STRING; - else - return fail(in[4]._list[0], "unknown spellarg type"_s); - ZString an = in[4]._list[1]._str; - spell->arg = intern_id(an); - } - std::vector<SExpr>::const_iterator it = in.begin() + 5; - for (;; ++it) - { - if (it == in.end()) - return fail(it[-1], "end of list scanning LET defs"_s); - if (is_comment(*it)) - continue; - if (it->_type != sexpr::LIST || it->_list.empty()) - break; - if (it->_list[0]._type != sexpr::TOKEN || it->_list[0]._str != "LET"_s) - break; - - if (it->_list[1]._type != sexpr::TOKEN) - return fail(it->_list[1], "let name not token"_s); - ZString name = it->_list[1]._str; - if (find_constant(name)) - return fail(it->_list[1], "constant exists"_s); - dumb_ptr<expr_t> expr; - if (!parse_expression(it->_list[2], expr)) - return false; - letdef_t let; - let.id = intern_id(name); - let.expr = expr; - spell->letdefv.push_back(let); - } - if (it + 1 != in.end()) - return fail(*it, "expected only one body entry besides LET"_s); - - // formally, 'guard' only refers to the first argument of '=>' - // but internally, spellbodies use the same thing - dumb_ptr<spellguard_t> guard; - if (!parse_spellbody(*it, guard)) - return false; - spell->spellguard = guard; - return add_spell(span, spell); - } - - static - bool parse_top(io::LineSpan span, const std::vector<SExpr>& vs) - { - if (vs.empty()) - { - span.error("Empty list at top"_s); - return false; - } - if (vs[0]._type != sexpr::TOKEN) - return fail(vs[0], "top not token"_s); - ZString cmd = vs[0]._str; - if (cmd == "CONST"_s) - return parse_const(span, vs); - if (cmd == "PROCEDURE"_s) - return parse_proc(span, vs); - if (cmd == "SET"_s) - return parse_top_set(vs); - if (cmd == "SPELL"_s) - return parse_spell(span, vs); - if (cmd == "TELEPORT-ANCHOR"_s) - return parse_anchor(span, vs); - return fail(vs[0], "Unknown top-level command"_s); - } - - static - bool loop(sexpr::Lexer& in) - { - SExpr s; - while (sexpr::parse(in, s)) - { - if (is_comment(s)) - continue; - if (s._type != sexpr::LIST) - return fail(s, "top-level entity not a list or comment"_s); - if (!parse_top(s._span, s._list)) - return false; - } - // handle low-level errors - if (in.peek() != sexpr::TOK_EOF) - { - in.span().error("parser gave up before end of file"_s); - return false; - } - return true; - } -} // namespace magic_v2 - -bool magic_init0() -{ - return magic_v2::init0(); -} - -bool load_magic_file_v2(ZString filename) -{ - sexpr::Lexer in(filename); - bool rv = magic_v2::loop(in); - if (!rv) - { - in.span().error(STRPRINTF("next token: %s '%s'"_fmt, sexpr::token_name(in.peek()), in.val_string())); - } - return rv; -} -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic-v2.hpp b/src/map/magic-v2.hpp deleted file mode 100644 index fac2773..0000000 --- a/src/map/magic-v2.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once -// magic-v2.hpp - second generation magic parser -// -// Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "fwd.hpp" - -#include "../strings/zstring.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -bool magic_init0(); -// must be called after itemdb initialization -bool load_magic_file_v2(ZString filename); -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic.cpp b/src/map/magic.cpp deleted file mode 100644 index 418312a..0000000 --- a/src/map/magic.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "magic.hpp" -// magic.cpp - Entry to the magic system. -// -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include <algorithm> -#include <utility> - -#include "../strings/xstring.hpp" - -#include "../generic/dumb_ptr.hpp" - -#include "../io/cxxstdio.hpp" - -#include "globals.hpp" -#include "magic-expr.hpp" -#include "magic-interpreter.hpp" -#include "magic-interpreter-base.hpp" -#include "magic-stmt.hpp" -#include "map.hpp" -#include "pc.hpp" - -#include "../poison.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -/// Return a pair of strings, {spellname, parameter} -/// Parameter may be empty. -static -std::pair<XString, XString> magic_tokenise(XString src) -{ - auto seeker = std::find(src.begin(), src.end(), ' '); - - if (seeker == src.end()) - { - return {src, XString()}; - } - else - { - XString rv1 = src.xislice_h(seeker); - ++seeker; - - while (seeker != src.end() && *seeker == ' ') - ++seeker; - - // Note: this very well could be empty - XString rv2 = src.xislice_t(seeker); - return {rv1, rv2}; - } -} - -int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation) -{ - if (pc_isdead(caster)) - return 0; - if (bool(caster->status.option & Opt0::HIDE)) - return 0; // No spellcasting while hidden - - int power = caster->matk1; - - // This was the only thing worth saving from magic_preprocess_message. - // May it rest only, and never rise again. - // For more information on how this code worked, travel through time - // and watch all the comments I wrote for myself while trying to figure - // out if it was safe to delete. - if (caster->state.shroud_active && caster->state.shroud_disappears_on_talk) - magic_unshroud(caster); - - auto pair = magic_tokenise(source_invocation); - XString spell_invocation = pair.first; - XString parameter = pair.second; - - dumb_ptr<spell_t> spell = magic_find_spell(spell_invocation); - - if (spell) - { - int near_miss; - dumb_ptr<env_t> env = - spell_create_env(&magic_conf, spell, caster, power, parameter); - const effect_set_t *effects; - - if (bool(spell->flags & SPELL_FLAG::NONMAGIC) || (power >= 1)) - effects = spell_trigger(spell, caster, env, &near_miss); - else - effects = nullptr; - - MAP_LOG_PC(caster, "CAST %s %s"_fmt, - spell->name, effects ? "SUCCESS"_s : "FAILURE"_s); - - if (effects) - { - dumb_ptr<invocation> invocation = spell_instantiate(effects, env); - - spell_bind(caster, invocation); - spell_execute(invocation); - - return bool(spell->flags & SPELL_FLAG::SILENT) ? -1 : 1; - } - else - magic_free_env(env); - - return 1; - } - - return 0; /* Not a spell */ -} -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/magic.hpp b/src/map/magic.hpp deleted file mode 100644 index 70d40dc..0000000 --- a/src/map/magic.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once -// magic.hpp - Entry to the magic system. -// -// Copyright © 2004-2011 The Mana World Development Team -// Copyright © 2011-2014 Ben Longbons <b.r.longbons@gmail.com> -// -// This file is part of The Mana World (Athena server) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. - -#include "fwd.hpp" - -#include "map.t.hpp" -#include "../mmo/skill.t.hpp" - - -namespace tmwa -{ -namespace map -{ -namespace magic -{ -/** - * Try to cast magic. - * - * As an intended side effect, the magic message may be distorted (text only). - * No, it can't. Thank God. - * - * \param caster Player attempting to cast magic - * \param source_invocation The prospective incantation - * \return 1 or -1 if the input message was magic and was handled by this function, 0 otherwise. -1 is returned when the - * message should not be repeated. - */ -int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation); -} // namespace magic -} // namespace map -} // namespace tmwa diff --git a/src/map/map.cpp b/src/map/map.cpp index d40977f..e7b0da8 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -69,9 +69,6 @@ #include "globals.hpp" #include "grfio.hpp" #include "itemdb.hpp" -#include "magic-interpreter.hpp" // for is_spell inline body -#include "magic-stmt.hpp" -#include "magic-v2.hpp" #include "map_conf.hpp" #include "mob.hpp" #include "quest.hpp" @@ -576,8 +573,6 @@ void map_delobject(BlockId id, BL type) return; map_delobjectnofree(id, type); - if (obj->bl_type == BL::PC) // [Fate] Not sure where else to put this... I'm not sure where delobject for PCs is called from - pc_cleanup(obj->is_player()); MapBlockLock::freeblock(obj); } @@ -1454,9 +1449,6 @@ void cleanup_sub(dumb_ptr<block_list> bl) case BL::ITEM: map_clearflooritem(bl->bl_id); break; - case BL::SPELL: - magic::spell_free_invocation(bl->is_spell()); - break; } } @@ -1497,8 +1489,6 @@ bool map_confs(io::Spanned<XString> key, io::Spanned<ZString> value) return mob_readskilldb(value.data); if (key.data == "skill_db"_s) return skill_readdb(value.data); - if (key.data == "magic_conf"_s) - return magic::load_magic_file_v2(value.data); if (key.data == "resnametable"_s) return load_resnametable(value.data); @@ -1515,16 +1505,7 @@ int map_scriptcont(dumb_ptr<map_session_data> sd, BlockId id) if (!bl) return 0; - switch (bl->bl_type) - { - case BL::NPC: - return npc_scriptcont(sd, id); - case BL::SPELL: - magic::spell_execute_script(bl->is_spell()); - break; - } - - return 0; + return npc_scriptcont(sd, id); } } // namespace map @@ -1571,7 +1552,6 @@ int do_init(Slice<ZString> argv) using namespace tmwa::map; ZString argv0 = argv.pop_front(); - runflag &= magic::magic_init0(); bool loaded_config_yet = false; while (argv) diff --git a/src/map/map.hpp b/src/map/map.hpp index 47eef57..7732af9 100644 --- a/src/map/map.hpp +++ b/src/map/map.hpp @@ -77,6 +77,13 @@ struct block_list short bl_x, bl_y; BL bl_type; + // register keys are ints (interned) + // Not anymore! Well, sort of. + DMap<SIR, int> regm; + // can't be DMap because we want predictable .c_str()s + // TODO this can change now + Map<SIR, RString> regstrm; + // This deletes the copy-ctor also // TODO give proper ctors. block_list& operator = (block_list&&) = delete; @@ -89,13 +96,11 @@ private: dumb_ptr<npc_data> as_npc(); dumb_ptr<mob_data> as_mob(); dumb_ptr<flooritem_data> as_item(); - dumb_ptr<magic::invocation> as_spell(); public: dumb_ptr<map_session_data> is_player(); dumb_ptr<npc_data> is_npc(); dumb_ptr<mob_data> is_mob(); dumb_ptr<flooritem_data> is_item(); - dumb_ptr<magic::invocation> is_spell(); }; struct walkpath_data @@ -107,7 +112,6 @@ struct status_change { Timer timer; int val1; - BlockId spell_invocation; /* [Fate] If triggered by a spell, record here */ }; struct quick_regeneration @@ -141,9 +145,9 @@ struct map_session_data : block_list, SessionData unsigned shroud_hides_name_talking:1; unsigned shroud_disappears_on_pickup:1; unsigned shroud_disappears_on_talk:1; - unsigned seen_motd:1; unsigned pvpchannel; unsigned pvp_rank; + unsigned npc_dialog_mes:1; } state; struct { @@ -205,9 +209,9 @@ struct map_session_data : block_list, SessionData // used by @hugo and @linus BlockId followtarget; - tick_t cast_tick; // [Fate] Next tick at which spellcasting is allowed - dumb_ptr<magic::invocation> active_spells; // [Fate] Singly-linked list of active spells linked to this PC + //tick_t cast_tick; // [Fate] Next tick at which spellcasting is allowed BlockId attack_spell_override; // [Fate] When an attack spell is active for this player, they trigger it + NpcEvent magic_attack; // like a weapon. Check pc_attack_timer() for details. // Weapon equipment slot (slot 4) item override StatusChange attack_spell_icon_override; @@ -254,13 +258,6 @@ struct map_session_data : block_list, SessionData int die_counter; - // register keys are ints (interned) - // Not anymore! Well, sort of. - DMap<SIR, int> regm; - // can't be DMap because we want predictable .c_str()s - // TODO this can change now - Map<SIR, RString> regstrm; - earray<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; AccountId trade_partner; @@ -343,12 +340,10 @@ private: dumb_ptr<npc_data_script> as_script(); dumb_ptr<npc_data_shop> as_shop(); dumb_ptr<npc_data_warp> as_warp(); - dumb_ptr<npc_data_message> as_message(); public: dumb_ptr<npc_data_script> is_script(); dumb_ptr<npc_data_shop> is_shop(); dumb_ptr<npc_data_warp> is_warp(); - dumb_ptr<npc_data_message> is_message(); }; class npc_data_script : public npc_data @@ -362,6 +357,9 @@ public: short xs, ys; bool event_needs_map; + // the npc containing the actual script + BlockId parent; + // Whether the timer advances if not beyond end. bool timer_active; // Tick counter through the timers. @@ -400,12 +398,6 @@ public: } warp; }; -class npc_data_message : public npc_data -{ -public: - RString message; -}; - constexpr int MOB_XP_BONUS_BASE = 1024; constexpr int MOB_XP_BONUS_SHIFT = 10; @@ -479,6 +471,9 @@ struct mob_data : block_list // [Fate] mob-specific stats earray<unsigned short, mob_stat, mob_stat::LAST> stats; short size; + // Npc Runscripts + std::list<RString> eventqueuel; + Array<Timer, MAX_EVENTTIMER> eventtimer; }; struct BlockLists @@ -635,13 +630,6 @@ dumb_ptr<flooritem_data> map_id_is_item(BlockId id) dumb_ptr<block_list> bl = map_id2bl(id); return bl ? bl->is_item() : nullptr; } -inline -dumb_ptr<magic::invocation> map_id_is_spell(BlockId id) -{ - dumb_ptr<block_list> bl = map_id2bl(id); - return bl ? bl->is_spell() : nullptr; -} - Option<Borrowed<map_local>> map_mapname2mapid(MapName); int map_mapname2ipport(MapName, Borrowed<IP4Address>, Borrowed<int>); @@ -675,25 +663,21 @@ inline dumb_ptr<map_session_data> block_list::as_player() { return dumb_ptr<map_ inline dumb_ptr<npc_data> block_list::as_npc() { return dumb_ptr<npc_data>(static_cast<npc_data *>(this)) ; } inline dumb_ptr<mob_data> block_list::as_mob() { return dumb_ptr<mob_data>(static_cast<mob_data *>(this)) ; } inline dumb_ptr<flooritem_data> block_list::as_item() { return dumb_ptr<flooritem_data>(static_cast<flooritem_data *>(this)) ; } -//inline dumb_ptr<invocation> block_list::as_spell() { return dumb_ptr<invocation>(static_cast<invocation *>(this)) ; } inline dumb_ptr<map_session_data> block_list::is_player() { return bl_type == BL::PC ? as_player() : nullptr; } inline dumb_ptr<npc_data> block_list::is_npc() { return bl_type == BL::NPC ? as_npc() : nullptr; } inline dumb_ptr<mob_data> block_list::is_mob() { return bl_type == BL::MOB ? as_mob() : nullptr; } inline dumb_ptr<flooritem_data> block_list::is_item() { return bl_type == BL::ITEM ? as_item() : nullptr; } -//inline dumb_ptr<invocation> block_list::is_spell() { return bl_type == BL::SPELL ? as_spell() : nullptr; } // struct invocation is defined in another header inline dumb_ptr<npc_data_script> npc_data::as_script() { return dumb_ptr<npc_data_script>(static_cast<npc_data_script *>(this)) ; } inline dumb_ptr<npc_data_shop> npc_data::as_shop() { return dumb_ptr<npc_data_shop>(static_cast<npc_data_shop *>(this)) ; } inline dumb_ptr<npc_data_warp> npc_data::as_warp() { return dumb_ptr<npc_data_warp>(static_cast<npc_data_warp *>(this)) ; } -inline dumb_ptr<npc_data_message> npc_data::as_message() { return dumb_ptr<npc_data_message>(static_cast<npc_data_message *>(this)) ; } inline dumb_ptr<npc_data_script> npc_data::is_script() { return npc_subtype == NpcSubtype::SCRIPT ? as_script() : nullptr ; } inline dumb_ptr<npc_data_shop> npc_data::is_shop() { return npc_subtype == NpcSubtype::SHOP ? as_shop() : nullptr ; } inline dumb_ptr<npc_data_warp> npc_data::is_warp() { return npc_subtype == NpcSubtype::WARP ? as_warp() : nullptr ; } -inline dumb_ptr<npc_data_message> npc_data::is_message() { return npc_subtype == NpcSubtype::MESSAGE ? as_message() : nullptr ; } void map_addmap(MapName mapname); void map_delmap(MapName mapname); diff --git a/src/map/map.t.hpp b/src/map/map.t.hpp index 267c049..d685c01 100644 --- a/src/map/map.t.hpp +++ b/src/map/map.t.hpp @@ -43,14 +43,12 @@ enum class BL : uint8_t NPC, MOB, ITEM, - SPELL, }; enum class NpcSubtype : uint8_t { WARP, SHOP, SCRIPT, - MESSAGE, COUNT, }; @@ -186,8 +184,6 @@ enum class MapCell : uint8_t UNWALKABLE = 0x01, // not in tmwa data _range = 0x04, - // set in code, not imported - NPC_NEAR = 0x80, }; ENUM_BITWISE_OPERATORS(MapCell) } diff --git a/src/map/mob.cpp b/src/map/mob.cpp index 7566e03..0da946a 100644 --- a/src/map/mob.cpp +++ b/src/map/mob.cpp @@ -1396,6 +1396,16 @@ int mob_target(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl, int dist) return 0; } +int mob_aggravate(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl) +{ + if (md->bl_type != BL::MOB) + return 0; + mob_target(md, bl, battle_get_range(bl)); + md->target_id = bl->bl_id; + md->attacked_id = bl->bl_id; + return 1; +} + /*========================================== * The ?? routine of an active monster *------------------------------------------ @@ -2691,7 +2701,6 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage, } // [MouseJstr] // SCRIPT実行 - if (md->npc_event) { if (sd == nullptr) { @@ -2701,7 +2710,17 @@ int mob_damage(dumb_ptr<block_list> src, dumb_ptr<mob_data> md, int damage, } } if (sd) - npc_event(sd, md->npc_event, 0); + { + if (md->npc_event) + npc_event(sd, md->npc_event, 0); + + // TODO: in the future, OnPCKillEvent, OnMobKillEvent and OnPCDieEvent should be combined + argrec_t arg[1] = + { + {"@mobID"_s, static_cast<int32_t>(unwrap<Species>(md->mob_class))}, + }; + npc_event_doall_l(stringish<ScriptLabel>("OnMobKillEvent"_s), sd->bl_id, arg); + } } clif_clearchar(md, BeingRemoveWhy::DEAD); diff --git a/src/map/mob.hpp b/src/map/mob.hpp index 6d87228..a466d0b 100644 --- a/src/map/mob.hpp +++ b/src/map/mob.hpp @@ -100,6 +100,7 @@ BlockId mob_once_spawn_area(dumb_ptr<map_session_data> sd, NpcEvent event); int mob_target(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl, int dist); +int mob_aggravate(dumb_ptr<mob_data> md, dumb_ptr<block_list> bl); int mob_stop_walking(dumb_ptr<mob_data> md, int type); int mob_stopattack(dumb_ptr<mob_data>); int mob_spawn(BlockId); diff --git a/src/map/npc-internal.hpp b/src/map/npc-internal.hpp index 993263f..8e9e030 100644 --- a/src/map/npc-internal.hpp +++ b/src/map/npc-internal.hpp @@ -31,6 +31,7 @@ namespace map struct event_data { dumb_ptr<npc_data_script> nd; + BlockId child; int pos; }; } // namespace map diff --git a/src/map/npc-parse.cpp b/src/map/npc-parse.cpp index 0bd23a9..164d793 100644 --- a/src/map/npc-parse.cpp +++ b/src/map/npc-parse.cpp @@ -92,7 +92,6 @@ void npc_delsrcfile(XString name) } } -static void register_npc_name(dumb_ptr<npc_data> nd) { earray<LString, NpcSubtype, NpcSubtype::COUNT> types //= @@ -100,12 +99,9 @@ void register_npc_name(dumb_ptr<npc_data> nd) "WARP"_s, "SHOP"_s, "SCRIPT"_s, - "MESSAGE"_s, }}; if (!nd->name) { - if (nd->npc_subtype == NpcSubtype::MESSAGE) - return; PRINTF("WARNING: npc with no name:\n%s @ %s,%d,%d\n"_fmt, types[nd->npc_subtype], nd->bl_m->name_, nd->bl_x, nd->bl_y); @@ -167,21 +163,6 @@ bool npc_load_warp(ast::npc::Warp& warp) nd->warp.xs = xs; nd->warp.ys = ys; - for (int i = 0; i < ys; i++) - { - for (int j = 0; j < xs; j++) - { - int x_lo = x - xs / 2; - int y_lo = y - ys / 2; - int xc = x_lo + j; - int yc = y_lo + i; - MapCell t = map_getcell(m, xc, yc); - if (bool(t & MapCell::UNWALKABLE)) - continue; - map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); - } - } - npc_warp++; nd->bl_type = BL::NPC; nd->npc_subtype = NpcSubtype::WARP; @@ -474,6 +455,7 @@ bool npc_load_script_none(ast::script::ScriptBody& body, ast::npc::ScriptNone& s nd->bl_type = BL::NPC; nd->npc_subtype = NpcSubtype::SCRIPT; + id_db.put(nd->bl_id, nd); // fix to get the oid in OnInit register_npc_name(nd); for (auto& pair : scriptlabel_db) @@ -554,21 +536,6 @@ bool npc_load_script_map(ast::script::ScriptBody& body, ast::npc::ScriptMap& scr int xs = script_map.xs.data, ys = script_map.ys.data; { - for (int i = 0; i < ys; i++) - { - for (int j = 0; j < xs; j++) - { - int x_lo = x - xs / 2; - int y_lo = y - ys / 2; - int xc = x_lo + j; - int yc = y_lo + i; - MapCell t = map_getcell(m, xc, yc); - if (bool(t & MapCell::UNWALKABLE)) - continue; - map_setcell(m, xc, yc, t | MapCell::NPC_NEAR); - } - } - nd->scr.xs = xs; nd->scr.ys = ys; nd->scr.event_needs_map = true; @@ -685,33 +652,6 @@ bool npc_load_script_any(ast::npc::Script *script) abort(); } -dumb_ptr<npc_data> npc_spawn_text(Borrowed<map_local> m, int x, int y, - Species npc_class, NpcName name, AString message) -{ - dumb_ptr<npc_data_message> retval; - retval.new_(); - retval->bl_id = npc_get_new_npc_id(); - retval->bl_x = x; - retval->bl_y = y; - retval->bl_m = m; - retval->bl_type = BL::NPC; - retval->npc_subtype = NpcSubtype::MESSAGE; - - retval->name = name; - if (message) - retval->message = message; - - retval->npc_class = npc_class; - retval->speed = 200_ms; - - clif_spawnnpc(retval); - map_addblock(retval); - map_addiddb(retval); - register_npc_name(retval); - - return retval; -} - static bool load_one_npc(io::LineCharReader& fp, bool& done) { diff --git a/src/map/npc-parse.hpp b/src/map/npc-parse.hpp index 9bc3448..f436fb9 100644 --- a/src/map/npc-parse.hpp +++ b/src/map/npc-parse.hpp @@ -29,16 +29,9 @@ namespace map { bool npc_load_warp(ast::npc::Warp& warp); -/** - * Spawns and installs a talk-only NPC - * - * \param message The message to speak. If message is nullptr, the NPC will not do anything at all. - */ -dumb_ptr<npc_data> npc_spawn_text(Borrowed<map_local> m, int x, int y, - Species class_, NpcName name, AString message); - void npc_addsrcfile(AString name); void npc_delsrcfile(XString name); +void register_npc_name(dumb_ptr<npc_data> nd); bool do_init_npc(void); } // namespace map } // namespace tmwa diff --git a/src/map/npc.cpp b/src/map/npc.cpp index 4296432..47bf820 100644 --- a/src/map/npc.cpp +++ b/src/map/npc.cpp @@ -61,6 +61,15 @@ namespace tmwa { namespace map { +static const std::vector<ByteCode> fake_buffer; +static const ScriptBuffer& fake_script = reinterpret_cast<const ScriptBuffer&>(fake_buffer); + +static +Borrowed<const ScriptBuffer> script_or_parent(dumb_ptr<npc_data_script> nd) +{ + return borrow(nd->scr.parent ? fake_script : *nd->scr.script); +} + BlockId npc_get_new_npc_id(void) { BlockId rv = npc_id; @@ -112,27 +121,26 @@ int npc_enable(NpcName name, bool flag) { // 有効化 nd->flag &= ~1; clif_spawnnpc(nd); + int xs = 0, ys = 0; + if (dumb_ptr<npc_data_script> nd_ = nd->is_script()) + { + xs = nd_->scr.xs; + ys = nd_->scr.ys; + } + + if (flag && (xs > 0 || ys > 0)) + map_foreachinarea(std::bind(npc_enable_sub, ph::_1, nd), + nd->bl_m, + nd->bl_x - xs, nd->bl_y - ys, + nd->bl_x + xs, nd->bl_y + ys, + BL::PC); } - else + else if (!(nd->flag & 1)) { // 無効化 - nd->flag |= 1; clif_clearchar(nd, BeingRemoveWhy::GONE); + nd->flag |= 1; } - int xs = 0, ys = 0; - if (dumb_ptr<npc_data_script> nd_ = nd->is_script()) - { - xs = nd_->scr.xs; - ys = nd_->scr.ys; - } - - if (flag && (xs > 0 || ys > 0)) - map_foreachinarea(std::bind(npc_enable_sub, ph::_1, nd), - nd->bl_m, - nd->bl_x - xs, nd->bl_y - ys, - nd->bl_x + xs, nd->bl_y + ys, - BL::PC); - return 0; } @@ -146,6 +154,75 @@ dumb_ptr<npc_data> npc_name2id(NpcName name) } /*========================================== + * NPC Spells Events + *------------------------------------------ + */ +static +NpcEvent spell_event2id(RString name) +{ + return spells_by_events.get(name); +} + +/*========================================== + * Spell Toknise + * Return a pair of strings, {spellname, parameter} + * Parameter may be empty. + *------------------------------------------ + */ +static +std::pair<XString, XString> magic_tokenise(XString src) +{ + auto seeker = std::find(src.begin(), src.end(), ' '); + + if (seeker == src.end()) + { + return {src, XString()}; + } + else + { + XString rv1 = src.xislice_h(seeker); + ++seeker; + + while (seeker != src.end() && *seeker == ' ') + ++seeker; + + // Note: this very well could be empty + XString rv2 = src.xislice_t(seeker); + return {rv1, rv2}; + } +} + +/*========================================== + * NPC Spell + *------------------------------------------ + */ +int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation) +{ + auto pair = magic_tokenise(source_invocation); + // Spell Cast + NpcEvent spell_event = spell_event2id(pair.first); + + RString spell_params = pair.second; + + if (spell_event.npc) + { + dumb_ptr<npc_data> nd = npc_name2id(spell_event.npc); + + if (nd) + { + argrec_t arg[1] = + { + {"@args$"_s, spell_params}, + }; + + npc_event(caster, spell_event, 0, arg); + return 1; + } + } + return 0; +} + +/*========================================== * イベントキューのイベント処理 *------------------------------------------ */ @@ -196,7 +273,9 @@ void npc_event_doall_sub(NpcEvent key, struct event_data *ev, if (name == p) { - run_script_l(ScriptPointer(borrow(*ev->nd->scr.script), ev->pos), rid, ev->nd->bl_id, + if (ev->nd->scr.parent != BlockId()) + return; // temporary npcs only respond to commands directly issued to them + run_script_l(ScriptPointer(script_or_parent(ev->nd), ev->pos), rid, ev->nd->bl_id, argv); (*c)++; } @@ -211,34 +290,6 @@ int npc_event_doall_l(ScriptLabel name, BlockId rid, Slice<argrec_t> args) return c; } -static -void npc_event_do_sub(NpcEvent key, struct event_data *ev, - int *c, NpcEvent name, BlockId rid, Slice<argrec_t> argv) -{ - nullpo_retv(ev); - - if (name == key) - { - run_script_l(ScriptPointer(borrow(*ev->nd->scr.script), ev->pos), rid, ev->nd->bl_id, - argv); - (*c)++; - } -} - -int npc_event_do_l(NpcEvent name, BlockId rid, Slice<argrec_t> args) -{ - int c = 0; - - if (!name.npc) - { - return npc_event_doall_l(name.label, rid, args); - } - - for (auto& pair : ev_db) - npc_event_do_sub(pair.first, &pair.second, &c, name, rid, args); - return c; -} - /*========================================== * 時計イベント実行 *------------------------------------------ @@ -286,6 +337,94 @@ int npc_event_do_oninit(void) return 0; } +/*========================================== + * + *------------------------------------------ + */ +static +void npc_eventtimer(TimerData *, tick_t, BlockId id, NpcEvent data) +{ + Option<P<struct event_data>> ev_ = ev_db.search(data); + dumb_ptr<npc_data_script> nd; + dumb_ptr<block_list> bl = map_id2bl(id); + + if (ev_.is_none() && data.label == stringish<ScriptLabel>("OnTouch"_s)) + return; + + P<struct event_data> ev = TRY_UNWRAP(ev_, + { + if (battle_config.error_log) + PRINTF("npc_event: event not found [%s]\n"_fmt, + data); + return; + }); + if ((nd = ev->nd) == nullptr) + { + if (battle_config.error_log) + PRINTF("npc_event: event not found [%s]\n"_fmt, + data); + return; + } + + if (nd->scr.event_needs_map) + { + int xs = nd->scr.xs; + int ys = nd->scr.ys; + if (nd->bl_m != bl->bl_m) + return; + if (xs > 0 + && (bl->bl_x < nd->bl_x - xs / 2 || nd->bl_x + xs / 2 < bl->bl_x)) + return; + if (ys > 0 + && (bl->bl_y < nd->bl_y - ys / 2 || nd->bl_y + ys / 2 < bl->bl_y)) + return; + } + + run_script(ScriptPointer(script_or_parent(nd), ev->pos), id, nd->bl_id); +} + +/*========================================== + * + *------------------------------------------ + */ +int npc_addeventtimer(dumb_ptr<block_list> bl, interval_t tick, NpcEvent name) +{ + int i; + + nullpo_retz(bl); + if (bl->bl_type == BL::MOB) + { + dumb_ptr<mob_data> md = bl->is_mob(); + for (i = 0; i < MAX_EVENTTIMER; i++) + if (!md->eventtimer[i]) + break; + + if (i < MAX_EVENTTIMER) + { + md->eventtimer[i] = Timer(gettick() + tick, + std::bind(npc_eventtimer, ph::_1, ph::_2, + md->bl_id, name)); + return 1; + } + } + if (bl->bl_type == BL::NPC) + { + dumb_ptr<npc_data> nd = bl->is_npc(); + for (i = 0; i < MAX_EVENTTIMER; i++) + if (!nd->eventtimer[i]) + break; + + if (i < MAX_EVENTTIMER) + { + nd->eventtimer[i] = Timer(gettick() + tick, + std::bind(npc_eventtimer, ph::_1, ph::_2, + nd->bl_id, name)); + return 1; + } + } + return 0; +} + /// Callback for npc OnTimer*: labels. /// This will be called later if you call npc_timerevent_start. /// This function may only expire, but not deactivate, the counter. @@ -313,7 +452,7 @@ void npc_timerevent(TimerData *, tick_t tick, BlockId id, interval_t data) id, next)); } - run_script(ScriptPointer(borrow(*nd->scr.script), te->pos), BlockId(), nd->bl_id); + run_script(ScriptPointer(script_or_parent(nd), te->pos), BlockId(), nd->bl_id); } /// Start (or resume) counting ticks to the next npc_timerevent. @@ -423,62 +562,81 @@ void npc_settimerevent_tick(dumb_ptr<npc_data_script> nd, interval_t newtimer) *------------------------------------------ */ int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname, - int mob_kill) + int mob_kill, Slice<argrec_t> args) { - Option<P<struct event_data>> ev_ = ev_db.search(eventname); - dumb_ptr<npc_data_script> nd; - - if (sd == nullptr) + if (!eventname.npc) { - PRINTF("npc_event nullpo?\n"_fmt); + return npc_event_doall_l(eventname.label, sd->bl_id, args); // XXX maybe merge this into npc_event? } + Option<P<struct event_data>> ev_ = ev_db.search(eventname); + dumb_ptr<npc_data_script> nd; + if (ev_.is_none() && eventname.label == stringish<ScriptLabel>("OnTouch"_s)) return 1; - P<struct event_data> ev = TRY_UNWRAP(ev_, + bool failed = false; + struct event_data ev {}; + P<struct event_data> ev2 = TRY_UNWRAP(ev_,{ failed = true; }); + if(failed) { - if (!mob_kill && battle_config.error_log) - PRINTF("npc_event: event not found [%s]\n"_fmt, - eventname); - return 0; - }); - if ((nd = ev->nd) == nullptr) + if (!eventname.label && eventname.npc && sd) + { + ev.nd = npc_name2id(eventname.npc)->is_script(); + ev.pos = 0; // start from the beginning of a npc + } + else + { + if (!mob_kill && battle_config.error_log) + PRINTF("npc_event: event not found [%s]\n"_fmt, + eventname); + return 0; + } + } + else + { + ev.nd = ev2->nd; + ev.pos = ev2->pos; + } + + if ((nd = ev.nd) == nullptr) { if (!mob_kill && battle_config.error_log) PRINTF("npc_event: event not found [%s]\n"_fmt, eventname); return 0; } - - if (nd->scr.event_needs_map) + if (sd) { - int xs = nd->scr.xs; - int ys = nd->scr.ys; - if (nd->bl_m != sd->bl_m) - return 1; - if (xs > 0 - && (sd->bl_x < nd->bl_x - xs / 2 || nd->bl_x + xs / 2 < sd->bl_x)) - return 1; - if (ys > 0 - && (sd->bl_y < nd->bl_y - ys / 2 || nd->bl_y + ys / 2 < sd->bl_y)) + if (nd->scr.event_needs_map) + { + int xs = nd->scr.xs; + int ys = nd->scr.ys; + if (nd->bl_m != sd->bl_m) + return 1; + if (xs > 0 + && (sd->bl_x < nd->bl_x - xs / 2 || nd->bl_x + xs / 2 < sd->bl_x)) + return 1; + if (ys > 0 + && (sd->bl_y < nd->bl_y - ys / 2 || nd->bl_y + ys / 2 < sd->bl_y)) + return 1; + } + if (sd->npc_id && sd->npc_pos > -1 && args.size() < 1) // if called from a timer we process async, otherwise sync + { + sd->eventqueuel.push_back(eventname); return 1; + } + if (nd->flag & 1) + { // 無効化されている + npc_event_dequeue(sd); + return 0; + } + sd->npc_id = nd->bl_id; } - - if (sd->npc_id) - { - sd->eventqueuel.push_back(eventname); - return 1; - } - if (nd->flag & 1) - { // 無効化されている - npc_event_dequeue(sd); - return 0; - } - - sd->npc_id = nd->bl_id; - sd->npc_pos = - run_script(ScriptPointer(borrow(*nd->scr.script), ev->pos), sd->bl_id, nd->bl_id); + int pos = run_script_l(ScriptPointer(script_or_parent(nd), ev.pos), + (sd? sd->bl_id : BlockId()), nd->bl_id, args); + if (sd) + sd->npc_pos = pos; return 0; } @@ -488,7 +646,7 @@ int npc_event(dumb_ptr<map_session_data> sd, NpcEvent eventname, */ int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int x, int y) { - int i, f = 1; + int i; int xs, ys; nullpo_retr(1, sd); @@ -499,10 +657,7 @@ int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int for (i = 0; i < m->npc_num; i++) { if (m->npc[i]->flag & 1) - { // 無効化されている - f = 0; continue; - } switch (m->npc[i]->npc_subtype) { @@ -510,11 +665,6 @@ int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int xs = m->npc[i]->is_warp()->warp.xs; ys = m->npc[i]->is_warp()->warp.ys; break; - case NpcSubtype::MESSAGE: - assert (0 && "I'm pretty sure these are never put on a map"_s); - xs = 0; - ys = 0; - break; case NpcSubtype::SCRIPT: xs = m->npc[i]->is_script()->scr.xs; ys = m->npc[i]->is_script()->scr.ys; @@ -530,11 +680,6 @@ int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int } if (i == m->npc_num) { - if (f) - { - if (battle_config.error_log) - PRINTF("npc_touch_areanpc : some bug \n"_fmt); - } return 1; } switch (m->npc[i]->npc_subtype) @@ -544,9 +689,6 @@ int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int pc_setpos(sd, m->npc[i]->is_warp()->warp.name, m->npc[i]->is_warp()->warp.x, m->npc[i]->is_warp()->warp.y, BeingRemoveWhy::GONE); break; - case NpcSubtype::MESSAGE: - assert (0 && "I'm pretty sure these NPCs are never put on a map."_s); - break; case NpcSubtype::SCRIPT: { NpcEvent aname; @@ -554,12 +696,12 @@ int npc_touch_areanpc(dumb_ptr<map_session_data> sd, Borrowed<map_local> m, int aname.label = stringish<ScriptLabel>("OnTouch"_s); if (sd->areanpc_id == m->npc[i]->bl_id) - return 1; + return 2; sd->areanpc_id = m->npc[i]->bl_id; if (npc_event(sd, aname, 0) > 0) npc_click(sd, m->npc[i]->bl_id); - break; + return 2; } } return 0; @@ -632,14 +774,7 @@ int npc_click(dumb_ptr<map_session_data> sd, BlockId id) npc_event_dequeue(sd); break; case NpcSubtype::SCRIPT: - sd->npc_pos = run_script(ScriptPointer(borrow(*nd->is_script()->scr.script), 0), sd->bl_id, id); - break; - case NpcSubtype::MESSAGE: - if (nd->is_message()->message) - { - clif_scriptmes(sd, id, nd->is_message()->message); - clif_scriptclose(sd, id); - } + sd->npc_pos = run_script(ScriptPointer(script_or_parent(nd->is_script()), 0), sd->bl_id, id); break; } @@ -666,14 +801,14 @@ int npc_scriptcont(dumb_ptr<map_session_data> sd, BlockId id) nd = map_id_is_npc(id); - if (!nd /* NPC was disposed? */ || nd->npc_subtype == NpcSubtype::MESSAGE) + if (!nd /* NPC was disposed? */) { clif_scriptclose(sd, id); npc_event_dequeue(sd); return 0; } - sd->npc_pos = run_script(ScriptPointer(borrow(*nd->is_script()->scr.script), sd->npc_pos), sd->bl_id, id); + sd->npc_pos = run_script(ScriptPointer(script_or_parent(nd->is_script()), sd->npc_pos), sd->bl_id, id); return 0; } @@ -859,18 +994,26 @@ void npc_free_internal(dumb_ptr<npc_data> nd_) if (nd_->npc_subtype == NpcSubtype::SCRIPT) { dumb_ptr<npc_data_script> nd = nd_->is_script(); + nd->scr.timerid.cancel(); nd->scr.timer_eventv.clear(); + nd->eventqueuel.clear(); + for (int i = 0; i < MAX_EVENTTIMER; i++) + nd->eventtimer[i].cancel(); + // destroy all children (puppets), if any + if (nd_->name && nd->scr.parent == BlockId()) { - nd->scr.script.reset(); - nd->scr.label_listv.clear(); + for (auto& pair : npcs_by_name) + if (pair.second->npc_subtype == NpcSubtype::SCRIPT + && pair.second->is_script()->scr.parent == nd_->bl_id) + npc_free(pair.second); } + + nd->scr.script.reset(); + nd->scr.label_listv.clear(); } - else if (nd_->npc_subtype == NpcSubtype::MESSAGE) - { - dumb_ptr<npc_data_message> nd = nd_->is_message(); - nd->message = AString(); - } + if (nd_->name) + npcs_by_name.put(nd_->name, nullptr); nd_.delete_(); } diff --git a/src/map/npc.hpp b/src/map/npc.hpp index b587f5f..e230ffe 100644 --- a/src/map/npc.hpp +++ b/src/map/npc.hpp @@ -44,7 +44,18 @@ constexpr Species FAKE_NPC_CLASS = wrap<Species>(127); constexpr Species INVISIBLE_CLASS = wrap<Species>(32767); int npc_event_dequeue(dumb_ptr<map_session_data> sd); -int npc_event(dumb_ptr<map_session_data> sd, NpcEvent npcname, int); +int npc_event(dumb_ptr<map_session_data>, NpcEvent, int, Slice<argrec_t>); +inline +int npc_event(dumb_ptr<map_session_data> sd, NpcEvent npcname, int i) +{ + return npc_event(sd, npcname, i, nullptr); +} +inline +int npc_event(BlockId rid, NpcEvent eventname, int mob_kill, Slice<argrec_t> args) +{ + return npc_event(rid ? map_id2bl(rid)->is_player() : nullptr, eventname, mob_kill, args); +} +int npc_addeventtimer(dumb_ptr<block_list> bl, interval_t tick, NpcEvent name); int npc_touch_areanpc(dumb_ptr<map_session_data>, Borrowed<map_local>, int, int); int npc_click(dumb_ptr<map_session_data>, BlockId); int npc_scriptcont(dumb_ptr<map_session_data>, BlockId); @@ -57,6 +68,7 @@ dumb_ptr<npc_data> npc_name2id(NpcName name); BlockId npc_get_new_npc_id(void); +int magic_message(dumb_ptr<map_session_data> caster, XString source_invocation); /** * Uninstalls and frees an NPC */ @@ -65,7 +77,11 @@ void npc_free(dumb_ptr<npc_data> npc); int npc_event_do_oninit(void); int npc_event_doall_l(ScriptLabel name, BlockId rid, Slice<argrec_t> argv); -int npc_event_do_l(NpcEvent name, BlockId rid, Slice<argrec_t> argv); +inline +int npc_event_do_l(NpcEvent name, BlockId rid, Slice<argrec_t> argv) +{ + return npc_event(rid, name, 0, argv); +} inline int npc_event_doall(ScriptLabel name) { diff --git a/src/map/pc.cpp b/src/map/pc.cpp index c47925f..bf75852 100644 --- a/src/map/pc.cpp +++ b/src/map/pc.cpp @@ -54,7 +54,6 @@ #include "globals.hpp" #include "intif.hpp" #include "itemdb.hpp" -#include "magic-stmt.hpp" #include "map.hpp" #include "map_conf.hpp" #include "npc.hpp" @@ -635,6 +634,51 @@ int pc_isequip(dumb_ptr<map_session_data> sd, IOff0 n) return 1; } +void pc_set_weapon_icon(dumb_ptr<map_session_data> sd, int count, + StatusChange icon, ItemNameId look) +{ + const StatusChange old_icon = sd->attack_spell_icon_override; + + sd->attack_spell_icon_override = icon; + sd->attack_spell_look_override = look; + + if (old_icon != StatusChange::ZERO && old_icon != icon) + clif_status_change(sd, old_icon, 0); + + clif_fixpcpos(sd); + if (count) + { + clif_changelook(sd, LOOK::WEAPON, unwrap<ItemNameId>(look)); + if (icon != StatusChange::ZERO) + clif_status_change(sd, icon, 1); + } + else + { + /* Set it to `normal' */ + clif_changelook(sd, LOOK::WEAPON, + static_cast<uint16_t>(sd->status.weapon)); + } +} + +void pc_set_attack_info(dumb_ptr<map_session_data> sd, interval_t speed, int range) +{ + sd->attack_spell_delay = speed; + sd->attack_spell_range = range; + + if (speed == interval_t::zero()) + { + pc_calcstatus(sd, 1); + clif_updatestatus(sd, SP::ASPD); + clif_updatestatus(sd, SP::ATTACKRANGE); + } + else + { + sd->aspd = speed; + clif_updatestatus(sd, SP::ASPD); + clif_updatestatus(sd, SP::ATTACKRANGE); + } +} + /*========================================== * session idに問題無し * char鯖から送られてきたステータスを設定 @@ -697,7 +741,7 @@ int pc_authok(AccountId id, int login_id2, ClientVersion client_version, // The above is no longer accurate now that we use <chrono>, but // I'm still not reverting this. // -o11c - sd->cast_tick = tick; // + pc_readglobalreg (sd, "MAGIC_CAST_TICK"_s); + //sd->cast_tick = tick; // + pc_readglobalreg (sd, "MAGIC_CAST_TICK"_s); // アカウント変数の送信要求 intif_request_accountreg(sd); @@ -806,10 +850,7 @@ void pc_show_motd(dumb_ptr<map_session_data> sd) // If you remove the sending of this message, // the license does not permit you to publicly use this software. - clif_displaymessage(sd->sess, "Server : ##7This server is Free Software, for details type @source in chat or use the tmwa-source tool"_s); - npc_event_doall_l(stringish<ScriptLabel>("OnPCLoginEvent"_s), sd->bl_id, nullptr); - - sd->state.seen_motd = true; + clif_displaymessage(sd->sess, "Server : ##7This server is Free Software, for details type @source in chat."_s); } /*========================================== @@ -2413,9 +2454,7 @@ void pc_walk(TimerData *, tick_t tick, BlockId id, unsigned char data) } } - if (bool(map_getcell(sd->bl_m, x, y) & MapCell::NPC_NEAR)) - npc_touch_areanpc(sd, sd->bl_m, x, y); - else + if (npc_touch_areanpc(sd, sd->bl_m, x, y) != 2) sd->areanpc_id = BlockId(); } interval_t i = calc_next_walk_step(sd); @@ -2519,9 +2558,7 @@ int pc_stop_walking(dumb_ptr<map_session_data> sd, int type) void pc_touch_all_relevant_npcs(dumb_ptr<map_session_data> sd) { - if (bool(map_getcell(sd->bl_m, sd->bl_x, sd->bl_y) & MapCell::NPC_NEAR)) - npc_touch_areanpc(sd, sd->bl_m, sd->bl_x, sd->bl_y); - else + if (npc_touch_areanpc(sd, sd->bl_m, sd->bl_x, sd->bl_y) != 2) sd->areanpc_id = BlockId(); } @@ -2608,13 +2645,23 @@ void pc_attack_timer(TimerData *, tick_t tick, BlockId id) if (sd->attackabletime > tick) return; // cannot attack yet - interval_t attack_spell_delay = sd->attack_spell_delay; - if (sd->attack_spell_override // [Fate] If we have an active attack spell, use that - && magic::spell_attack(id, sd->attacktarget)) + if (sd->attack_spell_override) // [Fate] If we have an active attack spell, use that { - // Return if the spell succeeded. If the spell had disspiated, spell_attack() may fail. - sd->attackabletime = tick + attack_spell_delay; - + // call_spell_event_script + argrec_t arg[1] = + { + {"@target_id"_s, static_cast<int32_t>(unwrap<BlockId>(bl->bl_id))}, + }; + npc_event_do_l(sd->magic_attack, sd->bl_id, arg); + sd->attackabletime = tick + sd->attack_spell_delay; + sd->attack_spell_charges--; + if (!sd->attack_spell_charges) + { + sd->attack_spell_override = BlockId(); + pc_set_weapon_icon(sd, 0, StatusChange::ZERO, ItemNameId()); + pc_set_attack_info(sd, interval_t::zero(), 0); + pc_calcstatus(sd, 0); + } } else { @@ -2681,7 +2728,8 @@ int pc_attack(dumb_ptr<map_session_data> sd, BlockId target_id, int type) if (bl->bl_type == BL::NPC) { // monster npcs [Valaris] - npc_click(sd, target_id); + if (battle_get_class(bl) != INVISIBLE_CLASS && !pc_isdead(sd)) + npc_click(sd, target_id); return 0; } @@ -3217,8 +3265,8 @@ int pc_damage(dumb_ptr<block_list> src, dumb_ptr<map_session_data> sd, clif_updatestatus(sd, SP::HP); pc_calcstatus(sd, 0); // [Fate] Reset magic - sd->cast_tick = gettick(); - magic::magic_stop_completely(sd); + //sd->cast_tick = gettick(); + //magic_stop_completely(sd); if (battle_config.death_penalty_type > 0 && sd->status.base_level >= 20) { @@ -3302,13 +3350,10 @@ int pc_damage(dumb_ptr<block_list> src, dumb_ptr<map_session_data> sd, if (src && src->bl_type == BL::PC) { // [Fate] PK death, trigger scripts - argrec_t arg[3] = + argrec_t arg[1] = { - {"@killerrid"_s, static_cast<int32_t>(unwrap<BlockId>(src->bl_id))}, {"@victimrid"_s, static_cast<int32_t>(unwrap<BlockId>(sd->bl_id))}, - {"@victimlvl"_s, sd->status.base_level}, }; - npc_event_doall_l(stringish<ScriptLabel>("OnPCKilledEvent"_s), sd->bl_id, arg); npc_event_doall_l(stringish<ScriptLabel>("OnPCKillEvent"_s), src->bl_id, arg); sd->state.pvp_rank = 0; @@ -3328,64 +3373,108 @@ int pc_damage(dumb_ptr<block_list> src, dumb_ptr<map_session_data> sd, * script用PCステータス読み出し *------------------------------------------ */ -int pc_readparam(dumb_ptr<map_session_data> sd, SP type) +int pc_readparam(dumb_ptr<block_list> bl, SP type) { + nullpo_retz(bl); + dumb_ptr<map_session_data> sd; + dumb_ptr<npc_data> nd; + dumb_ptr<mob_data> md; int val = 0; - nullpo_retz(sd); + if (bl->bl_type == BL::PC) + sd = bl->is_player(); + else if (bl->bl_type == BL::MOB) + md = bl->is_mob(); + else if (bl->bl_type == BL::NPC) + nd = bl->is_npc(); + else + return val; switch (type) { case SP::SKILLPOINT: - val = sd->status.skill_point; + val = sd ? sd->status.skill_point : 0; break; case SP::STATUSPOINT: - val = sd->status.status_point; + val = sd ? sd->status.status_point : 0; break; case SP::ZENY: - val = sd->status.zeny; + val = sd ? sd->status.zeny : 0; break; case SP::BASELEVEL: - val = sd->status.base_level; + val = battle_get_lv(bl); break; case SP::JOBLEVEL: - val = sd->status.job_level; + val = sd ? sd->status.job_level : 0; break; case SP::CLASS: - val = unwrap<Species>(sd->status.species); + val = unwrap<Species>(battle_get_class(bl)); break; case SP::SEX: - val = static_cast<uint8_t>(sd->status.sex); + if (sd) + val = static_cast<uint8_t>(sd->status.sex); + else if (nd) + val = static_cast<uint8_t>(nd->sex); break; case SP::WEIGHT: - val = sd->weight; + val = sd ? sd->weight : 0; break; case SP::MAXWEIGHT: - val = sd->max_weight; + val = sd ? sd->max_weight : 0; break; case SP::BASEEXP: - val = sd->status.base_exp; + val = sd ? sd->status.base_exp : 0; break; case SP::JOBEXP: - val = sd->status.job_exp; + val = sd ? sd->status.job_exp : 0; break; case SP::NEXTBASEEXP: - val = pc_nextbaseexp(sd); + val = sd ? pc_nextbaseexp(sd) : 0; break; case SP::NEXTJOBEXP: - val = pc_nextjobexp(sd); + val = sd ? pc_nextjobexp(sd) : 0; break; case SP::HP: - val = sd->status.hp; + val = battle_get_hp(bl); break; case SP::MAXHP: - val = sd->status.max_hp; + val = battle_get_max_hp(bl); + break; + case SP::HEALXP: + val = sd ? sd->heal_xp : 0; break; case SP::SP: - val = sd->status.sp; + val = sd ? sd->status.sp : 0; break; case SP::MAXSP: - val = sd->status.max_sp; + val = sd ? sd->status.max_sp : 0; + break; + case SP::BASE_ATK: + val = battle_get_baseatk(bl); + break; + case SP::ATK1: + val = battle_get_atk(bl); + break; + case SP::ATK2: + val = battle_get_atk2(bl); + break; + case SP::DEF1: + val = battle_get_def(bl); + break; + case SP::DEF2: + val = battle_get_def2(bl); + break; + case SP::MATK1: + val = battle_get_matk1(bl); + break; + case SP::MATK2: + val = battle_get_matk2(bl); + break; + case SP::MDEF1: + val = battle_get_mdef(bl); + break; + case SP::MDEF2: + val = battle_get_mdef2(bl); break; case SP::STR: case SP::AGI: @@ -3393,7 +3482,58 @@ int pc_readparam(dumb_ptr<map_session_data> sd, SP type) case SP::INT: case SP::DEX: case SP::LUK: - val = sd->status.attrs[sp_to_attr(type)]; + if (sd) + val = sd->status.attrs[sp_to_attr(type)]; + else + val = battle_get_stat(type, bl); + break; + case SP::SPEED: + val = battle_get_speed(bl).count(); + break; + case SP::HIT: + val = battle_get_hit(bl); + break; + case SP::FLEE1: + val = battle_get_flee(bl); + break; + case SP::FLEE2: + val = battle_get_flee2(bl); + break; + case SP::CRITICAL: + val = battle_get_critical(bl); + break; + case SP::GM: + val = sd ? pc_isGM(sd).get_all_bits() : 0; + break; + case SP::ATTACKRANGE: + val = battle_get_range(bl); + break; + case SP::POS_X: + val = bl->bl_x; + break; + case SP::POS_Y: + val = bl->bl_y; + break; + case SP::PVP_CHANNEL: + val = sd ? sd->state.pvpchannel : 0; + break; + case SP::BL_ID: + val = unwrap<BlockId>(bl->bl_id); + break; + case SP::BL_TYPE: + val = static_cast<uint8_t>(bl->bl_type); + break; + case SP::PARTNER: + val = sd ? unwrap<CharId>(sd->status.partner_id) : 0; + break; + case SP::CHAR_ID: + val = sd ? unwrap<CharId>(sd->status_key.char_id) : 0; + break; + case SP::ELTLVL: + val = static_cast<int>(battle_get_element(bl).level); + break; + case SP::ELTTYPE: + val = static_cast<int>(battle_get_element(bl).element); break; } @@ -3536,6 +3676,21 @@ int pc_setparam(dumb_ptr<map_session_data> sd, SP type, int val) case SP::LUK: pc_statusup2(sd, type, (val - sd->status.attrs[sp_to_attr(type)])); break; + case SP::PARTNER: + dumb_ptr<block_list> p_bl; + if (val < 2000000 && val >= 150000) + { + dumb_ptr<map_session_data> p_sd = nullptr; + if ((p_sd = map_nick2sd(map_charid2nick(wrap<CharId>(val)))) != nullptr) + p_bl = map_id2bl(p_sd->bl_id); + } + else + p_bl = map_id2bl(wrap<BlockId>(val)); + if (val < 1) + pc_divorce(sd); + else + p_bl ? pc_marriage(sd, p_bl->is_player()) : 0; + break; } clif_updatestatus(sd, type); @@ -3760,7 +3915,7 @@ int pc_changelook(dumb_ptr<map_session_data> sd, LOOK type, int val) * script用変数の値を読む *------------------------------------------ */ -int pc_readreg(dumb_ptr<map_session_data> sd, SIR reg) +int pc_readreg(dumb_ptr<block_list> sd, SIR reg) { nullpo_retz(sd); @@ -3771,7 +3926,7 @@ int pc_readreg(dumb_ptr<map_session_data> sd, SIR reg) * script用変数の値を設定 *------------------------------------------ */ -void pc_setreg(dumb_ptr<map_session_data> sd, SIR reg, int val) +void pc_setreg(dumb_ptr<block_list> sd, SIR reg, int val) { nullpo_retv(sd); @@ -3782,7 +3937,7 @@ void pc_setreg(dumb_ptr<map_session_data> sd, SIR reg, int val) * script用文字列変数の値を読む *------------------------------------------ */ -ZString pc_readregstr(dumb_ptr<map_session_data> sd, SIR reg) +ZString pc_readregstr(dumb_ptr<block_list> sd, SIR reg) { nullpo_retr(ZString(), sd); @@ -3794,7 +3949,7 @@ ZString pc_readregstr(dumb_ptr<map_session_data> sd, SIR reg) * script用文字列変数の値を設定 *------------------------------------------ */ -void pc_setregstr(dumb_ptr<map_session_data> sd, SIR reg, RString str) +void pc_setregstr(dumb_ptr<block_list> sd, SIR reg, RString str) { nullpo_retv(sd); @@ -4935,11 +5090,6 @@ void do_init_pc(void) ).detach(); } -void pc_cleanup(dumb_ptr<map_session_data> sd) -{ - magic::magic_stop_completely(sd); -} - void pc_invisibility(dumb_ptr<map_session_data> sd, int enabled) { if (enabled && !bool(sd->status.option & Opt0::INVISIBILITY)) diff --git a/src/map/pc.hpp b/src/map/pc.hpp index db1d5be..0f04698 100644 --- a/src/map/pc.hpp +++ b/src/map/pc.hpp @@ -80,6 +80,8 @@ int pc_counttargeted(dumb_ptr<map_session_data> sd, dumb_ptr<block_list> src, int pc_setrestartvalue(dumb_ptr<map_session_data> sd, int type); void pc_makesavestatus(dumb_ptr<map_session_data>); int pc_setnewpc(dumb_ptr<map_session_data>, AccountId, CharId, int, uint32_t /*tick_t*/, SEX); +void pc_set_weapon_icon(dumb_ptr<map_session_data> sd, int count,StatusChange icon, ItemNameId look); +void pc_set_attack_info(dumb_ptr<map_session_data> sd, interval_t speed, int range); int pc_authok(AccountId, int, ClientVersion, const CharKey *, const CharData *); int pc_authfail(AccountId accid); @@ -140,12 +142,12 @@ int pc_heal(dumb_ptr<map_session_data>, int, int); int pc_itemheal(dumb_ptr<map_session_data> sd, int hp, int sp); int pc_changelook(dumb_ptr<map_session_data>, LOOK, int); -int pc_readparam(dumb_ptr<map_session_data>, SP); +int pc_readparam(dumb_ptr<block_list>, SP); int pc_setparam(dumb_ptr<map_session_data>, SP, int); -int pc_readreg(dumb_ptr<map_session_data>, SIR); -void pc_setreg(dumb_ptr<map_session_data>, SIR, int); -ZString pc_readregstr(dumb_ptr<map_session_data> sd, SIR reg); -void pc_setregstr(dumb_ptr<map_session_data> sd, SIR reg, RString str); +int pc_readreg(dumb_ptr<block_list>, SIR); +void pc_setreg(dumb_ptr<block_list>, SIR, int); +ZString pc_readregstr(dumb_ptr<block_list> sd, SIR reg); +void pc_setregstr(dumb_ptr<block_list> sd, SIR reg, RString str); void update_quest(dumb_ptr<map_session_data> sd, VarName quest_var, int value); void update_allquest(dumb_ptr<map_session_data> sd); int pc_readglobalreg(dumb_ptr<map_session_data>, VarName ); @@ -168,7 +170,6 @@ int pc_divorce(dumb_ptr<map_session_data> sd); dumb_ptr<map_session_data> pc_get_partner(dumb_ptr<map_session_data> sd); void pc_set_gm_level(AccountId account_id, GmLevel level); void pc_setstand(dumb_ptr<map_session_data> sd); -void pc_cleanup(dumb_ptr<map_session_data> sd); // [Fate] Clean up after a logged-out PC int pc_read_gm_account(Session *, const std::vector<Packet_Repeat<0x2b15>>&); int pc_setpvptimer(dumb_ptr<map_session_data> sd, interval_t); diff --git a/src/map/script-call-internal.hpp b/src/map/script-call-internal.hpp index a887eb8..da6c082 100644 --- a/src/map/script-call-internal.hpp +++ b/src/map/script-call-internal.hpp @@ -25,6 +25,8 @@ #include "../mmo/ids.hpp" +#include "../strings/rstring.hpp" +#include "../generic/db.hpp" #include "script-persist.hpp" @@ -55,6 +57,13 @@ public: ScriptPointer scriptp, new_scriptp; int defsp, new_defsp, freeloop; int is_true = 0; + + // register keys are ints (interned) + // Not anymore! Well, sort of. + DMap<SIR, int> regm; + // can't be DMap because we want predictable .c_str()s + // TODO this can change now + Map<SIR, RString> regstrm; }; void run_func(ScriptState *st); @@ -70,13 +79,14 @@ enum class ScriptEndState }; dumb_ptr<map_session_data> script_rid2sd(ScriptState *st); -void get_val(dumb_ptr<map_session_data> sd, struct script_data *data); +void get_val(dumb_ptr<block_list> sd, struct script_data *data); __attribute__((deprecated)) void get_val(ScriptState *st, struct script_data *data); struct script_data get_val2(ScriptState *st, SIR reg); -void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd); -void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id); -void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd); +void set_scope_reg(ScriptState *, SIR, struct script_data *); +void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, struct script_data vd); +void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, int id); +void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, RString zd); __attribute__((warn_unused_result)) RString conv_str(ScriptState *st, struct script_data *data); __attribute__((warn_unused_result)) diff --git a/src/map/script-call.cpp b/src/map/script-call.cpp index 54f6c01..085d90a 100644 --- a/src/map/script-call.cpp +++ b/src/map/script-call.cpp @@ -78,14 +78,12 @@ dumb_ptr<map_session_data> script_rid2sd(ScriptState *st) * 変数の読み取り *------------------------------------------ */ -void get_val(dumb_ptr<map_session_data> sd, struct script_data *data) +void get_val(dumb_ptr<block_list> sd, struct script_data *data) { MATCH_BEGIN (*data) { MATCH_CASE (const ScriptDataParam&, u) { - if (sd == nullptr) - PRINTF("get_val error param SP::%d\n"_fmt, u.reg.sp()); int numi = 0; if (sd) numi = pc_readparam(sd, u.reg.sp()); @@ -106,7 +104,7 @@ void get_val(dumb_ptr<map_session_data> sd, struct script_data *data) if (postfix == '$') { RString str; - if (prefix == '@') + if (prefix == '@' || prefix == '.') { if (sd) str = pc_readregstr(sd, u.reg); @@ -130,7 +128,7 @@ void get_val(dumb_ptr<map_session_data> sd, struct script_data *data) else { int numi = 0; - if (prefix == '@') + if (prefix == '@' || prefix == '.') { if (sd) numi = pc_readreg(sd, u.reg); @@ -144,18 +142,18 @@ void get_val(dumb_ptr<map_session_data> sd, struct script_data *data) if (name[1] == '#') { if (sd) - numi = pc_readaccountreg2(sd, name); + numi = pc_readaccountreg2(sd->is_player(), name); } else { if (sd) - numi = pc_readaccountreg(sd, name); + numi = pc_readaccountreg(sd->is_player(), name); } } else { if (sd) - numi = pc_readglobalreg(sd, name); + numi = pc_readglobalreg(sd->is_player(), name); } *data = ScriptDataInt{numi}; } @@ -166,8 +164,39 @@ void get_val(dumb_ptr<map_session_data> sd, struct script_data *data) void get_val(ScriptState *st, struct script_data *data) { - dumb_ptr<map_session_data> sd = st->rid ? map_id2sd(st->rid) : nullptr; - get_val(sd, data); + dumb_ptr<block_list> bl = nullptr; + MATCH_BEGIN (*data) + { + MATCH_CASE (const ScriptDataParam&, u) + { + (void)u; // XXX travis complains if we don't use u + bl = map_id2bl(st->rid); + } + MATCH_CASE (const ScriptDataVariable&, u) + { + ZString name_ = variable_names.outtern(u.reg.base()); + VarName name = stringish<VarName>(name_); + char prefix = name.front(); + if (prefix == '.' && name[1] == '@') + { + if (name.back() == '$') + { + Option<P<RString>> s = st->regstrm.search(u.reg); + ZString val = s.map([](P<RString> s_) -> ZString { return *s_; }).copy_or(""_s); + *data = ScriptDataStr{val}; + } + else + *data = ScriptDataInt{st->regm.get(u.reg)}; + return; + } + if (prefix == '.' && st->oid) + bl = map_id2bl(st->oid); + else if (prefix != '$' && st->rid) + bl = map_id2bl(st->rid); + } + } + MATCH_END (); + get_val(bl, data); } /*========================================== @@ -185,12 +214,12 @@ struct script_data get_val2(ScriptState *st, SIR reg) * 変数設定用 *------------------------------------------ */ -void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct script_data vd) +void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, struct script_data vd) { if (type == VariableCode::PARAM) { int val = vd.get_if<ScriptDataInt>()->numi; - pc_setparam(sd, reg.sp(), val); + pc_setparam(sd->is_player(), reg.sp(), val); return; } assert (type == VariableCode::VARIABLE); @@ -203,7 +232,7 @@ void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct s if (postfix == '$') { RString str = vd.get_if<ScriptDataStr>()->str; - if (prefix == '@') + if (prefix == '@' || prefix == '.') { pc_setregstr(sd, reg, str); } @@ -219,7 +248,7 @@ void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct s else { int val = vd.get_if<ScriptDataInt>()->numi; - if (prefix == '@') + if (prefix == '@' || prefix == '.') { pc_setreg(sd, reg, val); } @@ -230,24 +259,47 @@ void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, struct s else if (prefix == '#') { if (name[1] == '#') - pc_setaccountreg2(sd, name, val); + pc_setaccountreg2(sd->is_player(), name, val); else - pc_setaccountreg(sd, name, val); + pc_setaccountreg(sd->is_player(), name, val); } else { - pc_setglobalreg(sd, name, val); + pc_setglobalreg(sd->is_player(), name, val); } } } -void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, int id) +void set_scope_reg(ScriptState *st, SIR reg, struct script_data *vd) +{ + ZString name = variable_names.outtern(reg.base()); + if (name.back() == '$') + { + if (auto *u = vd->get_if<ScriptDataStr>()) + { + if (!u->str) + { + st->regstrm.erase(reg); + return; + } + st->regstrm.insert(reg, u->str); + } + else + st->regstrm.insert(reg, conv_str(st, vd)); + } + else if (auto *u = vd->get_if<ScriptDataInt>()) + st->regm.put(reg, u->numi); + else + st->regm.put(reg, conv_num(st, vd)); +} + +void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, int id) { struct script_data vd = ScriptDataInt{id}; set_reg(sd, type, reg, vd); } -void set_reg(dumb_ptr<map_session_data> sd, VariableCode type, SIR reg, RString zd) +void set_reg(dumb_ptr<block_list> sd, VariableCode type, SIR reg, RString zd) { struct script_data vd = ScriptDataStr{zd}; set_reg(sd, type, reg, vd); @@ -676,7 +728,7 @@ void run_func(ScriptState *st) 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); + //assert (i == 0); pop_stack(st->stack, olddefsp - 4 - i, olddefsp); // 要らなくなったスタック(引数と復帰用データ)削除 @@ -830,20 +882,25 @@ void run_script_main(ScriptState *st, Borrowed<const ScriptBuffer> rootscript) abort(); } } + dumb_ptr<map_session_data> sd = map_id2sd(st->rid); switch (st->state) { case ScriptEndState::STOP: + if (sd && sd->npc_id == st->oid) + sd->state.npc_dialog_mes = 0; break; case ScriptEndState::END: - { - dumb_ptr<map_session_data> sd = map_id2sd(st->rid); st->scriptp.code = None; st->scriptp.pos = -1; if (sd && sd->npc_id == st->oid) + { + sd->state.npc_dialog_mes = 0; npc_event_dequeue(sd); - } + } break; case ScriptEndState::RERUNLINE: + if (sd && sd->npc_id == st->oid) + sd->state.npc_dialog_mes = 0; st->scriptp.pos = rerun_pos; break; } @@ -851,7 +908,6 @@ void run_script_main(ScriptState *st, Borrowed<const ScriptBuffer> rootscript) if (st->state != ScriptEndState::END) { // 再開するためにスタック情報を保存 - dumb_ptr<map_session_data> sd = map_id2sd(st->rid); if (sd) { sd->npc_stackbuf = stack->stack_datav; @@ -876,7 +932,29 @@ int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid, { struct script_stack stack; ScriptState st; - dumb_ptr<map_session_data> sd = map_id2sd(rid); + dumb_ptr<block_list> bl = map_id2bl(rid); + dumb_ptr<map_session_data> sd = bl? bl->is_player(): nullptr; + if (oid) + { + dumb_ptr<block_list> oid_bl = map_id2bl(oid); + if (oid_bl) + { + if (oid_bl->bl_type == BL::NPC) + { + dumb_ptr<npc_data> nd = oid_bl->is_npc(); + if(nd->npc_subtype == NpcSubtype::SCRIPT) + { + dumb_ptr<npc_data_script> nds = nd->is_script(); + if (nds->scr.parent) + { + dumb_ptr<npc_data_script> parent = map_id2bl(nds->scr.parent)->is_npc()->is_script(); + assert(parent->bl_type == BL::NPC && parent->npc_subtype == NpcSubtype::SCRIPT); + sp = ScriptPointer(borrow(*parent->scr.script), sp.pos); + } + } + } + } + } P<const ScriptBuffer> rootscript = TRY_UNWRAP(sp.code, return -1); int i; if (sp.pos >> 24) @@ -892,6 +970,7 @@ int run_script_l(ScriptPointer sp, BlockId rid, BlockId oid, st.scriptp = sp; st.rid = rid; st.oid = oid; + for (i = 0; i < args.size(); i++) { if (args[i].name.back() == '$') diff --git a/src/map/script-fun.cpp b/src/map/script-fun.cpp index 07076d5..beabef3 100644 --- a/src/map/script-fun.cpp +++ b/src/map/script-fun.cpp @@ -47,10 +47,10 @@ #include "globals.hpp" #include "intif.hpp" #include "itemdb.hpp" -#include "magic-interpreter-base.hpp" #include "map.hpp" #include "mob.hpp" #include "npc.hpp" +#include "npc-parse.hpp" #include "party.hpp" #include "pc.hpp" #include "script-call-internal.hpp" @@ -58,6 +58,8 @@ #include "script-persist.hpp" #include "skill.hpp" #include "storage.hpp" +#include "npc-internal.hpp" +#include "path.hpp" #include "../poison.hpp" @@ -70,6 +72,13 @@ namespace map #define AARG(n) (st->stack->stack_datav[st->start + 2 + (n)]) #define HARG(n) (st->end > st->start + 2 + (n)) +enum class MonsterAttitude +{ + HOSTILE = 0, + FRIENDLY = 1, + SERVANT = 2, + FROZEN = 3, +}; // // 埋め込み関数 // @@ -80,8 +89,16 @@ namespace map static void builtin_mes(ScriptState *st) { - RString mes = conv_str(st, &AARG(0)); - clif_scriptmes(script_rid2sd(st), st->oid, mes); + dumb_ptr<map_session_data> sd = script_rid2sd(st); + sd->state.npc_dialog_mes = 1; + RString mes = HARG(0) ? conv_str(st, &AARG(0)) : ""_s; + clif_scriptmes(sd, st->oid, mes); +} + +static +void builtin_clear(ScriptState *st) +{ + clif_npc_action(script_rid2sd(st), st->oid, 9, 0, 0, 0); } /*========================================== @@ -122,7 +139,6 @@ void builtin_callfunc(ScriptState *st) for (int i = st->start + 3; i < st->end; i++, j++) push_copy(st->stack, i); #endif - push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ push_int<ScriptDataInt>(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ @@ -142,6 +158,98 @@ void builtin_callfunc(ScriptState *st) OMATCH_END (); } +static +void builtin_call(ScriptState *st) +{ + struct script_data *sdata = &AARG(0); + get_val(st, sdata); + RString str; + if (sdata->is<ScriptDataStr>()) + { + str = conv_str(st, sdata); + Option<P<const ScriptBuffer>> scr_ = userfunc_db.get(str); + OMATCH_BEGIN (scr_) + { + OMATCH_CASE_SOME (scr) + { + int j = 0; + + for (int i = st->start + 3; i < st->end; i++, j++) + push_copy(st->stack, i); + + push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ + push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ + push_int<ScriptDataInt>(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ + push_script<ScriptDataRetInfo>(st->stack, TRY_UNWRAP(st->scriptp.code, abort())); // 現在のスクリプトをプッシュ + + st->scriptp = ScriptPointer(scr, 0); + st->defsp = st->start + 4 + j; + st->state = ScriptEndState::GOTO; + return; + } + OMATCH_CASE_NONE () + { + PRINTF("fatal: script: callfunc: function not found! [%s]\n"_fmt, str); + st->state = ScriptEndState::END; + abort(); + } + } + OMATCH_END (); + } + else + { + int pos_ = conv_num(st, &AARG(0)); + int j = 0; + + for (int i = st->start + 3; i < st->end; i++, j++) + push_copy(st->stack, i); + + push_int<ScriptDataInt>(st->stack, j); // 引数の数をプッシュ + push_int<ScriptDataInt>(st->stack, st->defsp); // 現在の基準スタックポインタをプッシュ + push_int<ScriptDataInt>(st->stack, st->scriptp.pos); // 現在のスクリプト位置をプッシュ + push_script<ScriptDataRetInfo>(st->stack, TRY_UNWRAP(st->scriptp.code, abort())); // 現在のスクリプトをプッシュ + + st->scriptp.pos = pos_; + st->defsp = st->start + 4 + j; + st->state = ScriptEndState::GOTO; + } + +} + +static +void builtin_getarg(ScriptState *st) +{ + int arg = conv_num(st, &AARG(0)); + if(st->defsp < 1 || !(st->stack->stack_datav[st->defsp - 1].is<ScriptDataRetInfo>())) + { + dumb_ptr<npc_data> nd = map_id_is_npc(st->oid); + if(nd) + PRINTF("builtin_getarg: no callfunc or callsub! @ %s\n"_fmt, nd->name); + else + PRINTF("builtin_getarg: no callfunc or callsub! (no npc)\n"_fmt); + st->state = ScriptEndState::END; + return; + } + + int i = conv_num(st, &st->stack->stack_datav[st->defsp - 4]); // Number of arguments. + if (arg > i || arg < 0 || i == 0) + { + const char a = 3; // ETX + if (HARG(1)) + push_copy(st->stack, st->start + 3); + else + push_str<ScriptDataStr>(st->stack, VString<1>(a)); + return; + } + push_copy(st->stack, (st->defsp - 4 - i) + arg); +} + +static +void builtin_void(ScriptState *) +{ + return; +} + /*========================================== * サブルーティンの呼び出し *------------------------------------------ @@ -182,12 +290,12 @@ void builtin_return(ScriptState *st) else PRINTF("Deprecated: return outside of callfunc or callsub! (no npc)\n"_fmt); } -#if 0 + if (HARG(0)) { // 戻り値有り push_copy(st->stack, st->start + 2); } -#endif + st->state = ScriptEndState::RETFUNC; } @@ -218,14 +326,22 @@ void builtin_close(ScriptState *st) PRINTF("Deprecated: close in a callfunc or callsub! (no npc)\n"_fmt); } st->state = ScriptEndState::END; - clif_scriptclose(script_rid2sd(st), st->oid); + dumb_ptr<map_session_data> sd = script_rid2sd(st); + if (sd->state.npc_dialog_mes) + clif_scriptclose(sd, st->oid); + else + clif_npc_action(sd, st->oid, 5, 0, 0, 0); } static void builtin_close2(ScriptState *st) { st->state = ScriptEndState::STOP; - clif_scriptclose(script_rid2sd(st), st->oid); + dumb_ptr<map_session_data> sd = script_rid2sd(st); + if (sd->state.npc_dialog_mes) + clif_scriptclose(sd, st->oid); + else + clif_npc_action(sd, st->oid, 5, 0, 0, 0); } /*========================================== @@ -252,7 +368,6 @@ void builtin_menu(ScriptState *st) buf += choice_str; buf += ':'; } - clif_scriptmenu(script_rid2sd(st), st->oid, AString(buf)); } else @@ -357,14 +472,43 @@ void builtin_max(ScriptState *st) static void builtin_min(ScriptState *st) { - int min, num; - min = conv_num(st, &AARG(0)); + int min = 0xFFFFFFF6, num; - for (int i = 1; HARG(i); i++) + if (HARG(1)) + { + min = conv_num(st, &AARG(0)); + for (int i = 1; HARG(i); i++) + { + num = conv_num(st, &AARG(i)); + if (num < min) + min = num; + } + } + else { - num = conv_num(st, &AARG(i)); - if (num < min) - min = num; + SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg; + ZString name = variable_names.outtern(reg.base()); + char prefix = name.front(); + if (prefix != '$' && prefix != '@' && prefix != '.') + { + PRINTF("builtin_max: illegal scope!\n"_fmt); + return; + } + for (int i = reg.index(); i < 256; i++) + { + struct script_data vd = get_val2(st, reg.iplus(i)); + MATCH_BEGIN (vd) + { + MATCH_CASE (const ScriptDataInt&, u) + { + if (u.numi < min) + min = u.numi; + continue; + } + } + MATCH_END (); + abort(); + } } push_int<ScriptDataInt>(st->stack, min); @@ -503,6 +647,118 @@ void builtin_heal(ScriptState *st) *------------------------------------------ */ static +void builtin_distance(ScriptState *st) +{ + dumb_ptr<block_list> source = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0)))); + dumb_ptr<block_list> target = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1)))); + int distance = 0; + int mode = HARG(2) ? conv_num(st, &AARG(2)) : 0; + + switch (mode) + { + // TODO implement case 1 (walk distance) + case 0: + default: + if (source->bl_m != target->bl_m) + { + // FIXME make it work even if source and target are not in the same map + distance = 0x7fffffff; + break; + } + int dx = abs(source->bl_x - target->bl_x); + int dy = abs(source->bl_y - target->bl_y); + distance = sqrt((dx * dx) + (dy * dy)); // Pythagoras' theorem + } + + push_int<ScriptDataInt>(st->stack, distance); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_target(ScriptState *st) +{ + // TODO maybe scrap all this and make it use battle_ functions? (add missing functions to battle) + + dumb_ptr<block_list> source = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0)))); + dumb_ptr<block_list> target = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1)))); + int flag = conv_num(st, &AARG(2)); + int val = 0; + + if (flag & 0x01) + { + int x0 = source->bl_x - AREA_SIZE; + int y0 = source->bl_y - AREA_SIZE; + int x1 = source->bl_x + AREA_SIZE; + int y1 = source->bl_y + AREA_SIZE; + if (target->bl_x >= x0 && target->bl_x <= x1 && target->bl_y >= y0 && target->bl_y <= y1) + val |= 0x01; // 0x01 target is in visible range + } + + if (flag & 0x02) + { + int range = battle_get_range(source); + int x2 = source->bl_x - range; + int y2 = source->bl_y - range; + int x3 = source->bl_x + range; + int y3 = source->bl_y + range; + if (target->bl_x >= x2 && target->bl_x <= x3 && target->bl_y >= y2 && target->bl_y <= y3) + val |= 0x02; // 0x02 target is in attack range + } + + if (flag & 0x04) + { + struct walkpath_data wpd; + if (!path_search(&wpd, source->bl_m, source->bl_x, source->bl_y, target->bl_x, target->bl_y, 0)) + val |= 0x04; // 0x04 target is walkable (has clear path to target) + } + + // TODO 0x08 target is visible (not behind collision) + + if (flag & 0x10) + { + if (target->bl_type != BL::PC || (target->bl_type == BL::PC && + (target->bl_m->flag.get(MapFlag::PVP) || pc_iskiller(source->is_player(), target->is_player())))) + val |= 0x10; // 0x10 target can be attacked by source (killer, killable and so on) + } + + if (flag & 0x20) + { + if (battle_check_range(source, target, 0)) + val |= 0x20; // 0x20 target is in line of sight + } + + push_int<ScriptDataInt>(st->stack, val); +} + +/*========================================== + * + *------------------------------------------ + */ +static +void builtin_injure(ScriptState *st) +{ + dumb_ptr<block_list> source = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(0)))); + dumb_ptr<block_list> target = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1)))); + int damage_caused = conv_num(st, &AARG(2)); + + // display damage first, because dealing damage may deallocate the target. + clif_damage(source, target, + gettick(), interval_t::zero(), interval_t::zero(), + damage_caused, 0, DamageType::NORMAL); + + battle_damage(source, target, damage_caused, 0); + + return; +} + +/*========================================== + * + *------------------------------------------ + */ +static void builtin_input(ScriptState *st) { dumb_ptr<map_session_data> sd = nullptr; @@ -549,6 +805,144 @@ void builtin_input(ScriptState *st) } } +static +void builtin_requestitem(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = nullptr; + script_data& scrd = AARG(0); + assert (scrd.is<ScriptDataVariable>()); + int amount = HARG(1) ? conv_num(st, &AARG(1)) : 1; + if (amount < 1 || amount > 16) + amount = 1; + + SIR reg = scrd.get_if<ScriptDataVariable>()->reg; + ZString name = variable_names.outtern(reg.base()); + char prefix = name.front(); + char postfix = name.back(); + + if (prefix != '$' && prefix != '@' && prefix != '.') + { + PRINTF("builtin_requestitem: illegal scope!\n"_fmt); + abort(); + } + + sd = script_rid2sd(st); + if (sd->state.menu_or_input) + { + // Second time (rerunline) + sd->state.menu_or_input = 0; + RString str = sd->npc_str; + RString val; + const char separator = ';'; + for (int j = 0; j < amount; j++) + { + auto find = std::find(str.begin(), str.end(), separator); + if (find == str.end()) + val = str.xislice_h(std::find(str.begin(), str.end(), ',')); + else + { + val = str.xislice_h(find); + val = val.xislice_h(std::find(val.begin(), val.end(), ',')); + str = str.xislice_t(find + 1); + } + + // check that the item exists in the inventory + int num = atoi(val.c_str()); + if (num < 1) + { + j--; + if (find == str.end()) + break; + continue; + } + ItemNameId nameid = wrap<ItemNameId>(num); + for (IOff0 i : IOff0::iter()) + if (sd->status.inventory[i].nameid == nameid) + goto pass; + fail: + j--; + if (find == str.end()) + break; + continue; + + pass: + // push to array + if (postfix == '$') + { + Option<P<struct item_data>> i_data = Some(itemdb_search(nameid)); + RString item_name = i_data.pmd_pget(&item_data::name).copy_or(stringish<ItemName>(""_s)); + if (item_name == ""_s) + goto fail; + if (name.startswith(".@"_s)) + { + struct script_data vd = script_data(ScriptDataStr{item_name}); + set_scope_reg(st, reg.iplus(j), &vd); + } + else + set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), item_name); + } + else + { + if (name.startswith(".@"_s)) + { + struct script_data vd = script_data(ScriptDataInt{num}); + set_scope_reg(st, reg.iplus(j), &vd); + } + else + set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), num); + } + if (find == str.end()) + break; + } + } + else + { + // First time - send prompt to client, then wait + st->state = ScriptEndState::RERUNLINE; + clif_scriptinputstr(sd, st->oid); // send string prompt + clif_npc_action(sd, st->oid, 10, amount, 0, 0); // send item request + sd->state.menu_or_input = 1; + } +} + +static +void builtin_requestlang(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + script_data& scrd = AARG(0); + assert (scrd.is<ScriptDataVariable>()); + SIR reg = scrd.get_if<ScriptDataVariable>()->reg; + ZString name = variable_names.outtern(reg.base()); + char postfix = name.back(); + + if (postfix != '$') + { + PRINTF("builtin_requestlang: illegal type (expects string)!\n"_fmt); + abort(); + } + + if (sd->state.menu_or_input) + { + // Second time (rerunline) + sd->state.menu_or_input = 0; + if (name.startswith(".@"_s)) + { + struct script_data vd = script_data(ScriptDataStr{sd->npc_str}); + set_scope_reg(st, reg, &vd); + } + else + set_reg(sd, VariableCode::VARIABLE, reg, sd->npc_str); + } + else + { + // First time - send prompt to client, then wait + st->state = ScriptEndState::RERUNLINE; + clif_npc_action(sd, st->oid, 0, 0, 0, 0); // send lang request + clif_scriptinputstr(sd, st->oid); // send string prompt + sd->state.menu_or_input = 1; + } +} + /*========================================== * *------------------------------------------ @@ -640,47 +1034,364 @@ void builtin_elif (ScriptState *st) } /*========================================== + * + *------------------------------------------ + */ +static +void builtin_foreach_sub(dumb_ptr<block_list> bl, NpcEvent event, BlockId caster) +{ + // call_spell_event_script + argrec_t arg[1] = + { + {"@target_id"_s, static_cast<int32_t>(unwrap<BlockId>(bl->bl_id))}, + }; + npc_event_do_l(event, caster, arg); +} +static +void builtin_foreach(ScriptState *st) +{ + int x0, y0, x1, y1, bl_num; + + dumb_ptr<block_list> caster = map_id2bl(st->rid); + bl_num = conv_num(st, &AARG(0)); + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(1)))); + x0 = conv_num(st, &AARG(2)); + y0 = conv_num(st, &AARG(3)); + x1 = conv_num(st, &AARG(4)); + y1 = conv_num(st, &AARG(5)); + ZString event_ = ZString(conv_str(st, &AARG(6))); + BL block_type; + NpcEvent event; + extract(event_, &event); + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return); + + switch (bl_num) + { + case 0: + block_type = BL::PC; + break; + case 1: + block_type = BL::NPC; + break; + case 2: + block_type = BL::MOB; + break; + case 3: + block_type = BL::NUL; + break; + default: + return; + } + + map_foreachinarea(std::bind(builtin_foreach_sub, ph::_1, event, caster->bl_id), + m, + x0, y0, + x1, y1, + block_type); +} +/*======================================== + * Destructs a temp NPC + *---------------------------------------- + */ +static +void builtin_destroy(ScriptState *st) +{ + BlockId id; + if (HARG(0)) + id = wrap<BlockId>(conv_num(st, &AARG(0))); + else + id = st->oid; + + dumb_ptr<npc_data_script> nd = map_id2bl(id)->is_npc()->is_script(); + if(!nd) + return; + //assert(nd->disposable == true); we don't care about it anymore + npc_free(nd); + if (!HARG(0)) + st->state = ScriptEndState::END; +} +/*======================================== + * Creates a temp NPC + *---------------------------------------- + */ + +static +void builtin_puppet(ScriptState *st) +{ + int x, y; + + dumb_ptr<block_list> bl = map_id2bl(st->oid); + dumb_ptr<npc_data_script> parent_nd = bl->is_npc()->is_script(); + dumb_ptr<npc_data_script> nd; + nd.new_(); + + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + x = conv_num(st, &AARG(1)); + y = conv_num(st, &AARG(2)); + Species sprite = wrap<Species>(static_cast<uint16_t>(conv_num(st, &AARG(4)))); + + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return); + + nd->bl_prev = nd->bl_next = nullptr; + nd->scr.event_needs_map = false; + + // PlayerName::SpellName + NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(3)))); + nd->name = npc; + + // Dynamically set location + nd->bl_m = m; + nd->bl_x = x; + nd->bl_y = y; + if (HARG(5) && HARG(6)) + { + nd->scr.xs = ((conv_num(st, &AARG(5)) * 2) + 1); // do the same equation as in AST + nd->scr.ys = ((conv_num(st, &AARG(6)) * 2) + 1); + } + nd->bl_id = npc_get_new_npc_id(); + nd->scr.parent = parent_nd->bl_id; + nd->dir = DIR::S; + nd->flag = 0; + nd->sit = DamageType::STAND; + nd->npc_class = sprite; + nd->speed = 200_ms; + nd->option = Opt0::ZERO; + nd->opt1 = Opt1::ZERO; + nd->opt2 = Opt2::ZERO; + nd->opt3 = Opt3::ZERO; + nd->scr.label_listv = parent_nd->scr.label_listv; + nd->bl_type = BL::NPC; + nd->npc_subtype = NpcSubtype::SCRIPT; + npc_script++; + + nd->n = map_addnpc(nd->bl_m, nd); + + map_addblock(nd); + clif_spawnnpc(nd); + + register_npc_name(nd); + + for (npc_label_list& el : parent_nd->scr.label_listv) + { + ScriptLabel lname = el.name; + int pos = el.pos; + + if (lname.startswith("On"_s)) + { + struct event_data ev {}; + ev.nd = nd; + ev.pos = pos; + NpcEvent buf; + buf.npc = nd->name; + buf.label = lname; + ev_db.insert(buf, ev); + } + } + + for (npc_label_list& el : parent_nd->scr.label_listv) + { + int t_ = 0; + ScriptLabel lname = el.name; + int pos = el.pos; + if (lname.startswith("OnTimer"_s) && extract(lname.xslice_t(7), &t_) && t_ > 0) + { + interval_t t = static_cast<interval_t>(t_); + + npc_timerevent_list tel {}; + tel.timer = t; + tel.pos = pos; + + auto it = std::lower_bound(nd->scr.timer_eventv.begin(), nd->scr.timer_eventv.end(), tel, + [](const npc_timerevent_list& l, const npc_timerevent_list& r) + { + return l.timer < r.timer; + } + ); + assert (it == nd->scr.timer_eventv.end() || it->timer != tel.timer); + + nd->scr.timer_eventv.insert(it, std::move(tel)); + } + } + + nd->scr.timer = interval_t::zero(); + nd->scr.next_event = nd->scr.timer_eventv.begin(); + + push_int<ScriptDataInt>(st->stack, unwrap<BlockId>(nd->bl_id)); +} + +/*========================================== * 変数設定 *------------------------------------------ */ static void builtin_set(ScriptState *st) { - dumb_ptr<map_session_data> sd = nullptr; + BlockId id; + dumb_ptr<block_list> bl = nullptr; if (auto *u = AARG(0).get_if<ScriptDataParam>()) { SIR reg = u->reg; - sd = script_rid2sd(st); + if(HARG(2)) + { + struct script_data *sdata = &AARG(2); + get_val(st, sdata); + CharName name; + if (sdata->is<ScriptDataStr>()) + { + name = stringish<CharName>(ZString(conv_str(st, sdata))); + if (name.to__actual()) + bl = map_nick2sd(name); + } + else + { + int num = conv_num(st, sdata); + if (num >= 2000000) + id = wrap<BlockId>(num); + else if (num >= 150000) + { + dumb_ptr<map_session_data> p_sd = nullptr; + if ((p_sd = map_nick2sd(map_charid2nick(wrap<CharId>(num)))) != nullptr) + id = p_sd->bl_id; + else + return; + } + else + return; + bl = map_id2bl(id); + } + } + + else + bl = script_rid2sd(st)->is_player(); + if (bl == nullptr) + return; int val = conv_num(st, &AARG(1)); - set_reg(sd, VariableCode::PARAM, reg, val); + set_reg(bl, VariableCode::PARAM, reg, val); return; } SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg; ZString name = variable_names.outtern(reg.base()); - char prefix = name.front(); - char postfix = name.back(); + VarName name_ = stringish<VarName>(name); + char prefix = name_.front(); + char postfix = name_.back(); if (prefix != '$') - sd = script_rid2sd(st); + { + if(HARG(2)) + { + struct script_data *sdata = &AARG(2); + get_val(st, sdata); + if(prefix == '.') + { + if (name_.startswith(".@"_s)) + { + PRINTF("builtin_set: illegal scope!\n"_fmt); + return; + } + NpcName n_name; + if (sdata->is<ScriptDataStr>()) + { + n_name = stringish<NpcName>(ZString(conv_str(st, sdata))); + bl = npc_name2id(n_name); + } + else + { + id = wrap<BlockId>(conv_num(st, sdata)); + bl = map_id2bl(id); + } + } + else + { + CharName c_name; + if (sdata->is<ScriptDataStr>()) + { + c_name = stringish<CharName>(ZString(conv_str(st, sdata))); + if (c_name.to__actual()) + bl = map_nick2sd(c_name); + } + else + { + id = wrap<BlockId>(conv_num(st, sdata)); + bl = map_id2bl(id); + } + } + } + else + { + if(prefix == '.') + { + if (name_.startswith(".@"_s)) + { + set_scope_reg(st, reg, &AARG(1)); + return; + } + bl = map_id2bl(st->oid)->is_npc(); + } + else + bl = map_id2bl(st->rid)->is_player(); + } + if (bl == nullptr) + return; + } if (postfix == '$') { // 文字列 RString str = conv_str(st, &AARG(1)); - set_reg(sd, VariableCode::VARIABLE, reg, str); + set_reg(bl, VariableCode::VARIABLE, reg, str); } else { // 数値 int val = conv_num(st, &AARG(1)); - set_reg(sd, VariableCode::VARIABLE, reg, val); + set_reg(bl, VariableCode::VARIABLE, reg, val); } } +// this is a special function that returns array index for a variable stored in another being +static +int getarraysize2(SIR reg, dumb_ptr<block_list> bl) +{ + int i = reg.index(), c = i; + bool zero = true; // index zero is empty + for (; i < 256; i++) + { + struct script_data vd = ScriptDataVariable{reg.iplus(i)}; + get_val(bl, &vd); + MATCH_BEGIN (vd) + { + MATCH_CASE (const ScriptDataStr&, u) + { + if (u.str[0]) + { + if (i == 0) + zero = false; // index zero is not empty + c = i; + } + continue; + } + MATCH_CASE (const ScriptDataInt&, u) + { + if (u.numi) + { + if (i == 0) + zero = false; // index zero is not empty + c = i; + } + continue; + } + } + MATCH_END (); + abort(); + } + return (c == 0 && zero) ? c : (c + 1); +} + /*========================================== * 配列変数設定 *------------------------------------------ @@ -688,26 +1399,61 @@ void builtin_set(ScriptState *st) static void builtin_setarray(ScriptState *st) { - dumb_ptr<map_session_data> sd = nullptr; + dumb_ptr<block_list> bl = nullptr; SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg; ZString name = variable_names.outtern(reg.base()); char prefix = name.front(); char postfix = name.back(); + int i = 1, j = 0; - if (prefix != '$' && prefix != '@') + if (prefix != '$' && prefix != '@' && prefix != '.') { PRINTF("builtin_setarray: illegal scope!\n"_fmt); return; } - if (prefix != '$') - sd = script_rid2sd(st); + if (prefix == '.' && !name.startswith(".@"_s)) + { + struct script_data *sdata = &AARG(1); + get_val(st, sdata); + i++; // 2nd argument is npc, not an array element + if (sdata->is<ScriptDataStr>()) + { + ZString tn = conv_str(st, sdata); + if (tn == "this"_s || tn == "oid"_s) + bl = map_id2bl(st->oid)->is_npc(); + else + { + NpcName name_ = stringish<NpcName>(tn); + bl = npc_name2id(name_); + } + } + else + { + int tid = conv_num(st, sdata); + if (tid == 0) + bl = map_id2bl(st->oid)->is_npc(); + else + bl = map_id2bl(wrap<BlockId>(tid))->is_npc(); + } + if (!bl) + { + PRINTF("builtin_setarray: npc not found\n"_fmt); + return; + } + if (st->oid && bl->bl_id != st->oid) + j = getarraysize2(reg, bl); + } + else if (prefix != '$' && !name.startswith(".@"_s)) + bl = map_id2bl(st->rid)->is_player(); - for (int j = 0, i = 1; i < st->end - st->start - 2 && j < 256; i++, j++) + for (; 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))); + if (name.startswith(".@"_s)) + set_scope_reg(st, reg.iplus(j), &AARG(i)); + else if (postfix == '$') + set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), conv_str(st, &AARG(i))); else - set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), conv_num(st, &AARG(i))); + set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), conv_num(st, &AARG(i))); } } @@ -718,27 +1464,31 @@ void builtin_setarray(ScriptState *st) static void builtin_cleararray(ScriptState *st) { - dumb_ptr<map_session_data> sd = nullptr; + dumb_ptr<block_list> bl = nullptr; SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg; ZString name = variable_names.outtern(reg.base()); char prefix = name.front(); char postfix = name.back(); int sz = conv_num(st, &AARG(2)); - if (prefix != '$' && prefix != '@') + if (prefix != '$' && prefix != '@' && prefix != '.') { PRINTF("builtin_cleararray: illegal scope!\n"_fmt); return; } - if (prefix != '$') - sd = script_rid2sd(st); + if (prefix == '.' && !name.startswith(".@"_s)) + bl = map_id2bl(st->oid)->is_npc(); + else if (prefix != '$' && !name.startswith(".@"_s)) + bl = map_id2bl(st->rid)->is_player(); for (int i = 0; i < sz; i++) { - if (postfix == '$') - set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARG(1))); + if (name.startswith(".@"_s)) + set_scope_reg(st, reg.iplus(i), &AARG(i)); + else if (postfix == '$') + set_reg(bl, VariableCode::VARIABLE, reg.iplus(i), conv_str(st, &AARG(1))); else - set_reg(sd, VariableCode::VARIABLE, reg.iplus(i), conv_num(st, &AARG(1))); + set_reg(bl, VariableCode::VARIABLE, reg.iplus(i), conv_num(st, &AARG(1))); } } @@ -782,7 +1532,7 @@ void builtin_getarraysize(ScriptState *st) ZString name = variable_names.outtern(reg.base()); char prefix = name.front(); - if (prefix != '$' && prefix != '@') + if (prefix != '$' && prefix != '@' && prefix != '.') { PRINTF("builtin_copyarray: illegal scope!\n"_fmt); return; @@ -1195,6 +1945,28 @@ void builtin_getcharid(ScriptState *st) } /*========================================== + * + *------------------------------------------ + */ +static +void builtin_getnpcid(ScriptState *st) +{ + dumb_ptr<npc_data> nd; + + if (HARG(0)) + nd = npc_name2id(stringish<NpcName>(ZString(conv_str(st, &AARG(0))))); + else + nd = map_id2bl(st->oid)->is_npc(); + if (nd == nullptr) + { + push_int<ScriptDataInt>(st->stack, -1); + return; + } + + push_int<ScriptDataInt>(st->stack, unwrap<BlockId>(nd->bl_id)); +} + +/*========================================== *指定IDのPT名取得 *------------------------------------------ */ @@ -1216,7 +1988,11 @@ void builtin_strcharinfo(ScriptState *st) dumb_ptr<map_session_data> sd; int num; - sd = script_rid2sd(st); + if (HARG(1)) //指定したキャラを状態異常にする + sd = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1))))->is_player(); + else + sd = script_rid2sd(st); + num = conv_num(st, &AARG(0)); if (num == 0) { @@ -1267,7 +2043,10 @@ void builtin_getequipid(ScriptState *st) int num; dumb_ptr<map_session_data> sd; - sd = script_rid2sd(st); + if (HARG(1)) + sd = map_nick2sd(stringish<CharName>(ZString(conv_str(st, &AARG(1))))); + else + sd = script_rid2sd(st); if (sd == nullptr) { PRINTF("getequipid: sd == nullptr\n"_fmt); @@ -1323,7 +2102,11 @@ void builtin_freeloop(ScriptState *st) static void builtin_bonus(ScriptState *st) { - SP type = SP(conv_num(st, &AARG(0))); + SP type; + if (auto *u = AARG(0).get_if<ScriptDataParam>()) + type = u->reg.sp(); + else + type = SP(conv_num(st, &AARG(0))); int val = conv_num(st, &AARG(1)); dumb_ptr<map_session_data> sd = script_rid2sd(st); pc_bonus(sd, type, val); @@ -1337,7 +2120,11 @@ void builtin_bonus(ScriptState *st) static void builtin_bonus2(ScriptState *st) { - SP type = SP(conv_num(st, &AARG(0))); + SP type; + if (auto *u = AARG(0).get_if<ScriptDataParam>()) + type = u->reg.sp(); + else + type = SP(conv_num(st, &AARG(0))); int type2 = conv_num(st, &AARG(1)); int val = conv_num(st, &AARG(2)); dumb_ptr<map_session_data> sd = script_rid2sd(st); @@ -1401,6 +2188,31 @@ void builtin_getskilllv(ScriptState *st) *------------------------------------------ */ static +void builtin_overrideattack(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + int charges = conv_num(st, &AARG(0)); + interval_t attack_delay = static_cast<interval_t>(conv_num(st, &AARG(1))); + int attack_range = conv_num(st, &AARG(2)); + StatusChange icon = StatusChange(conv_num(st, &AARG(3))); + ItemNameId look = wrap<ItemNameId>(static_cast<uint16_t>(conv_num(st, &AARG(4)))); + ZString event_ = ZString(conv_str(st, &AARG(5))); + + NpcEvent event; + extract(event_, &event); + + sd->attack_spell_override = st->oid; + sd->attack_spell_charges = charges; + sd->magic_attack = event; + pc_set_weapon_icon(sd, charges, icon, look); + pc_set_attack_info(sd, attack_delay, attack_range); +} + +/*========================================== + * + *------------------------------------------ + */ +static void builtin_getgmlevel(ScriptState *st) { push_int<ScriptDataInt>(st->stack, pc_isGM(script_rid2sd(st)).get_all_bits()); @@ -1594,6 +2406,72 @@ void builtin_getexp(ScriptState *st) } +static +void builtin_summon(ScriptState *st) +{ + NpcEvent event; + MapName map = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + int x = conv_num(st, &AARG(1)); + int y = conv_num(st, &AARG(2)); + dumb_ptr<block_list> owner_e = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(3)))); + dumb_ptr<map_session_data> owner = nullptr; + Species monster_id = wrap<Species>(conv_num(st, &AARG(4))); + MonsterAttitude monster_attitude = static_cast<MonsterAttitude>(conv_num(st, &AARG(5))); + interval_t lifespan = static_cast<interval_t>(conv_num(st, &AARG(6))); + if (HARG(7)) + extract(ZString(conv_str(st, &AARG(7))), &event); + + if (monster_attitude == MonsterAttitude::SERVANT + && owner_e->bl_type == BL::PC) + owner = owner_e->is_player(); // XXX in the future this should also work with mobs as owner + + BlockId mob_id = mob_once_spawn(owner, map, x, y, MobName(), monster_id, 1, event); + dumb_ptr<mob_data> mob = map_id_is_mob(mob_id); + + if (mob) + { + mob->mode = get_mob_db(monster_id).mode; + + switch (monster_attitude) + { + case MonsterAttitude::SERVANT: + mob->state.special_mob_ai = 1; + mob->mode |= MobMode::AGGRESSIVE; + break; + + case MonsterAttitude::FRIENDLY: + mob->mode = MobMode::CAN_ATTACK | (mob->mode & MobMode::CAN_MOVE); + break; + + case MonsterAttitude::HOSTILE: + mob->mode = MobMode::CAN_ATTACK | MobMode::AGGRESSIVE | (mob->mode & MobMode::CAN_MOVE); + if (owner) + { + mob->target_id = owner->bl_id; + mob->attacked_id = owner->bl_id; + } + break; + + case MonsterAttitude::FROZEN: + mob->mode = MobMode::ZERO; + break; + } + + mob->mode |= + MobMode::SUMMONED | MobMode::TURNS_AGAINST_BAD_MASTER; + + mob->deletetimer = Timer(gettick() + lifespan, + std::bind(mob_timer_delete, ph::_1, ph::_2, + mob_id)); + + if (owner) + { + mob->master_id = owner->bl_id; + mob->master_dist = 6; + } + } +} + /*========================================== * モンスター発生 *------------------------------------------ @@ -1712,6 +2590,20 @@ void builtin_addtimer(ScriptState *st) } /*========================================== + * NPCイベントタイマー追加 + *------------------------------------------ + */ +static +void builtin_addnpctimer(ScriptState *st) +{ + interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(0))); + ZString event_ = ZString(conv_str(st, &AARG(1))); + NpcEvent event; + extract(event_, &event); + npc_addeventtimer(npc_name2id(event.npc), tick, event); +} + +/*========================================== * NPCタイマー初期化 *------------------------------------------ */ @@ -1842,6 +2734,48 @@ void builtin_npcaction(ScriptState *st) } static +void builtin_camera(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + if (HARG(0)) + { + if (HARG(1) && !HARG(2)) + clif_npc_action(sd, st->oid, 2, 0, conv_num(st, &AARG(0)), conv_num(st, &AARG(1))); // camera to x, y + else + { + dumb_ptr<block_list> bl; + short x = 0, y = 0; + bool rel = false; + if (auto *u = AARG(0).get_if<ScriptDataInt>()) + bl = map_id2bl(wrap<BlockId>(u->numi)); + if (auto *g = AARG(0).get_if<ScriptDataStr>()) + { + if (g->str == "rid"_s || g->str == "player"_s) + bl = sd; + if (g->str == "relative"_s) + rel = true; + else if (g->str == "oid"_s || g->str == "npc"_s) + bl = map_id2bl(st->oid); + else + bl = npc_name2id(stringish<NpcName>(g->str)); + } + if (HARG(1) && HARG(2)) + { + x = conv_num(st, &AARG(1)); + y = conv_num(st, &AARG(2)); + } + if (rel) + clif_npc_action(sd, st->oid, 4, 0, x, y); // camera relative from current camera + else + clif_npc_action(sd, st->oid, 2, unwrap<BlockId>(bl->bl_id), x, y); // camera to actor + } + } + + else + clif_npc_action(sd, st->oid, 3, 0, 0, 0); // return camera +} + +static void builtin_setnpcdirection(ScriptState *st) { dumb_ptr<npc_data> nd_; @@ -1981,6 +2915,38 @@ void builtin_getmapusers(ScriptState *st) push_int<ScriptDataInt>(st->stack, users); } +static +void builtin_aggravate_sub(dumb_ptr<block_list> bl, dumb_ptr<block_list> target, int effect) +{ + dumb_ptr<mob_data> md = bl->is_mob(); + + if (mob_aggravate(md, target)) + clif_misceffect(bl, effect); +} + +static +void builtin_aggravate(ScriptState *st) +{ + dumb_ptr<block_list> target = map_id2bl(st->rid); + MapName str = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); + int x0 = conv_num(st, &AARG(1)); + int y0 = conv_num(st, &AARG(2)); + int x1 = conv_num(st, &AARG(3)); + int y1 = conv_num(st, &AARG(4)); + int effect = conv_num(st, &AARG(5)); + P<map_local> m = TRY_UNWRAP(map_mapname2mapid(str), + { + push_int<ScriptDataInt>(st->stack, -1); + return; + }); + + map_foreachinarea(std::bind(builtin_aggravate_sub, ph::_1, target, effect), + m, + x0, y0, + x1, y1, + BL::MOB); +} + /*========================================== * エリア指定ユーザー数所得 *------------------------------------------ @@ -2161,7 +3127,11 @@ void builtin_sc_end(ScriptState *st) { dumb_ptr<block_list> bl; StatusChange type = StatusChange(conv_num(st, &AARG(0))); - bl = map_id2bl(st->rid); + if (HARG(1)) //指定したキャラを状態異常にする + bl = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1)))); + else + bl = map_id2bl(st->rid); + skill_status_change_end(bl, type, nullptr); } @@ -2170,7 +3140,10 @@ void builtin_sc_check(ScriptState *st) { dumb_ptr<block_list> bl; StatusChange type = StatusChange(conv_num(st, &AARG(0))); - bl = map_id2bl(st->rid); + if (HARG(1)) //指定したキャラを状態異常にする + bl = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1)))); + else + bl = map_id2bl(st->rid); push_int<ScriptDataInt>(st->stack, skill_status_change_active(bl, type)); @@ -2207,10 +3180,17 @@ void builtin_resetstatus(ScriptState *st) static void builtin_attachrid(ScriptState *st) { - st->rid = wrap<BlockId>(conv_num(st, &AARG(0))); + dumb_ptr<map_session_data> sd = map_id2sd(st->rid); + BlockId newid = wrap<BlockId>(conv_num(st, &AARG(0))); + + if (sd && newid != st->rid) + sd->npc_id = BlockId(); + + st->rid = newid; push_int<ScriptDataInt>(st->stack, (map_id2sd(st->rid) != nullptr)); } + /*========================================== * RIDのデタッチ *------------------------------------------ @@ -2218,6 +3198,9 @@ void builtin_attachrid(ScriptState *st) static void builtin_detachrid(ScriptState *st) { + dumb_ptr<map_session_data> sd = map_id2sd(st->rid); + if (sd) + sd->npc_id = BlockId(); st->rid = BlockId(); } @@ -2357,7 +3340,12 @@ void builtin_setpvpchannel(ScriptState *st) static void builtin_getpvpflag(ScriptState *st) { - dumb_ptr<map_session_data> sd = script_rid2sd(st); + dumb_ptr<map_session_data> sd; + if (HARG(1)) //指定したキャラを状態異常にする + sd = map_id2bl(wrap<BlockId>(conv_num(st, &AARG(1))))->is_player(); + else + sd = script_rid2sd(st); + int num = conv_num(st, &AARG(0)); int flag = 0; @@ -2508,64 +3496,82 @@ void builtin_getitemlink(ScriptState *st) } static -void builtin_getspellinvocation(ScriptState *st) +void builtin_getpartnerid2(ScriptState *st) { - RString name = conv_str(st, &AARG(0)); - - AString invocation = magic::magic_find_invocation(name); - if (!invocation) - invocation = "..."_s; + dumb_ptr<map_session_data> sd = script_rid2sd(st); - push_str<ScriptDataStr>(st->stack, invocation); + push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status.partner_id)); } static -void builtin_getpartnerid2(ScriptState *st) +void builtin_chr(ScriptState *st) { - dumb_ptr<map_session_data> sd = script_rid2sd(st); + const char ascii = conv_num(st, &AARG(0)); + push_str<ScriptDataStr>(st->stack, VString<1>(ascii)); +} - push_int<ScriptDataInt>(st->stack, unwrap<CharId>(sd->status.partner_id)); +static +void builtin_ord(ScriptState *st) +{ + const char ascii = conv_str(st, &AARG(0)).front(); + push_int<ScriptDataInt>(st->stack, static_cast<int>(ascii)); } static void builtin_explode(ScriptState *st) { - dumb_ptr<map_session_data> sd = nullptr; + dumb_ptr<block_list> bl = nullptr; SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg; ZString name = variable_names.outtern(reg.base()); - const char separator = conv_str(st, &AARG(2))[0]; + const char separator = conv_str(st, &AARG(2)).front(); RString str = conv_str(st, &AARG(1)); RString val; char prefix = name.front(); char postfix = name.back(); - if (prefix != '$' && prefix != '@') + if (prefix != '$' && prefix != '@' && prefix != '.') { PRINTF("builtin_explode: illegal scope!\n"_fmt); return; } - if (prefix != '$') - sd = script_rid2sd(st); + if (prefix == '.' && !name.startswith(".@"_s)) + bl = map_id2bl(st->oid)->is_npc(); + else if (prefix != '$' && prefix != '.') + bl = map_id2bl(st->rid)->is_player(); for (int j = 0; j < 256; j++) { auto find = std::find(str.begin(), str.end(), separator); if (find == str.end()) { - if (postfix == '$') - set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), str); + if (name.startswith(".@"_s)) + { + struct script_data vd = script_data(ScriptDataInt{atoi(str.c_str())}); + if (postfix == '$') + vd = script_data(ScriptDataStr{str}); + set_scope_reg(st, reg.iplus(j), &vd); + } + else if (postfix == '$') + set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), str); else - set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), atoi(str.c_str())); + set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), atoi(str.c_str())); break; } { val = str.xislice_h(find); str = str.xislice_t(find + 1); - if (postfix == '$') - set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), val); + if (name.startswith(".@"_s)) + { + struct script_data vd = script_data(ScriptDataInt{atoi(val.c_str())}); + if (postfix == '$') + vd = script_data(ScriptDataStr{val}); + set_scope_reg(st, reg.iplus(j), &vd); + } + else if (postfix == '$') + set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), val); else - set_reg(sd, VariableCode::VARIABLE, reg.iplus(j), atoi(val.c_str())); + set_reg(bl, VariableCode::VARIABLE, reg.iplus(j), atoi(val.c_str())); } } } @@ -2771,6 +3777,128 @@ void builtin_specialeffect2(ScriptState *st) } +static +void builtin_get(ScriptState *st) +{ + BlockId id; + dumb_ptr<block_list> bl = nullptr; + if (auto *u = AARG(0).get_if<ScriptDataParam>()) + { + SIR reg = u->reg; + struct script_data *sdata = &AARG(1); + get_val(st, sdata); + CharName name; + if (sdata->is<ScriptDataStr>()) + { + name = stringish<CharName>(ZString(conv_str(st, sdata))); + if (name.to__actual()) + bl = map_nick2sd(name); + } + else + { + int num = conv_num(st, sdata); + if (num >= 2000000) + id = wrap<BlockId>(num); + else if (num >= 150000) + { + dumb_ptr<map_session_data> p_sd = nullptr; + if ((p_sd = map_nick2sd(map_charid2nick(wrap<CharId>(num)))) != nullptr) + id = p_sd->bl_id; + else + return; + } + else + return; + bl = map_id2bl(id); + } + + if (bl == nullptr) + return; + int var = pc_readparam(bl, reg.sp()); + push_int<ScriptDataInt>(st->stack, var); + return; + } + + struct script_data *sdata = &AARG(1); + get_val(st, sdata); + + SIR reg = AARG(0).get_if<ScriptDataVariable>()->reg; + ZString name_ = variable_names.outtern(reg.base()); + char prefix = name_.front(); + char postfix = name_.back(); + + if(prefix == '.') + { + if (name_.startswith(".@"_s)) + { + PRINTF("builtin_get: illegal scope!\n"_fmt); + return; + } + NpcName name; + if (sdata->is<ScriptDataStr>()) + { + name = stringish<NpcName>(ZString(conv_str(st, sdata))); + bl = npc_name2id(name); + } + else + { + id = wrap<BlockId>(conv_num(st, sdata)); + bl = map_id2bl(id); + } + } + else if(prefix != '$') + { + CharName name; + if (sdata->is<ScriptDataStr>()) + { + name = stringish<CharName>(ZString(conv_str(st, sdata))); + if (name.to__actual()) + bl = map_nick2sd(name); + } + else + { + id = wrap<BlockId>(conv_num(st, sdata)); + bl = map_id2bl(id); + } + } + else + { + PRINTF("builtin_get: illegal scope !\n"_fmt); + return; + } + + if (!bl) + { + PRINTF("builtin_get: no block list attached %s!\n"_fmt, conv_str(st, &AARG(1))); + if (postfix == '$') + push_str<ScriptDataStr>(st->stack, conv_str(st, &AARG(1))); + else + push_int<ScriptDataInt>(st->stack, 0); + return; + } + + if (postfix == '$') + { + ZString var = pc_readregstr(bl, reg); + push_str<ScriptDataStr>(st->stack, var); + } + else + { + int var; + if (prefix == '#' && bl) + { + if (name_.startswith("##"_s)) + var = pc_readaccountreg2(bl->is_player(), stringish<VarName>(name_)); + else + var = pc_readaccountreg(bl->is_player(), stringish<VarName>(name_)); + } + else + var = pc_readreg(bl, reg); + + push_int<ScriptDataInt>(st->stack, var); + } +} + /*========================================== * Nude [Valaris] *------------------------------------------ @@ -3069,6 +4197,20 @@ void builtin_npctalk(ScriptState *st) } /*========================================== + * register cmd + *------------------------------------------ + */ +static +void builtin_registercmd(ScriptState *st) +{ + RString evoke = conv_str(st, &AARG(0)); + ZString event_ = conv_str(st, &AARG(1)); + NpcEvent event; + extract(event_, &event); + spells_by_events.put(evoke, event); +} + +/*========================================== * getlook char info. getlook(arg) *------------------------------------------ */ @@ -3152,31 +4294,56 @@ void builtin_getsavepoint(ScriptState *st) static void builtin_areatimer_sub(dumb_ptr<block_list> bl, interval_t tick, NpcEvent event) { - pc_addeventtimer(bl->is_player(), tick, event); + if (bl->bl_type == BL::PC) + { + dumb_ptr<map_session_data> sd = map_id_is_player(bl->bl_id); + pc_addeventtimer(sd, tick, event); + } + else + { + npc_addeventtimer(bl, tick, event); + } } static void builtin_areatimer(ScriptState *st) { - int x0, y0, x1, y1; - - MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(0)))); - x0 = conv_num(st, &AARG(1)); - y0 = conv_num(st, &AARG(2)); - x1 = conv_num(st, &AARG(3)); - y1 = conv_num(st, &AARG(4)); - interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(5))); - ZString event_ = ZString(conv_str(st, &AARG(6))); + int x0, y0, x1, y1, bl_num; + + bl_num = conv_num(st, &AARG(0)); + MapName mapname = stringish<MapName>(ZString(conv_str(st, &AARG(1)))); + x0 = conv_num(st, &AARG(2)); + y0 = conv_num(st, &AARG(3)); + x1 = conv_num(st, &AARG(4)); + y1 = conv_num(st, &AARG(5)); + interval_t tick = static_cast<interval_t>(conv_num(st, &AARG(6))); + ZString event_ = conv_str(st, &AARG(7)); + BL block_type; NpcEvent event; extract(event_, &event); P<map_local> m = TRY_UNWRAP(map_mapname2mapid(mapname), return); + switch (bl_num) + { + case 0: + block_type = BL::PC; + break; + case 1: + block_type = BL::NPC; + break; + case 2: + block_type = BL::MOB; + break; + default: + return; + } + map_foreachinarea(std::bind(builtin_areatimer_sub, ph::_1, tick, event), m, x0, y0, x1, y1, - BL::PC); + block_type); } /*========================================== @@ -3305,6 +4472,18 @@ void builtin_gety(ScriptState *st) push_int<ScriptDataInt>(st->stack, sd->bl_y); } +/*============================ + * Gets the PC's direction + *---------------------------- + */ +static +void builtin_getdir(ScriptState *st) +{ + dumb_ptr<map_session_data> sd = script_rid2sd(st); + + push_int<ScriptDataInt>(st->stack, static_cast<uint8_t>(sd->dir)); +} + /* * Get the PC's current map's name */ @@ -3327,11 +4506,23 @@ void builtin_strnpcinfo(ScriptState *st) dumb_ptr<npc_data> nd; if(HARG(1)){ - NpcName npc = stringish<NpcName>(ZString(conv_str(st, &AARG(1)))); - nd = npc_name2id(npc); + struct script_data *sdata = &AARG(1); + get_val(st, sdata); + + if (sdata->is<ScriptDataStr>()) + { + NpcName name_ = stringish<NpcName>(ZString(conv_str(st, sdata))); + nd = npc_name2id(name_); + } + else + { + BlockId id = wrap<BlockId>(conv_num(st, sdata)); + nd = map_id2bl(id)->is_npc(); + } + if (!nd) { - PRINTF("builtin_strnpcinfo: no such npc: '%s'\n"_fmt, npc); + PRINTF("builtin_strnpcinfo: npc not found\n"_fmt); return; } } else { @@ -3417,11 +4608,15 @@ void builtin_mapexit(ScriptState *) BuiltinFunction builtin_functions[] = { - BUILTIN(mes, "s"_s, '\0'), + BUILTIN(mes, "?"_s, '\0'), + BUILTIN(clear, ""_s, '\0'), BUILTIN(goto, "L"_s, '\0'), BUILTIN(callfunc, "F"_s, '\0'), + BUILTIN(call, "F?*"_s, '.'), BUILTIN(callsub, "L"_s, '\0'), - BUILTIN(return, ""_s, '\0'), + BUILTIN(getarg, "i?"_s, '.'), + BUILTIN(return, "?"_s, '\0'), + BUILTIN(void, "?*"_s, '\0'), BUILTIN(next, ""_s, '\0'), BUILTIN(close, ""_s, '\0'), BUILTIN(close2, ""_s, '\0'), @@ -3431,11 +4626,15 @@ BuiltinFunction builtin_functions[] = BUILTIN(warp, "Mxy"_s, '\0'), BUILTIN(areawarp, "MxyxyMxy"_s, '\0'), BUILTIN(heal, "ii?"_s, '\0'), + BUILTIN(injure, "iii"_s, '\0'), BUILTIN(input, "N"_s, '\0'), + BUILTIN(requestitem, "N?"_s, '\0'), + BUILTIN(requestlang, "N"_s, '\0'), BUILTIN(if, "iF*"_s, '\0'), BUILTIN(elif, "iF*"_s, '\0'), BUILTIN(else, "F*"_s, '\0'), - BUILTIN(set, "Ne"_s, '\0'), + BUILTIN(set, "Ne?"_s, '\0'), + BUILTIN(get, "Ne"_s, '.'), BUILTIN(setarray, "Ne*"_s, '\0'), BUILTIN(cleararray, "Nei"_s, '\0'), BUILTIN(getarraysize, "N"_s, 'i'), @@ -3448,14 +4647,16 @@ BuiltinFunction builtin_functions[] = BUILTIN(makeitem, "IiMxy"_s, '\0'), BUILTIN(delitem, "Ii"_s, '\0'), BUILTIN(getcharid, "i?"_s, 'i'), + BUILTIN(getnpcid, "?"_s, 'i'), BUILTIN(getversion, ""_s, 'i'), - BUILTIN(strcharinfo, "i"_s, 's'), - BUILTIN(getequipid, "i"_s, 'i'), + BUILTIN(strcharinfo, "i?"_s, 's'), + BUILTIN(getequipid, "i?"_s, 'i'), 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(overrideattack, "iiiiiE"_s, '\0'), BUILTIN(getgmlevel, ""_s, 'i'), BUILTIN(end, ""_s, '\0'), BUILTIN(getopt2, ""_s, 'i'), @@ -3465,11 +4666,13 @@ BuiltinFunction builtin_functions[] = BUILTIN(gettime, "i"_s, 'i'), BUILTIN(openstorage, ""_s, '\0'), BUILTIN(getexp, "ii"_s, '\0'), + BUILTIN(summon, "Mxysmii?"_s, '\0'), BUILTIN(monster, "Mxysmi?"_s, '\0'), BUILTIN(areamonster, "Mxyxysmi?"_s, '\0'), BUILTIN(killmonster, "ME"_s, '\0'), BUILTIN(donpcevent, "E"_s, '\0'), BUILTIN(addtimer, "tE"_s, '\0'), + BUILTIN(addnpctimer, "tE"_s, '\0'), BUILTIN(initnpctimer, "?"_s, '\0'), BUILTIN(startnpctimer, "?"_s, '\0'), BUILTIN(stopnpctimer, "?"_s, '\0'), @@ -3477,6 +4680,7 @@ BuiltinFunction builtin_functions[] = BUILTIN(setnpctimer, "i?"_s, '\0'), BUILTIN(setnpcdirection, "iii?"_s, '\0'), BUILTIN(npcaction, "i???"_s, '\0'), + BUILTIN(camera, "???"_s, '\0'), BUILTIN(announce, "si"_s, '\0'), BUILTIN(mapannounce, "Msi"_s, '\0'), BUILTIN(getusers, "i"_s, 'i'), @@ -3486,8 +4690,8 @@ BuiltinFunction builtin_functions[] = 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(sc_end, "i?"_s, '\0'), + BUILTIN(sc_check, "i?"_s, 'i'), BUILTIN(debugmes, "s"_s, '\0'), BUILTIN(wgm, "s"_s, '\0'), BUILTIN(gmlog, "s"_s, '\0'), @@ -3501,14 +4705,13 @@ BuiltinFunction builtin_functions[] = BUILTIN(pvpon, "M"_s, '\0'), BUILTIN(pvpoff, "M"_s, '\0'), BUILTIN(setpvpchannel, "i"_s, '\0'), - BUILTIN(getpvpflag, "i"_s, 'i'), + BUILTIN(getpvpflag, "i?"_s, 'i'), BUILTIN(emotion, "i?"_s, '\0'), BUILTIN(mapwarp, "MMxy"_s, '\0'), BUILTIN(mobcount, "ME"_s, 'i'), BUILTIN(marriage, "P"_s, 'i'), BUILTIN(divorce, ""_s, 'i'), BUILTIN(getitemlink, "I"_s, 's'), - BUILTIN(getspellinvocation, "s"_s, 's'), BUILTIN(getpartnerid2, ""_s, 'i'), BUILTIN(explode, "Nss"_s, '\0'), BUILTIN(getinventorylist, ""_s, '\0'), @@ -3525,6 +4728,7 @@ BuiltinFunction builtin_functions[] = BUILTIN(npcareawarp, "xyxyis"_s, '\0'), BUILTIN(message, "Ps"_s, '\0'), BUILTIN(npctalk, "ss?"_s, '\0'), + BUILTIN(registercmd, "ss"_s, '\0'), BUILTIN(title, "s"_s, '\0'), BUILTIN(smsg, "e??"_s, '\0'), BUILTIN(remotecmd, "s?"_s, '\0'), @@ -3533,14 +4737,19 @@ BuiltinFunction builtin_functions[] = BUILTIN(getmask, ""_s, 'i'), BUILTIN(getlook, "i"_s, 'i'), BUILTIN(getsavepoint, "i"_s, '.'), - BUILTIN(areatimer, "MxyxytE"_s, '\0'), + BUILTIN(areatimer, "MxyxytEi"_s, '\0'), + BUILTIN(foreach, "iMxyxyE"_s, '\0'), BUILTIN(isin, "Mxyxy"_s, 'i'), BUILTIN(iscollision, "Mxy"_s, 'i'), BUILTIN(shop, "s"_s, '\0'), BUILTIN(isdead, ""_s, 'i'), + BUILTIN(aggravate, "Mxyxyi"_s, '\0'), BUILTIN(fakenpcname, "ssi"_s, '\0'), + BUILTIN(puppet, "mxysi??"_s, 'i'), + BUILTIN(destroy, "?"_s, '\0'), BUILTIN(getx, ""_s, 'i'), BUILTIN(gety, ""_s, 'i'), + BUILTIN(getdir, ""_s, 'i'), BUILTIN(getnpcx, "?"_s, 'i'), BUILTIN(getnpcy, "?"_s, 'i'), BUILTIN(strnpcinfo, "i?"_s, 's'), @@ -3549,11 +4758,15 @@ BuiltinFunction builtin_functions[] = BUILTIN(freeloop, "i"_s, '\0'), BUILTIN(if_then_else, "iii"_s, '.'), BUILTIN(max, "e?*"_s, 'i'), - BUILTIN(min, "ii*"_s, 'i'), + BUILTIN(min, "e?*"_s, 'i'), BUILTIN(average, "ii*"_s, 'i'), BUILTIN(sqrt, "i"_s, 'i'), BUILTIN(cbrt, "i"_s, 'i'), BUILTIN(pow, "ii"_s, 'i'), + BUILTIN(target, "iii"_s, 'i'), + BUILTIN(distance, "ii?"_s, 'i'), + BUILTIN(chr, "i"_s, 'i'), + BUILTIN(ord, "s"_s, 'i'), {nullptr, ""_s, ""_s, '\0'}, }; } // namespace map diff --git a/src/map/script-parse.cpp b/src/map/script-parse.cpp index a785748..f8d7b6b 100644 --- a/src/map/script-parse.cpp +++ b/src/map/script-parse.cpp @@ -283,6 +283,10 @@ ZString::iterator skip_word(ZString::iterator p) p++; // MAP鯖内共有変数用 if (*p == '@') p++; // 一時的変数用(like weiss) + if (*p == '.') + p++; // npc + if (*p == '@') + p++; // scope if (*p == '#') p++; // account変数用 if (*p == '#') @@ -613,6 +617,7 @@ ZString::iterator ScriptBuffer::parse_line(ZString::iterator p, bool *can_step) { // TODO should be LString, but no heterogenous lookup yet + // FIXME / TODO: allow destroy to both be a statement and a terminator static std::set<ZString> terminators = { @@ -623,6 +628,7 @@ ZString::iterator ScriptBuffer::parse_line(ZString::iterator p, bool *can_step) "end"_s, "mapexit"_s, "shop"_s, + "destroy"_s, }; *can_step = terminators.count(cmd->strs) == 0; } diff --git a/src/map/skill.cpp b/src/map/skill.cpp index 8a397a3..d9a7717 100644 --- a/src/map/skill.cpp +++ b/src/map/skill.cpp @@ -51,7 +51,6 @@ #include "battle_conf.hpp" #include "clif.hpp" #include "globals.hpp" -#include "magic-stmt.hpp" #include "mob.hpp" #include "pc.hpp" @@ -822,13 +821,6 @@ void skill_status_change_timer(TimerData *tid, tick_t tick, BlockId id, StatusCh if (bl->bl_type == BL::PC) sd = bl->is_player(); - if (sc_data[type].spell_invocation) - { // Must report termination - magic::spell_effect_report_termination(sc_data[type].spell_invocation, - bl->bl_id, type, 0); - sc_data[type].spell_invocation = BlockId(); - } - switch (type) { case StatusChange::SC_POISON: @@ -900,12 +892,12 @@ int skill_status_change_start(dumb_ptr<block_list> bl, StatusChange type, int val1, interval_t tick) { - return skill_status_effect(bl, type, val1, tick, BlockId()); + return skill_status_effect(bl, type, val1, tick); } int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type, int val1, - interval_t tick, BlockId spell_invocation) + interval_t tick) { dumb_ptr<map_session_data> sd = nullptr; eptr<struct status_change, StatusChange, StatusChange::MAX_STATUSCHANGE> sc_data; @@ -1009,10 +1001,10 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type, break; case StatusChange::SC_HASTE: - calc_flag = 1; - break; case StatusChange::SC_PHYS_SHIELD: case StatusChange::SC_MBARRIER: + calc_flag = 1; + break; case StatusChange::SC_HALT_REGENERATE: case StatusChange::SC_HIDE: break; @@ -1050,11 +1042,6 @@ int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type, clif_changeoption(bl); sc_data[type].val1 = val1; - if (sc_data[type].spell_invocation) // Supplant by newer spell - magic::spell_effect_report_termination(sc_data[type].spell_invocation, - bl->bl_id, type, 1); - - sc_data[type].spell_invocation = spell_invocation; /* タイマー設定 */ sc_data[type].timer = Timer(gettick() + tick, diff --git a/src/map/skill.hpp b/src/map/skill.hpp index 23881d4..8b3fbc7 100644 --- a/src/map/skill.hpp +++ b/src/map/skill.hpp @@ -108,7 +108,7 @@ int skill_castcancel(dumb_ptr<block_list> bl, int type); // ステータス異常 int skill_status_effect(dumb_ptr<block_list> bl, StatusChange type, int val1, - interval_t tick, BlockId spell_invocation); + interval_t tick); int skill_status_change_start(dumb_ptr<block_list> bl, StatusChange type, int val1, interval_t tick); |