summaryrefslogblamecommitdiff
path: root/src/emap/pc.c
blob: 8c372f09b1fa7af8577608a9bf8822cec5eadba4 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                                         
                                            
 

                            



                   
                        
                          
                       
                          

                          
                         


                           
                       
                    
                   
 

                               
                      
                    
                      
                        





                                   
 
                 
                  
 
                                           
                                


                                     
                                                                      
                   





                                   
 


                                       
 

                        




                                                       

                              




                                                       




                                                 
     


             

                                      
                       
       



                                          
                                                                     


                                
                       
       
                 
                                                                 
              
                                                          

     



                                                    
 
                        
                      

                                  
 

               
                   

               











                                                                          





















                                                                          

     



                                                          




                                        
                             

                             
 
                           





                                        
                       
       
                               
                                                                    


                                  

                                                   
 


                                            
 
                        


               


                        
                      
 















                                                                         



                                                            




                                          
                               

                               

                           


                  
 

                                       
 
                        













                                              
                
 
 
 
                                  













                                                                        
 


                                                 
 






                                        


                                                                 
                          





                                                         
                                                        

                                                             


                                                                 
                                                        

                                                                             
         


                     


                  
 


                                                 
 














                                                                      
 



                                                               





















                                                                        



                                                         
 
                                          


                  



                                                           
 
                                           

                  
 
                                                

                 
                                







                                         
 







                                                                         







                                      
 

                                                 
                                            

                                                                       











                                                             


                                                    



                                                                       
                                         





















                                                             




                                                                       





                                     
 



                                                               
 
          

               

                          
                                                                         





                                                                 

                          
                                                                    

                                
                                 
         

                                                                                  
             
                                               



                             
                         

                            
                                                              
                                    
         
                                                                       







                                             


                                  
                          
 
                                                     

                                                             
 
                                         
                        
                                           


                   
                 
     





                                            



                                                  
 
                                       




                                                        

                          
                                                                            



                  

                                                                               
 
                                             





                   















                                                                 





                                         


                                                                            







                                                        
                          
                                                                                


                  
 


                                                        
 
                                         




                                      











                                                        

                                                     

                                                               
 
                                               










                                                                           
 


                                                          
 



                                               





















































                                                                                                                                


                                                         
 



                                               





















                                                             


                                                                 

                                                                  
 

                                         






























                                                                                 




                                              






                                        



























































































































































































                                                                                                                                                


                                                                            




























                                                             
// Copyright (c) Copyright (c) Hercules Dev Team, licensed under GNU GPL.
// Copyright (c) 2014 - 2015 Evol developers

#include "common/hercules.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "common/HPMi.h"
#include "common/memmgr.h"
#include "common/mmo.h"
#include "common/nullpo.h"
#include "common/socket.h"
#include "common/strlib.h"
#include "common/timer.h"
#include "map/chrif.h"
#include "map/homunculus.h"
#include "map/elemental.h"
#include "map/itemdb.h"
#include "map/npc.h"
#include "map/pc.h"

#include "plugins/HPMHooking.h"

#include "emap/clif.h"
#include "emap/pc.h"
#include "emap/send.h"
#include "emap/script.h"
#include "emap/data/itemd.h"
#include "emap/data/mapd.h"
#include "emap/data/session.h"
#include "emap/struct/itemdext.h"
#include "emap/struct/mapdext.h"
#include "emap/struct/sessionext.h"

int langScriptId;
int mountScriptId;

int epc_readparam_pre(const TBL_PC **sdPtr,
                      int *type)
{
    if (*type == Const_ClientVersion)
    {
        struct SessionExt *data = session_get_bysd((TBL_PC*)(*sdPtr));
        hookStop();
        if (!data)
            return 0;
        return data->clientVersion;
    }
    return 0;
}

int epc_setregistry_pre(TBL_PC **sdPtr,
                        int64 *reg,
                        int *val)
{
    TBL_PC *sd = *sdPtr;

    if (*reg == langScriptId)
    {
        struct SessionExt *data = session_get_bysd(sd);
        if (!data)
            return 0;
        data->language = *val;
    }
    else if (*reg == mountScriptId)
    {
        struct SessionExt *data = session_get_bysd(sd);
        if (!data)
            return 0;
        if (data->mount != *val)
        {
            data->mount = *val;
            send_pc_info(&sd->bl, &sd->bl, SELF);
        }
    }

    return 0;
}

#define equipPos(mask, field, lookf) \
    if (pos & (mask)) \
    { \
        if (id) \
            sd->status.field = id->look; \
        else \
            sd->status.field = 0; \
        eclif_changelook2(&sd->bl, lookf, sd->status.field, id, n); \
    }

#define equipPos2(mask, lookf) \
    if (pos & (mask)) \
    { \
        if (id) \
            eclif_changelook2(&sd->bl, lookf, id->look, id, n); \
        else \
            eclif_changelook2(&sd->bl, lookf, 0, id, n); \
    }

void epc_equipitem_pos_pre(TBL_PC **sdPtr,
                           struct item_data **idPtr,
                           int *nPtr,
                           int *posPtr)
{
    const int n = *nPtr;
    int pos = *posPtr;
    TBL_PC *sd = *sdPtr;
    struct item_data *id = *idPtr;

    hookStop();

    if (!id || !sd)
        return;

    if (pos & (EQP_HAND_R|EQP_SHADOW_WEAPON))
    {
        if(id)
        {
            sd->weapontype1 = id->look;
        }
        else
        {
            sd->weapontype1 = 0;
        }
        pc->calcweapontype(sd);
        eclif_changelook2(&sd->bl, LOOK_WEAPON, sd->status.weapon, id, n);
    }
    if (pos & (EQP_HAND_L|EQP_SHADOW_SHIELD))
    {
        if (id)
        {
            if(id->type == IT_WEAPON)
            {
                sd->status.shield = 0;
                sd->weapontype2 = id->look;
            }
            else if (id->type == IT_ARMOR)
            {
                sd->status.shield = id->look;
                sd->weapontype2 = 0;
            }
        }
        else
        {
            sd->status.shield = sd->weapontype2 = 0;
        }
        pc->calcweapontype(sd);
        eclif_changelook2(&sd->bl, LOOK_SHIELD, sd->status.shield, id, n);
    }

    equipPos(EQP_HEAD_LOW, head_bottom, LOOK_HEAD_BOTTOM);
    equipPos(EQP_HEAD_TOP, head_top, LOOK_HEAD_TOP);
    equipPos(EQP_HEAD_MID, head_mid, LOOK_HEAD_MID);
    equipPos(EQP_GARMENT, robe, LOOK_ROBE);
    equipPos2(EQP_SHOES, LOOK_SHOES);
    equipPos2(EQP_COSTUME_HEAD_TOP, 13);
    equipPos2(EQP_COSTUME_HEAD_MID, 14);
    equipPos2(EQP_COSTUME_HEAD_LOW, 15);
    equipPos2(EQP_COSTUME_GARMENT, 16);
    equipPos2(EQP_ARMOR, 17);
    equipPos2(EQP_ACC_R, 18);
    equipPos2(EQP_ACC_L, 19);

    //skipping SHADOW slots
}

#undef equipPos
#undef equipPos2

#define unequipPos(mask, field, lookf) \
    if (pos & (mask)) \
    { \
        sd->status.field = 0; \
        eclif_changelook2(&sd->bl, lookf, sd->status.field, 0, n); \
    }

#define unequipPos2(mask, lookf) \
    if (pos & (mask)) \
        eclif_changelook2(&sd->bl, lookf, 0, 0, n);

void epc_unequipitem_pos_pre(TBL_PC **sdPtr,
                             int *nPtr,
                             int *posPtr)
{
    TBL_PC *sd = *sdPtr;
    if (!sd)
        return;

    hookStop();

    const int n = *nPtr;
    int pos = *posPtr;

    if (pos & EQP_HAND_R)
    {
        sd->weapontype1 = 0;
        sd->status.weapon = sd->weapontype2;
        pc->calcweapontype(sd);
        eclif_changelook2(&sd->bl, LOOK_WEAPON, sd->status.weapon, 0, n);
        if (!battle->bc->dancing_weaponswitch_fix)
            status_change_end(&sd->bl, SC_DANCING, INVALID_TIMER);
    }
    if (pos & EQP_HAND_L)
    {
        sd->status.shield = sd->weapontype2 = 0;
        pc->calcweapontype(sd);
        eclif_changelook2(&sd->bl, LOOK_SHIELD, sd->status.shield, 0, n);
    }

    unequipPos(EQP_HEAD_LOW, head_bottom, LOOK_HEAD_BOTTOM);
    unequipPos(EQP_HEAD_TOP, head_top, LOOK_HEAD_TOP);
    unequipPos(EQP_HEAD_MID, head_mid, LOOK_HEAD_MID);
    unequipPos(EQP_GARMENT, robe, LOOK_ROBE);
    unequipPos2(EQP_SHOES, LOOK_SHOES);
    unequipPos2(EQP_COSTUME_HEAD_TOP, 13);
    unequipPos2(EQP_COSTUME_HEAD_MID, 14);
    unequipPos2(EQP_COSTUME_HEAD_LOW, 15);
    unequipPos2(EQP_COSTUME_GARMENT, 16);
    unequipPos2(EQP_ARMOR, 17);
    unequipPos2(EQP_ACC_R, 18);
    unequipPos2(EQP_ACC_L, 19);
    //skipping SHADOW slots
}

#undef unequipPos
#undef unequipPos2

bool epc_can_attack_pre(TBL_PC **sdPtr,
                        int *target_id)
{
    TBL_PC *sd = *sdPtr;
    if (!sd)
        return false;

    struct MapdExt *data = mapd_get(sd->bl.m);
    if (!data)
        return true;
    if (data->flag.nopve)
    {
        if (map->id2md(*target_id))
        {
            hookStop();
            return false;
        }
    }
    return true;
}


void epc_validate_levels_pre(void)
{
    int i;
    for (i = 0; i < 7; i++) {
        if (!pc->db_checkid(i)) continue;
        if (i == JOB_WEDDING || i == JOB_XMAS || i == JOB_SUMMER)
            continue; //Classes that do not need exp tables.
        int j = pc->class2idx(i);
        if (!pc->max_level[j][0])
            ShowWarning("Class %d does not has a base exp table.\n", i);
        if (!pc->max_level[j][1])
            ShowWarning("Class %d does not has a job exp table.\n", i);
    }
    hookStop();
}

int epc_isequip_post(int retVal,
                     struct map_session_data *sd,
                     int n)
{
    if (retVal)
    {
        if (!sd)
            return 0;

        if (n < 0 || n >= MAX_INVENTORY)
            return 0;

        struct ItemdExt *data = itemd_get(sd->inventory_data[n]);
        if (!data)
            return retVal;

        if (sd->battle_status.str < data->requiredStr ||
            sd->battle_status.agi < data->requiredAgi ||
            sd->battle_status.vit < data->requiredVit ||
            sd->battle_status.int_ < data->requiredInt ||
            sd->battle_status.dex < data->requiredDex ||
            sd->battle_status.luk < data->requiredLuk ||
            sd->battle_status.max_hp < data->requiredMaxHp ||
            sd->battle_status.max_sp < data->requiredMaxSp ||
            sd->battle_status.batk < data->requiredAtk ||
            sd->battle_status.matk_min < data->requiredMAtkMin ||
            sd->battle_status.matk_max < data->requiredMAtkMax ||
            sd->battle_status.def < data->requiredDef ||
            sd->battle_status.mdef < data->requiredMDef ||
            (data->requiredSkill && !pc->checkskill(sd, data->requiredSkill))
        )
        {
            return 0;
        }
    }
    return retVal;
}

int epc_useitem_post(int retVal,
                     struct map_session_data *sd,
                     int n)
{
    if (!sd)
        return retVal;

    if (n < 0 || n >= MAX_INVENTORY)
        return retVal;

    struct ItemdExt *data = itemd_get(sd->inventory_data[n]);
    if (!data)
        return retVal;

    const int effect = retVal ? data->useEffect : data->useFailEffect;
    if (effect != -1)
        clif->specialeffect(&sd->bl, effect, AREA);
    return retVal;
}

static void equippost_effect(struct map_session_data *const sd,
                             const int n,
                             const bool retVal,
                             const bool equip)
{
    if (!sd)
        return;

    if (n < 0 || n >= MAX_INVENTORY)
        return;

    struct ItemdExt *data = itemd_get(sd->inventory_data[n]);
    if (!data)
        return;

    int effect;
    if (equip)
        effect = retVal ? data->useEffect : data->useFailEffect;
    else
        effect = retVal ? data->unequipEffect : data->unequipFailEffect;

    if (effect != -1)
        clif->specialeffect(&sd->bl, effect, AREA);
    return;
}

int epc_equipitem_post(int retVal,
                       struct map_session_data *sd,
                       int n,
                       int data __attribute__ ((unused)))
{
    equippost_effect(sd, n, retVal, true);
    return retVal;
}

int epc_unequipitem_post(int retVal,
                         struct map_session_data *sd,
                         int n,
                         int data __attribute__ ((unused)))
{
    equippost_effect(sd, n, retVal, false);
    return retVal;
}

int epc_check_job_name_pre(const char **namePtr)
{
    int val = -1;
    const char *name = *namePtr;
    if (script->get_constant(name, &val))
    {
        hookStop();
        return val;
    }
    hookStop();
    return -1;
}

int epc_setnewpc_post(int retVal,
                      struct map_session_data *sd,
                      int account_id __attribute__ ((unused)),
                      int char_id __attribute__ ((unused)),
                      int login_id1 __attribute__ ((unused)),
                      unsigned int client_tick  __attribute__ ((unused)),
                      int sex __attribute__ ((unused)),
                      int fd __attribute__ ((unused)))
{
    if (sd)
    {
        sd->battle_status.speed = 150;
        sd->base_status.speed = 150;
    }
    return retVal;
}

int epc_additem_post(int retVal,
                     struct map_session_data *sd,
                     struct item *item_data,
                     int amount __attribute__ ((unused)),
                     e_log_pick_type log_type __attribute__ ((unused)))
{
    if (!retVal)
    {
        struct ItemdExt *data = itemd_get_by_item(item_data);
        if (data && data->charmItem)
            status_calc_pc(sd, SCO_NONE);
    }
    return retVal;
}

static bool calcPc = false;

int epc_delitem_pre(struct map_session_data **sdPtr,
                    int *nPtr,
                    int *amountPtr,
                    int *typePtr __attribute__ ((unused)),
                    short *reasonPtr __attribute__ ((unused)),
                    e_log_pick_type *log_type __attribute__ ((unused)))
{
    struct map_session_data *sd = *sdPtr;
    if (!sd)
        return 1;
    const int n = *nPtr;
    const int amount = *amountPtr;

    if (sd->status.inventory[n].nameid == 0 ||
        amount <= 0 ||
        sd->status.inventory[n].amount < amount ||
        sd->inventory_data[n] == NULL)
    {
        return 1;
    }

    struct ItemdExt *data = itemd_get(sd->inventory_data[n]);
    if (data && data->charmItem)
        calcPc = true;

    return 0;
}

int epc_delitem_post(int retVal,
                     struct map_session_data *sd,
                     int n __attribute__ ((unused)),
                     int amount __attribute__ ((unused)),
                     int type __attribute__ ((unused)),
                     short reason __attribute__ ((unused)),
                     e_log_pick_type log_type __attribute__ ((unused)))
{
    if (!retVal && calcPc && sd)
        status_calc_pc(sd, SCO_NONE);
    calcPc = false;
    return retVal;
}

bool epc_can_insert_card_into_post(bool retVal,
                                   struct map_session_data* sd,
                                   int idx_card,
                                   int idx_equip)
{
    int f;
    if (retVal)
    {
        if (!sd)
            return retVal;
        struct ItemdExt *data = itemd_get(sd->inventory_data[idx_equip]);
        if (!data)
            return retVal;

        const int sz = VECTOR_LENGTH(data->allowedCards);

        if (sz == 0) // allow cards if AllowedCards list is empty
            return retVal;

        const int newCardId = sd->status.inventory[idx_card].nameid;
        int cardAmountLimit = 0;

        for (f = 0; f < sz; f ++)
        {
            struct ItemCardExt *const card = &VECTOR_INDEX(data->allowedCards, f);
            if (card->id == newCardId)
            {
                cardAmountLimit = card->amount;
                break;
            }
        }
        if (!cardAmountLimit)
            return false;

        int cardsAmount = 0;
        const int slots = sd->inventory_data[idx_equip]->slot;
        for (f = 0; f < slots; f ++)
        {
            const int cardId = sd->status.inventory[idx_equip].card[f];
            if (cardId == newCardId)
                cardsAmount ++;
        }
        return cardAmountLimit > cardsAmount;
    }
    return retVal;
}

// temporary inv index and item id
static int tempN = 0;
static int tempId = 0;
static int tempAmount = 0;

int epc_dropitem_pre(struct map_session_data **sdPtr,
                     int *nPtr,
                     int *amountPtr __attribute__ ((unused)))
{
    struct map_session_data *sd = *sdPtr;
    const int n = *nPtr;
    if (!sd || n < 0 || n >= MAX_INVENTORY)
    {
        tempN = 0;
        tempId = 0;
        return 0;
    }

    tempN = n;
    tempId = sd->status.inventory[n].nameid;
    return 1;
}

int epc_dropitem_post(int retVal,
                      struct map_session_data *sd,
                      int n,
                      int amount)
{
    if (retVal && n == tempN && tempId)
    {
        struct item_data *item = itemdb->search(tempId);
        if (!item)
            return retVal;
        struct ItemdExt *data = itemd_get(item);
        if (!data)
            return retVal;
        script_run_item_amount_script(sd, data->dropScript, tempId, amount);
    }
    return retVal;
}

int epc_takeitem_pre(struct map_session_data **sdPtr  __attribute__ ((unused)),
                     struct flooritem_data **fitemPtr)
{
    struct flooritem_data *fitem = *fitemPtr;
    if (!fitem)
    {
        tempN = 0;
        tempId = 0;
        return 0;
    }

    struct ItemdExt *data = itemd_get_by_item(&fitem->item_data);
    if (!data)
    {
        tempN = -1;
        tempId = fitem->item_data.nameid;
        tempAmount = fitem->item_data.amount;
        return 1;
    }

    if (!data->allowPickup)
    {
        hookStop();
        return 0;
    }

    tempN = -1;
    tempId = fitem->item_data.nameid;
    tempAmount = fitem->item_data.amount;
    return 1;
}

int epc_takeitem_post(int retVal,
                      struct map_session_data *sd,
                      struct flooritem_data *fitem __attribute__ ((unused)))
{
    if (retVal && tempN == -1 && tempId)
    {
        struct item_data *item = itemdb->search(tempId);
        if (!item)
            return retVal;
        struct ItemdExt *data = itemd_get(item);
        if (!data)
            return retVal;
        script_run_item_amount_script(sd, data->takeScript, tempId, tempAmount);
    }
    return retVal;
}

int epc_insert_card_pre(struct map_session_data **sdPtr,
                        int *idx_card,
                        int *idx_equip)
{
    struct map_session_data *sd = *sdPtr;
    if (!sd ||
        *idx_equip < 0 ||
        *idx_equip >= MAX_INVENTORY ||
        *idx_card < 0 ||
        *idx_card >= MAX_INVENTORY)
    {
        tempN = 0;
        tempId = 0;
        tempAmount = 0;
        return 0;
    }
    tempN = *idx_equip;
    tempId = sd->status.inventory[*idx_equip].nameid;
    tempAmount = sd->status.inventory[*idx_card].nameid;
    return 1;
}

int epc_insert_card_post(int retVal,
                         struct map_session_data* sd,
                         int idx_card __attribute__ ((unused)),
                         int idx_equip)
{
    if (retVal && idx_equip == tempN && tempId)
    {
        struct item_data *item = itemdb->search(tempId);
        if (!item)
            return retVal;
        struct ItemdExt *data = itemd_get(item);
        if (!data)
            return retVal;
        script_run_card_script(sd, data->insertScript, tempId, tempAmount);
    }
    return retVal;
}

bool epc_can_Adopt_pre(struct map_session_data **p1_sdPtr,
                       struct map_session_data **p2_sdPtr,
                       struct map_session_data **b_sdPtr)
{
    struct map_session_data *p1_sd = *p1_sdPtr;
    struct map_session_data *p2_sd = *p2_sdPtr;
    struct map_session_data *b_sd = *b_sdPtr;

    hookStop();

    if (!p1_sd || !p2_sd || !b_sd)
        return false;

    if (b_sd->status.father || b_sd->status.mother || b_sd->adopt_invite)
        return false; // already adopted baby / in adopt request

    if (!p1_sd->status.partner_id || !p1_sd->status.party_id || p1_sd->status.party_id != b_sd->status.party_id)
        return false; // You need to be married and in party with baby to adopt

    if (p1_sd->status.partner_id != p2_sd->status.char_id || p2_sd->status.partner_id != p1_sd->status.char_id)
        return false; // Not married, wrong married

    if (p2_sd->status.party_id != p1_sd->status.party_id)
        return false; // Both parents need to be in the same party

    // Parents need to have their ring equipped
//    if (!pc->isequipped(p1_sd, WEDDING_RING_M) && !pc->isequipped(p1_sd, WEDDING_RING_F))
//        return false;

//    if (!pc->isequipped(p2_sd, WEDDING_RING_M) && !pc->isequipped(p2_sd, WEDDING_RING_F))
//        return false;

    // Already adopted a baby
    if (p1_sd->status.child || p2_sd->status.child)
    {
        clif->adopt_reply(p1_sd, 0);
        hookStop();
        return false;
    }

    // Parents need at least lvl 70 to adopt
//    if (p1_sd->status.base_level < 70 || p2_sd->status.base_level < 70)
//    {
//        clif->adopt_reply(p1_sd, 1);
//        hookStop();
//        return false;
//    }

    if (b_sd->status.partner_id)
    {
        clif->adopt_reply(p1_sd, 2);
        hookStop();
        return false;
    }

//    if (!((b_sd->status.class_ >= JOB_NOVICE && b_sd->status.class_ <= JOB_THIEF) || b_sd->status.class_ == JOB_SUPER_NOVICE))
//        return false;

    hookStop();
    return true;
}

bool epc_adoption_pre(struct map_session_data **p1_sdPtr,
                      struct map_session_data **p2_sdPtr,
                      struct map_session_data **b_sdPtr)
{
    struct map_session_data *p1_sd = *p1_sdPtr;
    struct map_session_data *p2_sd = *p2_sdPtr;
    struct map_session_data *b_sd = *b_sdPtr;

    if (!pc->can_Adopt(p1_sd, p2_sd, b_sd))
    {
        hookStop();
        return false;
    }

    p1_sd->status.child = b_sd->status.char_id;
    p2_sd->status.child = b_sd->status.char_id;
    b_sd->status.father = p1_sd->status.char_id;
    b_sd->status.mother = p2_sd->status.char_id;

    // Baby Skills
    pc->skill(b_sd, WE_BABY, 1, SKILL_GRANT_PERMANENT);
    pc->skill(b_sd, WE_CALLPARENT, 1, SKILL_GRANT_PERMANENT);

    // Parents Skills
    pc->skill(p1_sd, WE_CALLBABY, 1, SKILL_GRANT_PERMANENT);
    pc->skill(p2_sd, WE_CALLBABY, 1, SKILL_GRANT_PERMANENT);

    hookStop();
    return true;
}

// copy from pc_process_chat_message
// exception only prevent call gm command if string start with ##
bool epc_process_chat_message_pre(struct map_session_data **sdPtr,
                                  const char **messagePtr)
{
    struct map_session_data *sd = *sdPtr;
    const char *message = *messagePtr;
    if (message && strlen(message) > 2 && message[0] == '#' && message[1] == '#')
    {
        // do nothing
    }
    else if (atcommand->exec(sd->fd, sd, message, true))
    {
        hookStop();
        return false;
    }

    if (!pc->can_talk(sd))
    {
        hookStop();
        return false;
    }

    if (battle->bc->min_chat_delay != 0)
    {
        if (DIFF_TICK(sd->cantalk_tick, timer->gettick()) > 0)
        {
            hookStop();
            return false;
        }
        sd->cantalk_tick = timer->gettick() + battle->bc->min_chat_delay;
    }

    pc->update_idle_time(sd, BCIDLE_CHAT);

    hookStop();
    return true;
}

int epc_dead_post(int retVal,
                  struct map_session_data *sd,
                  struct block_list *src)
{
    if (retVal > 0)
    {
        if (sd)
            send_pc_killed(sd->fd, src);
    }
    return retVal;
}

/*==========================================
 * Called when player changes job
 * Rewrote to make it tidider [Celest]
 *------------------------------------------*/
int epc_jobchange(struct map_session_data *sd,
                  int job,
                  int upper __attribute__ ((unused)))
{
    int i, fame_flag=0;
    int b_class, idx = 0;

    nullpo_ret(sd);

    if (job < 0)
        return 1;

    //Normalize job.
    b_class = pc->jobid2mapid(job);
    if (b_class == -1)
        return 1;
/*
    switch (upper)
    {
        case 1:
            b_class|= JOBL_UPPER;
            break;
        case 2:
            b_class|= JOBL_BABY;
            break;
    }
    //This will automatically adjust bard/dancer classes to the correct gender
    //That is, if you try to jobchange into dancer, it will turn you to bard.
    job = pc->mapid2jobid(b_class, sd->status.sex);
    if (job == -1)
        return 1;
*/

    if ((unsigned short)b_class == sd->class_)
        return 1; //Nothing to change.

/*
    // changing from 1st to 2nd job
    if ((b_class&JOBL_2) && !(sd->class_&JOBL_2) && (b_class&MAPID_UPPERMASK) != MAPID_SUPER_NOVICE)
    {
        sd->change_level_2nd = sd->status.job_level;
        pc_setglobalreg(sd, script->add_str("jobchange_level"), sd->change_level_2nd);
    }
    // changing from 2nd to 3rd job
    else if((b_class&JOBL_THIRD) && !(sd->class_&JOBL_THIRD))
    {
        sd->change_level_3rd = sd->status.job_level;
        pc_setglobalreg(sd, script->add_str("jobchange_level_3rd"), sd->change_level_3rd);
    }
*/

    if(sd->cloneskill_id)
    {
        idx = skill->get_index(sd->cloneskill_id);
        if (sd->status.skill[idx].flag == SKILL_FLAG_PLAGIARIZED)
        {
            sd->status.skill[idx].id = 0;
            sd->status.skill[idx].lv = 0;
            sd->status.skill[idx].flag = 0;
            clif->deleteskill(sd, sd->cloneskill_id);
        }
        sd->cloneskill_id = 0;
        pc_setglobalreg(sd, script->add_str("CLONE_SKILL"), 0);
        pc_setglobalreg(sd, script->add_str("CLONE_SKILL_LV"), 0);
    }

    if(sd->reproduceskill_id)
    {
        idx = skill->get_index(sd->reproduceskill_id);
        if (sd->status.skill[idx].flag == SKILL_FLAG_PLAGIARIZED)
        {
            sd->status.skill[idx].id = 0;
            sd->status.skill[idx].lv = 0;
            sd->status.skill[idx].flag = 0;
            clif->deleteskill(sd, sd->reproduceskill_id);
        }
        sd->reproduceskill_id = 0;
        pc_setglobalreg(sd, script->add_str("REPRODUCE_SKILL"),0);
        pc_setglobalreg(sd, script->add_str("REPRODUCE_SKILL_LV"),0);
    }

/*
    if ((b_class&MAPID_UPPERMASK) != (sd->class_&MAPID_UPPERMASK))
    { //Things to remove when changing class tree.
        const int class_ = pc->class2idx(sd->status.class_);
        short id;
        for (i = 0; i < MAX_SKILL_TREE && (id = pc->skill_tree[class_][i].id) > 0; i++)
        {
            //Remove status specific to your current tree skills.
            enum sc_type sc = status->skill2sc(id);
            if (sc > SC_COMMON_MAX && sd->sc.data[sc])
                status_change_end(&sd->bl, sc, INVALID_TIMER);
        }
    }

    if ((sd->class_&MAPID_UPPERMASK) == MAPID_STAR_GLADIATOR && (b_class&MAPID_UPPERMASK) != MAPID_STAR_GLADIATOR)
    {
        // going off star glad lineage, reset feel to not store no-longer-used vars in the database
        pc->resetfeel(sd);
    }
*/

    sd->status.class_ = job;
    fame_flag = pc->famerank(sd->status.char_id, sd->class_ & MAPID_UPPERMASK);
    sd->class_ = (unsigned short)b_class;
//    sd->status.job_level = 1;
//    sd->status.job_exp = 0;

    if (sd->status.base_level > pc->maxbaselv(sd))
    {
        sd->status.base_level = pc->maxbaselv(sd);
        sd->status.base_exp=0;
        pc->resetstate(sd);
        clif->updatestatus(sd, SP_STATUSPOINT);
        clif->updatestatus(sd, SP_BASELEVEL);
        clif->updatestatus(sd, SP_BASEEXP);
        clif->updatestatus(sd, SP_NEXTBASEEXP);
    }

    clif->updatestatus(sd, SP_JOBLEVEL);
    clif->updatestatus(sd, SP_JOBEXP);
    clif->updatestatus(sd, SP_NEXTJOBEXP);

    for (i = 0; i < EQI_MAX; i ++)
    {
        if (sd->equip_index[i] >= 0)
        {
            if (!pc->isequip(sd, sd->equip_index[i]))
                pc->unequipitem(sd, sd->equip_index[i], PCUNEQUIPITEM_FORCE); // unequip invalid item for class
        }
    }

    //Change look, if disguised, you need to undisguise
    //to correctly calculate new job sprite without
    if (sd->disguise != -1)
        pc->disguise(sd, -1);

    status->set_viewdata(&sd->bl, job);
    send_changelook2(sd, &sd->bl, sd->bl.id, LOOK_BASE, sd->vd.class_, 0, NULL, 0, AREA);
//    clif->changelook(&sd->bl, LOOK_BASE, sd->vd.class_); // move sprite update to prevent client crashes with incompatible equipment [Valaris]
    if (sd->vd.cloth_color)
        clif->changelook(&sd->bl, LOOK_CLOTHES_COLOR, sd->vd.cloth_color);
    if (sd->vd.body_style)
        clif->changelook(&sd->bl, LOOK_BODY2, sd->vd.body_style);

    //Update skill tree.
    pc->calc_skilltree(sd);
    clif->skillinfoblock(sd);

    if (sd->ed)
        elemental->delete(sd->ed, 0);
    if (sd->state.vending)
        vending->close(sd);

    map->foreachinmap(pc->jobchange_killclone, sd->bl.m, BL_MOB, sd->bl.id);

    //Remove peco/cart/falcon
    i = sd->sc.option;
    if (i & OPTION_RIDING && (!pc->checkskill(sd, KN_RIDING) || (sd->class_&MAPID_THIRDMASK) == MAPID_RUNE_KNIGHT))
        i &= ~ OPTION_RIDING;
    if (i & OPTION_FALCON && !pc->checkskill(sd, HT_FALCON))
        i &= ~ OPTION_FALCON;
    if (i & OPTION_DRAGON && !pc->checkskill(sd,RK_DRAGONTRAINING))
        i &= ~ OPTION_DRAGON;
    if (i & OPTION_WUGRIDER && !pc->checkskill(sd,RA_WUGMASTERY))
        i &= ~ OPTION_WUGRIDER;
    if (i & OPTION_WUG && !pc->checkskill(sd,RA_WUGMASTERY))
        i &= ~ OPTION_WUG;
    if (i & OPTION_MADOGEAR) //You do not need a skill for this.
        i &= ~ OPTION_MADOGEAR;
//#ifndef NEW_CARTS
//    if (i & OPTION_CART && !pc->checkskill(sd, MC_PUSHCART))
//        i &= ~ OPTION_CART;
//#else
    if (sd->sc.data[SC_PUSH_CART] && !pc->checkskill(sd, MC_PUSHCART))
        pc->setcart(sd, 0);
//#endif
    if (i != sd->sc.option)
        pc->setoption(sd, i);

    if (homun_alive(sd->hd) && !pc->checkskill(sd, AM_CALLHOMUN))
        homun->vaporize(sd, HOM_ST_REST);

    if ((sd->sc.data[SC_SPRITEMABLE] && pc->checkskill(sd, SU_SPRITEMABLE)))
        status_change_end(&sd->bl, SC_SPRITEMABLE, INVALID_TIMER);

    if (sd->status.manner < 0)
        clif->changestatus(sd, SP_MANNER, sd->status.manner);

    status_calc_pc(sd, SCO_FORCE);
    pc->checkallowskill(sd);
    pc->equiplookall(sd);

    //if you were previously famous, not anymore.
    if (fame_flag)
    {
        chrif->save(sd, 0);
        chrif->buildfamelist();
    }
    else if (sd->status.fame > 0)
    {
        //It may be that now they are famous?
        switch (sd->class_&MAPID_UPPERMASK)
        {
            case MAPID_BLACKSMITH:
            case MAPID_ALCHEMIST:
            case MAPID_TAEKWON:
                chrif->save(sd, 0);
                chrif->buildfamelist();
            break;
        }
    }

    return 0;
}