#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 #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 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 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 *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) { // display damage first, because dealing damage may deallocate the target. clif_damage(caster, target, gettick(), 0, 0, damage_caused, 0, 0, 0); battle_damage(caster, target, damage_caused, mp_damage); } 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 int op_set_hair_colour(env_t *env, int args_nr, 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 *env, int args_nr, 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 *env, 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 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 }, { "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 }, { 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 = 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, 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); /* 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; \ *entities_p = realloc(*entities_p, sizeof(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 0; case BL_MOB: if (filter == FOREACH_FILTER_MOB || filter == FOREACH_FILTER_ENTITY || filter == FOREACH_FILTER_TARGET) break; else return 0; 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 (invocation->flags & INVOCATION_FLAG_BOUND) return 0; else break; /* Add the spell */ } else return 0; case BL_NPC: if (filter == FOREACH_FILTER_NPC) break; else return 0; default: return 0; } ADD_ENTITY(target->id); #undef ADD_ENTITY 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; 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 = 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; } #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; 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); } 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 } 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 the caster died, we need to refresh here: invocation = (invocation_t *)map_id2bl(caster->attack_spell_override); if (invocation && !(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; }