diff options
Diffstat (limited to 'src/map/magic-stmt.c')
-rw-r--r-- | src/map/magic-stmt.c | 1323 |
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; +} |