#include "../common/cxxstdio.hpp"
#include "magic-expr.hpp"
#include "magic-expr-eval.hpp"
#include "magic-interpreter.hpp"
#include "magic-interpreter-aux.hpp"
#include "../poison.hpp"
#define INVISIBLE_NPC 127 /* used for local spell effects */
//#define DEBUG
#ifdef 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");
}
}
#endif
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
void invocation_timer_callback(timer_id, tick_t, custom_id_t id, custom_data_t)
{
invocation_t *invocation = (invocation_t *) map_id2bl(id);
if (invocation)
{
invocation->timer = 0;
spell_execute(invocation);
}
}
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;
}
void spell_free_invocation(invocation_t *invocation)
{
if (invocation->status_change_refs)
{
free(invocation->status_change_refs);
/* The following cleanup shouldn't be necessary, but I've added it to help tracking a certain bug */
invocation->status_change_refs = NULL;
invocation->status_change_refs_nr = 0;
}
if (bool(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, BL_SPELL); // also frees the object
// free(invocation);
}
static
void char_set_weapon_icon(character_t *subject, int count,
StatusChange icon, int 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->bl, old_icon, 0);
clif_fixpcpos(subject);
if (count)
{
clif_changelook(&subject->bl, LOOK_WEAPON, look);
if (icon != StatusChange::ZERO)
clif_status_change(&subject->bl, icon, 1);
}
else
{
/* Set it to `normal' */
clif_changelook(&subject->bl, LOOK_WEAPON, subject->status.weapon);
}
}
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);
}
}
void magic_stop_completely(character_t *c)
{
// Zap all status change references to spells
for (StatusChange i : erange(StatusChange(), MAX_STATUSCHANGE))
c->sc_data[i].spell_invocation = 0;
while (c->active_spells)
spell_free_invocation(c->active_spells);
if (c->attack_spell_override)
{
invocation_t *attack_spell =
(invocation_t *) map_id2bl(c->attack_spell_override);
if (attack_spell)
spell_free_invocation(attack_spell);
c->attack_spell_override = 0;
char_set_weapon_icon(c, 0, StatusChange::ZERO, 0);
char_set_attack_info(c, 0, 0);
}
}
/* 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
spell_free_invocation(invocation);
}
}
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
void timer_callback_effect(timer_id, tick_t, custom_id_t id, custom_data_t data)
{
entity_t *target = map_id2bl(id);
if (target)
clif_misceffect(target, data);
}
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
void timer_callback_effect_npc_delete(timer_id, tick_t,
custom_id_t npc_id, custom_data_t)
{
struct npc_data *effect_npc = (struct npc_data *) map_id2bl(npc_id);
npc_free(effect_npc);
}
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, "", "?");
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 *, int, 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, val_t *args)
{
entity_t *caster = (VAR(VAR_CASTER).ty == TY_ENTITY)
? map_id2bl(VAR(VAR_CASTER).v.v_int) : NULL;
entity_t *subject = ARGENTITY(0);
if (!caster)
caster = subject;
if (caster->type == BL_PC && subject->type == BL_PC)
{
character_t *caster_pc = (character_t *) caster;
character_t *subject_pc = (character_t *) subject;
MAP_LOG_PC(caster_pc, "SPELLHEAL-INSTA PC%d FOR %d",
subject_pc->status.char_id, ARGINT(1));
}
battle_heal(caster, subject, 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 *, int, 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 *, int, 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 *, int, val_t *args)
{
character_t *subject = ARGCHAR(0);
if (subject)
clif_displaymessage(subject->fd, ARGSTR(1));
return 0;
}
static
void timer_callback_kill_npc(timer_id, tick_t, custom_id_t npc_id,
custom_data_t)
{
struct npc_data *npc = (struct npc_data *) map_id2bl(npc_id);
if (npc)
npc_free(npc);
}
static
int op_messenger_npc(env_t *, int, 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;
char *map_name;
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:
map_name = map[character->bl.m].name;
// Warp part #1: update relevant data, interrupt trading etc.:
pc_setpos(character, map_name, character->bl.x, character->bl.y, 0);
// Warp part #2: now notify the client
clif_changemap(character, map_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 *, int, val_t *args)
{
entity_t *subject = ARGENTITY(0);
DIR 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 *, int, 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 *, int, 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,
StatusChange sc_id)
{
int index = invocation->status_change_refs_nr++;
status_change_ref_t *cr;
RECREATE(invocation->status_change_refs, status_change_ref_t, invocation->status_change_refs_nr);
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, 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, StatusChange(ARGINT(1)),
ARGINT(2), ARGINT(3), ARGINT(4), ARGINT(5),
ARGINT(6), 0, invocation_id);
if (invocation && subject->type == BL_PC)
record_status_change(invocation, subject->id, StatusChange(ARGINT(1)));
return 0;
}
static
int op_stop_status_change(env_t *, int, val_t *args)
{
entity_t *subject = ARGENTITY(0);
StatusChange sc = StatusChange(ARGINT(1));
skill_status_change_end(subject, sc, -1);
return 0;
}
static
int op_override_attack(env_t *env, int, val_t *args)
{
entity_t *psubject = ARGENTITY(0);
int charges = ARGINT(1);
int attack_delay = ARGINT(2);
int attack_range = ARGINT(3);
StatusChange icon = StatusChange(ARGINT(4));
int look = ARGINT(5);
int stopattack = ARGINT(6);
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)
spell_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)
{
invocation_t *attack_spell =
(invocation_t *) map_id2bl(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(env_t *, int, val_t *args)
{
struct item item;
entity_t *entity = ARGENTITY(0);
character_t *subject;
int stackable;
int count = ARGINT(2);
if (count <= 0)
return 0;
if (entity->type == BL_PC)
subject = (character_t *) entity;
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;
}
#define AGGRAVATION_MODE_ATTACKS_CASTER(n) ((n) == 0 || (n) == 2)
#define AGGRAVATION_MODE_MAKES_AGGRESSIVE(n) ((n) > 0)
static
int op_aggravate(env_t *, int, 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
#define MONSTER_ATTITUDE_FROZEN 3
static
int op_spawn(env_t *, int, 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 = 0x80 | (mob->mode & 1);
break;
case MONSTER_ATTITUDE_HOSTILE:
mob->mode = 0x84 | (mob->mode & 1);
if (owner)
{
mob->target_id = owner->bl.id;
mob->attacked_id = owner->bl.id;
}
break;
case MONSTER_ATTITUDE_FROZEN:
mob->mode = 0;
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
const char *get_invocation_name(env_t *env)
{
invocation_t *invocation;
if (VAR(VAR_INVOCATION).ty != TY_INVOCATION)
return "?";
invocation = (invocation_t *) map_id2bl(VAR(VAR_INVOCATION).v.v_int);
if (invocation)
return invocation->spell->name;
else
return "??";
}
static
int op_injure(env_t *env, int, 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 && !((character_t *) target)->special_state.killable && (caster->type != BL_PC || !((character_t *) caster)->special_state.killer))
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(), 0, 0, damage_caused, 0, 0, 0);
if (caster->type == BL_PC)
{
character_t *caster_pc = (character_t *) caster;
if (target->type == BL_MOB)
{
struct mob_data *mob = (struct mob_data *) target;
MAP_LOG_PC(caster_pc, "SPELLDMG MOB%d %d FOR %d BY %s",
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(env_t *, int, 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 *, int, 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
int op_set_hair_colour(env_t *, int, val_t *args)
{
character_t *c = (ETY(0) == BL_PC) ? ARGPC(0) : NULL;
if (!c)
return 1;
pc_changelook(c, LOOK_HAIR_COLOR, ARGINT(1));
return 0;
}
static
int op_set_hair_style(env_t *, int, val_t *args)
{
character_t *c = (ETY(0) == BL_PC) ? ARGPC(0) : NULL;
if (!c)
return 1;
pc_changelook(c, LOOK_HAIR, ARGINT(1));
return 0;
}
static
int op_drop_item_for (env_t *, int args_nr, val_t *args)
{
struct item item;
int stackable;
location_t *loc = &ARGLOCATION(0);
int count = ARGINT(2);
int time = ARGINT(3);
character_t *c = ((args_nr > 4) && (ETY(4) == BL_PC)) ? ARGPC(4) : NULL;
int delay = (args_nr > 5) ? ARGINT(5) : 0;
int delaytime[3] = { delay, delay, delay };
character_t *owners[3] = { c, NULL, NULL };
GET_ARG_ITEM(1, item, stackable);
if (stackable)
map_addflooritem_any(&item, count, loc->m, loc->x, loc->y,
owners, delaytime, time, 0);
else
while (count-- > 0)
map_addflooritem_any(&item, 1, loc->m, loc->x, loc->y,
owners, delaytime, time, 0);
return 0;
}
static
int op_gain_exp(env_t *, int, val_t *args)
{
character_t *c = (ETY(0) == BL_PC) ? ARGPC(0) : NULL;
if (!c)
return 1;
pc_gainexp_reason(c, ARGINT(1), ARGINT(2),
PC_GAINEXP_REASON(ARGINT(3)));
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},
{"stop_status_change", "ei", op_stop_status_change},
{"override_attack", "eiiiiii", 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},
{"set_hair_colour", "ei", op_set_hair_colour},
{"set_hair_style", "ei", op_set_hair_style},
{"drop_item", "l.ii", op_drop_item_for},
{"drop_item_for", "l.iiei", op_drop_item_for},
{"gain_experience", "eiii", op_gain_exp},
{NULL, NULL, NULL}
};
static
int operations_sorted = 0;
static
int operation_count;
static
int compare_operations(const void *lhs, const void *rhs)
{
return strcmp(((const op_t *) lhs)->name, ((const 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 = (op_t *)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,
StatusChange sc_id, int)
{
int i;
int index = -1;
invocation_t *invocation = (invocation_t *) map_id2bl(invocation_id);
if (!invocation || invocation->bl.type != BL_SPELL)
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)
{
entity_t *entity = map_id2bl(bl_id);
if (entity->type == BL_PC)
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 = ar->c.c_foreach.ty;
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,
CONT_STACK 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
void find_entities_in_area_c(entity_t *target,
int *entities_allocd_p,
int *entities_nr_p,
int **entities_p,
FOREACH_FILTER filter)
{
/* The following macro adds an entity to the result list: */
#define ADD_ENTITY(e) \
if (*entities_nr_p == *entities_allocd_p) { \
/* Need more space */ \
(*entities_allocd_p) += 32; \
RECREATE(*entities_p, int, *entities_allocd_p); \
} \
(*entities_p)[(*entities_nr_p)++] = e;
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 if (filter == FOREACH_FILTER_SPELL)
{ /* Check all spells bound to the caster */
invocation_t *invoc = ((character_t *) target)->active_spells;
/* Add all spells locked onto thie PC */
while (invoc)
{
ADD_ENTITY(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)
{
invocation_t *invocation = (invocation_t *) target;
/* 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;
}
ADD_ENTITY(target->id);
#undef ADD_ENTITY
}
static
void find_entities_in_area(area_t *area, int *entities_allocd_p,
int *entities_nr_p, int **entities_p, FOREACH_FILTER 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(std::bind(find_entities_in_area_c, ph::_1, entities_allocd_p, entities_nr_p, entities_p, filter),
m, x, y, x + width, y + height,
BL_NUL /* filter elsewhere */);
}
}
}
static
effect_t *run_foreach(invocation_t *invocation, effect_t *foreach,
effect_t *return_location)
{
val_t area;
FOREACH_FILTER 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;
int *entities;
int *shuffle_board;
int entities_nr = 0;
int i;
if (!ar)
return return_location;
CREATE(entities_collect, int, entities_allocd);
find_entities_in_area(area.v.v_area, &entities_allocd, &entities_nr,
&entities_collect, filter);
/* Now shuffle */
CREATE(shuffle_board, int, entities_nr);
CREATE(entities, int, entities_nr);
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;
ar->c.c_foreach.ty =
(filter == FOREACH_FILTER_SPELL) ? TY_INVOCATION : TY_ENTITY;
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;
CREATE(old_actuals, 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;
}
#ifdef DEBUG
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);
}
#endif
/**
* 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)
{
const int invocation_id = invocation->bl.id;
#define REFRESH_INVOCATION invocation = (invocation_t *) map_id2bl(invocation_id); if (!invocation) return 0;
#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;
FALLTHROUGH;
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;
argrec_t arg[] = { {"@target",
VAR(VAR_TARGET).ty ==
TY_ENTITY ? 0 : VAR(VAR_TARGET).
v.v_int}
,
{"@caster", invocation->caster}
,
{"@caster_name$", 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);
}
REFRESH_INVOCATION; // Script may have killed the caster
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]);
REFRESH_INVOCATION; // Effect may have killed the caster
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;
#undef REFRESH_INVOCATION
}
static
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)
{
if (invocation->timer)
{
FPRINTF(stderr,
"[magic] FATAL ERROR: Trying to add multiple timers to the same spell! Already had timer: %d\n",
invocation->timer);
/* *((int *)0x0) = 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);
}
void spell_execute_script(invocation_t *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(int caster_id, int target_id)
{
character_t *caster = (character_t *) map_id2bl(caster_id);
invocation_t *invocation;
int stop_attack = 0;
if (!caster)
return 0;
invocation = (invocation_t *) map_id2bl(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->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 the caster died, we need to refresh here:
invocation =
(invocation_t *) map_id2bl(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 = 0;
char_set_weapon_icon(caster, 0, StatusChange::ZERO, 0);
char_set_attack_info(caster, 0, 0);
if (stop_attack)
pc_stopattack(caster);
if (invocation)
spell_free_invocation(invocation);
}
return 1;
}