summaryrefslogblamecommitdiff
path: root/src/map/magic-expr.c
blob: a0a4935038b00928727135c24a88200481470a02 (plain) (tree)
1
2
3
4
5
6
7
8
9




                            

                              

                                          
                                      
 
                                              



               
                                     
 

               
 

                     
                        





                                           
 
                

 
                                       
 
                                                        
                    
 

                     
                        





                                                                   
 
                  

 
                                               
 












                                                         
 

 












                                    

 
                                            
 

                         
                   
                                                                     
                    
                                                      
                    
                                                      
                     



                                                              
                      
                            
                      
                                                                     
                

                                     

 
                                                
 




                                                                             
 

                  
                      

                                   

                    
                                      

                                            

                       
                   

                    

                                            

                       

                                                       

                         
                                        


                                                                          

                     


                                    

                      







                                                                              
         
                  

                



                                                                             
 

                        

 
                              
 

                        
 


                        

 
                         
 

                               

                    

 
                                                       
 




                                                                           




                                                         
                                 
 

                             
                                                     




                                     

 
                                     
 






                                                             

 










                                                                                             
         
     

 
                                                                           
 




























                                                                            

 
                                                                           
 

                                        

 
                                                                           
 









                                                                           

 
                                                                           
 



                                                      

 
                                                                          
 

                                         

 
                                                                           
 

                                         

 
                                                                           
 

                            

 
                                                                           
 

                            

 














                                                                           

 














                                                                          

 




























                                                                                        

 
                                                                              
 

                                        

 
                                                                             
 

                                        

 
                                                                              
 

                                        

 
                                                                              
 

                                         

 
                                                                              
 

                                         

 
                                                                           
 

                                             

 
                                                                           
 

                                             


          
                                                                         
 




                                          


    

                                                                 
 



                        

                           





                                 

                       
















                                              
                           




                                            

                           




                                             

                           




                                            

                           




                                             

                        






                                                                                        
         
     

 
                                                               
 

                     
                        

                                                                        

                           







                                                                     

                


                                                               

 
                                                                             
 



                                                                        

 
                                                                             
 







                                                                


          
                                                                       
 

                                                                     



                                                                                                                                                                     











                       





                                                                                                                 
                           
 

                    

          

















                                                                    

 




                                                                                                       
                                                                


             


                                                                                            
                                                                     
 

                                                     

 







                                                                              
                 





                               


          
                                                                       
 




                                             


          
                                                                        
 

                                  

 

                                                                                       
 

                                
 





                                                       
 

                   
 
                                                                                                                                               
 

                                            
 


                                           
 
             

 
          
                                                                       
 


                                                             
 
                                      
 

                 
 

                                                      

 
          
                                                                        
 




                                                             
 
                                      
 

                 
 







                                                                   
 

                       
 

          
                                                                       
 

                                                                   


          
                                                                    
 

                                                            

 
                                                                             
 

                                   


          
                                                                    
 








                                                                         


          










                                                                          
 

                          

 
                                                                              
 

                                  

 
                                                                           
 

                                                         

 
                                                                          
 

                                                         


          
                                                                     
 





                                                                      

 
          










                                                                      

 
                                                                              
 
                                                               
 

                 
 
                                               
 





                                 
 
             

 
          
                                                                          
 
                    
 

                                        
 
                                                 
 
             

 
                                                             
 









                                                                 



                           


































                                                                                 
                 



                                                                                                                                                  
 



                        


                

                                                                 


          
                                                                          
 

                                                               


          
                                                                            
 

                                        
 

                                 
 

                                                                       

 
                                                                            
 

                                     
 





                                                 
 
             


          

                                                                    
 

                                              
 

                                                                           


          
                                                                          
 



                                                       


          
                                                                    
 

                                                        


          
                                                                          
 

                                                        

 
                                                                             
 

                                    

 
          
                                                                        
 

                                                      


          
                                                                            
 

                                                        

 
                                                                              
 


                                                   

 
                                                                              
 

                                    

 
                                                                              
 



                                  
 



                   
 

                      
 

                            
 

                                             
 
             

 
                                                                            
 

                                        

 
          
                                                                      
 

                                                     

 
                                                                              
 
                                                      
 


                                                           


          


























































                                                                        
         
     
 
             

 
          
                                                                              
 
                                                            
 




                                                           

 


                                                                       




































                                                   
                                     





































                                                                    



                                    
                                                  
 
                                                                 

 
                                                   
 


                            
 


                              
 


                                      
 











                                                                              

 

                                                                   
 



                                  
 



















                                                              
 

                                                       
                                                      











                                                                    
                            
 















                                                                         
                 




                                                                             

         

























                                                                          

         





























                                                                        
                            
             
         
 






























                                                                      


   























                                                                            
 






                                                                                   
 

                                                                                                  
 

                                                                 
 






                                                                                  
 




















                                                                                           
 







                                                                                                   
         
     
 
             

 
                                                          
 

                     
                      

                                                  

                           




                                                                              

                       































                                                                          
                                           
                 
             
 


                                                

         




                                         

         
















                                                                         
                 








                                                                             

         
                




                                                                            

 
                                               
 

                                    
 

                                                      
 
                     
 
                          

 
                                                 
 

                                    
 

                                                      
 
                           
 
                             

 
                               
 


                                                       
 
#include "magic-expr.h"
#include "magic-expr-eval.h"
#include "itemdb.h"
#include <math.h>

#include "../common/mt_rand.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 = (area_t *)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_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 = (char *)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 = (char *) 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;
    CREATE (retval, 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 = (char *)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;
}

/* [Freeyorp] I'm putting this one in as name_of seems to have issues with summoned or spawned mobs. */
static int
fun_mob_id (env_t * env, int args_nr, val_t * result, val_t * args)
{
    if (ETY (0) != BL_MOB) return 1;
    RESULTINT = ((struct mob_data *) (ARGENTITY(0)))->mob_class;
    return 0;
}

#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;
}

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 = MRAND (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 = mt_random () & 0x7;
    else
        RESULTDIR = (mt_random () & 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 = MRAND (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 += MRAND (w);
            y += MRAND (h);

            if (!map_is_solid (m, x, y))
            {
                int  start_x = x;
                int  start_y = y;
                int  i;
                int  initial_dir = mt_random () & 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_status_option (env_t * env, int args_nr, val_t * result, val_t * args)
{
    RESULTINT =
        ((((struct map_session_data *) ARGENTITY (0))->
          status.option & ARGINT (0)) != 0);
    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},
    {"mob_id", "e", 'i', fun_mob_id},
    {"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},
    {"status_option", "ei", 'i', fun_status_option},
    {"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 (const 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 = (area_t *)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;
}