#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] = {"south", "south-west", "west", "north-west", "north", "north-east", "east", "south-east"};
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 void
make_spell(val_t *v)
{
if (v->ty == TY_INVOCATION) {
invocation_t *invoc = v->v.v_invocation; //(invocation_t *) map_id2bl(v->v.v_int);
if (!invoc)
v->ty = TY_FAIL;
else {
v->ty = TY_SPELL;
v->v.v_spell = invoc->spell;
}
}
}
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_neg(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;
}
}
}
int
magic_location_in_area(int m, int x, int y, 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: {
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 = magic_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(mdef);
BATTLE_GETTER(def);
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;
}
int // ret -1: not a string, ret 1: no such item, ret 0: OK
magic_find_item(val_t *args, int index, struct item *item, int *stackable)
{
struct item_data *item_data;
int must_add_sequentially;
if (TY(index) == TY_INT)
item_data = itemdb_exists(ARGINT(index));
else if (TY(index) == TY_STRING)
item_data = itemdb_searchname(ARGSTR(index));
else
return -1;
if (!item_data)
return 1;
must_add_sequentially = (item_data->type == 4
|| item_data->type == 5
|| item_data->type == 7
|| item_data->type == 8); /* Very elegant. */
if (stackable)
*stackable = !must_add_sequentially;
memset(item, 0, sizeof(struct item));
item->nameid = item_data->nameid;
item->identify = 1;
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 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(env_t *env, int args_nr, val_t *result, val_t *args)
{
character_t *chr = (ETY(0) == BL_PC) ? ARGPC(0) : NULL;
int stackable;
struct item item;
int i;
int retval = 0;
GET_ARG_ITEM(1, item, stackable);
if (!chr)
return 1;
for (i = 0; i < 11; i++)
if (chr->equip_index[i] >= 0
&& chr->status.inventory[chr->equip_index[i]].nameid == item.nameid) {
retval = i + 1;
break;
}
RESULTINT = retval;
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_is_dead(env_t *env, int args_nr, val_t *result, val_t *args)
{
RESULTINT = (ETY(0) == BL_PC
&& pc_isdead(ARGPC(0)));
return 0;
}
static int
fun_is_pc(env_t *env, int args_nr, val_t *result, val_t *args)
{
RESULTINT = (ETY(0) == BL_PC);
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 + dx, loc->y + dy)) {
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 && ETY(0) != BL_MOB)
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;
}
static int
fun_index(env_t *env, int args_nr, val_t *result, val_t *args)
{
RESULTINT = ARGSPELL(0)->index;
return 0;
}
static int
fun_is_exterior(env_t *env, int args_nr, val_t *result, val_t *args)
{
RESULTINT = map[ARGLOCATION(0).m].name[4] == '1';
return 0;
}
static int
fun_contains_string(env_t *env, int args_nr, val_t *result, val_t *args)
{
RESULTINT = NULL != strstr(ARGSTR(0), ARGSTR(1));
return 0;
}
static int
fun_strstr(env_t *env, int args_nr, val_t *result, val_t *args)
{
char *offset = strstr(ARGSTR(0), ARGSTR(1));
RESULTINT = offset - ARGSTR(0);
return offset == NULL;
}
static int
fun_strlen(env_t *env, int args_nr, val_t *result, val_t *args)
{
RESULTINT = strlen(ARGSTR(0));
return 0;
}
static int
fun_substr(env_t *env, int args_nr, val_t *result, val_t *args)
{
const char *src = ARGSTR(0);
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;
RESULTSTR = (char *) calloc(1, 1 + len);
memcpy(RESULTSTR, src + offset, len);
return 0;
}
static int
fun_sqrt(env_t *env, int args_nr, val_t *result, val_t *args)
{
RESULTINT = (int) sqrt(ARGINT(0));
return 0;
}
static int
fun_map_level(env_t *env, int args_nr, val_t *result, val_t *args)
{
RESULTINT = map[ARGLOCATION(0).m].name[4] - '0';
return 0;
}
static int
fun_map_nr(env_t *env, int args_nr, val_t *result, val_t *args)
{
const char *mapname = map[ARGLOCATION(0).m].name;
RESULTINT = ((mapname[0] - '0') * 100)
+ ((mapname[1] - '0') * 10)
+ ((mapname[2] - '0'));
return 0;
}
static int
fun_dir_towards(env_t *env, int args_nr, val_t *result, 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(env_t *env, int args_nr, val_t *result, val_t *args)
{
character_t *sd = (ETY(0) == BL_PC) ? ARGPC(0) : NULL;
if (!sd)
RESULTINT = 0;
else
RESULTINT = pc_extract_healer_exp(sd, ARGINT(1));
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 },
{ "neg", "i", 'i', fun_neg },
{ "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(mdef),
BATTLE_RECORD(def),
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 },
{ "is_equipped", "e.", 'i', fun_is_equipped },
{ "spell_index", "S", 'i', fun_index },
{ "is_exterior", "l", 'i', fun_is_exterior },
{ "contains_string", "ss", 'i', fun_contains_string },
{ "strstr", "ss", 'i', fun_strstr },
{ "strlen", "s", 'i', fun_strlen },
{ "substr", "sii", 's', fun_substr },
{ "sqrt", "i", 'i', fun_sqrt },
{ "map_level", "l", 'i', fun_map_level },
{ "map_nr", "l", 'i', fun_map_nr },
{ "dir_towards", "lli", 'd', fun_dir_towards },
{ "is_dead", "e", 'i', fun_is_dead },
{ "is_pc", "e", 'i', fun_is_pc },
{ "extract_healer_experience", "ei", 'i', fun_extract_healer_xp },
{ 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 */
case TY_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 != 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;
}
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 == TY_INVOCATION) {
invocation_t *t = (invocation_t *) map_id2bl(v.v.v_int);
if (!t)
dest->ty = TY_UNDEF;
else {
env_t *env = t->env;
val_t v = VAR(id);
magic_copy_var(dest, &v);
}
} else {
fprintf(stderr, "[magic] Attempt to access field %s on non-spell\n", env->base_env->var_name[id]);
dest->ty = TY_FAIL;
}
break;
}
default:
fprintf(stderr, "[magic] 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;
}