summaryrefslogtreecommitdiff
path: root/src/map/magic-expr.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/map/magic-expr.c')
-rw-r--r--src/map/magic-expr.c1286
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;
+}