diff options
Diffstat (limited to 'src/map/magic-expr.c')
-rw-r--r-- | src/map/magic-expr.c | 1286 |
1 files changed, 1286 insertions, 0 deletions
diff --git a/src/map/magic-expr.c b/src/map/magic-expr.c new file mode 100644 index 0000000..2fbaa4f --- /dev/null +++ b/src/map/magic-expr.c @@ -0,0 +1,1286 @@ +#include "magic-expr.h" +#include "magic-expr-eval.h" +#include "itemdb.h" +#include <math.h> + +#define IS_SOLID(c) ((c) == 1 || (c) == 5) + +int +map_is_solid(int m, int x, int y) +{ + return (IS_SOLID(map_getcell(m, x, y))); +} + +#undef IS_SOLID + +static void +free_area(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; + } + + free(area); +} + +static area_t * +dup_area(area_t *area) +{ + area_t *retval = malloc(sizeof(area_t)); + *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 TY_STRING: dest->v.v_string = strdup(dest->v.v_string); + break; + case TY_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 TY_STRING: free(v->v.v_string); + break; + case TY_AREA: free_area(v->v.v_area); + break; + default: break; + } +} + +static char * +show_entity(entity_t *entity) +{ + switch (entity->type) { + case BL_PC: + return ((struct map_session_data *)entity)->status.name; + case BL_NPC: + return ((struct npc_data *)entity)->name; + case BL_MOB: + return ((struct mob_data *)entity)->name; + case BL_ITEM: + /* Sorry about this one... */ + return ((struct item_data *)(&((struct flooritem_data *)entity)->item_data))->name; + case BL_SKILL: + return "%skill"; + case BL_PET: + return ((struct pet_data *)entity)->name; + 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 char *dirs[8] = {"S", "SW", "W", "NW", "N", "NE", "E", "SE"}; + char *buf; + + switch (v->ty) { + case TY_UNDEF: + buf = strdup("UNDEF"); + break; + + case TY_INT: + buf = malloc(32); + sprintf(buf, "%i", v->v.v_int); + break; + + case TY_STRING: + return; + + case TY_DIR: + buf = strdup(dirs[v->v.v_int]); + break; + + case TY_ENTITY: + buf = strdup(show_entity(v->v.v_entity)); + break; + + case TY_LOCATION: + buf = malloc(128); + sprintf(buf, "<\"%s\", %d, %d>", map[v->v.v_location.m].name, + v->v.v_location.x, v->v.v_location.y); + break; + + case TY_AREA: + buf = strdup("%area"); + free_area(v->v.v_area); + break; + + case TY_SPELL: + buf = strdup(v->v.v_spell->name); + break; + + case TY_INVOCATION: { + invocation_t *invocation = within_op + ? v->v.v_invocation + : (invocation_t *) map_id2bl(v->v.v_int); + buf = strdup(invocation->spell->name); + } + break; + + default: + fprintf(stderr, "[magic] INTERNAL ERROR: Cannot stringify %d\n", v->ty); + return; + } + + v->v.v_string = buf; + v->ty = TY_STRING; +} + +static void +intify(val_t *v) +{ + if (v->ty == TY_INT) + return; + + magic_clear_var(v); + v->ty = TY_INT; + v->v.v_int = 1; +} + + +area_t * +area_new(int ty) +{ + area_t *retval = (area_t *)aCalloc(sizeof(area_t), 1); + retval->ty = ty; + return retval; +} + +area_t * +area_union(area_t *area, area_t *other_area) +{ + 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 == TY_LOCATION) { + area_t *a = malloc(sizeof (area_t)); + v->ty = TY_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 == TY_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 = TY_LOCATION; + v->v.v_location = location; + } +} + +static int +fun_add(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (TY(0) == TY_INT && TY(1) == TY_INT) { + /* Integer addition */ + RESULTINT = ARGINT(0) + ARGINT(1); + result->ty = TY_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 = TY_AREA; + } else { + /* Anything else -> string concatenation */ + stringify(&args[0], 1); + stringify(&args[1], 1); + /* Yes, we could speed this up. */ + RESULTSTR = (char *) malloc(1 + strlen(ARGSTR(0)) + strlen(ARGSTR(1))); + strcpy(RESULTSTR, ARGSTR(0)); + strcat(RESULTSTR, ARGSTR(1)); + result->ty = TY_STRING; + } + return 0; +} + +static int +fun_sub(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = ARGINT(0) - ARGINT(1); + return 0; +} + + +static int +fun_mul(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = ARGINT(0) * ARGINT(1); + return 0; +} + + +static int +fun_div(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (!ARGINT(1)) + return 1; /* division by zero */ + RESULTINT = ARGINT(0) / ARGINT(1); + return 0; +} + +static int +fun_mod(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (!ARGINT(1)) + return 1; /* division by zero */ + RESULTINT = ARGINT(0) % ARGINT(1); + return 0; +} + +static int +fun_or(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = ARGINT(0) || ARGINT(1); + return 0; +} + +static int +fun_and(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = ARGINT(0) && ARGINT(1); + return 0; +} + +static int +fun_not(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = !ARGINT(0); + return 0; +} + +static int +fun_gte(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (TY(0) == TY_STRING || TY(1) == TY_STRING) { + stringify(&args[0], 1); + stringify(&args[1], 1); + RESULTINT = strcmp(ARGSTR(0), ARGSTR(1)) >= 0; + } else { + intify(&args[0]); + intify(&args[1]); + RESULTINT = ARGINT(0) >= ARGINT(1); + } + return 0; +} + +static int +fun_gt(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (TY(0) == TY_STRING || TY(1) == TY_STRING) { + stringify(&args[0], 1); + stringify(&args[1], 1); + RESULTINT = strcmp(ARGSTR(0), ARGSTR(1)) > 0; + } else { + intify(&args[0]); + intify(&args[1]); + RESULTINT = ARGINT(0) > ARGINT(1); + } + return 0; +} + + +static int +fun_eq(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (TY(0) == TY_STRING || TY(1) == TY_STRING) { + stringify(&args[0], 1); + stringify(&args[1], 1); + RESULTINT = strcmp(ARGSTR(0), ARGSTR(1)) == 0; + } else if (TY(0) == TY_DIR && TY(1) == TY_DIR) + RESULTINT = ARGDIR(0) == ARGDIR(1); + else if (TY(0) == TY_ENTITY && TY(1) == TY_ENTITY) + RESULTINT = ARGENTITY(0) == ARGENTITY(1); + else if (TY(0) == TY_LOCATION && TY(1) == TY_LOCATION) + RESULTINT = (ARGLOCATION(0).x == ARGLOCATION(1).x + && ARGLOCATION(0).y == ARGLOCATION(1).y + && ARGLOCATION(0).m == ARGLOCATION(1).m); + else if (TY(0) == TY_AREA && TY(1) == TY_AREA) + RESULTINT = ARGAREA(0) == ARGAREA(1); /* Probably not that great an idea... */ + else if (TY(0) == TY_SPELL && TY(1) == TY_SPELL) + RESULTINT = ARGSPELL(0) == ARGSPELL(1); + else if (TY(0) == TY_INVOCATION && TY(1) == TY_INVOCATION) + RESULTINT = ARGINVOCATION(0) == ARGINVOCATION(1); + else { + intify(&args[0]); + intify(&args[1]); + RESULTINT = ARGINT(0) == ARGINT(1); + } + return 0; +} + +static int +fun_bitand(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = ARGINT(0) & ARGINT(1); + return 0; +} + +static int +fun_bitor(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = ARGINT(0) | ARGINT(1); + return 0; +} + +static int +fun_bitxor(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = ARGINT(0) ^ ARGINT(1); + return 0; +} + +static int +fun_bitshl(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = ARGINT(0) << ARGINT(1); + return 0; +} + +static int +fun_bitshr(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = ARGINT(0) >> ARGINT(1); + return 0; +} + +static int +fun_max(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = MAX(ARGINT(0), ARGINT(1)); + return 0; +} + +static int +fun_min(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = MIN(ARGINT(0), ARGINT(1)); + return 0; +} + +static int +fun_if_then_else(env_t *env, int args_nr, val_t *result, 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(int *m, int *x, int *y, int *width, int *height, area_t *area) +{ + 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; + } + } +} + +static int +location_in_area(int m, int x, int y, area_t *area) +{ + switch (area->ty) { + case AREA_UNION: + return location_in_area(m, x, y, area->a.a_union[0]) + || location_in_area(m, x, y, area->a.a_union[1]); + case AREA_LOCATION: + case AREA_RECT: + case AREA_BAR: { + int 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(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = location_in_area(ARGLOCATION(0).m, + ARGLOCATION(0).x, + ARGLOCATION(0).y, + ARGAREA(1)); + return 0; +} + +static int +fun_skill(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (ETY(0) != BL_PC + || ARGINT(1) < 0 + || ARGINT(1) >= MAX_SKILL + || ARGPC(0)->status.skill[ARGINT(1)].id != ARGINT(1)) + RESULTINT = 0; + else + RESULTINT = ARGPC(0)->status.skill[ARGINT(1)].lv; + return 0; +} + +static int +fun_has_shroud(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = (ETY(0) == BL_PC + && ARGPC(0)->state.shroud_active); + return 0; +} + +#define BATTLE_GETTER(name) static int fun_get_##name(env_t *env, int args_nr, val_t *result, 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(max_hp); +BATTLE_GETTER(dir); + +#define MMO_GETTER(name) static int fun_get_##name(env_t *env, int args_nr, val_t *result, val_t *args) { \ + if (ETY(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(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (TY(0) == TY_ENTITY) { + RESULTSTR = strdup(show_entity(ARGENTITY(0))); + return 0; + } else if (TY(0) == TY_SPELL) { + RESULTSTR = strdup(ARGSPELL(0)->name); + return 0; + } else if (TY(0) == TY_INVOCATION) { + RESULTSTR = strdup(ARGINVOCATION(0)->spell->name); + return 0; + } + return 1; +} + +#define COPY_LOCATION(dest, src) (dest).x = (src).x; (dest).y = (src).y; (dest).m = (src).m; + +static int +fun_location(env_t *env, int args_nr, val_t *result, val_t *args) +{ + COPY_LOCATION(RESULTLOCATION, *(ARGENTITY(0))); + return 0; +} + +/* Recall that glibc's rand() isnt' too bad in the lower bits */ + +static int +fun_random(env_t *env, int args_nr, val_t *result, val_t *args) +{ + int delta = ARGINT(0); + if (delta < 0) + delta = -delta; + if (delta == 0) { + RESULTINT = 0; + return 0; + } + RESULTINT = rand() % delta; + if (ARGINT(0) < 0) + RESULTINT = -RESULTINT; + return 0; +} + +static int +fun_random_dir(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (ARGINT(0)) + RESULTDIR = rand() & 0x7; + else + RESULTDIR = (rand() & 0x3) * 2; + return 0; +} + +static int +fun_hash_entity(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = ARGENTITY(0)->id; + return 0; +} + +static int +fun_count_item(env_t *env, int args_nr, val_t *result, val_t *args) +{ + character_t *chr = (ETY(0) == BL_PC) ? ARGPC(0) : NULL; + int item_id; + + if (TY(1) == TY_INT) + item_id = ARGINT(1); + else if (TY(1) == TY_STRING) { + struct item_data *bitem = itemdb_searchname(ARGSTR(1)); + if (!bitem) { + fprintf(stderr, "Unknown item `%s' used in spell\n", ARGSTR(1)); + return 1; + } else + item_id = bitem->nameid; + } else + return 0; + + if (!chr) + return 1; + + RESULTINT = pc_count_all_items(chr, item_id); + return 0; +} + + +static int +fun_is_married(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = (ETY(0) == BL_PC + && ARGPC(0)->status.partner_id); + return 0; +} + +static int +fun_partner(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (ETY(0) == BL_PC + && ARGPC(0)->status.partner_id) { + RESULTENTITY = (entity_t *) map_nick2sd(map_charid2nick(ARGPC(0)->status.partner_id)); + return 0; + } else + return 1; +} + +static int +fun_awayfrom(env_t *env, int args_nr, val_t *result, val_t *args) +{ + location_t *loc = &ARGLOCATION(0); + int dx = heading_x[ARGDIR(1)]; + int dy = heading_y[ARGDIR(1)]; + int distance = ARGINT(2); + while (distance-- && !map_is_solid(loc->m, loc->x, loc->y)) { + loc->x += dx; + loc->y += dy; + } + + RESULTLOCATION = *loc; + return 0; +} + +static int +fun_failed(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = TY(0) == TY_FAIL; + return 0; +} + +static int +fun_npc(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTENTITY = (entity_t *)npc_name2id(ARGSTR(0)); + return RESULTENTITY == NULL; +} + +static int +fun_pc(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTENTITY = (entity_t *)map_nick2sd(ARGSTR(0)); + return RESULTENTITY == NULL; +} + +static int +fun_distance(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (ARGLOCATION(0).m != ARGLOCATION(1).m) + RESULTINT = INT_MAX; + else + RESULTINT = MAX(abs(ARGLOCATION(0).x - ARGLOCATION(1).x), + abs(ARGLOCATION(0).y - ARGLOCATION(1).y)); + return 0; +} + + +static int +fun_rdistance(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (ARGLOCATION(0).m != ARGLOCATION(1).m) + RESULTINT = INT_MAX; + else { + int dx = ARGLOCATION(0).x - ARGLOCATION(1).x; + int dy = ARGLOCATION(0).y - ARGLOCATION(1).y; + RESULTINT = (int) (sqrt((dx*dx) + (dy*dy))); + } + return 0; +} + + +static int +fun_anchor(env_t *env, int args_nr, val_t *result, val_t *args) +{ + 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 != TY_AREA) { + magic_clear_var(result); + return 1; + } + + return 0; +} + + + +static int +fun_line_of_sight(env_t *env, int args_nr, val_t *result, val_t *args) +{ + entity_t e1, e2; + + COPY_LOCATION(e1, ARGLOCATION(0)); + COPY_LOCATION(e2, ARGLOCATION(1)); + + RESULTINT = battle_check_range(&e1, &e2, 0); + + return 0; +} + +void +magic_random_location(location_t *dest, area_t *area) +{ + switch (area->ty) { + case AREA_UNION: { + int rv = rand() % area->size; + if (rv < area->a.a_union[0]->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: { + int m, x, y, w, h; + magic_area_rect(&m, &x, &y, &w, &h, area); + + if (w <= 1) + w = 1; + + if (h <= 1) + h = 1; + + x += rand() % w; + y += rand() % h; + + if (!map_is_solid(m, x, y)) { + int start_x = x; + int start_y = y; + int i; + int initial_dir = rand() & 0x7; + int dir = initial_dir; + + /* try all directions, up to a distance to 10, for a free slot */ + do { + x = start_x; + y = start_y; + + for (i = 0; i < 10 && map_is_solid(m, x, y); i++) { + x += heading_x[dir]; + y += heading_y[dir]; + } + + dir = (dir + 1) & 0x7; + } while (map_is_solid(m, x, y) && dir != initial_dir); + + } + /* We've tried our best. If the map is still solid, the engine will automatically randomise the target location if we try to warp. */ + + dest->m = m; + dest->x = x; + dest->y = y; + break; + } + + default: + fprintf(stderr, "Unknown area type %d\n", area->ty); + } +} + +static int +fun_pick_location(env_t *env, int args_nr, val_t *result, val_t *args) +{ + magic_random_location(&result->v.v_location, ARGAREA(0)); + return 0; +} + +static int +fun_read_script_int(env_t *env, int args_nr, val_t *result, val_t *args) +{ + entity_t *subject_p = ARGENTITY(0); + char *var_name = ARGSTR(1); + + if (subject_p->type != BL_PC) + return 1; + + RESULTINT = pc_readglobalreg((character_t *) subject_p, var_name); + return 0; +} + +static int +fun_rbox(env_t *env, int args_nr, val_t *result, 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(env_t *env, int args_nr, val_t *result, val_t *args) +{ + if (ETY(0) != BL_PC) + return 1; + + RESULTINT = battle_get_sc_data(ARGENTITY(0))[ARGINT(1)].timer != -1; + return 0; +} + +static int +fun_element(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = battle_get_element(ARGENTITY(0)) % 10; + return 0; +} + +static int +fun_element_level(env_t *env, int args_nr, val_t *result, val_t *args) +{ + RESULTINT = battle_get_element(ARGENTITY(0)) / 10; + return 0; +} + +#define BATTLE_RECORD2(sname, name) { sname, "e", 'i', fun_get_##name } +#define BATTLE_RECORD(name) BATTLE_RECORD2(#name, name) +static fun_t functions[] = { + { "+", "..", '.', fun_add }, + { "-", "ii", 'i', fun_sub }, + { "*", "ii", 'i', fun_mul }, + { "/", "ii", 'i', fun_div }, + { "%", "ii", 'i', fun_mod }, + { "||", "ii", 'i', fun_or }, + { "&&", "ii", 'i', fun_and }, + { ">", "..", 'i', fun_gt }, + { ">=", "..", 'i', fun_gte }, + { "=", "..", 'i', fun_eq }, + { "|", "..", 'i', fun_bitor }, + { "&", "ii", 'i', fun_bitand }, + { "^", "ii", 'i', fun_bitxor }, + { "<<", "ii", 'i', fun_bitshl }, + { ">>", "ii", 'i', fun_bitshr }, + { "not", "i", 'i', fun_not }, + { "max", "ii", 'i', fun_max }, + { "min", "ii", 'i', fun_min }, + { "is_in", "la", 'i', fun_is_in }, + { "if_then_else", "i__", '_', fun_if_then_else }, + { "skill", "ei", 'i', fun_skill }, + BATTLE_RECORD(str), + BATTLE_RECORD(agi), + BATTLE_RECORD(vit), + BATTLE_RECORD(dex), + BATTLE_RECORD(luk), + BATTLE_RECORD(int), + BATTLE_RECORD2("level", lv), + BATTLE_RECORD(hp), + BATTLE_RECORD(max_hp), + BATTLE_RECORD(sp), + BATTLE_RECORD(max_sp), + { "dir", "e", 'd', fun_get_dir }, + { "name_of", ".", 's', fun_name_of }, + { "location", "e", 'l', fun_location }, + { "random", "i", 'i', fun_random }, + { "random_dir", "i", 'd', fun_random_dir }, + { "hash_entity", "e", 'i', fun_hash_entity }, + { "is_married", "e", 'i', fun_is_married }, + { "partner", "e", 'e', fun_partner }, + { "awayfrom", "ldi", 'l', fun_awayfrom }, + { "failed", "_", 'i', fun_failed }, + { "pc", "s", 'e', fun_pc }, + { "npc", "s", 'e', fun_npc }, + { "distance", "ll", 'i', fun_distance }, + { "rdistance", "ll", 'i', fun_rdistance }, + { "anchor", "s", 'a', fun_anchor }, + { "random_location", "a", 'l', fun_pick_location }, + { "script_int", "es", 'i', fun_read_script_int }, + { "rbox", "li", 'a', fun_rbox }, + { "count_item", "e.", 'i', fun_count_item }, + { "line_of_sight", "ll", 'i', fun_line_of_sight }, + { "running_status_update", "ei", 'i', fun_running_status_update }, + { "element", "e", 'i', fun_element }, + { "element_level", "e", 'i', fun_element_level }, + { "has_shroud", "e", 'i', fun_has_shroud }, + { NULL, NULL, '.', NULL } +}; + +static int functions_are_sorted = 0; + +int +compare_fun(const void *lhs, const void *rhs) +{ + return strcmp(((fun_t *)lhs)->name, + ((fun_t *)rhs)->name); +} + +fun_t * +magic_get_fun(char *name, int *index) +{ + static int functions_nr; + fun_t *result; + fun_t key; + + if (!functions_are_sorted) { + fun_t *it = functions; + + while (it->name) ++it; + functions_nr = it - functions; + + qsort(functions, functions_nr, sizeof(fun_t), + compare_fun); + functions_are_sorted = 1; + } + + key.name = name; + result = (fun_t *) bsearch(&key, functions, functions_nr, sizeof(fun_t), + compare_fun); + + if (result && index) + *index = result - functions; + + return result; +} + +static int // 1 on failure +eval_location(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, TY_STRING) + && CHECK_TYPE(&x, TY_INT) + && CHECK_TYPE(&y, TY_INT)) { + int map_id = map_mapname2mapid(m.v.v_string); + magic_clear_var(&m); + if (map_id < 0) + 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 area_t * +eval_area(env_t *env, e_area_t *expr) +{ + area_t *area = malloc(sizeof(area_t)); + 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)) { + free(area); + 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]); + } + free(area); + 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, TY_INT) + && CHECK_TYPE(&height, TY_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 { + free(area); + 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_int; + + if (CHECK_TYPE(&width, TY_INT) + && CHECK_TYPE(&depth, TY_INT) + && CHECK_TYPE(&dir, TY_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 { + free(area); + 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); + free(area); + return NULL; + } +} + +static int +type_key(char ty_key) +{ + switch (ty_key) { + case 'i': return TY_INT; + case 'd': return TY_DIR; + case 's': return TY_STRING; + case 'e': return TY_ENTITY; + case 'l': return TY_LOCATION; + case 'a': return TY_AREA; + case 'S': return TY_SPELL; + case 'I': return TY_INVOCATION; + default: return -1; + } +} + +int +magic_signature_check(char *opname, char *funname, char *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]; + int ty = arg->ty; + int desired_ty = type_key(ty_key); + + if (ty == TY_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 = TY_FAIL; + } else if (ty == TY_INVOCATION) { + arg->v.v_invocation = (invocation_t *) map_id2bl(arg->v.v_int); + if (!arg->v.v_entity) + ty = arg->ty = TY_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 == TY_FAIL + && ty_key != '_') + return 1; /* Fail `in a sane way': This is a perfectly permissible error */ + + if (ty == desired_ty + || desired_ty < 0 /* `dontcare' */) + continue; + + if (ty == TY_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 TY_INT: intify(arg); break; /* 100% success rate */ + case TY_STRING: stringify(arg, 1); break; /* 100% success rate */ + case TY_AREA: make_area(arg); break; /* Only works for locations */ + case TY_LOCATION: make_location(arg); break; /* Only works for some areas */ + default: break; /* We'll fail right below */ + } + + ty = arg->ty; + if (ty != desired_ty) { /* Coercion failed? */ + if (ty != TY_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(env_t *env, val_t *dest, 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 = TY_FAIL; + else + dest->ty = TY_LOCATION; + break; + + case EXPR_AREA: + if ((dest->v.v_area = eval_area(env, &expr->e.e_area))) + dest->ty = TY_AREA; + else + dest->ty = TY_FAIL; + break; + + case EXPR_FUNAPP: { + val_t arguments[MAX_ARGS]; + int args_nr = expr->e.e_funapp.args_nr; + int i; + fun_t *f = functions + expr->e.e_funapp.id; + + 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, args_nr, dest, arguments)) + dest->ty = TY_FAIL; + else { + int dest_ty = type_key(f->ret_ty); + if (dest_ty != -1) + dest->ty = dest_ty; + + /* translate entity back into persistent int */ + if (dest->ty == TY_ENTITY) { + if (dest->v.v_entity) + dest->v.v_int = dest->v.v_entity->id; + else + dest->ty = TY_FAIL; + } + } + + for (i = 0; i < args_nr; ++i) + magic_clear_var(&arguments[i]); + break; + } + + case EXPR_ID: { + val_t v = VAR(expr->e.e_id); + magic_copy_var(dest, &v); + break; + } + + default: + fprintf(stderr, "INTERNAL ERROR: Unknown expression type %d\n", expr->ty); + break; + } +} + +int +magic_eval_int(env_t *env, expr_t *expr) +{ + val_t result; + magic_eval(env, &result, expr); + + if (result.ty == TY_FAIL + || result.ty == TY_UNDEF) + return 0; + + intify(&result); + + return result.v.v_int; +} + +char * +magic_eval_str(env_t *env, expr_t *expr) +{ + val_t result; + magic_eval(env, &result, expr); + + if (result.ty == TY_FAIL + || result.ty == TY_UNDEF) + return strdup("?"); + + stringify(&result, 0); + + return result.v.v_string; +} + +expr_t * +magic_new_expr(int ty) +{ + expr_t *expr = (expr_t *)malloc(sizeof(expr_t)); + expr->ty = ty; + return expr; +} |