summaryrefslogtreecommitdiff
path: root/src/map/magic-stmt.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/map/magic-stmt.c')
-rw-r--r--src/map/magic-stmt.c1323
1 files changed, 1323 insertions, 0 deletions
diff --git a/src/map/magic-stmt.c b/src/map/magic-stmt.c
new file mode 100644
index 0000000..49c75b1
--- /dev/null
+++ b/src/map/magic-stmt.c
@@ -0,0 +1,1323 @@
+#include "magic-interpreter.h"
+#include "magic-expr.h"
+#include "magic-expr-eval.h"
+#include "magic-interpreter-aux.h"
+
+int
+clif_spawn_fake_npc_for_player(struct map_session_data *sd, int fake_npc_id);
+
+#define INVISIBLE_NPC 127 /* used for local spell effects */
+
+//#define DEBUG
+
+static void
+print_val(val_t *v)
+{
+ switch (v->ty) {
+ case TY_UNDEF: fprintf(stderr, "UNDEF"); break;
+ case TY_INT: fprintf(stderr, "%d", v->v.v_int); break;
+ case TY_DIR: fprintf(stderr, "dir%d", v->v.v_int); break;
+ case TY_STRING: fprintf(stderr, "`%s'", v->v.v_string); break;
+ default: fprintf(stderr, "ty%d", v->ty); break;
+ }
+}
+
+static void
+dump_env(env_t *env)
+{
+ int i;
+ for (i = 0; i < env->base_env->vars_nr; i++) {
+ val_t *v = &env->vars[i];
+ val_t *bv = &env->base_env->vars[i];
+
+ fprintf(stderr, "%02x %30s ", i, env->base_env->var_name[i]);
+ print_val(v);
+ fprintf(stderr, "\t(");
+ print_val(bv);
+ fprintf(stderr, ")\n");
+ }
+}
+
+static void
+clear_activation_record(cont_activation_record_t *ar)
+{
+ switch (ar->ty) {
+ case CONT_STACK_FOREACH:
+ free(ar->c.c_foreach.entities);
+ break;
+ case CONT_STACK_PROC:
+ free(ar->c.c_proc.old_actuals);
+ break;
+ }
+}
+
+static int
+invocation_timer_callback(int _, unsigned int __, int id, int data)
+{
+ invocation_t *invocation = (invocation_t *) map_id2bl(id);
+
+ if (invocation) {
+ invocation->timer = 0;
+ spell_execute(invocation);
+ }
+ return 0;
+}
+
+static void
+clear_stack(invocation_t *invocation)
+{
+ int i;
+
+ for (i = 0; i < invocation->stack_size; i++)
+ clear_activation_record(&invocation->stack[i]);
+
+ invocation->stack_size = 0;
+}
+
+static void
+free_invocation(invocation_t *invocation)
+{
+ if (invocation->status_change_refs)
+ free(invocation->status_change_refs);
+
+ if (invocation->flags & INVOCATION_FLAG_BOUND) {
+ entity_t *e = map_id2bl(invocation->subject);
+ if (e && e->type == BL_PC)
+ spell_unbind((character_t *) e, invocation);
+ }
+
+ clear_stack(invocation);
+
+ if (invocation->timer)
+ delete_timer(invocation->timer, invocation_timer_callback);
+
+ magic_free_env(invocation->env);
+
+ map_delblock(&invocation->bl);
+ map_delobject(invocation->bl.id); // also frees the object
+// free(invocation);
+}
+
+void
+magic_stop_completely(character_t *c)
+{
+ while (c->active_spells)
+ free_invocation(c->active_spells);
+}
+
+/* Spell execution has finished normally or we have been notified by a finished skill timer */
+static void
+try_to_finish_invocation(invocation_t *invocation)
+{
+ if (invocation->status_change_refs_nr == 0 && !invocation->current_effect) {
+ if (invocation->end_effect) {
+ clear_stack(invocation);
+ invocation->current_effect = invocation->end_effect;
+ invocation->end_effect = NULL;
+ spell_execute(invocation);
+ } else
+ free_invocation(invocation);
+ }
+}
+
+static void
+char_set_attack_info(character_t *subject, int speed, int range)
+{
+ subject->attack_spell_delay = speed;
+ subject->attack_spell_range = range;
+
+ if (speed == 0) {
+ 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);
+ }
+}
+
+static void
+char_set_weapon_icon(character_t *subject, int count, int icon, int look)
+{
+// const int magic_item_inventory_index = -1;
+// const int weapon_position = 4;
+
+ // The icon isn't working at the moment.
+
+ subject->attack_spell_icon_override = icon;
+ subject->attack_spell_look_override = look;
+
+ clif_fixpcpos(subject);
+ if (count) {
+// /* Set it to `override' */
+// clif_additem(subject, magic_item_inventory_index, count, 0, icon);
+ clif_changelook(&subject->bl, LOOK_WEAPON, look);
+// clif_equipitemack(subject, magic_item_inventory_index, weapon_position, 1);
+ } else {
+ /* Set it to `normal' */
+ clif_changelook(&subject->bl, LOOK_WEAPON, subject->status.weapon);
+
+// if (subject->equip_index[weapon_position] == -1)
+// clif_equipitemack(subject, 0, weapon_position, 1);
+// else
+// clif_equipitemack(subject, subject->equip_index[weapon_position], weapon_position, 1);
+ }
+}
+
+static int
+trigger_spell(int subject, int spell)
+{
+ invocation_t *invocation = (invocation_t *)map_id2bl(spell);
+
+ if (!invocation)
+ return 0;
+
+ invocation = spell_clone_effect(invocation);
+
+ spell_bind((character_t *)map_id2bl(subject), invocation);
+ magic_clear_var(&invocation->env->vars[VAR_CASTER]);
+ invocation->env->vars[VAR_CASTER].ty = TY_ENTITY;
+ invocation->env->vars[VAR_CASTER].v.v_int = subject;
+
+ return invocation->bl.id;
+}
+
+static void
+entity_warp(entity_t *target, int destm, int destx, int desty);
+
+static void
+char_update(character_t *character)
+{
+ entity_warp((entity_t *)character, character->bl.m, character->bl.x, character->bl.y);
+}
+
+static int
+timer_callback_effect(int _, unsigned int __, int id, int data)
+{
+ clif_misceffect(map_id2bl(id), data);
+ return 0;
+}
+
+static void
+entity_effect(entity_t *entity, int effect_nr, int delay)
+{
+ add_timer(gettick() + delay,
+ &timer_callback_effect,
+ entity->id,
+ effect_nr);
+}
+
+void
+magic_unshroud(character_t *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->fd, "Your shroud has been dispelled!");
+// entity_effect(&other_char->bl, MAGIC_EFFECT_REVEAL);
+}
+
+
+static int
+timer_callback_effect_npc_delete(int timer_id, unsigned int odelay, int npc_id, int _)
+{
+ struct npc_data *effect_npc = (struct npc_data *)map_id2bl(npc_id);
+ npc_free(effect_npc);
+
+ return 0;
+}
+
+static struct npc_data *
+local_spell_effect(int m, int x, int y, int effect, int tdelay)
+{
+ int delay = 30000; /* 1 minute should be enough for all interesting spell effects, I hope */
+ struct npc_data *effect_npc = npc_spawn_text(m, x, y,
+ INVISIBLE_NPC, "", NULL);
+ int effect_npc_id = effect_npc->bl.id;
+
+ entity_effect(&effect_npc->bl, effect, tdelay);
+ add_timer(gettick() + delay,
+ timer_callback_effect_npc_delete,
+ effect_npc_id, 0);
+
+ return effect_npc;
+}
+
+
+static int
+op_sfx(env_t *env, int args_nr, val_t *args)
+{
+ int delay = ARGINT(2);
+
+ if (TY(0) == TY_ENTITY) {
+ entity_effect(ARGENTITY(0), ARGINT(1), delay);
+ } else if (TY(0) == TY_LOCATION) {
+ local_spell_effect(ARGLOCATION(0).m,
+ ARGLOCATION(0).x,
+ ARGLOCATION(0).y,
+ ARGINT(1), delay);
+ } else return 1;
+
+ return 0;
+}
+
+
+static int
+op_instaheal(env_t *env, int args_nr, val_t *args)
+{
+ entity_t *caster = (VAR(VAR_CASTER).ty == TY_ENTITY)
+ ? map_id2bl(VAR(VAR_CASTER).v.v_int)
+ : NULL;
+ if (!caster)
+ caster = ARGENTITY(0);
+
+ battle_heal(caster, ARGENTITY(0), ARGINT(1), ARGINT(2), 0);
+ return 0;
+}
+
+
+static int
+op_itemheal(env_t *env, int args_nr, val_t *args)
+{
+ entity_t *subject = ARGENTITY(0);
+ if (subject->type == BL_PC) {
+ pc_itemheal((struct map_session_data *) subject,
+ ARGINT(1),
+ ARGINT(2));
+ } else return op_instaheal(env, args_nr, args);
+
+ return 0;
+}
+
+
+#define SHROUD_HIDE_NAME_TALKING_FLAG (1 << 0)
+#define SHROUD_DISAPPEAR_ON_PICKUP_FLAG (1 << 1)
+#define SHROUD_DISAPPEAR_ON_TALK_FLAG (1 << 2)
+
+#define ARGCHAR(n) (ARGENTITY(n)->type == BL_PC) ? (character_t *)(ARGENTITY(n)) : NULL
+
+static int
+op_shroud(env_t *env, int args_nr, val_t *args)
+{
+ character_t *subject = ARGCHAR(0);
+ int arg = ARGINT(1);
+
+ if (!subject)
+ return 0;
+
+ subject->state.shroud_active = 1;
+ subject->state.shroud_hides_name_talking = (arg & SHROUD_HIDE_NAME_TALKING_FLAG) != 0;
+ subject->state.shroud_disappears_on_pickup = (arg & SHROUD_DISAPPEAR_ON_PICKUP_FLAG) != 0;
+ subject->state.shroud_disappears_on_talk = (arg & SHROUD_DISAPPEAR_ON_TALK_FLAG) != 0;
+ return 0;
+}
+
+static int
+op_reveal(env_t *env, int args_nr, val_t *args)
+{
+ character_t *subject = ARGCHAR(0);
+
+ if (subject && subject->state.shroud_active)
+ magic_unshroud(subject);
+
+ return 0;
+}
+
+static int
+op_message(env_t *env, int args_nr, val_t *args)
+{
+ character_t *subject = ARGCHAR(0);
+
+ if (subject)
+ clif_displaymessage(subject->fd, ARGSTR(1));
+
+ return 0;
+}
+
+static int
+timer_callback_kill_npc(int timer_id, unsigned int odelay, int npc_id, int data)
+{
+ struct npc_data *npc = (struct npc_data *) map_id2bl(npc_id);
+ if (npc)
+ npc_free(npc);
+
+ return 0;
+}
+
+static int
+op_messenger_npc(env_t *env, int args_nr, val_t *args)
+{
+ struct npc_data *npc;
+ location_t *loc = &ARGLOCATION(0);
+
+ npc = npc_spawn_text(loc->m, loc->x, loc->y,
+ ARGINT(1), ARGSTR(2), ARGSTR(3));
+
+ add_timer(gettick() + ARGINT(4),
+ &timer_callback_kill_npc,
+ npc->bl.id,
+ 0);
+
+ return 0;
+}
+
+static void
+entity_warp(entity_t *target, int destm, int destx, int desty)
+{
+ if (target->type == BL_PC
+ || target->type == BL_MOB) {
+
+ switch (target->type) {
+ case BL_PC: {
+ character_t *character = (character_t *) target;
+ clif_clearchar_area(&character->bl, 3);
+ map_delblock(&character->bl);
+ 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:
+ clif_changemap(character, map[character->bl.m].name, character->bl.x, character->bl.y);
+
+ break;
+ }
+ case BL_MOB:
+ target->x = destx;
+ target->y = desty;
+ target->m = destm;
+ clif_fixmobpos((struct mob_data *) target);
+ break;
+ }
+ }
+}
+
+static int
+op_move(env_t *env, int args_nr, val_t *args)
+{
+ entity_t *subject = ARGENTITY(0);
+ int dir = ARGDIR(1);
+
+ int newx = subject->x + heading_x[dir];
+ int newy = subject->y + heading_y[dir];
+
+ if (!map_is_solid(subject->m, newx, newy))
+ entity_warp(subject, subject->m, newx, newy);
+
+ return 0;
+}
+
+static int
+op_warp(env_t *env, int args_nr, val_t *args)
+{
+ entity_t *subject = ARGENTITY(0);
+ location_t *loc = &ARGLOCATION(1);
+
+ entity_warp(subject, loc->m, loc->x, loc->y);
+
+ return 0;
+}
+
+static int
+op_banish(env_t *env, int args_nr, val_t *args)
+{
+ entity_t *subject = ARGENTITY(0);
+
+ if (subject->type == BL_MOB) {
+ struct mob_data *mob = (struct mob_data *) subject;
+
+ if (mob->mode & MOB_MODE_SUMMONED)
+ mob_catch_delete(mob, 3);
+ }
+
+ return 0;
+}
+
+static void
+record_status_change(invocation_t *invocation, int bl_id, int sc_id)
+{
+ int index = invocation->status_change_refs_nr++;
+ status_change_ref_t *cr;
+
+ if (invocation->status_change_refs)
+ invocation->status_change_refs = realloc(invocation->status_change_refs,
+ sizeof(status_change_ref_t)
+ * invocation->status_change_refs_nr);
+ else
+ invocation->status_change_refs = malloc(sizeof(status_change_ref_t));
+
+ cr = &invocation->status_change_refs[index];
+
+ cr->sc_type = sc_id;
+ cr->bl_id = bl_id;
+}
+
+static int
+op_status_change(env_t *env, int args_nr, val_t *args)
+{
+ entity_t *subject = ARGENTITY(0);
+ int invocation_id = VAR(VAR_INVOCATION).ty == TY_INVOCATION
+ ? VAR(VAR_INVOCATION).v.v_int
+ : 0;
+ invocation_t *invocation = (invocation_t *)map_id2bl(invocation_id);
+
+ skill_status_effect(subject, ARGINT(1), ARGINT(2), ARGINT(3), ARGINT(4), ARGINT(5), ARGINT(6), 0, invocation_id);
+
+ if (invocation)
+ record_status_change(invocation, subject->id, ARGINT(1));
+
+ return 0;
+}
+
+static int
+op_override_attack(env_t *env, int args_nr, val_t *args)
+{
+ entity_t *psubject = ARGENTITY(0);
+ int charges = ARGINT(1);
+ int attack_delay = ARGINT(2);
+ int attack_range = ARGINT(3);
+ int icon = ARGINT(4);
+ int look = ARGINT(5);
+ character_t *subject;
+
+ if (psubject->type != BL_PC)
+ return 0;
+
+ subject = (character_t *) psubject;
+
+ if (subject->attack_spell_override) {
+ invocation_t *old_invocation = (invocation_t *)map_id2bl(subject->attack_spell_override);
+ if (old_invocation)
+ free_invocation(old_invocation);
+ }
+
+ subject->attack_spell_override = trigger_spell(subject->bl.id, VAR(VAR_INVOCATION).v.v_int);
+ subject->attack_spell_charges = charges;
+
+ if (subject->attack_spell_override) {
+ char_set_weapon_icon(subject, charges, icon, look);
+ char_set_attack_info(subject, attack_delay, attack_range);
+ }
+
+ return 0;
+}
+
+
+static int
+op_create_item(env_t *env, int args_nr, val_t *args)
+{
+ struct item_data *item_data;
+ struct item item;
+ entity_t *entity = ARGENTITY(0);
+ character_t *subject;
+ int must_add_sequentially;
+ int count = ARGINT(2);
+ if (count <= 0)
+ return 0;
+
+ if (entity->type == BL_PC)
+ subject = (character_t *) entity;
+ else
+ return 0;
+
+ if (TY(1) == TY_INT)
+ item_data = itemdb_exists(ARGINT(1));
+ else if (TY(1) == TY_STRING)
+ item_data = itemdb_searchname(ARGSTR(1));
+ else
+ return 1;
+
+ if (!item_data)
+ return 0;
+
+ must_add_sequentially = (item_data->type == 4
+ || item_data->type == 5
+ || item_data->type == 7
+ || item_data->type == 8); /* Very elegant. */
+
+ memset(&item, 0, sizeof(struct item));
+ item.nameid = item_data->nameid;
+ item.identify = 1;
+
+ if (must_add_sequentially)
+ while (count--)
+ pc_additem(subject, &item, 1);
+ else
+ pc_additem(subject, &item, count);
+
+ return 0;
+}
+
+
+#define AGGRAVATION_MODE_ATTACKS_CASTER(n) ((n) == 0 || (n) == 2)
+#define AGGRAVATION_MODE_MAKES_AGGRESSIVE(n) ((n) > 0)
+
+static int
+op_aggravate(env_t *env, int args_nr, val_t *args)
+{
+ entity_t *victim = ARGENTITY(2);
+ int mode = ARGINT(1);
+ entity_t *target = ARGENTITY(0);
+ struct mob_data *other;
+
+ if (target->type == BL_MOB)
+ other = (struct mob_data *) target;
+ else
+ return 0;
+
+ mob_target(other, victim, battle_get_range(victim));
+
+ if (AGGRAVATION_MODE_MAKES_AGGRESSIVE(mode))
+ other->mode = 0x85 | (other->mode & MOB_SENSIBLE_MASK); /* war */
+
+ if (AGGRAVATION_MODE_ATTACKS_CASTER(mode)) {
+ other->target_id = victim->id;
+ other->attacked_id = victim->id;
+ }
+
+ return 0;
+}
+
+#define MONSTER_ATTITUDE_HOSTILE 0
+#define MONSTER_ATTITUDE_FRIENDLY 1
+#define MONSTER_ATTITUDE_SERVANT 2
+
+static int
+op_spawn(env_t *env, int args_nr, val_t *args)
+{
+ area_t *area = ARGAREA(0);
+ entity_t *owner_e = ARGENTITY(1);
+ int monster_id = ARGINT(2);
+ int monster_attitude = ARGINT(3);
+ int monster_count = ARGINT(4);
+ int monster_lifetime = ARGINT(5);
+ int i;
+
+ character_t *owner = (monster_attitude == MONSTER_ATTITUDE_SERVANT && owner_e->type == BL_PC)
+ ? (character_t *) owner_e
+ : NULL;
+
+ for (i = 0; i < monster_count; i++) {
+ location_t loc;
+ magic_random_location(&loc, area);
+
+ int mob_id;
+ struct mob_data *mob;
+
+ mob_id = mob_once_spawn(owner, map[loc.m].name, loc.x, loc.y, "--ja--", // Is that needed?
+ monster_id, 1, "");
+
+ mob = (struct mob_data *)map_id2bl(mob_id);
+
+ if (mob) {
+ mob->mode = mob_db[monster_id].mode;
+
+ switch (monster_attitude) {
+
+ case MONSTER_ATTITUDE_SERVANT:
+ mob->state.special_mob_ai = 1;
+ mob->mode |= 0x04;
+ break;
+
+ case MONSTER_ATTITUDE_FRIENDLY:
+ mob->mode = 0x81;
+ break;
+
+ case MONSTER_ATTITUDE_HOSTILE:
+ mob->mode = 0x85;
+ if (owner) {
+ mob->target_id = owner->bl.id;
+ mob->attacked_id = owner->bl.id;
+ }
+ break;
+ }
+
+ mob->mode |= MOB_MODE_SUMMONED | MOB_MODE_TURNS_AGAINST_BAD_MASTER;
+
+ mob->deletetimer = add_timer(gettick() + monster_lifetime,
+ mob_timer_delete,
+ mob_id, 0);
+
+ if (owner) {
+ mob->master_id = owner->bl.id;
+ mob->master_dist = 6;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+op_injure(env_t *env, int args_nr, val_t *args)
+{
+ entity_t *caster = ARGENTITY(0);
+ entity_t *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->type == BL_PC
+ && !map[target->m].flag.pvp)
+ 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;
+
+ if (damage_caused || mp_damage) {
+ battle_damage(caster, target, damage_caused, mp_damage);
+ clif_damage(caster, target, gettick(),
+ 0, 0, damage_caused, 0, 0, 0);
+ }
+
+ return 0;
+}
+
+static int
+op_emote(env_t *env, int args_nr, val_t *args)
+{
+ entity_t *victim = ARGENTITY(0);
+ int emotion = ARGINT(1);
+ clif_emotion(victim, emotion);
+
+ return 0;
+}
+
+static int
+op_set_script_variable(env_t *env, int args_nr, val_t *args)
+{
+ character_t *c = (ETY(0) == BL_PC)? ARGPC(0) : NULL;
+
+ if (!c)
+ return 1;
+
+ pc_setglobalreg(c, ARGSTR(1), ARGINT(2));
+
+ return 0;
+}
+
+
+static op_t operations[] =
+{
+ { "sfx", ".ii", op_sfx },
+ { "instaheal", "eii", op_instaheal },
+ { "itemheal", "eii", op_itemheal },
+ { "shroud", "ei", op_shroud },
+ { "unshroud", "e", op_reveal },
+ { "message", "es", op_message },
+ { "messenger_npc", "lissi", op_messenger_npc },
+ { "move", "ed", op_move },
+ { "warp", "el", op_warp },
+ { "banish", "e", op_banish },
+ { "status_change", "eiiiiii", op_status_change },
+ { "override_attack", "eiiiii", op_override_attack },
+ { "create_item", "e.i", op_create_item },
+ { "aggravate", "eie", op_aggravate },
+ { "spawn", "aeiiii", op_spawn },
+ { "injure", "eeii", op_injure },
+ { "emote", "ei", op_emote },
+ { "set_script_variable", "esi", op_set_script_variable },
+ { NULL, NULL, NULL }
+};
+
+static int operations_sorted = 0;
+static int operation_count;
+
+
+int
+compare_operations(const void *lhs, const void *rhs)
+{
+ return strcmp(((op_t *)lhs)->name, ((op_t *)rhs)->name);
+}
+
+
+op_t *
+magic_get_op(char *name, int *index)
+{
+ op_t key;
+
+ if (!operations_sorted) {
+ op_t *opc = operations;
+
+ while (opc->name)
+ ++opc;
+
+ operation_count = opc - operations;
+
+ qsort(operations, operation_count, sizeof(op_t), compare_operations);
+ operations_sorted = 1;
+ }
+
+ key.name = name;
+ op_t *op = bsearch(&key, operations, operation_count, sizeof(op_t), compare_operations);
+
+ if (op && index)
+ *index = op - operations;
+
+ return op;
+}
+
+
+void
+spell_effect_report_termination(int invocation_id, int bl_id, int sc_id, int supplanted)
+{
+ int i;
+ int index = -1;
+ invocation_t *invocation = (invocation_t *) map_id2bl(invocation_id);
+
+ if (!invocation)
+ return;
+
+ for (i = 0; i < invocation->status_change_refs_nr; i++) {
+ status_change_ref_t *cr = &invocation->status_change_refs[i];
+ if (cr->sc_type == sc_id && cr->bl_id == bl_id) {
+ index = i;
+ break;
+ }
+ }
+
+ if (index == -1) {
+ fprintf(stderr, "[magic] INTERNAL ERROR: spell-effect-report-termination: tried to terminate on unexpected bl %d, sc %d\n", bl_id, sc_id);
+ return;
+ }
+
+ if (index == invocation->status_change_refs_nr -1)
+ invocation->status_change_refs_nr--;
+ else /* Copy last change ref to the one we are deleting */
+ invocation->status_change_refs[index] = invocation->status_change_refs[--invocation->status_change_refs_nr];
+
+ try_to_finish_invocation(invocation);
+}
+
+static effect_t *
+return_to_stack(invocation_t *invocation)
+{
+ if (!invocation->stack_size)
+ return NULL;
+ else {
+ cont_activation_record_t *ar = invocation->stack + (invocation->stack_size - 1);
+ switch (ar->ty) {
+
+ case CONT_STACK_PROC: {
+ effect_t *ret = ar->return_location;
+ int i;
+
+ for (i = 0; i < ar->c.c_proc.args_nr; i++) {
+ val_t *var = &invocation->env->vars[ar->c.c_proc.formals[i]];
+ magic_clear_var(var);
+ *var = ar->c.c_proc.old_actuals[i];
+ }
+
+ clear_activation_record(ar);
+ --invocation->stack_size;
+
+ return ret;
+ }
+
+ case CONT_STACK_FOREACH: {
+ int entity_id;
+ val_t *var = &invocation->env->vars[ar->c.c_foreach.id];
+
+ do {
+ if (ar->c.c_foreach.index >= ar->c.c_foreach.entities_nr) {
+ effect_t *ret = ar->return_location;
+ clear_activation_record(ar);
+ --invocation->stack_size;
+ return ret;
+ }
+
+ entity_id = ar->c.c_foreach.entities[ar->c.c_foreach.index++];
+ } while (!entity_id || !map_id2bl(entity_id));
+
+ magic_clear_var(var);
+ var->ty = TY_ENTITY;
+ var->v.v_int = entity_id;
+
+ return ar->c.c_foreach.body;
+ }
+
+ case CONT_STACK_FOR:
+ if (ar->c.c_for.current > ar->c.c_for.stop) {
+ effect_t *ret = ar->return_location;
+ clear_activation_record(ar);
+ --invocation->stack_size;
+ return ret;
+ }
+
+ magic_clear_var(&invocation->env->vars[ar->c.c_for.id]);
+ invocation->env->vars[ar->c.c_for.id].ty = TY_INT;
+ invocation->env->vars[ar->c.c_for.id].v.v_int = ar->c.c_for.current++;
+
+ return ar->c.c_for.body;
+
+ default:
+ fprintf(stderr, "[magic] INTERNAL ERROR: While executing spell `%s': stack corruption\n", invocation->spell->name);
+ return NULL;
+ }
+ }
+}
+
+static cont_activation_record_t *
+add_stack_entry(invocation_t *invocation, int ty, effect_t *return_location)
+{
+ cont_activation_record_t *ar = invocation->stack + invocation->stack_size++;
+ if (invocation->stack_size >= MAX_STACK_SIZE) {
+ fprintf(stderr, "[magic] Execution stack size exceeded in spell `%s'; truncating effect\n", invocation->spell->name);
+ invocation->stack_size--;
+ return NULL;
+ }
+
+ ar->ty = ty;
+ ar->return_location = return_location;
+ return ar;
+}
+
+static int
+find_entities_in_area_c(entity_t *target, va_list va)
+{
+ int *entities_allocd_p = va_arg(va, int *);
+ int *entities_nr_p = va_arg(va, int *);
+ int **entities_p = va_arg(va, int **);
+ int filter = va_arg(va, int);
+
+ switch (target->type) {
+
+ case BL_PC:
+ if (filter == FOREACH_FILTER_PC
+ || filter == FOREACH_FILTER_ENTITY
+ || (filter == FOREACH_FILTER_TARGET
+ && map[target->m].flag.pvp))
+ break;
+ else
+ return 0;
+
+ case BL_MOB:
+ if (filter == FOREACH_FILTER_MOB
+ || filter == FOREACH_FILTER_ENTITY
+ || filter == FOREACH_FILTER_TARGET)
+ break;
+ else
+ return 0;
+
+ default:
+ return 0;
+ }
+
+ if (*entities_nr_p == *entities_allocd_p) {
+ /* Need more space */
+ (*entities_allocd_p) += 32;
+ *entities_p = realloc(*entities_p, sizeof(int) * (*entities_allocd_p));
+ }
+
+ (*entities_p)[(*entities_nr_p)++] = target->id;
+
+ return 0;
+}
+
+static void
+find_entities_in_area(area_t *area, int *entities_allocd_p, int *entities_nr_p, int **entities_p, int filter)
+{
+ switch (area->ty) {
+ case AREA_UNION:
+ find_entities_in_area(area->a.a_union[0], entities_allocd_p, entities_nr_p, entities_p, filter);
+ find_entities_in_area(area->a.a_union[1], entities_allocd_p, entities_nr_p, entities_p, filter);
+ break;
+
+ default: {
+ int m, x, y, width, height;
+ magic_area_rect(&m, &x, &y, &width, &height, area);
+ map_foreachinarea(find_entities_in_area_c,
+ m, x, y, x+width, y+height,
+ 0 /* filter elsewhere */,
+ entities_allocd_p, entities_nr_p, entities_p, filter);
+ }
+ }
+}
+
+static effect_t *
+run_foreach(invocation_t *invocation, effect_t *foreach, effect_t *return_location)
+{
+ val_t area;
+ int filter = foreach->e.e_foreach.filter;
+ int id = foreach->e.e_foreach.id;
+ effect_t *body = foreach->e.e_foreach.body;
+
+ magic_eval(invocation->env, &area, foreach->e.e_foreach.area);
+
+ if (area.ty != TY_AREA) {
+ magic_clear_var(&area);
+ fprintf(stderr, "[magic] Error in spell `%s': FOREACH loop over non-area\n", invocation->spell->name);
+ return return_location;
+ } else {
+ cont_activation_record_t *ar = add_stack_entry(invocation, CONT_STACK_FOREACH, return_location);
+ int entities_allocd = 64;
+ int *entities_collect = malloc(entities_allocd * sizeof(int));
+ int *entities;
+ int *shuffle_board;
+ int entities_nr = 0;
+ int i;
+
+ if (!ar)
+ return return_location;
+
+ find_entities_in_area(area.v.v_area, &entities_allocd, &entities_nr, &entities_collect, filter);
+
+ /* Now shuffle */
+ shuffle_board = malloc((sizeof(int) * (1 + entities_nr))); // +1: to avoid spurious warnings in memory profilers
+ entities = malloc((sizeof(int) * (1 + entities_nr))); // +1: to avoid spurious warnings in memory profilers
+ for (i = 0; i < entities_nr; i++)
+ shuffle_board[i] = i;
+
+ for (i = entities_nr - 1 ; i >= 0; i--) {
+ int random_index = rand() % (i + 1);
+ entities[i] = entities_collect[shuffle_board[random_index]];
+ shuffle_board[random_index] = shuffle_board[i]; // thus, we are guaranteed only to use unused indices
+ }
+
+ free(entities_collect);
+ free(shuffle_board);
+ /* Done shuffling */
+
+ ar->c.c_foreach.id = id;
+ ar->c.c_foreach.body = body;
+ ar->c.c_foreach.index = 0;
+ ar->c.c_foreach.entities_nr = entities_nr;
+ ar->c.c_foreach.entities = entities;
+
+ magic_clear_var(&area);
+
+ return return_to_stack(invocation);
+ }
+}
+
+static effect_t *
+run_for(invocation_t *invocation, effect_t *for_, effect_t *return_location)
+{
+ cont_activation_record_t *ar;
+ int id = for_->e.e_for.id;
+ val_t start;
+ val_t stop;
+
+ magic_eval(invocation->env, &start, for_->e.e_for.start);
+ magic_eval(invocation->env, &stop, for_->e.e_for.stop);
+
+ if (start.ty != TY_INT || stop.ty != TY_INT) {
+ 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", invocation->spell->name);
+ return return_location;
+ }
+
+ ar = add_stack_entry(invocation, CONT_STACK_FOR, return_location);
+
+ if (!ar)
+ return return_location;
+
+ ar->c.c_for.id = id;
+ ar->c.c_for.current = start.v.v_int;
+ ar->c.c_for.stop = stop.v.v_int;
+ ar->c.c_for.body = for_->e.e_for.body;
+
+ return return_to_stack(invocation);
+}
+
+static effect_t *
+run_call(invocation_t *invocation, effect_t *return_location)
+{
+ effect_t *current = invocation->current_effect;
+ cont_activation_record_t *ar;
+ int args_nr = current->e.e_call.args_nr;
+ int *formals = current->e.e_call.formals;
+ val_t *old_actuals = aCalloc(sizeof(val_t), args_nr);
+ int i;
+
+ ar = add_stack_entry(invocation, CONT_STACK_PROC, return_location);
+ ar->c.c_proc.args_nr = args_nr;
+ ar->c.c_proc.formals = formals;
+ ar->c.c_proc.old_actuals = old_actuals;
+ for (i = 0; i < args_nr; i++) {
+ val_t *env_val = &invocation->env->vars[formals[i]];
+ val_t result;
+ magic_copy_var(&old_actuals[i], env_val);
+ magic_eval(invocation->env, &result, current->e.e_call.actuals[i]);
+ *env_val = result;
+ }
+
+ return current->e.e_call.body;
+}
+
+static void
+print_cfg(int i, effect_t *e)
+{
+ int j;
+ for (j = 0; j < i; j++)
+ printf(" ");
+
+ printf("%p: ", e);
+
+ if (!e) {
+ puts(" -- end --");
+ return;
+ }
+
+ switch (e->ty) {
+ case EFFECT_SKIP:
+ puts("SKIP"); break;
+ case EFFECT_END:
+ puts("END"); break;
+ case EFFECT_ABORT:
+ puts("ABORT"); break;
+ case EFFECT_ASSIGN:
+ puts("ASSIGN"); break;
+ case EFFECT_FOREACH:
+ puts("FOREACH");
+ print_cfg(i+1, e->e.e_foreach.body);
+ break;
+ case EFFECT_FOR:
+ puts("FOR");
+ print_cfg(i+1, e->e.e_for.body);
+ break;
+ case EFFECT_IF:
+ puts("IF");
+ for (j = 0; j < i; j++)
+ printf(" ");
+ puts("THEN");
+ print_cfg(i+1, e->e.e_if.true_branch);
+ for (j = 0; j < i; j++)
+ printf(" ");
+ puts("ELSE");
+ print_cfg(i+1, e->e.e_if.false_branch);
+ break;
+ case EFFECT_SLEEP:
+ puts("SLEEP"); break;
+ case EFFECT_SCRIPT:
+ puts("SCRIPT"); break;
+ case EFFECT_BREAK:
+ puts("BREAK"); break;
+ case EFFECT_OP:
+ puts("OP"); break;
+ }
+ print_cfg(i, e->next);
+}
+
+
+/**
+ * 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 int
+spell_run(invocation_t *invocation, int allow_delete)
+{
+#ifdef DEBUG
+ fprintf(stderr, "Resuming execution: invocation of `%s'\n", invocation->spell->name);
+ print_cfg(1, invocation->current_effect);
+#endif
+ while (invocation->current_effect) {
+ effect_t *e = invocation->current_effect;
+ effect_t *next = e->next;
+ int i;
+
+#ifdef DEBUG
+ fprintf(stderr, "Next step of type %d\n", e->ty);
+ dump_env(invocation->env);
+#endif
+
+ switch (e->ty) {
+ case EFFECT_SKIP:
+ break;
+
+ case EFFECT_ABORT:
+ invocation->flags |= INVOCATION_FLAG_ABORTED;
+ invocation->end_effect = NULL;
+ case EFFECT_END:
+ clear_stack(invocation);
+ next = NULL;
+ break;
+
+ case EFFECT_ASSIGN:
+ magic_eval(invocation->env, &invocation->env->vars[e->e.e_assign.id], e->e.e_assign.expr);
+ break;
+
+ case EFFECT_FOREACH:
+ next = run_foreach(invocation, e, next);
+ break;
+
+ case EFFECT_FOR:
+ next = run_for(invocation, e, next);
+ break;
+
+ case EFFECT_IF:
+ if (magic_eval_int(invocation->env, e->e.e_if.cond))
+ next = e->e.e_if.true_branch;
+ else
+ next = e->e.e_if.false_branch;
+ break;
+
+ case EFFECT_SLEEP: {
+ int sleeptime = magic_eval_int(invocation->env, e->e.e_sleep);
+ invocation->current_effect = next;
+ if (sleeptime > 0)
+ return sleeptime;
+ break;
+ }
+
+ case EFFECT_SCRIPT: {
+ character_t *caster = (character_t *) map_id2bl(invocation->caster);
+ if (caster) {
+ env_t *env = invocation->env;
+ character_t *caster = (character_t *)map_id2bl(invocation->caster);
+ argrec_t arg[] = { {"@target", .v.i = VAR(VAR_TARGET).ty == TY_ENTITY ? 0 : VAR(VAR_TARGET).v.v_int },
+ {"@caster", .v.i = invocation->caster },
+ {"@caster_name$", .v.s = caster ? caster->status.name : "" }};
+ int message_recipient = VAR(VAR_SCRIPTTARGET).ty == TY_ENTITY
+ ? VAR(VAR_SCRIPTTARGET).v.v_int
+ : invocation->caster;
+ character_t *recipient = (character_t *) map_id2bl(message_recipient);
+
+ if (recipient->npc_id && recipient->npc_id != invocation->bl.id)
+ break; /* 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(e->e.e_script,
+ invocation->script_pos,
+ message_recipient,
+ invocation->bl.id,
+ 3, 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 -1; /* Signal `wait for script' */
+ } else
+ invocation->script_pos = 0;
+ clif_clearchar_id(invocation->bl.id, 1, caster->fd);
+ }
+ break;
+ }
+
+ case EFFECT_BREAK:
+ next = return_to_stack(invocation);
+ break;
+
+ case EFFECT_OP: {
+ op_t *op = &operations[e->e.e_op.id];
+ val_t args[MAX_ARGS];
+
+ for (i = 0; i < e->e.e_op.args_nr; i++)
+ magic_eval(invocation->env, &args[i], e->e.e_op.args[i]);
+
+ if (!magic_signature_check("effect", op->name, op->signature,
+ e->e.e_op.args_nr, args,
+ e->e.e_op.line_nr, e->e.e_op.column))
+ op->op(invocation->env, e->e.e_op.args_nr, args);
+
+ for (i = 0; i < e->e.e_op.args_nr; i++)
+ magic_clear_var(&args[i]);
+ break;
+ }
+
+ case EFFECT_CALL:
+ next = run_call(invocation, next);
+ break;
+
+ default:
+ fprintf(stderr, "[magic] INTERNAL ERROR: Unknown effect %d\n", e->ty);
+ }
+
+
+ if (!next)
+ next = return_to_stack(invocation);
+
+ invocation->current_effect = next;
+ }
+
+ if (allow_delete)
+ try_to_finish_invocation(invocation);
+ return 0;
+}
+
+
+extern void
+spell_update_location(invocation_t *invocation);
+
+void
+spell_execute_d(invocation_t *invocation, int allow_deletion)
+{
+ int delta;
+
+ spell_update_location(invocation);
+ delta = spell_run(invocation, allow_deletion);
+
+ if (delta > 0)
+ invocation->timer = add_timer(gettick() + delta,
+ &invocation_timer_callback,
+ invocation->bl.id, 0);
+
+ /* If 0, the script cleaned itself. If -1 (wait-for-script), we must wait for the user. */
+}
+
+void
+spell_execute(invocation_t *invocation)
+{
+ spell_execute_d(invocation, 1);
+}
+
+int
+spell_attack(int caster_id, int target_id)
+{
+ character_t *caster = (character_t *)map_id2bl(caster_id);
+ invocation_t *invocation = (invocation_t *)map_id2bl(caster->attack_spell_override);
+
+ if (invocation
+ && caster->attack_spell_charges > 0) {
+ magic_clear_var(&invocation->env->vars[VAR_TARGET]);
+ invocation->env->vars[VAR_TARGET].ty = TY_ENTITY;
+ invocation->env->vars[VAR_TARGET].v.v_int = 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 (!(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 */
+ free_invocation(invocation);
+ } else if (!invocation
+ || caster->attack_spell_charges <= 0) {
+ caster->attack_spell_override = 0;
+ char_set_weapon_icon(caster, 0, 0, 0);
+ char_set_attack_info(caster, 0, 0);
+
+ if (invocation)
+ free_invocation(invocation);
+ }
+
+ return 1;
+}