summaryrefslogblamecommitdiff
path: root/src/scripting/lua.cpp
blob: c6a75d9042b9618a4669779ea8d6631afd530827 (plain) (tree)
1
2
3
4
5
6
7
8
  
                   
                                                            
                                           
  
                                         
  
                                                                           



                                                                        
                                                                      




                                                                     
                                                                            

   
 






                    
                           

















                                           

                                      



                                
                                    
                         
                            
 
                   
                 
 



                                                                   
                                      

   









                                                                  
   






















                                                                              



























                                                           



















































































                                                                   




















                                                                     
   
                                                   
                                      
   
                                    



                                      

                                                
     
                                                                             


                                      
                                     
                          
                                
             


   
                                                     
                                     
   
                                   


                                      
                 
     
                                                                            


                                     
                                     

                                                           













                                                         

                                                                   





                              
         
                                                                                

                     
     



                                
   
                                                                               
                                      


                                        



                                      

                                                       


                                     
                                     



                                  

                                          
 


                                

                                



             
                                              
                                     






                                       
                                                                                


                                     
                                     
                                




             
                                                           
                                                                          
   
                                   
 

                                              


                                           
 
                             
                                  

                                    


                                  
                                                                


                 
                                
                                



                                



                                                                   

                                

                                      




                                                                         

                                    
                                     
                                
             

 
   
                                        
                                   
   
                                 





                                      
                                                                          



                                   
                                     





                                
                              

                                             
                                   




                          
                                    





             

                               
   
                                    











                             
                                                             
                                                  
   
                                 
 


                                

                                      
                                                               
     
                                                                          




                    
                                   
     
                                


                                                    



                                                   

           
                                                                        

                 
 






                                                                         

          

                                          
                                             




                                       


             
   





































                                                                          
                                 






































































                                                                             
                                 























                                                                      

                                                          





                                                                               



                                                                              
                                                                     
   
                                       



                                      
                                                                                


                                           
                     

                                      

                                                                          
         

                                                             

                     
 
                                             


                                       
         







                                                                        
         











                                                                               
         

                                                                        


                                     

                                                                                






                                    


                                                    
                                               







                          

                                                                       
                                            
                                                                
   
                                      

                                      
                                                           
     
                                                                               

                 




                                           
                     

                   
                                          
     












                                                                    

                    
                                                       
                                                                    
                                  


            
                                                         

                                   



                    
   




































































                                                                               






























































                                                                                 
                                            








                                                              
                 






                                       
                                                                
                                                    




                                                                          
   
                                  


                                      
                                         
     
































                                                                       

                                                                 



                                  
     


                    




                                

                                                            
                        

                                  
         
 



                                      


















                                                                         
             

                                                                             
                            

                                      
             



                                            

                          

                                              

                      
















                              

 
   

                                                                           
   
                                           
 



                                         
     

                                                        


                                  
                                       
             


   

                                                 


                                            


                                       
     

                                                         


                                  
                                  
             


   

                                                       


                                         


                                       
     

                                                       


                                  
                                                   


             
   

                                                    


                                              


                                       
     

                                                           


                                  
                                                       



             

                                                             


                                              



                                         
     

                                                           


                                  
                                         
             

 
   

                                             
   
                                   
 
                              





                                                                            

                 



                                         
 
   


                                                         
   
                                   
 

                                      
 
                                  
                                       

                           

                                                                     
                                                                      

                                                                   




             

                                                      
   
                                  
 


                                                 





                                                                           
 
                                 

                                             


        






                                                                           

   


                                                                            
   
                                     


                                  


                           




                                                                  
                             



             
   


                                              



























                                                              
   

                                                            
   
                                                 
 



                                  

                                               
         

                                                          
         







                                                                          

                                                                








                                                     

                                                                  
                     
         

                                                                              

     



             

                                                                           















                                                 

                                                                           
                                            























                                                                 




                                                                            















                                                        


   

                                      
   
                                       
 




                                                    
                 
     
                                                                       
             


   

                                             


                                         




                                               
                 
     
                                                                         
             


   

                                                   


                                         





                                  
                                            
     



                                                                             
             


   

                                                   


                                            




                                                  
                 
     
                                                                            
             


   

                                                         


                                            

                                  
                                                              




                                 
                                                                            
             


   

                                                                    
   
                             
 
                                  
 





                                       
                                                             
             


   

                                                                    
   
                             
 
                                  
 





                                       
                                                             
             

 
















                                                              
   
                                                                     
                                                      
   
                                       
 

                                      
 
                                             

           
                                                                    

                 
 

                           
     





















                                                                              


                                   
                 
                                
                                
 

                                
 

   

















                                                             





                                                         
                                  
















                                                          
                                              




                                                       
                                
                                  

          

                              
     
                                       



             
                                                        

                                                                       
   
                                      

                                      

           

                                                              
                 

     

                                           
     
                                                                       








                                           
                                                                     



                                
   
                                  
                                               
   
                                   







                                                                       
                                                               





                                     

                                               
   
                                   



                                           
                                                                       


                 
                        
                                           
                                                    




             
                                    
                                                  
   
                                     














                                                                         

                                                  
   
                                     














                                                                          
   
                                                       
                                         
   
                                      

                                      


                                           
     
                                                                               

                 




                                                                               



                         
   

                                                           

                                                                             
   
                                       
 



                                           
                                                              



                                                  




                                                                                
                                  
                                    
 


                                                                            










                                                                            
                                                            





                                       
   


                                                                            
   
                                     



                                                                          
                                                       
 
                                      



                                                   

        
                                                                              





                 
   
                                                                       
                                                                             
                                                          
                                                
   
                                             
 














                                            
 
                                             




                                                           
                                                                            




                                                                            
                                                                           
                                                            










                                                     
 
   



                                                            







                                                
                                             






















                                                                            

                                      
   
                                     






                                          
                                                                           
                                   






             
                                    

                                                                         
   
                                       


                                                         
                                                                                


                 


                                  
                                                                            


                 
                                                          


             
   


                                                          
   
                                      
 


                                                     
                                     



                                                                               
 
                                             
 

















                                                                              



             
   


                                                                   





                                         

                                                       


                 


                                      
                                





                                                           
                                            
 





                              
   
                                               
                                                        
   
                                    



                                      
                                                                             


                 

                                            




                            
   

                                                           


                                                             
   
                                     



                                      
                                                                              


                 


                                                  
 
                                                   



             
   

                                                               





                                           

                                                        


                 
                                          

                  

                                                                



                           
                                                




             

                                                      





                                           

                                                        


                 
                                          



             

                                                                   





                                           

                                                        


                 
                                          

                  

                                                                



                           
                                                




             

                                                      





                                           

                                                        


                 
                                          

             

   

                                                             





                                           

                                                        


                 
                                       
 
                                            


             
   

                                     
   
                                         
 

                              




                                                                                
                                       

             

   

                                                  
   
                                         
 

                              
     
                                                                                  



                                               
                                    




             

                                                       






                                                                 

                                                       

                 
                                            





                            

                                                      








                                                                                 
                                            





                                               

                                                               








                                                                                  
                                            





                                               
   
                                       
                                           













                                                                                
                                     
                                                                   
   
                                      
 

                                                      


             




                                                                              
                                      


                          

                                     


                                           
                                   





                                        


















                                              





                                                       
                                    






                                  
             

 
   

                                     
   
                                   
 
                                             





                                                                


                           
 
   

                                                 



                                                  
                                             













                                                                      
                                       






                                                    
                                             















                                                                        








                                                     
   
                                                                          
                                      
                                                 


                                  

                                      
                                            
 





                                                            

            

                                                                              


                                   
                                               



                        

                                                     

 
   
















                                                                          

                                                    












                                                                             
 
   


                                                         







                                       




                                                                           



















                                                   







                                                                  
                       
                 
     
                                        
     
 

                                                                       
























                                                                          
                                             



                                                 
     

                                            


        
                                    









                                              
                                               













                                               
                                               









                                              
                                               



                                              







                                                              
















                                                        


                                                                              

                                              











                                                                
                       

              
                             
                          
 







                                                                     
                                                               
                                         

                                                                                



                                                                                











                                                                                


                                                                                







                                                                                
                                                                                

                                                                                
                                                                                

                                                                                

                                                                                
                                                                                

                                                                                



                                                                                








                                                                                

                                                                                




                                                                                
                                                                                
                                                                                
                                                                                






















                                                                                
                                                                                
                                                                                
                                                                                

                                                                                
                                                                                

                                                                                
                                                                                
                                                                                
                                                                                
                                                                                


                                                                                
                                                                                
                                                                                
                                                                                
                                                                                

                      
                                             
                                                                   
 




                                                                                







                                                                                











                                                                                
                                                                       

                                                                                
 
                                                          
                                                                    
                                        
                                          
 




                                                                       
                                        
 
/*
 *  The Mana Server
 *  Copyright (C) 2007-2010  The Mana World Development Team
 *  Copyright (C) 2010  The Mana Developers
 *
 *  This file is part of The Mana Server.
 *
 *  The Mana Server is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  The Mana Server is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with The Mana Server.  If not, see <http://www.gnu.org/licenses/>.
 */


#include <cassert>

extern "C" {
#include <lualib.h>
#include <lauxlib.h>
}

#include "common/defines.h"
#include "common/resourcemanager.h"
#include "game-server/accountconnection.h"
#include "game-server/buysell.h"
#include "game-server/character.h"
#include "game-server/collisiondetection.h"
#include "game-server/effect.h"
#include "game-server/gamehandler.h"
#include "game-server/inventory.h"
#include "game-server/item.h"
#include "game-server/itemmanager.h"
#include "game-server/mapcomposite.h"
#include "game-server/mapmanager.h"
#include "game-server/monster.h"
#include "game-server/monstermanager.h"
#include "game-server/npc.h"
#include "game-server/postman.h"
#include "game-server/quest.h"
#include "game-server/state.h"
#include "game-server/statuseffect.h"
#include "game-server/statusmanager.h"
#include "game-server/trigger.h"
#include "net/messageout.h"
#include "scripting/luautil.h"
#include "scripting/luascript.h"
#include "scripting/scriptmanager.h"
#include "utils/logger.h"
#include "utils/speedconv.h"

#include <string.h>
#include <math.h>

/*
 * This file includes all script bindings available to LUA scripts.
 * When you add or change a script binding please document it on
 *
 * http://doc.manasource.org/scripting
 */

static Script *getScript(lua_State *s)
{
    lua_pushlightuserdata(s, (void *)&registryKey);
    lua_gettable(s, LUA_REGISTRYINDEX);
    Script *script = static_cast<Script *>(lua_touserdata(s, -1));
    lua_pop(s, 1);
    return script;
}


/**
 * mana.on_character_death( function(Character*) ): void
 * Sets a listener function to the character death event.
 */
static int on_character_death(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    Character::setDeathCallback(getScript(s));
    return 0;
}

/**
 * mana.on_character_death_accept( function(Character*) ): void
 * Sets a listener function that is called when the player clicks on the OK
 * button after the death message appeared. It should be used to implement the
 * respawn mechanic.
 */
static int on_character_death_accept(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    Character::setDeathAcceptedCallback(getScript(s));
    return 0;
}

static int on_npc_quest_reply(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    LuaScript::setQuestReplyCallback(getScript(s));
    return 0;
}

static int on_npc_post_reply(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    LuaScript::setPostReplyCallback(getScript(s));
    return 0;
}

static int on_being_death(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    LuaScript::setDeathNotificationCallback(getScript(s));
    return 0;
}

static int on_being_remove(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    LuaScript::setRemoveNotificationCallback(getScript(s));
    return 0;
}

static int on_update(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    Script::setUpdateCallback(getScript(s));
    return 0;
}

static int on_npc_start(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    NPC::setStartCallback(getScript(s));
    return 0;
}

static int on_npc_next(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    NPC::setNextCallback(getScript(s));
    return 0;
}

static int on_npc_choose(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    NPC::setChooseCallback(getScript(s));
    return 0;
}

static int on_npc_integer(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    NPC::setIntegerCallback(getScript(s));
    return 0;
}

static int on_npc_string(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    NPC::setStringCallback(getScript(s));
    return 0;
}

static int on_npc_update(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    NPC::setUpdateCallback(getScript(s));
    return 0;
}

static int on_create_npc_delayed(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    Script::setCreateNpcDelayedCallback(getScript(s));
    return 0;
}

static int on_map_initialize(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    MapComposite::setInitializeCallback(getScript(s));
    return 0;
}

static int on_craft(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    ScriptManager::setCraftCallback(getScript(s));
    return 0;
}

static int on_use_special(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    ScriptManager::setSpecialCallback(getScript(s));
    return 0;
}

static int on_get_special_recharge_cost(lua_State *s)
{
    luaL_checktype(s, 1, LUA_TFUNCTION);
    ScriptManager::setGetSpecialRechargeCostCallback(getScript(s));
    return 0;
}

static int get_item_class(lua_State *s)
{
    const char *name = luaL_checkstring(s, 1);
    LuaItemClass::push(s, itemManager->getItemByName(name));
    return 1;
}

static int get_monster_class(lua_State *s)
{
    const char *name = luaL_checkstring(s, 1);
    LuaMonsterClass::push(s, monsterManager->getMonsterByName(name));
    return 1;
}

static int get_status_effect(lua_State *s)
{
    const char *name = luaL_checkstring(s, 1);
    LuaStatusEffect::push(s, StatusManager::getStatusByName(name));
    return 1;
}

/**
 * mana.npc_message(NPC*, Character*, string): void
 * Callback for sending a NPC_MESSAGE.
 */
static int npc_message(lua_State *s)
{
    NPC *p = getNPC(s, 1);
    Character *q = getCharacter(s, 2);
    size_t l;
    const char *m = luaL_checklstring(s, 3, &l);
    if (!p || !q)
    {
        raiseScriptError(s, "npc_message called with incorrect parameters.");
        return 0;
    }
    MessageOut msg(GPMSG_NPC_MESSAGE);
    msg.writeInt16(p->getPublicID());
    msg.writeString(m, l);
    gameHandler->sendTo(q, msg);
    return 0;
}

/**
 * mana.npc_choice(NPC*, Character*, string...): void
 * Callback for sending a NPC_CHOICE.
 */
static int npc_choice(lua_State *s)
{
    NPC *p = getNPC(s, 1);
    Character *q = getCharacter(s, 2);
    if (!p || !q)
    {
        raiseScriptError(s, "npc_choice called with incorrect parameters.");
        return 0;
    }
    MessageOut msg(GPMSG_NPC_CHOICE);
    msg.writeInt16(p->getPublicID());
    for (int i = 3, i_end = lua_gettop(s); i <= i_end; ++i)
    {
        if (lua_isstring(s, i))
        {
            msg.writeString(lua_tostring(s, i));
        }
        else if (lua_istable(s, i))
        {
            lua_pushnil(s);
            while (lua_next(s, i) != 0) {
                if (lua_isstring(s, -1))
                {
                    msg.writeString(lua_tostring(s, -1));
                }
                else
                {
                    raiseScriptError(s, "npc_choice called "
                                     "with incorrect parameters.");
                    return 0;
                }
                lua_pop(s, 1);
            }
        }
        else
        {
            raiseScriptError(s, "npc_choice called with incorrect parameters.");
            return 0;
        }
    }
    gameHandler->sendTo(q, msg);
    return 0;
}

/**
 * mana.npc_integer(NPC*, Character*, int min, int max, int defaut = min): void
 * Callback for sending a NPC_INTEGER.
 */
static int npc_ask_integer(lua_State *s)
{
    NPC *p = getNPC(s, 1);
    Character *q = getCharacter(s, 2);
    if (!p || !q)
    {
        raiseScriptError(s, "npc_ask_integer called "
                         "with incorrect parameters.");
        return 0;
    }
    MessageOut msg(GPMSG_NPC_NUMBER);
    msg.writeInt16(p->getPublicID());

    int min = lua_tointeger(s, 3);
    int max = lua_tointeger(s, 4);
    int default_num = min;
    if (lua_gettop(s) == 5)
        default_num = lua_tointeger(s, 5);

    msg.writeInt32(min);
    msg.writeInt32(max);
    msg.writeInt32(default_num);
    gameHandler->sendTo(q, msg);

    return 0;
}

/**
 * mana.npc_ask_string(NPC*, Character*): void
 * Callback for sending a NPC_STRING.
 */
static int npc_ask_string(lua_State *s)
{
    NPC *p = getNPC(s, 1);
    Character *q = getCharacter(s, 2);
    if (!p || !q)
    {
        raiseScriptError(s, "npc_ask_string called with incorrect parameters.");
        return 0;
    }
    MessageOut msg(GPMSG_NPC_STRING);
    msg.writeInt16(p->getPublicID());
    gameHandler->sendTo(q, msg);

    return 0;
}

/**
 * mana.npc_create(string name, int id, int x, int y): NPC*
 * Callback for creating a NPC on the current map with the current script.
 */
static int npc_create(lua_State *s)
{
    const char *name = luaL_checkstring(s, 1);
    const int id = luaL_checkint(s, 2);
    const int gender = luaL_checkint(s, 3);
    const int x = luaL_checkint(s, 4);
    const int y = luaL_checkint(s, 5);

    Script *t = getScript(s);
    NPC *q = new NPC(name, id, t);
    q->setGender(getGender(gender));

    MapComposite *m = t->getMap();
    if (!m)
    {
        raiseScriptError(s, "npc_create called outside a map.");
        return 0;
    }
    q->setMap(m);
    q->setPosition(Point(x, y));
    GameState::enqueueInsert(q);
    lua_pushlightuserdata(s, q);
    return 1;
}

/**
 * mana.npc_end(NPC*, Character*): void
 * Callback for ending a NPC conversation with the given character.
 */
static int npc_end(lua_State *s)
{
    NPC *p = getNPC(s, 1);
    Character *q = getCharacter(s, 2);
    if (!p || !q)
    {
        raiseScriptError(s, "npc_end called with incorrect parameters.");
        return 0;
    }

    MessageOut msg(GPMSG_NPC_CLOSE);
    msg.writeInt16(p->getPublicID());
    gameHandler->sendTo(q, msg);
    return 0;
}

/**
 * mana.npc_post(NPC*, Character*): void
 * Callback for sending a NPC_POST.
 */
static int npc_post(lua_State *s)
{
    NPC *p = getNPC(s, 1);
    Character *q = getCharacter(s, 2);

    if (!p || !q)
    {
        raiseScriptError(s, "npc_post called with incorrect parameters.");
        return 0;
    }

    MessageOut msg(GPMSG_NPC_POST);
    msg.writeInt16(p->getPublicID());
    gameHandler->sendTo(q, msg);

    return 0;
}

/**
 * mana.npc_enable(NPC*): void
 * Enable a NPC if it has previously disabled
 */
static int npc_enable(lua_State *s)
{
    NPC *p = getNPC(s, 1);
    if (p)
    {
        p->enable(true);
        GameState::enqueueInsert(p);
    }

    return 0;
}

/**
 * mana.npc_disable(NPC*): void
 * Disable a NPC.
 */
static int npc_disable(lua_State *s)
{
    NPC *p = getNPC(s, 1);
    if (p)
    {
        p->enable(false);
        GameState::remove(p);
    }

    return 0;
}

/**
 * mana.chr_warp(Character*, nil/int map, int x, int y): void
 * Callback for warping a player to another place.
 */
static int chr_warp(lua_State *s)
{
    int x = luaL_checkint(s, 3);
    int y = luaL_checkint(s, 4);

    Character *q = getCharacter(s, 1);
    bool b = lua_isnil(s, 2);
    if (!q || !(b || lua_isnumber(s, 2) || lua_isstring(s, 2)))
    {
        raiseScriptError(s, "chr_warp called with incorrect parameters.");
        return 0;
    }
    MapComposite *m;
    if (b)
    {
        m = getScript(s)->getMap();
    }
    else if (lua_isnumber(s, 2))
    {
        m = MapManager::getMap(lua_tointeger(s, 2));
    }
    else
    {
        m = MapManager::getMap(lua_tostring(s, 2));
    }
    if (!m)
    {
        raiseScriptError(s, "chr_warp called with a non-existing map.");
        return 0;
    }

    Map *map = m->getMap();

    // If the wanted warp place is unwalkable
    if (!map->getWalk(x / map->getTileWidth(), y / map->getTileHeight()))
    {
        int c = 50;
        LOG_INFO("chr_warp called with a non-walkable place.");
        do
        {
            x = rand() % map->getWidth();
            y = rand() % map->getHeight();
        } while (!map->getWalk(x, y) && --c);
        x *= map->getTileWidth();
        y *= map->getTileHeight();
    }
    GameState::enqueueWarp(q, m, x, y);

    return 0;
}

/**
 * Callback for gathering inventory information.
 * mana.chr_get_inventory(character): table[]{slot, item id, name, amount}
 * Returns in the inventory slots order, the slot id, the item ids,
 * name and amount. Only slots not empty are returned.
 * @Example
 * To get a piece of information, you can do something like this:
 * -- This will print the 2nd non-empty slot id.
 * local my_table = mana.chr_get_inventory(character)
 * print(my_table[2].slot)
 */
static int chr_get_inventory(lua_State *s)
{
    Character *q = getCharacter(s, 1);
    if (!q)
    {
        raiseScriptError(s, "chr_get_inventory"
                         " called with incorrect parameters.");
        return 0;
    }

    // Create a lua table with the inventory ids.
    const InventoryData invData = q->getPossessions().getInventory();

    lua_newtable(s);
    int firstTableStackPosition = lua_gettop(s);
    int tableIndex = 1;

    std::string itemName = "";

    for (InventoryData::const_iterator it = invData.begin(),
        it_end = invData.end(); it != it_end; ++it)
    {
        if (!it->second.itemId || !it->second.amount)
            continue;

        lua_pushinteger(s, tableIndex);

        // Create the sub-table (value of the main one)
        lua_createtable(s, 0, 4);
        int subTableStackPosition = lua_gettop(s);
        // Stores the item info in it.
        lua_pushstring(s, "slot");
        lua_pushinteger(s, it->first); // The slot id
        lua_settable(s, subTableStackPosition);

        lua_pushstring(s, "id");
        lua_pushinteger(s, it->second.itemId);
        lua_settable(s, subTableStackPosition);

        lua_pushstring(s, "name");
        itemName = itemManager->getItem(it->second.itemId)->getName();
        lua_pushstring(s, itemName.c_str());
        lua_settable(s, subTableStackPosition);

        lua_pushstring(s, "amount");
        lua_pushinteger(s, it->second.amount);
        lua_settable(s, subTableStackPosition);

        // Add the sub-table as value of the main one.
        lua_settable(s, firstTableStackPosition);
        ++tableIndex;
    }

    return 1;
}

/**
 * Callback for gathering equiupment information.
 * mana.chr_get_inventory(character): table[](slot, item id, name)
 * Returns in the inventory slots order, the slot id, the item ids, and name.
 * Only slots not empty are returned.
 * @Example
 * To get a piece of information, you can do something like this:
 * -- This will print the 2nd non-empty slot id.
 * local my_table = mana.chr_get_equipment(character)
 * print(my_table[2].slot)
 */
static int chr_get_equipment(lua_State *s)
{
    Character *q = getCharacter(s, 1);
    if (!q)
    {
        raiseScriptError(s, "chr_get_equipment"
                         " called with incorrect parameters.");
        return 0;
    }

    // Create a lua table with the inventory ids.
    const EquipData equipData = q->getPossessions().getEquipment();

    lua_newtable(s);
    int firstTableStackPosition = lua_gettop(s);
    int tableIndex = 1;

    std::string itemName = "";
    std::set<unsigned> itemInstances;

    for (EquipData::const_iterator it = equipData.begin(),
        it_end = equipData.end(); it != it_end; ++it)
    {
        if (!it->second.itemId || !it->second.itemInstance)
            continue;

        // Only count multi-slot items once.
        if (!itemInstances.insert(it->second.itemInstance).second)
            continue;

        lua_pushinteger(s, tableIndex);

        // Create the sub-table (value of the main one)
        lua_createtable(s, 0, 3);
        int subTableStackPosition = lua_gettop(s);
        // Stores the item info in it.
        lua_pushstring(s, "slot");
        lua_pushinteger(s, it->first); // The slot id
        lua_settable(s, subTableStackPosition);

        lua_pushstring(s, "id");
        lua_pushinteger(s, it->second.itemId);
        lua_settable(s, subTableStackPosition);

        lua_pushstring(s, "name");
        itemName = itemManager->getItem(it->second.itemId)->getName();
        lua_pushstring(s, itemName.c_str());
        lua_settable(s, subTableStackPosition);

        // Add the sub-table as value of the main one.
        lua_settable(s, firstTableStackPosition);
        ++tableIndex;
    }

    return 1;
}

/**
 * mana.chr_inv_change(Character*, (int id || string name,
 *                     int nb)...): bool success
 * Callback for inserting/removing items in inventory.
 * The function can be called several times in a row, but it is better to
 * perform all the changes at once, so as to reduce bandwidth. Removals
 * (negative amount) should be passed first, then insertions (positive amount).
 * If a removal fails, all the previous operations are canceled (except for
 * items dropped on the floor, hence why removals should be passed first), and
 * the function returns false. Otherwise the function will return true.
 * Note that previously when the item identifier was zero, money was modified;
 * however currency is now handled through attributes. This breaks backwards
 * compatibility with old scripts, and so logs a warning.
 * Note: If an insertion fails, extra items are dropped on the floor.
 */
static int chr_inv_change(lua_State *s)
{
    Character *q = getCharacter(s, 1);
    if (!q)
    {
        raiseScriptError(s, "chr_inv_change called with incorrect parameters.");
        return 0;
    }
    int nb_items = (lua_gettop(s) - 1) / 2;
    Inventory inv(q);
    for (int i = 0; i < nb_items; ++i)
    {
        if (!(lua_isnumber(s, i * 2 + 2) || lua_isstring(s, i * 2 + 2)) ||
            !lua_isnumber(s, i * 2 + 3))
        {
            raiseScriptError(s, "chr_inv_change called with "
                             "incorrect parameters.");
            return 0;
        }

        int nb = lua_tointeger(s, i * 2 + 3);
        ItemClass *ic;
        int id;
        if (lua_isnumber(s, i * 2 + 2))
        {
            int id = lua_tointeger(s, i * 2 + 2);
            if (id == 0)
            {
                LOG_WARN("chr_inv_change called with id 0! "
                         "Currency is now handled through attributes!");
                continue;
            }
            ic = itemManager->getItem(id);
        }
        else
        {
            ic = itemManager->getItemByName(lua_tostring(s, i * 2 + 2));
        }

        if (!ic)
        {
            raiseScriptError(s, "chr_inv_change called with an unknown item.");
            continue;
        }
        id = ic->getDatabaseID();
        if (nb < 0)
        {
            // Removing too much item is a success as for the scripter's
            // point of view. We log it anyway.
            nb = inv.remove(id, -nb);
            if (nb)
            {
                LOG_WARN("mana.chr_inv_change() removed more items than owned: "
                     << "character: " << q->getName() << " item id: " << id);
            }
        }
        else
        {
            nb = inv.insert(id, nb);
            if (nb)
            {
                Item *item = new Item(ic, nb);
                item->setMap(q->getMap());
                item->setPosition(q->getPosition());
                GameState::enqueueInsert(item);
            }
        }
    }
    lua_pushboolean(s, 1);
    return 1;
}

/**
 * mana.chr_inv_count(Character*, bool in inventory, bool in equipment,
 *                    int item_id...): int count...
 * Callback for counting items in inventory.
 * The function can search in inventory and/or in the equipment.
 */
static int chr_inv_count(lua_State *s)
{
    Character *q = getCharacter(s, 1);
    if (!q || !lua_isboolean(s, 2) || !lua_isboolean(s, 3))
    {
        raiseScriptError(s, "chr_inv_count called with incorrect parameters.");
        return 0;
    }

    bool inInventory = lua_toboolean(s, 2);
    bool inEquipment = lua_toboolean(s, 3);

    int nb_items = lua_gettop(s) - 3;
    Inventory inv(q);

    int id, nb = 0;
    for (int i = 4; i < nb_items + 4; ++i)
    {
        ItemClass *it;
        if (lua_isnumber(s, i))
            it = itemManager->getItem(lua_tointeger(s, i));
        else
            it = itemManager->getItemByName(lua_tostring(s, i));

        if (!it)
        {
            raiseScriptError(s, "chr_inv_count called with invalid "
                             "item id or name.");
            return 0;
        }
        id = it->getDatabaseID();
        if (id == 0)
        {
            LOG_WARN("chr_inv_count called with id 0! "
                     "Currency is now handled through attributes!");
            lua_pushinteger(s, 0);
        }
        else
        {
            nb = inv.count(id, inInventory, inEquipment);
            lua_pushinteger(s, nb);
        }
    }
    return nb_items;
}

/**
 * mana.chr_equip_slot(Character*, int inventorySlot): bool success
 * Makes the character equip the item in the given inventory slot.
 */
static int chr_equip_slot(lua_State *s)
{
    int inventorySlot = luaL_checkint(s, 2);

    Character *ch = getCharacter(s, 1);
    if (!ch)
    {
        raiseScriptError(s, "chr_equip_slot "
                         "called for nonexistent character.");
        return 0;
    }
    Inventory inv(ch);

    lua_pushboolean(s, inv.equip(inventorySlot));
    return 1;
}

/**
 * mana.chr_equip_item(Character*, int itemId || string itemName): bool success
 * Makes the character equip the item id when it's existing
 * in the player's inventory.
 */
static int chr_equip_item(lua_State *s)
{
    // Get the itemId
    ItemClass *it;
    if (lua_isnumber(s, 2))
        it = itemManager->getItem(lua_tointeger(s, 2));
    else
        it = itemManager->getItemByName(lua_tostring(s, 2));

    if (!it)
    {
        raiseScriptError(s, "chr_equip_item called with invalid "
                         "item id or name.");
        return 0;
    }
    unsigned int id = it->getDatabaseID();
    if (!id)
    {
        LOG_WARN("chr_equip_item called with id 0! "
                 "Currency is now handled through attributes!");
        lua_pushboolean(s, false);
        return 1;
    }

    Character *ch = getCharacter(s, 1);
    if (!ch)
    {
        raiseScriptError(s, "chr_equip_item "
                         "called for nonexistent character.");
        return 0;
    }
    Inventory inv(ch);

    int inventorySlot = inv.getFirstSlot(id);
    bool success = false;

    if (inventorySlot > -1)
        success = inv.equip(inventorySlot);

    lua_pushboolean(s, success);
    return 1;
}

/**
 * mana.chr_unequip_slot(Character*, int inventorySlot): bool success
 * Makes the character unequip the item in the given equipment slot.
 */
static int chr_unequip_slot(lua_State *s)
{
    int equipmentSlot = luaL_checkint(s, 2);

    Character *ch = getCharacter(s, 1);
    if (!ch)
    {
        raiseScriptError(s, "chr_unequip_slot "
                         "called for nonexistent character.");
        return 0;
    }
    Inventory inv(ch);

    lua_pushboolean(s, inv.unequip(inv.getSlotItemInstance(equipmentSlot)));
    return 1;
}

/**
 * mana.chr_unequip_item(Character*, int itemId || string itemName): bool success
 * Makes the character unequip the item(s) corresponding to the id
 * when it's existing in the player's equipment.
 * Returns true when every item were unequipped from equipment.
 */
static int chr_unequip_item(lua_State *s)
{
    // Get the itemId
    ItemClass *it;
    if (lua_isnumber(s, 2))
        it = itemManager->getItem(lua_tointeger(s, 2));
    else
        it = itemManager->getItemByName(lua_tostring(s, 2));

    if (!it)
    {
        raiseScriptError(s, "chr_unequip_item called with invalid "
                         "item id or name.");
        return 0;
    }
    unsigned int id = it->getDatabaseID();
    if (!id)
    {
        LOG_WARN("chr_unequip_item called with id 0! "
                 "Currency is now handled through attributes!");
        lua_pushboolean(s, false);
        return 1;
    }

    Character *ch = getCharacter(s, 1);
    if (!ch)
    {
        raiseScriptError(s, "chr_unequip_item "
                         "called for nonexistent character.");
        return 0;
    }
    Inventory inv(ch);
    lua_pushboolean(s, inv.unequipItem(id));
    return 1;
}

/**
 * mana.chr_get_level(Character*): int level
 * Tells the character current level.
 */
static int chr_get_level(lua_State *s)
{
    Character *ch = getCharacter(s, 1);
    if (!ch)
    {
        raiseScriptError(s, "chr_get_level "
                         "called for nonexistent character.");
        return 0;
    }

    lua_pushinteger(s, ch->getLevel());
    return 1;
}

/**
 * mana.npc_trade(NPC*, Character*, bool sell, table items): int
 * Callback for trading between a player and an NPC.
 * Let the player buy or sell only a subset of predeterminded items.
 * @param table items: a subset of buyable/sellable items.
 * When selling, if the 4 parameter is omitted or invalid,
 * everything in the player inventory can be sold.
 * @return 0 if something to buy/sell, 1 if no items, 2 in case of errors.
 */
static int npc_trade(lua_State *s)
{
    NPC *p = getNPC(s, 1);
    Character *q = getCharacter(s, 2);
    if (!p || !q || !lua_isboolean(s, 3))
    {
        raiseWarning(s, "npc_trade called with incorrect parameters.");
        lua_pushinteger(s, 2); // return value
        return 1; // Returns 1 parameter
    }

    bool sellMode = lua_toboolean(s, 3);
    BuySell *t = new BuySell(q, sellMode);
    if (!lua_istable(s, 4))
    {
        if (sellMode)
        {
            // Can sell everything
            if (!t->registerPlayerItems())
            {
                // No items to sell in player inventory
                t->cancel();
                lua_pushinteger(s, 1);
                return 1;
            }

            if (t->start(p))
            {
              lua_pushinteger(s, 0);
              return 1;
            }
            else
            {
              lua_pushinteger(s, 1);
              return 1;
            }
        }
        else
        {
            raiseWarning(s, "npc_trade[Buy] called with invalid "
                         "or empty items table parameter.");
            t->cancel();
            lua_pushinteger(s, 2);
            return 1;
        }
    }

    int nbItems = 0;

    lua_pushnil(s);
    while (lua_next(s, 4))
    {
        if (!lua_istable(s, -1))
        {
            raiseWarning(s, "npc_trade called with invalid "
                         "or empty items table parameter.");
            t->cancel();
            lua_pushinteger(s, 2);
            return 1;
        }

        int v[3];
        for (int i = 0; i < 3; ++i)
        {
            lua_rawgeti(s, -1, i + 1);
            if (i == 0) // item id or name
            {
                ItemClass *it;
                if (lua_isnumber(s, -1))
                    it = itemManager->getItem(lua_tointeger(s, -1));
                else
                    it = itemManager->getItemByName(lua_tostring(s, -1));

                if (!it)
                {
                    raiseWarning(s, "npc_trade called with incorrect "
                                 "item id or name.");
                    t->cancel();
                    lua_pushinteger(s, 2);
                    return 1;
                }
                v[0] = it->getDatabaseID();
            }
            else if (!lua_isnumber(s, -1))
            {
                raiseWarning(s, "npc_trade called with incorrect parameters "
                             "in item table.");
                t->cancel();
                lua_pushinteger(s, 2);
                return 1;
            }
            else
            {
                v[i] = lua_tointeger(s, -1);
            }
            lua_pop(s, 1);
        }
        if (t->registerItem(v[0], v[1], v[2]))
            nbItems++;
        lua_pop(s, 1);
    }

    if (nbItems == 0)
    {
        t->cancel();
        lua_pushinteger(s, 1);
        return 1;
    }
    if (t->start(p))
    {
      lua_pushinteger(s, 0);
      return 1;
    }
    else
    {
      lua_pushinteger(s, 1);
      return 1;
    }
}

/**
 * mana.being_apply_status(Being*, int id, int time): void
 * Applies a status effect with id to the being given for a amount of time.
 */
static int being_apply_status(lua_State *s)
{
    const int id = luaL_checkint(s, 2);
    const int time = luaL_checkint(s, 3);

    if (!lua_isuserdata(s, 1))
    {
        raiseScriptError(s, "being_apply_status called "
                         "with incorrect parameters.");
        return 0;
    }
    Being *being = getBeing(s, 1);
    being->applyStatusEffect(id, time);
    return 0;
}

/**
 * mana.being_remove_status(Being*, int id): void
 * Removes the given status effect.
 */
static int being_remove_status(lua_State *s)
{
    const int id = luaL_checkint(s, 2);

    if (!lua_isuserdata(s, 1))
    {
        raiseScriptError(s, "being_remove_status called "
                         "with incorrect parameters.");
        return 0;
    }
    Being *being = getBeing(s, 1);
    being->removeStatusEffect(id);
    return 0;
}

/**
 * mana.being_has_status(Being*, int id): bool
 * Returns whether a being has the given status effect.
 */
static int being_has_status(lua_State *s)
{
    const int id = luaL_checkint(s, 2);

    if (!lua_isuserdata(s, 1))
    {
        raiseScriptError(s, "being_has_status called "
                         "with incorrect parameters.");
        return 0;
    }
    Being *being = getBeing(s, 1);
    lua_pushboolean(s, being->hasStatusEffect(id));
    return 1;
}

/**
 * mana.being_get_status_time(Being*, int id): int
 * Returns the time left on the given status effect.
 */
static int being_get_status_time(lua_State *s)
{
    const int id = luaL_checkint(s, 2);

    if (!lua_isuserdata(s, 1))
    {
        raiseScriptError(s, "being_get_status_time called "
                         "with incorrect parameters.");
        return 0;
    }
    Being *being = getBeing(s, 1);
    lua_pushinteger(s, being->getStatusEffectTime(id));
    return 1;
}

/**
 * mana.being_set_status_time(Being*, int id, int time): void
 * Sets the time left on the given status effect.
 */
static int being_set_status_time(lua_State *s)
{
    const int id = luaL_checkint(s, 2);
    const int time = luaL_checkint(s, 3);

    if (!lua_isuserdata(s, 1))
    {
        raiseScriptError(s, "being_set_status_time called "
                         "with incorrect parameters.");
        return 0;
    }
    Being *being = getBeing(s, 1);
    being->setStatusEffectTime(id, time);
    return 0;
}

/**
 * mana.being_type(Being*): ThingType
 * Returns the Thing type of the given being.
 */
static int being_type(lua_State *s)
{
    if (!lua_isuserdata(s, 1))
    {
        raiseScriptError(s, "being_type called with incorrect parameters.");
        return 0;
    }

    Being *being = getBeing(s, 1);
    if (!being)
        return 0;
    lua_pushinteger(s, being->getType());
    return 1;
}


/**
 * being_walk(Being *, int x, int y[, float speed]): void
 * Function for making a being walk to a position.
 * The speed is in tile per second.
 */
static int being_walk(lua_State *s)
{
    const int x = luaL_checkint(s, 2);
    const int y = luaL_checkint(s, 3);

    Being *being = getBeing(s, 1);
    being->setDestination(Point(x, y));

    if (lua_isnumber(s, 4))
    {
        being->setAttribute(ATTR_MOVE_SPEED_TPS, lua_tonumber(s, 4));
        being->setAttribute(ATTR_MOVE_SPEED_RAW, utils::tpsToRawSpeed(
                being->getModifiedAttribute(ATTR_MOVE_SPEED_TPS)));
    }

    return 0;
}

/**
 * mana.being_say(Being* source, string message): void
 * Makes the being say something.
 */
static int being_say(lua_State *s)
{
    const char *message = luaL_checkstring(s, 2);

    if (!lua_isuserdata(s, 1))
    {
        raiseScriptError(s, "being_say called with incorrect parameters.");
        return 0;
    }

    Being *being = getBeing(s, 1);

    if (being && message[0] != 0)
    {
        GameState::sayAround(being, message);
    }
    else
    {
        raiseScriptError(s, "being_say called with incorrect parameters.");
        return 0;
    }

    return 0;
}


/**
 * mana.being_damage(Being* victim, int value, int delta, int cth, int type,
 *                   int element): void
 * Applies combat damage to a being.
 */
static int being_damage(lua_State *s)
{
    Being *being = getBeing(s, 1);

    if (!being->canFight())
        return 0;

    Damage dmg((unsigned short) lua_tointeger(s, 2), /* base */
               (unsigned short) lua_tointeger(s, 3), /* delta */
               (unsigned short) lua_tointeger(s, 4), /* cth */
               (unsigned char)  lua_tointeger(s, 6), /* element */
               DAMAGE_PHYSICAL);                     /* type */
    being->damage(NULL, dmg);

    return 0;
}

/**
 * mana.being_heal(Being* [, int value]): void
 * Restores hit points of a being.
 * Without a value the being is fully healed.
 */
static int being_heal(lua_State *s)
{
    Being *being = getBeing(s, 1);
    if (!being)
    {
        raiseScriptError(s,
            "being_heal called for nonexistent being.");
        return 0;
    }

    if (lua_gettop(s) == 1) // when there is only one argument
    {
        being->heal();
    }
    else if (lua_isnumber(s, 2))
    {
        being->heal(lua_tointeger(s, 2));
    }
    else
    {
        raiseScriptError(s,
            "being_heal called with illegal healing value.");
    }

    return 0;
}

/**
 * mana.being_get_base_attribute(Being*, int attribute): int
 * Gets the base attribute of a being.
 */
static int being_get_base_attribute(lua_State *s)
{
    Being *being = getBeing(s, 1);

    if (being)
    {
        lua_Integer attr = lua_tointeger(s, 2);
        if (attr)
        {
            lua_pushinteger(s, being->getAttribute(attr));
            return 1;
        }
        raiseScriptError(s,
            "being_get_base_attribute called with incorrect parameters.");
    }

    return 0;
}

/**
 * mana.being_get_modified_attribute(Being*, int attribute): int
 * Gets the modified attribute of a being.
 */
static int being_get_modified_attribute(lua_State *s)
{
    Being *being = getBeing(s, 1);

    if (being)
    {
        lua_Integer attr = lua_tointeger(s, 2);
        if (attr)
        {
            lua_pushinteger(s, being->getModifiedAttribute(attr));
            return 1;
        }
        raiseScriptError(s,
            "being_get_modified_attribute called with incorrect parameters.");
    }

    return 0;
}

/**
 * mana.being_set_base_attribute(Being*, int attribute, double value): void
 * Sets the base attribute of a being.
 */
static int being_set_base_attribute(lua_State *s)
{
    Being *being = getBeing(s, 1);
    if (!being)
        return 0;

    lua_Integer attr = lua_tointeger(s, 2);
    lua_Number value = lua_tonumber(s, 3);

    being->setAttribute(attr, value);

    return 0;
}

/**
 * mana.being_apply_attribute_modifier(Being*, int attribute, double value,
 *     int layer, int [duration, int [effect-id]]): void
 * Applies an attribute modifier to a being.
 */
static int being_apply_attribute_modifier(lua_State *s)
{
    Being *being = getBeing(s, 1);
    if (!being)
        return 0;

    lua_Integer attr  = lua_tointeger(s,2);
    lua_Number value  = lua_tonumber(s, 3);
    lua_Integer layer = lua_tonumber(s, 4);
    lua_Integer duration = 0;
    lua_Integer effectId = 0;
    if (lua_isnumber(s, 5))
    {
        duration = lua_tointeger(s, 5);
        if (lua_isnumber(s, 6))
            effectId = lua_tointeger(s, 6);
    }

    being->applyModifier(attr, value, layer, duration, effectId);

    return 0;
}

/**
 * mana.being_remove_attribute_modifier(Being*, int attribute, double value,
 *     int layer, int [effect-id]]): void
 * Removes an attribute modifier to a being.
 */
static int being_remove_attribute_modifier(lua_State *s)
{
    Being *being = getBeing(s, 1);
    if (!being)
        return 0;

    lua_Integer attr  = lua_tointeger(s,2);
    lua_Number value  = lua_tonumber(s, 3);
    lua_Integer layer = lua_tonumber(s, 4);
    lua_Integer effectId = 0;
    if (lua_isnumber(s, 5))
        effectId = lua_tointeger(s, 5);

    being->removeModifier(attr, value, layer, effectId);

    return 0;
}

/**
 * mana.being_get_name(Being*): string
 * Gets the being's name.
 */
static int being_get_name(lua_State *s)
{
    Being *being = getBeing(s, 1);

    if (being)
    {
        lua_pushstring(s, being->getName().c_str());
        return 1;
    }
    raiseScriptError(s, "being_get_name called for an invalid being.");
    return 0;
}

/**
 * mana.being_get_action(Being*): BeingAction
 * Gets the being's current action.
 */
static int being_get_action(lua_State *s)
{
    Being *being = getBeing(s, 1);

    if (being)
    {
        lua_pushinteger(s, being->getAction());
        return 1;
    }
    raiseScriptError(s, "being_get_action called for an invalid being.");
    return 0;
}

/**
 * mana.being_set_action(Being*, BeingAction): void
 * Sets the being's current action.
 */
static int being_set_action(lua_State *s)
{
    Being *being = getBeing(s, 1);

    int act = lua_tointeger(s, 2);

    if (being)
    {
        being->setAction((BeingAction) act);
    }
    else
    {
        raiseScriptError(s, "being_set_action called for an invalid being.");
    }
    return 0;
}

/**
 * mana.being_get_direction(Being*): BeingDirection
 * Gets the being's current direction.
 */
static int being_get_direction(lua_State *s)
{
    Being *being = getBeing(s, 1);

    if (being)
    {
        lua_pushinteger(s, being->getDirection());
        return 1;
    }
    raiseScriptError(s, "being_get_direction called for an invalid being.");
    return 0;
}

/**
 * mana.being_set_direction(Being*, BeingDirection): void
 * Sets the being's current direction.
 */
static int being_set_direction(lua_State *s)
{
    Being *being = getBeing(s, 1);

    BeingDirection dir = (BeingDirection) lua_tointeger(s, 2);

    if (being)
    {
        being->setDirection(dir);
    }
    raiseScriptError(s, "being_set_direction called for an invalid being.");
    return 0;
}

/**
 * mana.posX(Being*): int xcoord
 * Function for getting the x-coordinate of the position of a being.
 */
static int posX(lua_State *s)
{
    Being *being = getBeing(s, 1);

    if (being)
    {
        int x = being->getPosition().x;
        lua_pushinteger(s, x);
        return 1;
    }
    raiseScriptError(s, "posX called for an invalid being.");
    return 0;
}

/**
 * mana.posY(Being*): int ycoord
 * Function for getting the y-coordinate of the position of a being.
 */
static int posY(lua_State *s)
{
    Being *being = getBeing(s, 1);

    if (being)
    {
        int y = being->getPosition().y;
        lua_pushinteger(s, y);
        return 1;
    }
    raiseScriptError(s, "posY called for an invalid being.");
    return 0;
}

static int monster_class_on_update(lua_State *s)
{
    MonsterClass *monsterClass = LuaMonsterClass::check(s, 1);
    luaL_checktype(s, 2, LUA_TFUNCTION);
    monsterClass->setUpdateCallback(getScript(s));
    return 0;
}

static int monster_class_on(lua_State *s)
{
    MonsterClass *monsterClass = LuaMonsterClass::check(s, 1);
    const char *event = luaL_checkstring(s, 2);
    luaL_checktype(s, 3, LUA_TFUNCTION);
    monsterClass->setEventCallback(event, getScript(s));
    return 0;
}

/**
 * mana.monster_create(int id || string name, int x, int y): Monster*
 * Callback for creating a monster on the current map.
 */
static int monster_create(lua_State *s)
{
    const int x = luaL_checkint(s, 2);
    const int y = luaL_checkint(s, 3);

    MapComposite *m = getScript(s)->getMap();
    if (!m)
    {
        raiseScriptError(s, "monster_create called outside a map.");
        return 0;
    }

    MonsterClass *spec;
    if (lua_isnumber(s, 1))
    {
        int monsterId = luaL_checkint(s, 1);
        spec = monsterManager->getMonster(monsterId);
        if (!spec)
        {
            raiseScriptError(s, "monster_create called with invalid "
                             "monster ID: %d", monsterId);
            //LOG_WARN("LuaMonster_Create invalid monster ID: " << monsterId);
            return 0;
        }
    }
    else
    {
        std::string monsterName = lua_tostring(s, 1);
        spec = monsterManager->getMonsterByName(monsterName);
        if (!spec)
        {
            raiseScriptError(s, "monster_create called with "
                             "invalid monster name: %s", monsterName.c_str());
            //LOG_WARN("LuaMonster_Create invalid monster name: "
            // << monsterName);
            return 0;
         }
    }

    Monster *q = new Monster(spec);
    q->setMap(m);
    q->setPosition(Point(x, y));
    GameState::enqueueInsert(q);

    lua_pushlightuserdata(s, q);
    return 1;
}

/**
 * mana.monster_get_name(int monster_id): string monster_name
 * Returns the name of the monster with the given id.
 */
static int monster_get_name(lua_State *s)
{
    const int id = luaL_checkint(s, 1);
    MonsterClass *spec = monsterManager->getMonster(id);
    if (!spec)
    {
        raiseScriptError(s, "monster_get_name "
                         "called with unknown monster id.");
        return 0;
    }
    lua_pushstring(s, spec->getName().c_str());
    return 1;
}

/**
 * mana.monster_change_anger(Monster*, Being*, int anger)
 * Makes a monster angry at a being
 */
static int monster_change_anger(lua_State *s)
{
    const int anger = luaL_checkint(s, 3);
    Monster *m = getMonster(s, 1);
    if (!m)
    {
        raiseScriptError(s, "monster_change_anger called "
                         "for a nonexisting monster");
        return 0;
    }
    Being *being = getBeing(s, 2);
    if (!being)
    {
        raiseScriptError(s, "monster_change_anger called "
                         "for a nonexisting being");
    }
    m->changeAnger(being, anger);
    return 0;
}

/**
 * mana.monster_remove(Monster*): bool success
 * Remove a monster object without kill event.
 * return whether the monster was enqueued for removal.
 */
static int monster_remove(lua_State *s)
{
    bool monsterRemoved = false;
    Monster *m = getMonster(s, 1);
    if (m)
    {
        GameState::remove(m);
        monsterRemoved = true;
    }
    lua_pushboolean(s, monsterRemoved);
    return 1;
}

/**
 * mana.chr_get_chest(Character*, string): nil or string
 * Callback for getting a quest variable. Starts a recovery and returns
 * immediatly, if the variable is not known yet.
 */
static int chr_get_quest(lua_State *s)
{
    Character *q = getCharacter(s, 1);
    if (!q)
    {
        raiseScriptError(s, "chr_get_quest "
                         "called for nonexistent character.");
        return 0;
    }

    const char *m = luaL_checkstring(s, 2);
    if (m[0] == 0)
    {
        raiseScriptError(s, "chr_get_quest called with empty string.");
        return 0;
    }
    std::string value, name = m;
    bool res = getQuestVar(q, name, value);
    if (res)
    {
        lua_pushstring(s, value.c_str());
        return 1;
    }
    QuestCallback f = { &LuaScript::getQuestCallback, getScript(s) };
    recoverQuestVar(q, name, f);
    return 0;
}

/**
 * mana.getvar_map(string): string
 * Gets the value of a persistent map variable.
 */
static int getvar_map(lua_State *s)
{
    const char *m = luaL_checkstring(s, 1);
    if (m[0] == 0)
    {
        raiseScriptError(s, "getvar_map called for unnamed variable.");
        return 0;
    }

    std::string value = getScript(s)->getMap()->getVariable(m);

    lua_pushstring(s, value.c_str());
    return 1;
}

/**
 * mana.setvar_map(string, string): void
 * Sets the value of a persistent map variable.
 */
static int setvar_map(lua_State *s)
{
    const char *m = luaL_checkstring(s, 1);
    if (m[0] == 0)
    {
        raiseScriptError(s, "setvar_map called for unnamed variable.");
        return 0;
    }

    std::string key = m;
    std::string value = lua_tostring(s, 2);
    getScript(s)->getMap()->setVariable(key, value);

    return 0;
}

/**
 * mana.getvar_world(string): string
 * Gets the value of a persistent global variable.
 */
static int getvar_world(lua_State *s)
{
    const char *m = luaL_checkstring(s, 1);
    if (m[0] == 0)
    {
        raiseScriptError(s, "getvar_world called for unnamed variable.");
        return 0;
    }

    std::string value = GameState::getVariable(m);

    lua_pushstring(s, value.c_str());
    return 1;
}

/**
 * mana.setvar_world(string, string): void
 * Sets the value of a persistent global variable.
 */
static int setvar_world(lua_State *s)
{
    const char *m = luaL_checkstring(s, 1);
    if (m[0] == 0)
    {
        raiseScriptError(s, "setvar_world called with unnamed variable.");
        return 0;
    }

    std::string key = lua_tostring(s, 1);
    std::string value = lua_tostring(s, 2);
    GameState::setVariable(key, value);

    return 0;
}

/**
 * mana.chr_set_chest(Character*, string, string): void
 * Callback for setting a quest variable.
 */
static int chr_set_quest(lua_State *s)
{
    Character *q = getCharacter(s, 1);
    const char *m = luaL_checkstring(s, 2);
    const char *n = luaL_checkstring(s, 3);
    if (m[0] == 0 || strlen(m) == 0)
    {
        raiseScriptError(s, "chr_set_quest called with incorrect parameters.");
        return 0;
    }
    if (!q)
    {
        raiseScriptError(s, "chr_set_quest called for nonexistent character.");
        return 0;
    }
    setQuestVar(q, m, n);
    return 0;
}

/**
 * mana.trigger_create(int x, int y, int width, int height,
 *                     string function, int id)
 * Creates a trigger area. Whenever an actor enters this area, a Lua function
 * is called.
 */
static int trigger_create(lua_State *s)
{
    const int x = luaL_checkint(s, 1);
    const int y = luaL_checkint(s, 2);
    const int width = luaL_checkint(s, 3);
    const int height = luaL_checkint(s, 4);
    //TODO: Turn the function string to a lua function pointer
    const char *function = luaL_checkstring(s, 5);
    const int id = luaL_checkint(s, 6);

    if (!lua_isboolean(s, 7))
    {
        raiseScriptError(s, "trigger_create called with incorrect parameters.");
        return 0;
    }

    Script *script = getScript(s);
    bool once = lua_toboolean(s, 7);

    LOG_INFO("Created script trigger at " << x << ":" << y
             << " (" << width << "x" << height << ") function: " << function
             << " (" << id << ")");

    MapComposite *m = script->getMap();

    if (!m)
    {
        raiseScriptError(s, "trigger_create called for nonexistent a map.");
        return 0;
    }

    ScriptAction *action = new ScriptAction(script, function, id);
    Rectangle r = { x, y, width, height };
    TriggerArea *area = new TriggerArea(m, r, action, once);

    bool ret = GameState::insert(area);
    lua_pushboolean(s, ret);
    return 1;
}

/**
 * private message: mana.chat_message(Being* recipent, string message): void
 * @todo global message: mana.chat_message(string message): void
 * Creates a chat message in the users chatlog(s).
 */
static int chat_message(lua_State *s)
{
    if (lua_gettop(s) == 2 && lua_isuserdata(s, 1) && lua_isstring(s, 2) )
    {
        Being *being = getBeing(s, 1);
        const std::string message = lua_tostring(s, 2);

        if (being && !message.empty())
        {
            GameState::sayTo(being, NULL, message);
        }
    }
    else
    {
        raiseScriptError(s, "chat_message called with incorrect parameters.");
        return 0;
    }

    return 0;
}

/**
 * mana.get_beings_in_circle(int x, int y, int radius): table of Being*
 * mana.get_beings_in_circle(handle centerBeing, int radius): table of Being*
 * Gets a LUA table with the Being* pointers of all beings
 * inside of a circular area of the current map.
 */
static int get_beings_in_circle(lua_State *s)
{
    int x, y, r;
    if (lua_islightuserdata(s, 1))
    {
        Being *b = getBeing(s, 1);
        const Point &pos = b->getPosition();
        x = pos.x;
        y = pos.y;
        r = luaL_checkint(s, 2);
    }
    else
    {
        x = luaL_checkint(s, 1);
        y = luaL_checkint(s, 2);
        r = luaL_checkint(s, 3);
    }

    MapComposite *m = getScript(s)->getMap();

    //create a lua table with the beings in the given area.
    lua_newtable(s);
    int tableStackPosition = lua_gettop(s);
    int tableIndex = 1;
    for (BeingIterator i(m->getAroundPointIterator(Point(x, y), r)); i; ++i)
    {
        char t = (*i)->getType();
        if (t == OBJECT_NPC || t == OBJECT_CHARACTER || t == OBJECT_MONSTER)
        {
            Being *b = static_cast<Being *> (*i);
            if (Collision::circleWithCircle(b->getPosition(), b->getSize(),
                                            Point(x, y), r))
            {
                lua_pushinteger(s, tableIndex);
                lua_pushlightuserdata (s, b);
                lua_settable (s, tableStackPosition);
                tableIndex++;
            }
        }
    }

    return 1;
}

/**
 * mana.get_beings_in_rectangle(int x, int y, int width,
 *                              int height): table of Being*
 * Gets a LUA table with the Being* pointers of all beings
 * inside of a rectangular area of the current map.
 */
static int get_beings_in_rectangle(lua_State *s)
{
    const int x = luaL_checkint(s, 1);
    const int y = luaL_checkint(s, 2);
    const int w = luaL_checkint(s, 3);
    const int h = luaL_checkint(s, 4);

    MapComposite *m = getScript(s)->getMap();

    //create a lua table with the beings in the given area.
    lua_newtable(s);
    int tableStackPosition = lua_gettop(s);
    int tableIndex = 1;
    Rectangle rect = {x, y ,w, h};
    for (BeingIterator i(
         m->getInsideRectangleIterator(rect)); i; ++i)
    {
        char t = (*i)->getType();
        if (t == OBJECT_NPC || t == OBJECT_CHARACTER || t == OBJECT_MONSTER)
        {
            Being *b = static_cast<Being *> (*i);
            lua_pushinteger(s, tableIndex);
            lua_pushlightuserdata (s, b);
            lua_settable (s, tableStackPosition);
            tableIndex++;
        }
    }
     return 1;
 }

/**
 * mana.chr_get_post(Character*): void
 * Gets the post for the character.
 */
static int chr_get_post(lua_State *s)
{
    if (lua_isuserdata(s, 1))
    {
        Character *c = getCharacter(s, 1);

        if (c)
        {
            PostCallback f = { &LuaScript::getPostCallback, getScript(s) };
            postMan->getPost(c, f);
        }
    }

    return 0;
}

/**
 * mana.being_register(Being*): void
 * Makes the server call the on_being_death and on_being_remove callbacks
 * when the being dies or is removed from the map.
 */
static int being_register(lua_State *s)
{
    if (!lua_islightuserdata(s, 1) || lua_gettop(s) != 1)
    {
        raiseScriptError(s, "being_register called with incorrect parameters.");
        return 0;
    }

    Being *being = getBeing(s, 1);
    if (!being)
    {
        raiseScriptError(s, "being_register called for nonexistent being.");
        return 0;
    }

    being->addListener(getScript(s)->getScriptListener());
    return 0;
}

/**
 * mana.effect_create (int id, int x, int y): void
 * mana.effect_create (int id, Being*): void
 * Triggers a special effect from the clients effects.xml.
 */
static int effect_create(lua_State *s)
{
    const int id = luaL_checkint(s, 1);

    if (((!lua_isnumber(s, 2) || !lua_isnumber(s, 3))
         && (!lua_isuserdata(s, 2))))
    {
        raiseScriptError(s, "effect_create called with incorrect parameters.");
        return 0;
    }

    MapComposite *m = getScript(s)->getMap();

    if (lua_isuserdata(s, 2))
    {
        // being mode
        Being *b = getBeing(s, 2);
        if (!b)
        {
            raiseScriptError(s, "effect_create called on non-existent being");
            return 0;
        }
        Effects::show(id, m, b);
    }
    else
    {
        // positional mode
        int x = lua_tointeger(s, 2);
        int y = lua_tointeger(s, 3);
        Effects::show(id, m, Point(x, y));
    }

    return 0;
}

/**
 * mana.chr_shake_screen(Character*, int x, int y[, float strength,
 *                       int radius]): void
 * Shake the screen for a given character.
 */
static int chr_shake_screen(lua_State *s)
{
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_shake_screen called "
                         "for nonexistent character.");
        return 0;
    }

    const int x = luaL_checkint(s, 2);
    const int y = luaL_checkint(s, 3);

    MessageOut msg(GPMSG_SHAKE);
    msg.writeInt16(x);
    msg.writeInt16(y);

    if (lua_isnumber(s, 4))
        msg.writeInt16((int) (lua_tonumber(s, 4) * 10000));
    if (lua_isnumber(s, 5))
        msg.writeInt16(lua_tointeger(s, 5));

    c->getClient()->send(msg);

    return 0;
}


/**
 * mana.chr_get_exp(Character*, int skill): int
 * Gets the exp total in a skill of a specific character
 */
static int chr_get_exp(lua_State *s)
{
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_get_exp called for nonexistent character.");
        return 0;
    }

    const int skill = luaL_checkint(s, 2);
    const int exp = c->getExperience(skill);

    lua_pushinteger(s, exp);
    return 1;
}

/**
 * mana.chr_give_exp(Character*, int skill,
 *                   int amount[, int optimal_level]): void
 * Gives the character a certain amount of experience points
 * in a skill. Can also be used to reduce the exp amount when
 * desired.
 */
static int chr_give_exp(lua_State *s)
{
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_give_exp called for nonexistent character.");
        return 0;
    }

    const int skill = luaL_checkint(s, 2);
    const int exp = luaL_checkint(s, 3);
    const int optimalLevel = luaL_optint(s, 4, 0);

    c->receiveExperience(skill, exp, optimalLevel);

    return 0;
}

/**
 * mana.chr_set_hair_style(Character*, int style_id): void
 * Sets the given character's hair style to the given style id.
 */
static int chr_set_hair_style(lua_State *s)
{
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_set_hair_style called "
                         "for nonexistent character.");
        return 0;
    }

    const int style = luaL_checkint(s, 2);
    if (style < 0)
    {
        raiseScriptError(s, "chr_set_hair_style called "
                         "for nonexistent style id %d.", style);
        return 0;
    }

    c->setHairStyle(style);
    c->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE);

    return 0;
}

/**
 * mana.chr_get_hair_style(Character*): int hair_style
 * Gets the hair style of the given character.
 */
static int chr_get_hair_style(lua_State *s)
{
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_get_hair_style called "
                         "for nonexistent character.");
        return 0;
    }

    lua_pushinteger(s, c->getHairStyle());
    return 1;
}

/**
 * mana.chr_set_hair_color(Character*, int color_id): void
 * Set the hair color of the given character to the given color id.
 */
static int chr_set_hair_color(lua_State *s)
{
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_set_hair_color called "
                         "for nonexistent character.");
        return 0;
    }

    const int color = luaL_checkint(s, 2);
    if (color < 0)
    {
        raiseScriptError(s, "chr_set_hair_color called "
                         "for nonexistent style id %d.", color);
        return 0;
    }

    c->setHairColor(color);
    c->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE);

    return 0;
}

/**
 * mana.chr_get_hair_color(Character*): int hair_color
 * Get the hair color of the given character.
 */
static int chr_get_hair_color(lua_State *s)
{
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_get_hair_color called "
                         "for nonexistent character.");
        return 0;
    }

    lua_pushinteger(s, c->getHairColor());
    return 1;
}

/**
 * mana.chr_get_kill_count(Character*, int monster_type): int
 * Get the number of monsters the player killed of a type.
 */
static int chr_get_kill_count(lua_State *s)
{
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_get_kill_count called "
                         "for nonexistent character.");
        return 0;
    }

    const int id = luaL_checkint(s, 2);

    lua_pushinteger(s, c->getKillCount(id));
    return 1;
}

/**
 * mana.being_get_gender(Being*): int
 * Get the gender of the being.
 */
static int being_get_gender(lua_State *s)
{
    Being *b = getBeing(s, 1);
    if (!b)
    {
        raiseScriptError(s, "chr_get_gender called for nonexistent character.");
        return 0;
    }

    lua_pushinteger(s, b->getGender());
    return 1;
}

/**
 * mana.being_set_gender(Being*, int gender): void
 * Set the gender of the being.
 */
static int being_set_gender(lua_State *s)
{
    Being *b = getBeing(s, 1);
    if (!b)
    {
        raiseScriptError(s, "being_set_gender called for nonexistent character.");
        return 0;
    }

    const int gender = luaL_checkinteger(s, 2);
    b->setGender(getGender(gender));

    return 0;
}

/**
 * mana.chr_give_special(Character*, int special): void
 * Enables a special for a character.
 */
static int chr_give_special(lua_State *s)
{
    // cost_type is ignored until we have more than one cost type
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_give_special called "
                         "for nonexistent character.");
        return 0;
    }
    const int special = luaL_checkint(s, 2);

    c->giveSpecial(special);
    return 0;
}

/**
 * mana.chr_has_special(Character*, int special): bool
 * Checks whether a character has a given special.
 */
static int chr_has_special(lua_State *s)
{
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_has_special called for nonexistent character.");
        return 0;
    }
    const int special = luaL_checkint(s, 2);

    lua_pushboolean(s, c->hasSpecial(special));
    return 1;
}

/**
 * mana.chr_take_special(Character*, int special): bool success
 * Removes a special from a character.
 */
static int chr_take_special(lua_State *s)
{
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_take_special called for nonexistent character.");
        return 0;
    }
    const int special = luaL_checkint(s, 2);

    lua_pushboolean(s, c->hasSpecial(special));
    c->takeSpecial(special);
    return 1;
}

/**
 * mana.chr_get_rights(Character*): int
 * Returns the rights level of a character.
 */
static int chr_get_rights(lua_State *s)
{
    Character *c = getCharacter(s, 1);
    if (!c)
    {
        raiseScriptError(s, "chr_get_rights called for nonexistent character.");
        return 0;
    }
    lua_pushinteger(s, c->getAccountLevel());
    return 1;
}

/**
 * mana.exp_for_level(int level): int
 * Returns the exp total necessary to reach a specific skill level.
 */
static int exp_for_level(lua_State *s)
{
    const int level = luaL_checkint(s, 1);
    lua_pushinteger(s, Character::expForLevel(level));
    return 1;
}

/**
 * Returns four useless tables for testing the STL container push wrappers.
 * This function can be removed when there are more useful functions which use
 * them.
 */
static int test_tableget(lua_State *s)
{

    std::list<float> list;
    std::vector<std::string> svector;
    std::vector<int> ivector;
    std::map<std::string, std::string> map;
    std::set<int> set;

    LOG_INFO("Pushing Float List");
    list.push_back(12.636);
    list.push_back(0.0000000045656);
    list.push_back(185645445634566.346);
    list.push_back(7835458.11);
    pushSTLContainer<float>(s, list);

    LOG_INFO("Pushing String Vector");
    svector.push_back("All");
    svector.push_back("your");
    svector.push_back("base");
    svector.push_back("are");
    svector.push_back("belong");
    svector.push_back("to");
    svector.push_back("us!");
    pushSTLContainer<std::string>(s, svector);

    LOG_INFO("Pushing Integer Vector");
    ivector.resize(10);
    for (int i = 1; i < 10; i++)
    {
        ivector[i-1] = i * i;
    }
    pushSTLContainer<int>(s, ivector);

    LOG_INFO("Pushing String/String Map");
    map["Apple"] = "red";
    map["Banana"] = "yellow";
    map["Lime"] = "green";
    map["Plum"] = "blue";
    pushSTLContainer<std::string, std::string>(s, map);

    LOG_INFO("Pushing Integer Set");
    set.insert(12);
    set.insert(8);
    set.insert(14);
    set.insert(10);
    pushSTLContainer<int>(s, set);


    return 5;
}

/**
 * mana.get_map_id(): int
 * Returns the id of the current map.
 */
static int get_map_id(lua_State *s)
{
    MapComposite *m = getScript(s)->getMap();
    if (!m)
    {
        raiseScriptError(s, "get_map_id called outside a map.");
        return 0;
    }
    int id = m->getID();
    lua_pushinteger(s, id);
    return 1;
}

/**
 * mana.get_map_property(string property): string
 * Returns the value of a map property.
 */
static int get_map_property(lua_State *s)
{
    const char *property = luaL_checkstring(s, 1);
    MapComposite *m = getScript(s)->getMap();
    if (!m)
    {
        raiseScriptError(s, "get_map_property called outside a map.");
        return 0;
    }
    Map *map = m->getMap();
    std::string value = map->getProperty(property);
    const char *v = &value[0];

    lua_pushstring(s, v);
    return 1;
}

/**
 * mana.is_walkable(int x, int y): bool
 * Returns whether the pixel on the map is walkable.
 */
static int is_walkable(lua_State *s)
{
    const int x = luaL_checkint(s, 1);
    const int y = luaL_checkint(s, 2);

    MapComposite *m = getScript(s)->getMap();
    if (!m)
    {
        raiseScriptError(s, "is_walkable called outside a map.");
        return 0;
    }
    Map *map = m->getMap();

    // If the wanted warp place is unwalkable
    if (map->getWalk(x / map->getTileWidth(), y / map->getTileHeight()))
        lua_pushboolean(s, 1);
    else
        lua_pushboolean(s, 0);

    return 1;
}

static int item_class_on(lua_State *s)
{
    ItemClass *itemClass = LuaItemClass::check(s, 1);
    const char *event = luaL_checkstring(s, 2);
    luaL_checktype(s, 3, LUA_TFUNCTION);
    itemClass->setEventCallback(event, getScript(s));
    return 0;
}

/**
 * mana.drop_item(int x, int y, int id || string name[, int number]): bool
 * Creates an item stack on the floor.
 * @Returns whether the insertion was successful.
 */
static int item_drop(lua_State *s)
{
    const int x = luaL_checkint(s, 1);
    const int y = luaL_checkint(s, 2);
    const int number = luaL_optint(s, 4, 1);

    ItemClass *ic;
    if (lua_isnumber(s, 3))
        ic = itemManager->getItem(lua_tointeger(s, 3));
    else
        ic = itemManager->getItemByName(lua_tostring(s, 3));

    if (!ic)
    {
        raiseScriptError(s, "item_drop called with unknown item id or name.");
        return 0;
    }
    Item *i = new Item(ic, number);

    MapComposite* map = getScript(s)->getMap();

    i->setMap(map);
    Point pos(x, y);
    i->setPosition(pos);
    lua_pushboolean(s, GameState::insertOrDelete(i));
    return 1;
}

/**
 * mana.item_get_name(int item_id): string item_name
 * Returns the name of the item with the given id.
 */
static int item_get_name(lua_State *s)
{
    const int id = luaL_checkint(s, 1);
    ItemClass *it = itemManager->getItem(id);
    if (!it)
    {
        raiseScriptError(s, "item_get_name called with unknown item id.");
        return 0;
    }
    lua_pushstring(s, it->getName().c_str());
    return 1;
}

/**
 * mana.log(int log_level, string log_message): void
 * Logs the given message to the log.
 */
static int log(lua_State *s)
{
    const int loglevel = luaL_checkint(s, 1);
    const std::string message = lua_tostring(s, 2);

    if (loglevel >= utils::Logger::Fatal && loglevel <= utils::Logger::Debug)
         utils::Logger::output(message, (utils::Logger::Level) loglevel);
    else
        raiseScriptError(s, "log called with unknown loglevel");

    return 0;
}

/**
 * mana.get_distance(Being*, Being*): int
 * mana.get_distance(int x1, int y1, int x2, int y2): int
 * Gets the distance between two beings or two points.
 */
static int get_distance(lua_State *s)
{
    int x1, y1, x2, y2;
    if (lua_gettop(s) == 2)
    {
        Being *being1 = getBeing(s, 1);
        Being *being2 = getBeing(s, 2);
        if (!being1 || !being2)
        {
            raiseScriptError(s, "get_distance called for invalid beings.");
            return 0;
        }
        x1 = being1->getPosition().x;
        y1 = being1->getPosition().y;
        x2 = being2->getPosition().x;
        y2 = being2->getPosition().y;
    }
    else
    {
        x1 = luaL_checkint(s, 1);
        y1 = luaL_checkint(s, 2);
        x2 = luaL_checkint(s, 3);
        y2 = luaL_checkint(s, 4);
    }
    const int dx = x1 - x2;
    const int dy = y1 - y2;
    const float dist = sqrt((dx * dx) + (dy * dy));
    lua_pushinteger(s, dist);

    return 1;
}

/**
 * mana.map_get_objects(): table of all objects
 * mana.map_get_objects(string type): table of all objects of type
 * Gets the objects of a map.
 */
static int map_get_objects(lua_State *s)
{
    const bool filtered = (lua_gettop(s) == 1);
    const char *filter;
    if (filtered)
    {
        filter = luaL_checkstring(s, 1);
    }

    MapComposite *m = getScript(s)->getMap();
    const std::vector<MapObject*> &objects = m->getMap()->getObjects();

    if (!filtered)
        pushSTLContainer<MapObject*>(s, objects);
    else
    {
        std::vector<MapObject*> filteredObjects;
        for (std::vector<MapObject*>::const_iterator it = objects.begin();
             it != objects.end(); ++it)
        {
            if (utils::compareStrI((*it)->getType(), filter) == 0)
            {
                filteredObjects.push_back(*it);
            }
        }
        pushSTLContainer<MapObject*>(s, filteredObjects);
    }
    return 1;
}

/**
 * mana.map_object_get_property(handle object, string key)
 * Returns the value of the object property 'key'.
 */
static int map_object_get_property(lua_State *s)
{
    const char *key = luaL_checkstring(s, 2);
    MapObject *obj = LuaMapObject::check(s, 1);

    std::string property = obj->getProperty(key);
    if (!property.empty())
    {
        lua_pushstring(s, property.c_str());
        return 1;
    }
    else
    {
        // scripts can check for nil
        return 0;
    }
}

/**
 * mana.map_object_get_bounds(object)
 * Returns 4 int: x/y/width/height of object.
 */
static int map_object_get_bounds(lua_State *s)
{
    MapObject *obj = LuaMapObject::check(s, 1);
    const Rectangle &bounds = obj->getBounds();
    lua_pushinteger(s, bounds.x);
    lua_pushinteger(s, bounds.y);
    lua_pushinteger(s, bounds.w);
    lua_pushinteger(s, bounds.h);
    return 4;
}

/**
 * mana.map_object_get_name(object)
 * Returns the name of the object.
 */
static int map_object_get_name(lua_State *s)
{
    MapObject *obj = LuaMapObject::check(s, 1);
    lua_pushstring(s, obj->getName().c_str());
    return 1;
}

/**
 * mana.map_object_get_type(object)
 * Returns the type of the object.
 */
static int map_object_get_type(lua_State *s)
{
    MapObject *obj = LuaMapObject::check(s, 1);
    lua_pushstring(s, obj->getType().c_str());
    return 1;
}

static int status_effect_on_tick(lua_State *s)
{
    StatusEffect *statusEffect = LuaStatusEffect::check(s, 1);
    luaL_checktype(s, 2, LUA_TFUNCTION);
    statusEffect->setTickCallback(getScript(s));
    return 0;
}

/**
 * mana.announce(text [, sender])
 * Does a global announce
 */
static int announce(lua_State *s)
{
    const char *message = luaL_checkstring(s, 1);
    const char *sender = luaL_optstring(s, 2, "Server");

    MessageOut msg(GAMSG_ANNOUNCE);
    msg.writeString(message);
    msg.writeInt16(0); // Announce from server so id = 0
    msg.writeString(sender);
    accountHandler->send(msg);
    return 0;
}

static int require_loader(lua_State *s)
{
    // Add .lua extension (maybe only do this when it doesn't have it already)
    const char *file = luaL_checkstring(s, 1);
    std::string filename = file;
    filename.append(".lua");

    const std::string path = ResourceManager::resolve(filename);
    if (!path.empty())
        luaL_loadfile(s, path.c_str());
    else
        lua_pushstring(s, "File not found");

    return 1;
}


LuaScript::LuaScript():
    nbArgs(-1)
{
    mState = luaL_newstate();
    luaL_openlibs(mState);

    // Register package loader that goes through the resource manager
    // table.insert(package.loaders, 2, require_loader)
    lua_getglobal(mState, "package");
    lua_getfield(mState, -1, "loaders");
    lua_pushcfunction(mState, require_loader);
    lua_rawseti(mState, -2, 2);
    lua_pop(mState, 2);

    // Put the callback functions in the scripting environment.
    static luaL_Reg const callbacks[] = {
        { "on_character_death",              &on_character_death              },
        { "on_character_death_accept",       &on_character_death_accept       },
        { "on_npc_quest_reply",              &on_npc_quest_reply              },
        { "on_npc_post_reply",               &on_npc_post_reply               },
        { "on_being_death",                  &on_being_death                  },
        { "on_being_remove",                 &on_being_remove                 },
        { "on_update",                       &on_update                       },
        { "on_npc_start",                    &on_npc_start                    },
        { "on_npc_next",                     &on_npc_next                     },
        { "on_npc_choose",                   &on_npc_choose                   },
        { "on_npc_integer",                  &on_npc_integer                  },
        { "on_npc_string",                   &on_npc_string                   },
        { "on_npc_update",                   &on_npc_update                   },
        { "on_create_npc_delayed",           &on_create_npc_delayed           },
        { "on_map_initialize",               &on_map_initialize               },
        { "on_craft",                        &on_craft                        },
        { "on_use_special",                  &on_use_special                  },
        { "on_get_special_recharge_cost",    &on_get_special_recharge_cost    },
        { "get_item_class",                  &get_item_class                  },
        { "get_monster_class",               &get_monster_class               },
        { "get_status_effect",               &get_status_effect               },
        { "npc_create",                      &npc_create                      },
        { "npc_message",                     &npc_message                     },
        { "npc_choice",                      &npc_choice                      },
        { "npc_trade",                       &npc_trade                       },
        { "npc_post",                        &npc_post                        },
        { "npc_enable",                      &npc_enable                      },
        { "npc_disable",                     &npc_disable                     },
        { "chr_warp",                        &chr_warp                        },
        { "chr_get_inventory",               &chr_get_inventory               },
        { "chr_inv_change",                  &chr_inv_change                  },
        { "chr_inv_count",                   &chr_inv_count                   },
        { "chr_get_equipment",               &chr_get_equipment               },
        { "chr_equip_slot",                  &chr_equip_slot                  },
        { "chr_equip_item",                  &chr_equip_item                  },
        { "chr_unequip_slot",                &chr_unequip_slot                },
        { "chr_unequip_item",                &chr_unequip_item                },
        { "chr_get_level",                   &chr_get_level                   },
        { "chr_get_quest",                   &chr_get_quest                   },
        { "chr_set_quest",                   &chr_set_quest                   },
        { "getvar_map",                      &getvar_map                      },
        { "setvar_map",                      &setvar_map                      },
        { "getvar_world",                    &getvar_world                    },
        { "setvar_world",                    &setvar_world                    },
        { "chr_get_post",                    &chr_get_post                    },
        { "chr_get_exp",                     &chr_get_exp                     },
        { "chr_give_exp",                    &chr_give_exp                    },
        { "chr_get_rights",                  &chr_get_rights                  },
        { "chr_set_hair_style",              &chr_set_hair_style              },
        { "chr_get_hair_style",              &chr_get_hair_style              },
        { "chr_set_hair_color",              &chr_set_hair_color              },
        { "chr_get_hair_color",              &chr_get_hair_color              },
        { "chr_get_kill_count",              &chr_get_kill_count              },
        { "being_get_gender",                &being_get_gender                },
        { "being_set_gender",                &being_set_gender                },
        { "chr_give_special",                &chr_give_special                },
        { "chr_has_special",                 &chr_has_special                 },
        { "chr_take_special",                &chr_take_special                },
        { "exp_for_level",                   &exp_for_level                   },
        { "monster_create",                  &monster_create                  },
        { "monster_get_name",                &monster_get_name                },
        { "monster_change_anger",            &monster_change_anger            },
        { "monster_remove",                  &monster_remove                  },
        { "being_apply_status",              &being_apply_status              },
        { "being_remove_status",             &being_remove_status             },
        { "being_has_status",                &being_has_status                },
        { "being_set_status_time",           &being_set_status_time           },
        { "being_get_status_time",           &being_get_status_time           },
        { "being_type",                      &being_type                      },
        { "being_walk",                      &being_walk                      },
        { "being_say",                       &being_say                       },
        { "being_damage",                    &being_damage                    },
        { "being_heal",                      &being_heal                      },
        { "being_get_name",                  &being_get_name                  },
        { "being_get_action",                &being_get_action                },
        { "being_set_action",                &being_set_action                },
        { "being_get_direction",             &being_get_direction             },
        { "being_set_direction",             &being_set_direction             },
        { "being_apply_attribute_modifier",  &being_apply_attribute_modifier  },
        { "being_remove_attribute_modifier", &being_remove_attribute_modifier },
        { "being_set_base_attribute",        &being_set_base_attribute        },
        { "being_get_modified_attribute",    &being_get_modified_attribute    },
        { "being_get_base_attribute",        &being_get_base_attribute        },
        { "posX",                            &posX                            },
        { "posY",                            &posY                            },
        { "trigger_create",                  &trigger_create                  },
        { "chat_message",                    &chat_message                    },
        { "get_beings_in_circle",            &get_beings_in_circle            },
        { "get_beings_in_rectangle",         &get_beings_in_rectangle         },
        { "being_register",                  &being_register                  },
        { "effect_create",                   &effect_create                   },
        { "chr_shake_screen",                &chr_shake_screen                },
        { "test_tableget",                   &test_tableget                   },
        { "get_map_id",                      &get_map_id                      },
        { "get_map_property",                &get_map_property                },
        { "is_walkable",                     &is_walkable                     },
        { "item_drop",                       &item_drop                       },
        { "item_get_name",                   &item_get_name                   },
        { "npc_ask_integer",                 &npc_ask_integer                 },
        { "npc_end",                         &npc_end                         },
        { "npc_ask_string",                  &npc_ask_string                  },
        { "log",                             &log                             },
        { "get_distance",                    &get_distance                    },
        { "map_get_objects",                 &map_get_objects                 },
        { "announce",                        &announce                        },
        { NULL, NULL }
    };
    luaL_register(mState, "mana", callbacks);
    lua_pop(mState, 1);                     // pop the 'mana' table

    static luaL_Reg const members_ItemClass[] = {
        { "on",                              &item_class_on                   },
        { NULL, NULL }
    };

    static luaL_Reg const members_MapObject[] = {
        { "property",                        &map_object_get_property         },
        { "bounds",                          &map_object_get_bounds           },
        { "name",                            &map_object_get_name             },
        { "type",                            &map_object_get_type             },
        { NULL, NULL }
    };

    static luaL_Reg const members_MonsterClass[] = {
        { "on_update",                       &monster_class_on_update         },
        { "on",                              &monster_class_on                },
        { NULL, NULL }
    };

    static luaL_Reg const members_StatusEffect[] = {
        { "on_tick",                         &status_effect_on_tick           },
        { NULL, NULL }
    };

    LuaItemClass::registerType(mState, "ItemClass", members_ItemClass);
    LuaMapObject::registerType(mState, "MapObject", members_MapObject);
    LuaMonsterClass::registerType(mState, "MonsterClass", members_MonsterClass);
    LuaStatusEffect::registerType(mState, "StatusEffect", members_StatusEffect);

    // Make script object available to callback functions.
    lua_pushlightuserdata(mState, const_cast<char *>(&registryKey));
    lua_pushlightuserdata(mState, this);
    lua_rawset(mState, LUA_REGISTRYINDEX);

    // Push the error handler to first index of the stack
    lua_getglobal(mState, "debug");
    lua_getfield(mState, -1, "traceback");
    lua_remove(mState, 1);                  // remove the 'debug' table

    loadFile("scripts/lua/libmana.lua");
}