#include "magic-expr-eval.hpp" #include "magic-expr.hpp" #include "magic-interpreter-aux.hpp" #include <cassert> #include <cmath> #include "../strings/mstring.hpp" #include "../strings/fstring.hpp" #include "../strings/zstring.hpp" #include "../strings/vstring.hpp" #include "../common/cxxstdio.hpp" #include "../common/random.hpp" #include "battle.hpp" #include "npc.hpp" #include "pc.hpp" #include "itemdb.hpp" #include "../poison.hpp" static void free_area(dumb_ptr<area_t> area) { if (!area) return; switch (area->ty) { case AREA::UNION: free_area(area->a.a_union[0]); free_area(area->a.a_union[1]); break; default: break; } area.delete_(); } static dumb_ptr<area_t> dup_area(dumb_ptr<area_t> area) { dumb_ptr<area_t> retval = dumb_ptr<area_t>::make(); *retval = *area; switch (area->ty) { case AREA::UNION: retval->a.a_union[0] = dup_area(retval->a.a_union[0]); retval->a.a_union[1] = dup_area(retval->a.a_union[1]); break; default: break; } return retval; } void magic_copy_var(val_t *dest, val_t *src) { *dest = *src; switch (dest->ty) { case TYPE::STRING: dest->v.v_string = dest->v.v_string.dup(); break; case TYPE::AREA: dest->v.v_area = dup_area(dest->v.v_area); break; default: break; } } void magic_clear_var(val_t *v) { switch (v->ty) { case TYPE::STRING: v->v.v_string.delete_(); break; case TYPE::AREA: free_area(v->v.v_area); break; default: break; } } static FString show_entity(dumb_ptr<block_list> entity) { switch (entity->bl_type) { case BL::PC: return entity->is_player()->status.name.to__actual(); case BL::NPC: return entity->is_npc()->name; case BL::MOB: return entity->is_mob()->name; case BL::ITEM: assert (0 && "There is no way this code did what it was supposed to do!"); /* Sorry about this one... */ // WTF? item_data is a struct item, not a struct item_data // return ((struct item_data *) (&entity->is_item()->item_data))->name; abort(); case BL::SPELL: return {"%invocation(ERROR:this-should-not-be-an-entity)"}; default: return {"%unknown-entity"}; } } static void stringify(val_t *v, int within_op) { static earray<ZString, DIR, DIR::COUNT> dirs //= {{ {"south"}, {"south-west"}, {"west"}, {"north-west"}, {"north"}, {"north-east"}, {"east"}, {"south-east"}, }}; FString buf; switch (v->ty) { case TYPE::UNDEF: buf = "UNDEF"; break; case TYPE::INT: buf = STRPRINTF("%i", v->v.v_int); break; case TYPE::STRING: return; case TYPE::DIR: buf = dirs[v->v.v_dir]; break; case TYPE::ENTITY: buf = show_entity(v->v.v_entity); break; case TYPE::LOCATION: buf = STRPRINTF("<\"%s\", %d, %d>", v->v.v_location.m->name_, v->v.v_location.x, v->v.v_location.y); break; case TYPE::AREA: buf = "%area"; free_area(v->v.v_area); break; case TYPE::SPELL: buf = v->v.v_spell->name; break; case TYPE::INVOCATION: { dumb_ptr<invocation> invocation_ = within_op ? v->v.v_invocation : map_id2bl(v->v.v_int)->is_spell(); buf = invocation_->spell->name; } break; default: FPRINTF(stderr, "[magic] INTERNAL ERROR: Cannot stringify %d\n", v->ty); return; } v->v.v_string = dumb_string::copys(buf); v->ty = TYPE::STRING; } static void intify(val_t *v) { if (v->ty == TYPE::INT) return; magic_clear_var(v); v->ty = TYPE::INT; v->v.v_int = 1; } static dumb_ptr<area_t> area_new(AREA ty) { auto retval = dumb_ptr<area_t>::make(); retval->ty = ty; return retval; } static dumb_ptr<area_t> area_union(dumb_ptr<area_t> area, dumb_ptr<area_t> other_area) { dumb_ptr<area_t> retval = area_new(AREA::UNION); retval->a.a_union[0] = area; retval->a.a_union[1] = other_area; retval->size = area->size + other_area->size; /* Assume no overlap */ return retval; } /** * Turns location into area, leaves other types untouched */ static void make_area(val_t *v) { if (v->ty == TYPE::LOCATION) { auto a = dumb_ptr<area_t>::make(); v->ty = TYPE::AREA; a->ty = AREA::LOCATION; a->a.a_loc = v->v.v_location; v->v.v_area = a; } } static void make_location(val_t *v) { if (v->ty == TYPE::AREA && v->v.v_area->ty == AREA::LOCATION) { location_t location = v->v.v_area->a.a_loc; free_area(v->v.v_area); v->ty = TYPE::LOCATION; v->v.v_location = location; } } static void make_spell(val_t *v) { if (v->ty == TYPE::INVOCATION) { dumb_ptr<invocation> invoc = v->v.v_invocation; //invoc = (dumb_ptr<invocation>) map_id2bl(v->v.v_int); if (!invoc) v->ty = TYPE::FAIL; else { v->ty = TYPE::SPELL; v->v.v_spell = invoc->spell; } } } static int fun_add(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ARG_TYPE(0) == TYPE::INT && ARG_TYPE(1) == TYPE::INT) { /* Integer addition */ RESULTINT = ARGINT(0) + ARGINT(1); result->ty = TYPE::INT; } else if (ARG_MAY_BE_AREA(0) && ARG_MAY_BE_AREA(1)) { /* Area union */ make_area(&args[0]); make_area(&args[1]); RESULTAREA = area_union(ARGAREA(0), ARGAREA(1)); ARGAREA(0) = NULL; ARGAREA(1) = NULL; result->ty = TYPE::AREA; } else { /* Anything else -> string concatenation */ stringify(&args[0], 1); stringify(&args[1], 1); /* Yes, we could speed this up. */ // ugh MString m; m += ARGSTR(0); m += ARGSTR(1); RESULTSTR = dumb_string::copys(FString(m)); result->ty = TYPE::STRING; } return 0; } static int fun_sub(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ARGINT(0) - ARGINT(1); return 0; } static int fun_mul(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ARGINT(0) * ARGINT(1); return 0; } static int fun_div(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (!ARGINT(1)) return 1; /* division by zero */ RESULTINT = ARGINT(0) / ARGINT(1); return 0; } static int fun_mod(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (!ARGINT(1)) return 1; /* division by zero */ RESULTINT = ARGINT(0) % ARGINT(1); return 0; } static int fun_or(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ARGINT(0) || ARGINT(1); return 0; } static int fun_and(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ARGINT(0) && ARGINT(1); return 0; } static int fun_not(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = !ARGINT(0); return 0; } static int fun_neg(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ~ARGINT(0); return 0; } static int fun_gte(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ARG_TYPE(0) == TYPE::STRING || ARG_TYPE(1) == TYPE::STRING) { stringify(&args[0], 1); stringify(&args[1], 1); RESULTINT = ARGSTR(0) >= ARGSTR(1); } else { intify(&args[0]); intify(&args[1]); RESULTINT = ARGINT(0) >= ARGINT(1); } return 0; } static int fun_gt(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ARG_TYPE(0) == TYPE::STRING || ARG_TYPE(1) == TYPE::STRING) { stringify(&args[0], 1); stringify(&args[1], 1); RESULTINT = ARGSTR(0) > ARGSTR(1); } else { intify(&args[0]); intify(&args[1]); RESULTINT = ARGINT(0) > ARGINT(1); } return 0; } static int fun_eq(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ARG_TYPE(0) == TYPE::STRING || ARG_TYPE(1) == TYPE::STRING) { stringify(&args[0], 1); stringify(&args[1], 1); RESULTINT = ARGSTR(0) == ARGSTR(1); } else if (ARG_TYPE(0) == TYPE::DIR && ARG_TYPE(1) == TYPE::DIR) RESULTINT = ARGDIR(0) == ARGDIR(1); else if (ARG_TYPE(0) == TYPE::ENTITY && ARG_TYPE(1) == TYPE::ENTITY) RESULTINT = ARGENTITY(0) == ARGENTITY(1); else if (ARG_TYPE(0) == TYPE::LOCATION && ARG_TYPE(1) == TYPE::LOCATION) RESULTINT = (ARGLOCATION(0).x == ARGLOCATION(1).x && ARGLOCATION(0).y == ARGLOCATION(1).y && ARGLOCATION(0).m == ARGLOCATION(1).m); else if (ARG_TYPE(0) == TYPE::AREA && ARG_TYPE(1) == TYPE::AREA) RESULTINT = ARGAREA(0) == ARGAREA(1); /* Probably not that great an idea... */ else if (ARG_TYPE(0) == TYPE::SPELL && ARG_TYPE(1) == TYPE::SPELL) RESULTINT = ARGSPELL(0) == ARGSPELL(1); else if (ARG_TYPE(0) == TYPE::INVOCATION && ARG_TYPE(1) == TYPE::INVOCATION) RESULTINT = ARGINVOCATION(0) == ARGINVOCATION(1); else { intify(&args[0]); intify(&args[1]); RESULTINT = ARGINT(0) == ARGINT(1); } return 0; } static int fun_bitand(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ARGINT(0) & ARGINT(1); return 0; } static int fun_bitor(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ARGINT(0) | ARGINT(1); return 0; } static int fun_bitxor(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ARGINT(0) ^ ARGINT(1); return 0; } static int fun_bitshl(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ARGINT(0) << ARGINT(1); return 0; } static int fun_bitshr(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ARGINT(0) >> ARGINT(1); return 0; } static int fun_max(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = max(ARGINT(0), ARGINT(1)); return 0; } static int fun_min(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = min(ARGINT(0), ARGINT(1)); return 0; } static int fun_if_then_else(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ARGINT(0)) magic_copy_var(result, &args[1]); else magic_copy_var(result, &args[2]); return 0; } void magic_area_rect(map_local **m, int *x, int *y, int *width, int *height, area_t& area_) { area_t *area = &area_; // diff hack switch (area->ty) { case AREA::UNION: break; case AREA::LOCATION: *m = area->a.a_loc.m; *x = area->a.a_loc.x; *y = area->a.a_loc.y; *width = 1; *height = 1; break; case AREA::RECT: *m = area->a.a_rect.loc.m; *x = area->a.a_rect.loc.x; *y = area->a.a_rect.loc.y; *width = area->a.a_rect.width; *height = area->a.a_rect.height; break; case AREA::BAR: { int tx = area->a.a_bar.loc.x; int ty = area->a.a_bar.loc.y; int twidth = area->a.a_bar.width; int tdepth = area->a.a_bar.width; *m = area->a.a_bar.loc.m; switch (area->a.a_bar.dir) { case DIR::S: *x = tx - twidth; *y = ty; *width = twidth * 2 + 1; *height = tdepth; break; case DIR::W: *x = tx - tdepth; *y = ty - twidth; *width = tdepth; *height = twidth * 2 + 1; break; case DIR::N: *x = tx - twidth; *y = ty - tdepth; *width = twidth * 2 + 1; *height = tdepth; break; case DIR::E: *x = tx; *y = ty - twidth; *width = tdepth; *height = twidth * 2 + 1; break; default: FPRINTF(stderr, "Error: Trying to compute area of NE/SE/NW/SW-facing bar"); *x = tx; *y = ty; *width = *height = 1; } break; } } } int magic_location_in_area(map_local *m, int x, int y, dumb_ptr<area_t> area) { switch (area->ty) { case AREA::UNION: return magic_location_in_area(m, x, y, area->a.a_union[0]) || magic_location_in_area(m, x, y, area->a.a_union[1]); case AREA::LOCATION: case AREA::RECT: case AREA::BAR: { map_local *am; int ax, ay, awidth, aheight; magic_area_rect(&am, &ax, &ay, &awidth, &aheight, *area); return (am == m && (x >= ax) && (y >= ay) && (x < ax + awidth) && (y < ay + aheight)); } default: FPRINTF(stderr, "INTERNAL ERROR: Invalid area\n"); return 0; } } static int fun_is_in(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = magic_location_in_area(ARGLOCATION(0).m, ARGLOCATION(0).x, ARGLOCATION(0).y, ARGAREA(1)); return 0; } static int fun_skill(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ENTITY_TYPE(0) != BL::PC // don't convert to enum until after the range check || ARGINT(1) < 0 || ARGINT(1) >= uint16_t(MAX_SKILL)) { RESULTINT = 0; } else { SkillID id = static_cast<SkillID>(ARGINT(1)); RESULTINT = ARGPC(0)->status.skill[id].lv; } return 0; } static int fun_his_shroud(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = (ENTITY_TYPE(0) == BL::PC && ARGPC(0)->state.shroud_active); return 0; } #define BATTLE_GETTER(name) \ static \ int fun_get_##name(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) \ { \ RESULTINT = battle_get_##name(ARGENTITY(0)); \ return 0; \ } BATTLE_GETTER(str) BATTLE_GETTER(agi) BATTLE_GETTER(vit) BATTLE_GETTER(dex) BATTLE_GETTER(luk) BATTLE_GETTER(int) BATTLE_GETTER(lv) BATTLE_GETTER(hp) BATTLE_GETTER(mdef) BATTLE_GETTER(def) BATTLE_GETTER(max_hp) static int fun_get_dir(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTDIR = battle_get_dir(ARGENTITY(0)); return 0; } #define MMO_GETTER(name) \ static \ int fun_get_##name(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) \ { \ if (ENTITY_TYPE(0) == BL::PC) \ RESULTINT = ARGPC(0)->status.name; \ else \ RESULTINT = 0; \ return 0; \ } MMO_GETTER(sp) MMO_GETTER(max_sp) static int fun_name_of(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ARG_TYPE(0) == TYPE::ENTITY) { RESULTSTR = dumb_string::copys(show_entity(ARGENTITY(0))); return 0; } else if (ARG_TYPE(0) == TYPE::SPELL) { RESULTSTR = dumb_string::copys(ARGSPELL(0)->name); return 0; } else if (ARG_TYPE(0) == TYPE::INVOCATION) { RESULTSTR = dumb_string::copys(ARGINVOCATION(0)->spell->name); return 0; } return 1; } /* [Freeyorp] I'm putting this one in as name_of seems to have issues with summoned or spawned mobs. */ static int fun_mob_id(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ENTITY_TYPE(0) != BL::MOB) return 1; RESULTINT = ARGMOB(0)->mob_class; return 0; } inline void COPY_LOCATION(block_list& dest, location_t& src) { dest.bl_x = src.x; dest.bl_y = src.y; dest.bl_m = src.m; } inline void COPY_LOCATION(location_t& dest, block_list& src) { dest.x = src.bl_x; dest.y = src.bl_y; dest.m = src.bl_m; } static int fun_location(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { COPY_LOCATION(RESULTLOCATION, *(ARGENTITY(0))); return 0; } static int fun_random(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { int delta = ARGINT(0); if (delta < 0) delta = -delta; if (delta == 0) { RESULTINT = 0; return 0; } RESULTINT = random_::to(delta); if (ARGINT(0) < 0) RESULTINT = -RESULTINT; return 0; } static int fun_random_dir(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ARGINT(0)) RESULTDIR = random_::choice({DIR::S, DIR::SW, DIR::W, DIR::NW, DIR::N, DIR::NE, DIR::E, DIR::SE}); else RESULTDIR = random_::choice({DIR::S, DIR::W, DIR::N, DIR::E}); return 0; } static int fun_hash_entity(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ARGENTITY(0)->bl_id; return 0; } int // ret -1: not a string, ret 1: no such item, ret 0: OK magic_find_item(const_array<val_t> args, int index, struct item *item_, int *stackable) { struct item_data *item_data; int must_add_sequentially; if (ARG_TYPE(index) == TYPE::INT) item_data = itemdb_exists(ARGINT(index)); else if (ARG_TYPE(index) == TYPE::STRING) item_data = itemdb_searchname(stringish<ItemName>(ARGSTR(index))); else return -1; if (!item_data) return 1; // Very elegant. must_add_sequentially = ( item_data->type == ItemType::WEAPON || item_data->type == ItemType::ARMOR || item_data->type == ItemType::_7 || item_data->type == ItemType::_8); if (stackable) *stackable = !must_add_sequentially; *item_ = item(); item_->nameid = item_data->nameid; item_->identify = 1; return 0; } static int fun_count_item(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { dumb_ptr<map_session_data> chr = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : NULL; int stackable; struct item item; GET_ARG_ITEM(1, item, stackable); if (!chr) return 1; RESULTINT = pc_count_all_items(chr, item.nameid); return 0; } static int fun_is_equipped(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { dumb_ptr<map_session_data> chr = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : NULL; int stackable; struct item item; bool retval = false; GET_ARG_ITEM(1, item, stackable); if (!chr) return 1; for (EQUIP i : EQUIPs) if (chr->equip_index[i] >= 0 && chr->status.inventory[chr->equip_index[i]].nameid == item.nameid) { retval = true; break; } RESULTINT = retval; return 0; } static int fun_is_married(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = (ENTITY_TYPE(0) == BL::PC && ARGPC(0)->status.partner_id); return 0; } static int fun_is_dead(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = (ENTITY_TYPE(0) == BL::PC && pc_isdead(ARGPC(0))); return 0; } static int fun_is_pc(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = (ENTITY_TYPE(0) == BL::PC); return 0; } static int fun_partner(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ENTITY_TYPE(0) == BL::PC && ARGPC(0)->status.partner_id) { RESULTENTITY = map_nick2sd(map_charid2nick(ARGPC(0)->status.partner_id)); return 0; } else return 1; } static int fun_awayfrom(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { location_t *loc = &ARGLOCATION(0); int dx = dirx[ARGDIR(1)]; int dy = diry[ARGDIR(1)]; int distance = ARGINT(2); while (distance-- && !bool(read_gatp(loc->m, loc->x + dx, loc->y + dy) & MapCell::UNWALKABLE)) { loc->x += dx; loc->y += dy; } RESULTLOCATION = *loc; return 0; } static int fun_failed(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = ARG_TYPE(0) == TYPE::FAIL; return 0; } static int fun_npc(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { NpcName name = stringish<NpcName>(ARGSTR(0)); RESULTENTITY = npc_name2id(name); return RESULTENTITY == NULL; } static int fun_pc(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { CharName name = stringish<CharName>(ARGSTR(0)); RESULTENTITY = map_nick2sd(name); return RESULTENTITY == NULL; } static int fun_distance(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ARGLOCATION(0).m != ARGLOCATION(1).m) RESULTINT = 0x7fffffff; else RESULTINT = max(abs(ARGLOCATION(0).x - ARGLOCATION(1).x), abs(ARGLOCATION(0).y - ARGLOCATION(1).y)); return 0; } static int fun_rdistance(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ARGLOCATION(0).m != ARGLOCATION(1).m) RESULTINT = 0x7fffffff; else { int dx = ARGLOCATION(0).x - ARGLOCATION(1).x; int dy = ARGLOCATION(0).y - ARGLOCATION(1).y; RESULTINT = static_cast<int>(sqrt((dx * dx) + (dy * dy))); } return 0; } static int fun_anchor(dumb_ptr<env_t> env, val_t *result, const_array<val_t> args) { dumb_ptr<teleport_anchor_t> anchor = magic_find_anchor(ARGSTR(0)); if (!anchor) return 1; magic_eval(env, result, anchor->location); make_area(result); if (result->ty != TYPE::AREA) { magic_clear_var(result); return 1; } return 0; } static int fun_line_of_sight(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { block_list e1, e2; COPY_LOCATION(e1, ARGLOCATION(0)); COPY_LOCATION(e2, ARGLOCATION(1)); RESULTINT = battle_check_range(dumb_ptr<block_list>(&e1), dumb_ptr<block_list>(&e2), 0); return 0; } void magic_random_location(location_t *dest, dumb_ptr<area_t> area) { switch (area->ty) { case AREA::UNION: { if (random_::chance({area->a.a_union[0]->size, area->size})) magic_random_location(dest, area->a.a_union[0]); else magic_random_location(dest, area->a.a_union[1]); break; } case AREA::LOCATION: case AREA::RECT: case AREA::BAR: { map_local *m; int x, y, w, h; magic_area_rect(&m, &x, &y, &w, &h, *area); if (w <= 1) w = 1; if (h <= 1) h = 1; // This is not exactly the same as the old logic, // but it's better. auto pair = map_randfreecell(m, x, y, w, h); dest->m = m; dest->x = pair.first; dest->y = pair.second; break; } default: FPRINTF(stderr, "Unknown area type %d\n", area->ty); } } static int fun_pick_location(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { magic_random_location(&result->v.v_location, ARGAREA(0)); return 0; } static int fun_read_script_int(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { dumb_ptr<block_list> subject_p = ARGENTITY(0); VarName var_name = stringish<VarName>(ARGSTR(1)); if (subject_p->bl_type != BL::PC) return 1; RESULTINT = pc_readglobalreg(subject_p->is_player(), var_name); return 0; } static int fun_rbox(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { location_t loc = ARGLOCATION(0); int radius = ARGINT(1); RESULTAREA = area_new(AREA::RECT); RESULTAREA->a.a_rect.loc.m = loc.m; RESULTAREA->a.a_rect.loc.x = loc.x - radius; RESULTAREA->a.a_rect.loc.y = loc.y - radius; RESULTAREA->a.a_rect.width = radius * 2 + 1; RESULTAREA->a.a_rect.height = radius * 2 + 1; return 0; } static int fun_running_status_update(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { if (ENTITY_TYPE(0) != BL::PC && ENTITY_TYPE(0) != BL::MOB) return 1; StatusChange sc = static_cast<StatusChange>(ARGINT(1)); RESULTINT = bool(battle_get_sc_data(ARGENTITY(0))[sc].timer); return 0; } static int fun_status_option(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = (bool((ARGPC(0))->status.option & static_cast<Option>(ARGINT(0)))); return 0; } static int fun_element(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = static_cast<int>(battle_get_element(ARGENTITY(0)).element); return 0; } static int fun_element_level(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = battle_get_element(ARGENTITY(0)).level; return 0; } static int fun_is_exterior(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { #warning "Evil assumptions!" RESULTINT = ARGLOCATION(0).m->name_[4] == '1'; return 0; } static int fun_contains_string(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = NULL != strstr(ARGSTR(0).c_str(), ARGSTR(1).c_str()); return 0; } static int fun_strstr(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { const char *offset = strstr(ARGSTR(0).c_str(), ARGSTR(1).c_str()); RESULTINT = offset - ARGSTR(0).c_str(); return offset == NULL; } static int fun_strlen(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = strlen(ARGSTR(0).c_str()); return 0; } static int fun_substr(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { const char *src = ARGSTR(0).c_str(); const int slen = strlen(src); int offset = ARGINT(1); int len = ARGINT(2); if (len < 0) len = 0; if (offset < 0) offset = 0; if (offset > slen) offset = slen; if (offset + len > slen) len = slen - offset; const char *begin = src + offset; const char *end = begin + len; RESULTSTR = dumb_string::copy(begin, end); return 0; } static int fun_sqrt(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { RESULTINT = static_cast<int>(sqrt(ARGINT(0))); return 0; } static int fun_map_level(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { #warning "Evil assumptions!" RESULTINT = ARGLOCATION(0).m->name_[4] - '0'; return 0; } static int fun_map_nr(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { #warning "Evil assumptions!" MapName mapname = ARGLOCATION(0).m->name_; RESULTINT = ((mapname[0] - '0') * 100) + ((mapname[1] - '0') * 10) + ((mapname[2] - '0')); return 0; } static int fun_dir_towards(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { int dx; int dy; if (ARGLOCATION(0).m != ARGLOCATION(1).m) return 1; dx = ARGLOCATION(1).x - ARGLOCATION(0).x; dy = ARGLOCATION(1).y - ARGLOCATION(0).y; if (ARGINT(1)) { /* 8-direction mode */ if (abs(dx) > abs(dy) * 2) { /* east or west */ if (dx < 0) RESULTINT = 2 /* west */ ; else RESULTINT = 6 /* east */ ; } else if (abs(dy) > abs(dx) * 2) { /* north or south */ if (dy > 0) RESULTINT = 0 /* south */ ; else RESULTINT = 4 /* north */ ; } else if (dx < 0) { /* north-west or south-west */ if (dy < 0) RESULTINT = 3 /* north-west */ ; else RESULTINT = 1 /* south-west */ ; } else { /* north-east or south-east */ if (dy < 0) RESULTINT = 5 /* north-east */ ; else RESULTINT = 7 /* south-east */ ; } } else { /* 4-direction mode */ if (abs(dx) > abs(dy)) { /* east or west */ if (dx < 0) RESULTINT = 2 /* west */ ; else RESULTINT = 6 /* east */ ; } else { /* north or south */ if (dy > 0) RESULTINT = 0 /* south */ ; else RESULTINT = 4 /* north */ ; } } return 0; } static int fun_extract_healer_xp(dumb_ptr<env_t>, val_t *result, const_array<val_t> args) { dumb_ptr<map_session_data> sd = (ENTITY_TYPE(0) == BL::PC) ? ARGPC(0) : NULL; if (!sd) RESULTINT = 0; else RESULTINT = pc_extract_healer_exp(sd, ARGINT(1)); return 0; } #define MAGIC_FUNCTION(name, args, ret, impl) {{name}, {{name}, {args}, ret, impl}} #define MAGIC_FUNCTION1(name, args, ret) MAGIC_FUNCTION(#name, args, ret, fun_##name) static std::map<ZString, fun_t> functions = { MAGIC_FUNCTION("+", "..", '.', fun_add), MAGIC_FUNCTION("-", "ii", 'i', fun_sub), MAGIC_FUNCTION("*", "ii", 'i', fun_mul), MAGIC_FUNCTION("/", "ii", 'i', fun_div), MAGIC_FUNCTION("%", "ii", 'i', fun_mod), MAGIC_FUNCTION("||", "ii", 'i', fun_or), MAGIC_FUNCTION("&&", "ii", 'i', fun_and), MAGIC_FUNCTION(">", "..", 'i', fun_gt), MAGIC_FUNCTION(">=", "..", 'i', fun_gte), MAGIC_FUNCTION("=", "..", 'i', fun_eq), MAGIC_FUNCTION("|", "..", 'i', fun_bitor), MAGIC_FUNCTION("&", "ii", 'i', fun_bitand), MAGIC_FUNCTION("^", "ii", 'i', fun_bitxor), MAGIC_FUNCTION("<<", "ii", 'i', fun_bitshl), MAGIC_FUNCTION(">>", "ii", 'i', fun_bitshr), MAGIC_FUNCTION1(not, "i", 'i'), MAGIC_FUNCTION1(neg, "i", 'i'), MAGIC_FUNCTION1(max, "ii", 'i'), MAGIC_FUNCTION1(min, "ii", 'i'), MAGIC_FUNCTION1(is_in, "la", 'i'), MAGIC_FUNCTION1(if_then_else, "i__", '_'), MAGIC_FUNCTION1(skill, "ei", 'i'), MAGIC_FUNCTION("str", "e", 'i', fun_get_str), MAGIC_FUNCTION("agi", "e", 'i', fun_get_agi), MAGIC_FUNCTION("vit", "e", 'i', fun_get_vit), MAGIC_FUNCTION("dex", "e", 'i', fun_get_dex), MAGIC_FUNCTION("luk", "e", 'i', fun_get_luk), MAGIC_FUNCTION("int", "e", 'i', fun_get_int), MAGIC_FUNCTION("level", "e", 'i', fun_get_lv), MAGIC_FUNCTION("mdef", "e", 'i', fun_get_mdef), MAGIC_FUNCTION("def", "e", 'i', fun_get_def), MAGIC_FUNCTION("hp", "e", 'i', fun_get_hp), MAGIC_FUNCTION("max_hp", "e", 'i', fun_get_max_hp), MAGIC_FUNCTION("sp", "e", 'i', fun_get_sp), MAGIC_FUNCTION("max_sp", "e", 'i', fun_get_max_sp), MAGIC_FUNCTION("dir", "e", 'd', fun_get_dir), MAGIC_FUNCTION1(name_of, ".", 's'), MAGIC_FUNCTION1(mob_id, "e", 'i'), MAGIC_FUNCTION1(location, "e", 'l'), MAGIC_FUNCTION1(random, "i", 'i'), MAGIC_FUNCTION1(random_dir, "i", 'd'), MAGIC_FUNCTION1(hash_entity, "e", 'i'), MAGIC_FUNCTION1(is_married, "e", 'i'), MAGIC_FUNCTION1(partner, "e", 'e'), MAGIC_FUNCTION1(awayfrom, "ldi", 'l'), MAGIC_FUNCTION1(failed, "_", 'i'), MAGIC_FUNCTION1(pc, "s", 'e'), MAGIC_FUNCTION1(npc, "s", 'e'), MAGIC_FUNCTION1(distance, "ll", 'i'), MAGIC_FUNCTION1(rdistance, "ll", 'i'), MAGIC_FUNCTION1(anchor, "s", 'a'), MAGIC_FUNCTION("random_location", "a", 'l', fun_pick_location), MAGIC_FUNCTION("script_int", "es", 'i', fun_read_script_int), MAGIC_FUNCTION1(rbox, "li", 'a'), MAGIC_FUNCTION1(count_item, "e.", 'i'), MAGIC_FUNCTION1(line_of_sight, "ll", 'i'), MAGIC_FUNCTION1(running_status_update, "ei", 'i'), MAGIC_FUNCTION1(status_option, "ei", 'i'), MAGIC_FUNCTION1(element, "e", 'i'), MAGIC_FUNCTION1(element_level, "e", 'i'), MAGIC_FUNCTION1(his_shroud, "e", 'i'), MAGIC_FUNCTION1(is_equipped, "e.", 'i'), MAGIC_FUNCTION1(is_exterior, "l", 'i'), MAGIC_FUNCTION1(contains_string, "ss", 'i'), MAGIC_FUNCTION1(strstr, "ss", 'i'), MAGIC_FUNCTION1(strlen, "s", 'i'), MAGIC_FUNCTION1(substr, "sii", 's'), MAGIC_FUNCTION1(sqrt, "i", 'i'), MAGIC_FUNCTION1(map_level, "l", 'i'), MAGIC_FUNCTION1(map_nr, "l", 'i'), MAGIC_FUNCTION1(dir_towards, "lli", 'd'), MAGIC_FUNCTION1(is_dead, "e", 'i'), MAGIC_FUNCTION1(is_pc, "e", 'i'), MAGIC_FUNCTION("extract_healer_experience", "ei", 'i', fun_extract_healer_xp), }; fun_t *magic_get_fun(ZString name) { auto it = functions.find(name); if (it == functions.end()) return nullptr; return &it->second; } // 1 on failure static int eval_location(dumb_ptr<env_t> env, location_t *dest, e_location_t *expr) { val_t m, x, y; magic_eval(env, &m, expr->m); magic_eval(env, &x, expr->x); magic_eval(env, &y, expr->y); if (CHECK_TYPE(&m, TYPE::STRING) && CHECK_TYPE(&x, TYPE::INT) && CHECK_TYPE(&y, TYPE::INT)) { MapName name = VString<15>(ZString(m.v.v_string)); map_local *map_id = map_mapname2mapid(name); magic_clear_var(&m); if (!map_id) return 1; dest->m = map_id; dest->x = x.v.v_int; dest->y = y.v.v_int; return 0; } else { magic_clear_var(&m); magic_clear_var(&x); magic_clear_var(&y); return 1; } } static dumb_ptr<area_t> eval_area(dumb_ptr<env_t> env, e_area_t& expr_) { e_area_t *expr = &expr_; // temporary hack to reduce diff auto area = dumb_ptr<area_t>::make(); area->ty = expr->ty; switch (expr->ty) { case AREA::LOCATION: area->size = 1; if (eval_location(env, &area->a.a_loc, &expr->a.a_loc)) { area.delete_(); return NULL; } else return area; case AREA::UNION: { int i, fail = 0; for (i = 0; i < 2; i++) { area->a.a_union[i] = eval_area(env, *expr->a.a_union[i]); if (!area->a.a_union[i]) fail = 1; } if (fail) { for (i = 0; i < 2; i++) { if (area->a.a_union[i]) free_area(area->a.a_union[i]); } area.delete_(); return NULL; } area->size = area->a.a_union[0]->size + area->a.a_union[1]->size; return area; } case AREA::RECT: { val_t width, height; magic_eval(env, &width, expr->a.a_rect.width); magic_eval(env, &height, expr->a.a_rect.height); area->a.a_rect.width = width.v.v_int; area->a.a_rect.height = height.v.v_int; if (CHECK_TYPE(&width, TYPE::INT) && CHECK_TYPE(&height, TYPE::INT) && !eval_location(env, &(area->a.a_rect.loc), &expr->a.a_rect.loc)) { area->size = area->a.a_rect.width * area->a.a_rect.height; magic_clear_var(&width); magic_clear_var(&height); return area; } else { area.delete_(); magic_clear_var(&width); magic_clear_var(&height); return NULL; } } case AREA::BAR: { val_t width, depth, dir; magic_eval(env, &width, expr->a.a_bar.width); magic_eval(env, &depth, expr->a.a_bar.depth); magic_eval(env, &dir, expr->a.a_bar.dir); area->a.a_bar.width = width.v.v_int; area->a.a_bar.depth = depth.v.v_int; area->a.a_bar.dir = dir.v.v_dir; if (CHECK_TYPE(&width, TYPE::INT) && CHECK_TYPE(&depth, TYPE::INT) && CHECK_TYPE(&dir, TYPE::DIR) && !eval_location(env, &area->a.a_bar.loc, &expr->a.a_bar.loc)) { area->size = (area->a.a_bar.width * 2 + 1) * area->a.a_bar.depth; magic_clear_var(&width); magic_clear_var(&depth); magic_clear_var(&dir); return area; } else { area.delete_(); magic_clear_var(&width); magic_clear_var(&depth); magic_clear_var(&dir); return NULL; } } default: FPRINTF(stderr, "INTERNAL ERROR: Unknown area type %d\n", area->ty); area.delete_(); return NULL; } } static TYPE type_key(char ty_key) { switch (ty_key) { case 'i': return TYPE::INT; case 'd': return TYPE::DIR; case 's': return TYPE::STRING; case 'e': return TYPE::ENTITY; case 'l': return TYPE::LOCATION; case 'a': return TYPE::AREA; case 'S': return TYPE::SPELL; case 'I': return TYPE::INVOCATION; default: return TYPE::NEGATIVE_1; } } int magic_signature_check(ZString opname, ZString funname, ZString signature, int args_nr, val_t *args, int line, int column) { int i; for (i = 0; i < args_nr; i++) { val_t *arg = &args[i]; char ty_key = signature[i]; TYPE ty = arg->ty; TYPE desired_ty = type_key(ty_key); if (ty == TYPE::ENTITY) { /* Dereference entities in preparation for calling function */ arg->v.v_entity = map_id2bl(arg->v.v_int); if (!arg->v.v_entity) ty = arg->ty = TYPE::FAIL; } else if (ty == TYPE::INVOCATION) { arg->v.v_invocation = map_id2bl(arg->v.v_int)->is_spell(); if (!arg->v.v_entity) ty = arg->ty = TYPE::FAIL; } if (!ty_key) { FPRINTF(stderr, "[magic-eval]: L%d:%d: Too many arguments (%d) to %s `%s'\n", line, column, args_nr, opname, funname); return 1; } if (ty == TYPE::FAIL && ty_key != '_') return 1; /* Fail `in a sane way': This is a perfectly permissible error */ if (ty == desired_ty || desired_ty == TYPE::NEGATIVE_1) continue; if (ty == TYPE::UNDEF) { FPRINTF(stderr, "[magic-eval]: L%d:%d: Argument #%d to %s `%s' undefined\n", line, column, i + 1, opname, funname); return 1; } /* If we are here, we have a type mismatch but no failure _yet_. Try to coerce. */ switch (desired_ty) { case TYPE::INT: intify(arg); break; /* 100% success rate */ case TYPE::STRING: stringify(arg, 1); break; /* 100% success rate */ case TYPE::AREA: make_area(arg); break; /* Only works for locations */ case TYPE::LOCATION: make_location(arg); break; /* Only works for some areas */ case TYPE::SPELL: make_spell(arg); break; /* Only works for still-active invocatoins */ default: break; /* We'll fail right below */ } ty = arg->ty; if (ty != desired_ty) { /* Coercion failed? */ if (ty != TYPE::FAIL) FPRINTF(stderr, "[magic-eval]: L%d:%d: Argument #%d to %s `%s' of incorrect type (%d)\n", line, column, i + 1, opname, funname, ty); return 1; } } return 0; } void magic_eval(dumb_ptr<env_t> env, val_t *dest, dumb_ptr<expr_t> expr) { switch (expr->ty) { case EXPR::VAL: magic_copy_var(dest, &expr->e.e_val); break; case EXPR::LOCATION: if (eval_location(env, &dest->v.v_location, &expr->e.e_location)) dest->ty = TYPE::FAIL; else dest->ty = TYPE::LOCATION; break; case EXPR::AREA: if ((dest->v.v_area = eval_area(env, expr->e.e_area))) dest->ty = TYPE::AREA; else dest->ty = TYPE::FAIL; break; case EXPR::FUNAPP: { val_t arguments[MAX_ARGS]; int args_nr = expr->e.e_funapp.args_nr; int i; fun_t *f = expr->e.e_funapp.funp; for (i = 0; i < args_nr; ++i) magic_eval(env, &arguments[i], expr->e.e_funapp.args[i]); if (magic_signature_check("function", f->name, f->signature, args_nr, arguments, expr->e.e_funapp.line_nr, expr->e.e_funapp.column) || f->fun(env, dest, const_array<val_t>(arguments, args_nr))) dest->ty = TYPE::FAIL; else { TYPE dest_ty = type_key(f->ret_ty); if (dest_ty != TYPE::NEGATIVE_1) dest->ty = dest_ty; /* translate entity back into persistent int */ if (dest->ty == TYPE::ENTITY) { if (dest->v.v_entity) dest->v.v_int = dest->v.v_entity->bl_id; else dest->ty = TYPE::FAIL; } } for (i = 0; i < args_nr; ++i) magic_clear_var(&arguments[i]); break; } case EXPR::ID: { val_t v = env->VAR(expr->e.e_id); magic_copy_var(dest, &v); break; } case EXPR::SPELLFIELD: { val_t v; int id = expr->e.e_field.id; magic_eval(env, &v, expr->e.e_field.expr); if (v.ty == TYPE::INVOCATION) { dumb_ptr<invocation> t = map_id2bl(v.v.v_int)->is_spell(); if (!t) dest->ty = TYPE::UNDEF; else { val_t val = t->env->VAR(id); magic_copy_var(dest, &val); } } else { FPRINTF(stderr, "[magic] Attempt to access field %s on non-spell\n", env->base_env->varv[id].name); dest->ty = TYPE::FAIL; } break; } default: FPRINTF(stderr, "[magic] INTERNAL ERROR: Unknown expression type %d\n", expr->ty); break; } } int magic_eval_int(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr) { val_t result; magic_eval(env, &result, expr); if (result.ty == TYPE::FAIL || result.ty == TYPE::UNDEF) return 0; intify(&result); return result.v.v_int; } FString magic_eval_str(dumb_ptr<env_t> env, dumb_ptr<expr_t> expr) { val_t result; magic_eval(env, &result, expr); if (result.ty == TYPE::FAIL || result.ty == TYPE::UNDEF) return {"?"}; stringify(&result, 0); return result.v.v_string.str(); } dumb_ptr<expr_t> magic_new_expr(EXPR ty) { auto expr = dumb_ptr<expr_t>::make(); expr->ty = ty; return expr; }