diff options
Diffstat (limited to 'src/map/magic-stmt.cpp')
-rw-r--r-- | src/map/magic-stmt.cpp | 1547 |
1 files changed, 0 insertions, 1547 deletions
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 |