#include "magic-interpreter-base.hpp" #include "magic-interpreter-aux.hpp" #include "magic-interpreter.hpp" #include "../strings/fstring.hpp" #include "../strings/xstring.hpp" #include "../io/cxxstdio.hpp" #include "../common/timer.hpp" #include "magic-expr.hpp" #include "pc.hpp" #include "../poison.hpp" static void set_int_p(val_t *v, int i, TYPE t) { v->ty = t; v->v.v_int = i; } #warning "This code should die" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-macros" #define set_int(v, i) set_int_p(v, i, TYPE::INT) #define set_dir(v, i) set_int_p(v, i, TYPE::DIR) #define SETTER(tty, dyn_ty, field) (val_t *v, tty x) { v->ty = dyn_ty; v->v.field = x; } static void set_string SETTER(dumb_string, TYPE::STRING, v_string) static void set_entity(val_t *v, dumb_ptr e) { v->ty = TYPE::ENTITY; v->v.v_int = e->bl_id; } static void set_invocation(val_t *v, dumb_ptr i) { v->ty = TYPE::INVOCATION; v->v.v_int = i->bl_id; } static void set_spell SETTER(dumb_ptr, TYPE::SPELL, v_spell) #define setenv(f, v, x) f(&(env->varu[v]), x) #define set_env_int(v, x) setenv(set_int, v, x) #define set_env_dir(v, x) setenv(set_dir, v, x) #define set_env_string(v, x) setenv(set_string, v, x) #define set_env_entity(v, x) setenv(set_entity, v, x) #define set_env_location(v, x) setenv(set_location, v, x) #define set_env_area(v, x) setenv(set_area, v, x) #define set_env_invocation(v, x) setenv(set_invocation, v, x) #define set_env_spell(v, x) setenv(set_spell, v, x) #pragma GCC diagnostic pop magic_conf_t magic_conf; /* Global magic conf */ env_t magic_default_env = { &magic_conf, NULL }; FString magic_find_invocation(XString spellname) { auto it = magic_conf.spells_by_name.find(spellname); if (it != magic_conf.spells_by_name.end()) return it->second->invocation; return FString(); } dumb_ptr magic_find_spell(XString invocation) { auto it = magic_conf.spells_by_invocation.find(invocation); if (it != magic_conf.spells_by_invocation.end()) return it->second; return NULL; } /* -------------------------------------------------------------------------------- */ /* Spell anchors */ /* -------------------------------------------------------------------------------- */ FString magic_find_anchor_invocation(XString anchor_name) { auto it = magic_conf.anchors_by_name.find(anchor_name); if (it != magic_conf.anchors_by_name.end()) return it->second->invocation; return FString(); } dumb_ptr magic_find_anchor(XString name) { auto it = magic_conf.anchors_by_invocation.find(name); if (it != magic_conf.anchors_by_invocation.end()) return it->second; return NULL; } /* -------------------------------------------------------------------------------- */ /* Spell guard checks */ /* -------------------------------------------------------------------------------- */ static dumb_ptr alloc_env(magic_conf_t *conf) { auto env = dumb_ptr::make(); env->varu = make_unique(conf->varv.size()); env->base_env = conf; return env; } static dumb_ptr clone_env(dumb_ptr src) { dumb_ptr retval = alloc_env(src->base_env); for (int i = 0; i < src->base_env->varv.size(); i++) magic_copy_var(&retval->varu[i], &src->varu[i]); return retval; } void magic_free_env(dumb_ptr env) { for (int i = 0; i < env->base_env->varv.size(); i++) magic_clear_var(&env->varu[i]); // handled by std::unique_ptr now. Was a memory leak before. // delete[] env->vars; env.delete_(); } dumb_ptr spell_create_env(magic_conf_t *conf, dumb_ptr spell, dumb_ptr caster, int spellpower, XString param) { dumb_ptr env = alloc_env(conf); switch (spell->spellarg_ty) { case SPELLARG::STRING: set_env_string(spell->arg, dumb_string::copys(param)); break; case SPELLARG::PC: { CharName name = stringish(param); dumb_ptr subject = map_nick2sd(name); if (!subject) subject = caster; set_env_entity(spell->arg, subject); break; } case SPELLARG::NONE: break; default: FPRINTF(stderr, "Unexpected spellarg type %d\n", spell->spellarg_ty); } set_env_entity(VAR_CASTER, caster); set_env_int(VAR_SPELLPOWER, spellpower); set_env_spell(VAR_SPELL, spell); return env; } static void free_components(dumb_ptr *component_holder) { if (*component_holder == NULL) return; free_components(&(*component_holder)->next); (*component_holder).delete_(); *component_holder = NULL; } void magic_add_component(dumb_ptr *component_holder, int id, int count) { if (count <= 0) return; if (*component_holder == NULL) { auto component = dumb_ptr::make(); component->next = NULL; component->item_id = id; component->count = count; *component_holder = component; } else { dumb_ptr component = *component_holder; if (component->item_id == id) { component->count += count; return; } else magic_add_component(&component->next, id, count); /* Tail-recurse; gcc can optimise this. Not that it matters. */ } } static void copy_components(dumb_ptr *component_holder, dumb_ptr component) { if (component == NULL) return; magic_add_component(component_holder, component->item_id, component->count); copy_components(component_holder, component->next); } typedef struct spellguard_check { dumb_ptr catalysts, components; int mana; interval_t casttime; } spellguard_check_t; static int check_prerequisites(dumb_ptr caster, dumb_ptr component) { while (component) { if (pc_count_all_items(caster, component->item_id) < component->count) return 0; /* insufficient */ component = component->next; } return 1; } static void consume_components(dumb_ptr caster, dumb_ptr component) { while (component) { pc_remove_items(caster, component->item_id, component->count); component = component->next; } } static int spellguard_can_satisfy(spellguard_check_t *check, dumb_ptr caster, dumb_ptr env, int *near_miss) { tick_t tick = gettick(); int retval = check_prerequisites(caster, check->catalysts); if (retval && near_miss) *near_miss = 1; // close enough! retval = retval && caster->cast_tick <= tick /* Hasn't cast a spell too recently */ && check->mana <= caster->status.sp && check_prerequisites(caster, check->components); if (retval) { interval_t casttime = check->casttime; if (env->VAR(VAR_MIN_CASTTIME).ty == TYPE::INT) casttime = max(casttime, static_cast(env->VAR(VAR_MIN_CASTTIME).v.v_int)); caster->cast_tick = tick + casttime; /* Make sure not to cast too frequently */ consume_components(caster, check->components); pc_heal(caster, 0, -check->mana); } return retval; } static effect_set_t *spellguard_check_sub(spellguard_check_t *check, dumb_ptr guard, dumb_ptr caster, dumb_ptr env, int *near_miss) { if (guard == NULL) return NULL; switch (guard->ty) { case SPELLGUARD::CONDITION: if (!magic_eval_int(env, guard->s.s_condition)) return NULL; break; case SPELLGUARD::COMPONENTS: copy_components(&check->components, guard->s.s_components); break; case SPELLGUARD::CATALYSTS: copy_components(&check->catalysts, guard->s.s_catalysts); break; case SPELLGUARD::CHOICE: { spellguard_check_t altcheck = *check; effect_set_t *retval; altcheck.components = NULL; altcheck.catalysts = NULL; copy_components(&altcheck.catalysts, check->catalysts); copy_components(&altcheck.components, check->components); retval = spellguard_check_sub(&altcheck, guard->next, caster, env, near_miss); free_components(&altcheck.catalysts); free_components(&altcheck.components); if (retval) return retval; else return spellguard_check_sub(check, guard->s.s_alt, caster, env, near_miss); } case SPELLGUARD::MANA: check->mana += magic_eval_int(env, guard->s.s_mana); break; case SPELLGUARD::CASTTIME: check->casttime += static_cast(magic_eval_int(env, guard->s.s_mana)); break; case SPELLGUARD::EFFECT: if (spellguard_can_satisfy(check, caster, env, near_miss)) return &guard->s.s_effect; else return NULL; default: FPRINTF(stderr, "Unexpected spellguard type %d\n", guard->ty); return NULL; } return spellguard_check_sub(check, guard->next, caster, env, near_miss); } static effect_set_t *check_spellguard(dumb_ptr guard, dumb_ptr caster, dumb_ptr env, int *near_miss) { spellguard_check_t check; effect_set_t *retval; check.catalysts = NULL; check.components = NULL; check.mana = 0; check.casttime = interval_t::zero(); retval = spellguard_check_sub(&check, guard, caster, env, near_miss); free_components(&check.catalysts); free_components(&check.components); return retval; } /* -------------------------------------------------------------------------------- */ /* Public API */ /* -------------------------------------------------------------------------------- */ effect_set_t *spell_trigger(dumb_ptr spell, dumb_ptr caster, dumb_ptr env, int *near_miss) { dumb_ptr guard = spell->spellguard; if (near_miss) *near_miss = 0; for (letdef_t& ld : spell->letdefv) magic_eval(env, &env->varu[ld.id], ld.expr); return check_spellguard(guard, caster, env, near_miss); } static void spell_set_location(dumb_ptr invocation, dumb_ptr entity) { magic_clear_var(&invocation->env->varu[VAR_LOCATION]); invocation->env->varu[VAR_LOCATION].ty = TYPE::LOCATION; invocation->env->varu[VAR_LOCATION].v.v_location.m = entity->bl_m; invocation->env->varu[VAR_LOCATION].v.v_location.x = entity->bl_x; invocation->env->varu[VAR_LOCATION].v.v_location.y = entity->bl_y; } void spell_update_location(dumb_ptr invocation) { if (bool(invocation->spell->flags & SPELL_FLAG::LOCAL)) return; else { dumb_ptr owner_bl = map_id2bl(invocation->subject); if (!owner_bl) return; dumb_ptr owner = owner_bl->is_player(); spell_set_location(invocation, owner); } } dumb_ptr spell_instantiate(effect_set_t *effect_set, dumb_ptr env) { dumb_ptr retval; retval.new_(); dumb_ptr caster; retval->env = env; retval->caster = env->VAR(VAR_CASTER).v.v_int; retval->spell = env->VAR(VAR_SPELL).v.v_spell; retval->stack_size = 0; retval->current_effect = effect_set->effect; retval->trigger_effect = effect_set->at_trigger; retval->end_effect = effect_set->at_end; caster = map_id2bl(retval->caster); // must still exist retval->bl_id = map_addobject(retval); retval->bl_type = BL::SPELL; retval->bl_m = caster->bl_m; retval->bl_x = caster->bl_x; retval->bl_y = caster->bl_y; map_addblock(retval); set_env_invocation(VAR_INVOCATION, retval); return retval; } dumb_ptr spell_clone_effect(dumb_ptr base) { dumb_ptr retval; retval.new_(); // block_list in general is not copyable // since this is the only call site, it is expanded here //*retval = *base; retval->next_invocation = NULL; retval->flags = INVOCATION_FLAG::ZERO; dumb_ptr env = retval->env = clone_env(base->env); retval->spell = base->spell; retval->caster = base->caster; retval->subject = 0; // retval->timer = 0; retval->stack_size = 0; // retval->stack = undef; retval->script_pos = 0; // huh? retval->current_effect = base->trigger_effect; retval->trigger_effect = base->trigger_effect; retval->end_effect = NULL; // retval->status_change_refs = NULL; retval->bl_id = 0; retval->bl_prev = NULL; retval->bl_next = NULL; retval->bl_m = base->bl_m; retval->bl_x = base->bl_x; retval->bl_y = base->bl_y; retval->bl_type = base->bl_type; retval->bl_id = map_addobject(retval); set_env_invocation(VAR_INVOCATION, retval); return retval; } void spell_bind(dumb_ptr subject, dumb_ptr invocation) { /* Only bind nonlocal spells */ if (!bool(invocation->spell->flags & SPELL_FLAG::LOCAL)) { if (bool(invocation->flags & INVOCATION_FLAG::BOUND) || invocation->subject || invocation->next_invocation) { int *i = NULL; FPRINTF(stderr, "[magic] INTERNAL ERROR: Attempt to re-bind spell invocation `%s'\n", invocation->spell->name); *i = 1; return; } invocation->next_invocation = subject->active_spells; subject->active_spells = invocation; invocation->flags |= INVOCATION_FLAG::BOUND; invocation->subject = subject->bl_id; } spell_set_location(invocation, subject); } int spell_unbind(dumb_ptr subject, dumb_ptr invocation_) { dumb_ptr *seeker = &subject->active_spells; while (*seeker) { if (*seeker == invocation_) { *seeker = invocation_->next_invocation; invocation_->flags &= ~INVOCATION_FLAG::BOUND; invocation_->next_invocation = NULL; invocation_->subject = 0; return 0; } seeker = &((*seeker)->next_invocation); } return 1; }