// Copyright (c) Athena Dev Teams - Licensed under GNU GPL // For more information, see LICENCE in the main folder #include "../common/nullpo.h" #include "../common/malloc.h" #include "../common/random.h" #include "../common/showmsg.h" #include "../common/strlib.h" #include "../common/utils.h" #include "itemdb.h" #include "battle.h" // struct battle_config #include "cashshop.h" #include "intif.h" #include static DBMap *itemdb; /// Item DB static DBMap *itemdb_combo; /// Item Combo DB static DBMap *itemdb_group; /// Item Group DB struct item_data *dummy_item; /// This is the default dummy item used for non-existant items. [Skotlex] /** * Check if combo exists * @param combo_id * @return NULL if not exist, or struct item_combo* */ struct item_combo *itemdb_combo_exists(unsigned short combo_id) { return (struct item_combo *)uidb_get(itemdb_combo, combo_id); } /** * Check if item group exists * @param group_id * @return NULL if not exist, or s_item_group_db * */ struct s_item_group_db *itemdb_group_exists(unsigned short group_id) { return (struct s_item_group_db *)uidb_get(itemdb_group, group_id); } /** * Search for item name * name = item alias, so we should find items aliases first. if not found then look for "jname" (full name) * @see DBApply */ static int itemdb_searchname_sub(DBKey key, DBData *data, va_list ap) { struct item_data *item = (struct item_data *)db_data2ptr(data), **dst, **dst2; char *str; str = va_arg(ap,char *); dst = va_arg(ap,struct item_data **); dst2 = va_arg(ap,struct item_data **); //Absolute priority to Aegis code name. if (strcmpi(item->name,str) == 0) *dst = item; //Second priority to Client displayed name. if (strcmpi(item->jname,str) == 0) *dst2 = item; return 0; } /*========================================== * Return item data from item name. (lookup) * @param str Item Name * @return item data *------------------------------------------*/ struct item_data* itemdb_searchname(const char *str) { struct item_data *item = NULL, * item2 = NULL; itemdb->foreach(itemdb,itemdb_searchname_sub,str,&item,&item2); return item ? item : item2; } /** * @see DBMatcher */ static int itemdb_searchname_array_sub(DBKey key, DBData data, va_list ap) { struct item_data *item = (struct item_data *)db_data2ptr(&data); char *str = va_arg(ap,char *); if (stristr(item->jname,str)) return 0; if (stristr(item->name,str)) return 0; return strcmpi(item->jname,str); } /*========================================== * Founds up to N matches. Returns number of matches [Skotlex] * @param *data * @param size * @param str * @return Number of matches item *------------------------------------------*/ int itemdb_searchname_array(struct item_data** data, int size, const char *str) { DBData *db_data[MAX_SEARCH]; int i, count = 0, db_count; db_count = itemdb->getall(itemdb, (DBData**)&db_data, size, itemdb_searchname_array_sub, str); for (i = 0; i < db_count && count < size; i++) data[count++] = (struct item_data*)db_data2ptr(db_data[i]); return count; } /** * Return a random group entry from Item Group * @param group_id * @param sub_group: 0 is 'must' item group, random groups start from 1 to MAX_ITEMGROUP_RANDGROUP+1 * @return Item group entry or NULL on fail */ struct s_item_group_entry *itemdb_get_randgroupitem(uint16 group_id, uint8 sub_group) { struct s_item_group_db *group = (struct s_item_group_db *) uidb_get(itemdb_group, group_id); struct s_item_group_entry *list = NULL; uint16 qty = 0; if (!group) { ShowError("itemdb_get_randgroupitem: Invalid group id %d\n", group_id); return NULL; } if (sub_group > MAX_ITEMGROUP_RANDGROUP+1) { ShowError("itemdb_get_randgroupitem: Invalid sub_group %d\n", sub_group); return NULL; } if (sub_group == 0) { list = group->must; qty = group->must_qty; } else { list = group->random[sub_group-1].data; qty = group->random[sub_group-1].data_qty; } if (!qty) { ShowError("itemdb_get_randgroupitem: No item entries for group id %d and sub group %d\n", group_id, sub_group); return NULL; } return &list[rnd()%qty]; } /** * Return a random Item ID from from Item Group * @param group_id * @param sub_group: 0 is 'must' item group, random groups start from 1 to MAX_ITEMGROUP_RANDGROUP+1 * @return Item ID or UNKNOWN_ITEM_ID on fail */ unsigned short itemdb_searchrandomid(uint16 group_id, uint8 sub_group) { struct s_item_group_entry *entry = itemdb_get_randgroupitem(group_id, sub_group); return entry ? entry->nameid : UNKNOWN_ITEM_ID; } /** [Cydh] * Gives item(s) to the player based on item group * @param sd: Player that obtains item from item group * @param group_id: The group ID of item that obtained by player * @param *group: struct s_item_group from itemgroup_db[group_id].random[idx] or itemgroup_db[group_id].must[sub_group][idx] */ static void itemdb_pc_get_itemgroup_sub(struct map_session_data *sd, struct s_item_group_entry *data) { uint16 i, get_amt = 0; struct item tmp; nullpo_retv(data); memset(&tmp, 0, sizeof(tmp)); tmp.nameid = data->nameid; tmp.bound = data->bound; tmp.identify = 1; tmp.expire_time = (data->duration) ? (unsigned int)(time(NULL) + data->duration*60) : 0; if (data->isNamed) { tmp.card[0] = itemdb_isequip(data->nameid) ? CARD0_FORGE : CARD0_CREATE; tmp.card[1] = 0; tmp.card[2] = GetWord(sd->status.char_id, 0); tmp.card[3] = GetWord(sd->status.char_id, 1); } if (!itemdb_isstackable(data->nameid)) get_amt = 1; else get_amt = data->amount; // Do loop for non-stackable item for (i = 0; i < data->amount; i += get_amt) { char flag = 0; tmp.unique_id = data->GUID ? pc_generate_unique_id(sd) : 0; // Generate GUID if ((flag = pc_additem(sd, &tmp, get_amt, LOG_TYPE_SCRIPT))) { clif_additem(sd, 0, 0, flag); if (pc_candrop(sd, &tmp)) map_addflooritem(&tmp, tmp.amount, sd->bl.m, sd->bl.x,sd->bl.y, 0, 0, 0, 0, 0); } else if (!flag && data->isAnnounced) intif_broadcast_obtain_special_item(sd, data->nameid, sd->itemid, ITEMOBTAIN_TYPE_BOXITEM); } } /** [Cydh] * Find item(s) that will be obtained by player based on Item Group * @param group_id: The group ID that will be gained by player * @param nameid: The item that trigger this item group * @return val: 0:success, 1:no sd, 2:invalid item group */ char itemdb_pc_get_itemgroup(uint16 group_id, struct map_session_data *sd) { uint16 i = 0; struct s_item_group_db *group; nullpo_retr(1,sd); if (!(group = (struct s_item_group_db *) uidb_get(itemdb_group, group_id))) { ShowError("itemdb_pc_get_itemgroup: Invalid group id '%d' specified.\n",group_id); return 2; } // Get the 'must' item(s) if (group->must_qty) { for (i = 0; i < group->must_qty; i++) if (&group->must[i]) itemdb_pc_get_itemgroup_sub(sd,&group->must[i]); } // Get the 'random' item each random group for (i = 0; i < MAX_ITEMGROUP_RANDGROUP; i++) { uint16 rand; if (!(&group->random[i]) || !group->random[i].data_qty) //Skip empty random group continue; rand = rnd()%group->random[i].data_qty; if (!(&group->random[i].data[rand]) || !group->random[i].data[rand].nameid) continue; itemdb_pc_get_itemgroup_sub(sd,&group->random[i].data[rand]); } return 0; } /** Searches for the item_data. Use this to check if item exists or not. * @param nameid * @return *item_data if item is exist, or NULL if not */ struct item_data* itemdb_exists(unsigned short nameid) { return ((struct item_data*)uidb_get(itemdb,nameid)); } /// Returns name type of ammunition [Cydh] const char *itemdb_typename_ammo (enum e_item_ammo ammo) { switch (ammo) { case AMMO_ARROW: return "Arrow"; case AMMO_THROWABLE_DAGGER: return "Throwable Dagger"; case AMMO_BULLET: return "Bullet"; case AMMO_SHELL: return "Shell"; case AMMO_GRENADE: return "Grenade"; case AMMO_SHURIKEN: return "Shuriken"; case AMMO_KUNAI: return "Kunai"; case AMMO_CANNONBALL: return "Cannonball"; case AMMO_THROWABLE_ITEM: return "Throwable Item/Sling Item"; } return "Ammunition"; } /// Returns human readable name for given item type. /// @param type Type id to retrieve name for ( IT_* ). const char* itemdb_typename(enum item_types type) { switch(type) { case IT_HEALING: return "Potion/Food"; case IT_USABLE: return "Usable"; case IT_ETC: return "Etc."; case IT_WEAPON: return "Weapon"; case IT_ARMOR: return "Armor"; case IT_CARD: return "Card"; case IT_PETEGG: return "Pet Egg"; case IT_PETARMOR: return "Pet Accessory"; case IT_AMMO: return "Arrow/Ammunition"; case IT_DELAYCONSUME: return "Delay-Consume Usable"; case IT_SHADOWGEAR: return "Shadow Equipment"; case IT_CASH: return "Cash Usable"; } return "Unknown Type"; } /*========================================== * Converts the jobid from the format in itemdb * to the format used by the map server. [Skotlex] *------------------------------------------*/ static void itemdb_jobid2mapid(unsigned int *bclass, unsigned int jobmask) { int i; bclass[0]= bclass[1]= bclass[2]= 0; //Base classes if (jobmask & 1<nameid = 500; dummy_item->weight = 1; dummy_item->value_sell = 1; dummy_item->type = IT_ETC; //Etc item safestrncpy(dummy_item->name, "UNKNOWN_ITEM", sizeof(dummy_item->name)); safestrncpy(dummy_item->jname, "Unknown Item", sizeof(dummy_item->jname)); dummy_item->view_id = UNKNOWN_ITEM_ID; } /** * Create new item data * @param nameid */ static struct item_data *itemdb_create_item(unsigned short nameid) { struct item_data *id; CREATE(id, struct item_data, 1); memset(id, 0, sizeof(struct item_data)); id->nameid = nameid; id->type = IT_ETC; //Etc item uidb_put(itemdb, nameid, id); return id; } /*========================================== * Loads an item from the db. If not found, it will return the dummy item. * @param nameid * @return *item_data or *dummy_item if item not found *------------------------------------------*/ struct item_data* itemdb_search(unsigned short nameid) { struct item_data* id = NULL; if (nameid == dummy_item->nameid) id = dummy_item; else if (!(id = (struct item_data*)uidb_get(itemdb, nameid))) { ShowWarning("itemdb_search: Item ID %hu does not exists in the item_db. Using dummy data.\n", nameid); id = dummy_item; } return id; } /** Checks if item is equip type or not * @param id Item data * @return True if item is equip, false otherwise */ bool itemdb_isequip2(struct item_data *id) { nullpo_ret(id); switch (id->type) { case IT_WEAPON: case IT_ARMOR: case IT_AMMO: case IT_SHADOWGEAR: return true; default: return false; } } /** Checks if item is stackable or not * @param id Item data * @return True if item is stackable, false otherwise */ bool itemdb_isstackable2(struct item_data *id) { nullpo_ret(id); switch(id->type) { case IT_WEAPON: case IT_ARMOR: case IT_PETEGG: case IT_PETARMOR: case IT_SHADOWGEAR: return false; default: return true; } } /*========================================== * Trade Restriction functions [Skotlex] *------------------------------------------*/ bool itemdb_isdropable_sub(struct item_data *item, int gmlv, int unused) { return (item && (!(item->flag.trade_restriction&1) || gmlv >= item->gm_lv_trade_override)); } bool itemdb_cantrade_sub(struct item_data* item, int gmlv, int gmlv2) { return (item && (!(item->flag.trade_restriction&2) || gmlv >= item->gm_lv_trade_override || gmlv2 >= item->gm_lv_trade_override)); } bool itemdb_canpartnertrade_sub(struct item_data* item, int gmlv, int gmlv2) { return (item && (item->flag.trade_restriction&4 || gmlv >= item->gm_lv_trade_override || gmlv2 >= item->gm_lv_trade_override)); } bool itemdb_cansell_sub(struct item_data* item, int gmlv, int unused) { return (item && (!(item->flag.trade_restriction&8) || gmlv >= item->gm_lv_trade_override)); } bool itemdb_cancartstore_sub(struct item_data* item, int gmlv, int unused) { return (item && (!(item->flag.trade_restriction&16) || gmlv >= item->gm_lv_trade_override)); } bool itemdb_canstore_sub(struct item_data* item, int gmlv, int unused) { return (item && (!(item->flag.trade_restriction&32) || gmlv >= item->gm_lv_trade_override)); } bool itemdb_canguildstore_sub(struct item_data* item, int gmlv, int unused) { return (item && (!(item->flag.trade_restriction&64) || gmlv >= item->gm_lv_trade_override)); } bool itemdb_canmail_sub(struct item_data* item, int gmlv, int unused) { return (item && (!(item->flag.trade_restriction&128) || gmlv >= item->gm_lv_trade_override)); } bool itemdb_canauction_sub(struct item_data* item, int gmlv, int unused) { return (item && (!(item->flag.trade_restriction&256) || gmlv >= item->gm_lv_trade_override)); } bool itemdb_isrestricted(struct item* item, int gmlv, int gmlv2, bool (*func)(struct item_data*, int, int)) { struct item_data* item_data = itemdb_search(item->nameid); int i; if (!func(item_data, gmlv, gmlv2)) return false; if(item_data->slot == 0 || itemdb_isspecial(item->card[0])) return true; for(i = 0; i < item_data->slot; i++) { if (!item->card[i]) continue; if (!func(itemdb_search(item->card[i]), gmlv, gmlv2)) return false; } return true; } /** Specifies if item-type should drop unidentified. * @param nameid ID of item */ char itemdb_isidentified(unsigned short nameid) { int type=itemdb_type(nameid); switch (type) { case IT_WEAPON: case IT_ARMOR: case IT_PETARMOR: case IT_SHADOWGEAR: return 0; default: return 1; } } /** Search by name for the override flags available items (Give item another sprite) * Structure: , */ static bool itemdb_read_itemavail(char* str[], int columns, int current) { unsigned short nameid, sprite; struct item_data *id; nameid = atoi(str[0]); if( ( id = itemdb_exists(nameid) ) == NULL ) { ShowWarning("itemdb_read_itemavail: Invalid item id %hu.\n", nameid); return false; } sprite = atoi(str[1]); if( sprite > 0 ) { id->flag.available = 1; id->view_id = sprite; } else { id->flag.available = 0; } return true; } static int itemdb_group_free(DBKey key, DBData *data, va_list ap); /** Read item group data * Structure: GroupID,ItemID,Rate{,Amount,isMust,isAnnounced,Duration,GUID,isBound,isNamed} */ static void itemdb_read_itemgroup_sub(const char* filename, bool silent) { FILE *fp; int ln = 0, entries = 0; char line[1024]; if ((fp=fopen(filename,"r")) == NULL) { if(silent == 0) ShowError("Can't read %s\n", filename); return; } while (fgets(line,sizeof(line),fp)) { DBData data; int group_id = -1; unsigned int j, prob = 1; uint8 rand_group = 1; char *str[10], *p; struct s_item_group_random *random = NULL; struct s_item_group_db *group = NULL; struct s_item_group_entry entry; bool found = false; ln++; if (line[0] == '/' && line[1] == '/') continue; if (strstr(line,"import")) { char w1[16], w2[64]; if (sscanf(line,"%15[^:]: %63[^\r\n]",w1,w2) == 2 && strcmpi(w1,"import") == 0) { itemdb_read_itemgroup_sub(w2, 0); continue; } } memset(str,0,sizeof(str)); for (j = 0, p = line; j < 9 && p;j++) { str[j] = p; p = strchr(p,','); if (p) *p++=0; } if (str[0] == NULL) //Empty Group ID continue; if (j < 3) { if (j > 1) // Or else it barks on blank lines... ShowWarning("itemdb_read_itemgroup: Insufficient fields for entry at %s:%d\n", filename, ln); continue; } memset(&entry, 0, sizeof(entry)); entry.amount = 1; entry.bound = BOUND_NONE; // Checking group_id trim(str[0]); if (ISDIGIT(str[0][0])) group_id = atoi(str[0]); else // Try reads group id by const script_get_constant(trim(str[0]), &group_id); if (group_id < 0) { ShowWarning("itemdb_read_itemgroup: Invalid Group ID '%s' (%s:%d)\n", str[0], filename, ln); continue; } // Remove from DB if (strcmpi(str[1], "clear") == 0 && itemdb_group->remove(itemdb_group, db_ui2key(group_id), &data)) { itemdb_group_free(db_ui2key(group_id), &data, NULL); ShowNotice("Item Group '%s' has been cleared.\n", str[0]); continue; } // Checking sub group prob = atoi(str[2]); if (str[4] != NULL) rand_group = atoi(str[4]); if (rand_group < 0 || rand_group > MAX_ITEMGROUP_RANDGROUP) { ShowWarning("itemdb_read_itemgroup: Invalid sub group '%d' for group '%s' in %s:%d\n", rand_group, str[0], filename, ln); continue; } if (rand_group != 0 && prob < 1) { ShowWarning("itemdb_read_itemgroup: Random item must has probability. Group '%s' in %s:%d\n", str[0], filename, ln); continue; } // Checking item trim(str[1]); if (ISDIGIT(str[1][0]) && ISDIGIT(str[1][1]) && itemdb_exists((entry.nameid = atoi(str[1])))) found = true; else { struct item_data *id = itemdb_searchname(str[1]); if (id) { entry.nameid = id->nameid; found = true; } } if (!found) { ShowWarning("itemdb_read_itemgroup: Non-existant item '%s' in %s:%d\n", str[1], filename, ln); continue; } if (str[3] != NULL) entry.amount = cap_value(atoi(str[3]),1,MAX_AMOUNT); if (str[5] != NULL) entry.isAnnounced= atoi(str[5]); if (str[6] != NULL) entry.duration = cap_value(atoi(str[6]),0,UINT16_MAX); if (str[7] != NULL) entry.GUID = atoi(str[7]); if (str[8] != NULL) entry.bound = cap_value(atoi(str[8]),BOUND_NONE,BOUND_MAX-1); if (str[9] != NULL) entry.isNamed = atoi(str[9]); if (!(group = (struct s_item_group_db *) uidb_get(itemdb_group, group_id))) { CREATE(group, struct s_item_group_db, 1); group->id = group_id; uidb_put(itemdb_group, group->id, group); } // Must item (rand_group == 0), place it here if (!rand_group) { RECREATE(group->must, struct s_item_group_entry, group->must_qty+1); group->must[group->must_qty++] = entry; // If 'must' item isn't set as random item, skip the next process if (!prob) { entries++; continue; } rand_group = 0; } else rand_group -= 1; random = &group->random[rand_group]; RECREATE(random->data, struct s_item_group_entry, random->data_qty+prob); // Put the entry to its rand_group for (j = random->data_qty; j < random->data_qty+prob; j++) random->data[j] = entry; random->data_qty += prob; entries++; } fclose(fp); ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", entries, filename); return; } static void itemdb_read_itemgroup(const char* basedir, bool silent) { char filepath[256]; sprintf(filepath, "%s/%s", basedir, "item_group_db.txt"); itemdb_read_itemgroup_sub(filepath, silent); return; } /** Read item forbidden by mapflag (can't equip item) * Structure: , */ static bool itemdb_read_noequip(char* str[], int columns, int current) { unsigned short nameid; struct item_data *id; nameid = atoi(str[0]); if( ( id = itemdb_exists(nameid) ) == NULL ) { ShowWarning("itemdb_read_noequip: Invalid item id %hu.\n", nameid); return false; } id->flag.no_equip |= atoi(str[1]); return true; } /** Reads item trade restrictions [Skotlex] * Structure: ,, */ static bool itemdb_read_itemtrade(char* str[], int columns, int current) { unsigned short nameid, flag, gmlv; struct item_data *id; nameid = atoi(str[0]); if( ( id = itemdb_exists(nameid) ) == NULL ) { //ShowWarning("itemdb_read_itemtrade: Invalid item id %d.\n", nameid); //return false; // FIXME: item_trade.txt contains items, which are commented in item database. return true; } flag = atoi(str[1]); gmlv = atoi(str[2]); if( flag > 511 ) {//Check range ShowWarning("itemdb_read_itemtrade: Invalid trading mask %hu for item id %hu.\n", flag, nameid); return false; } if( gmlv < 1 ) { ShowWarning("itemdb_read_itemtrade: Invalid override GM level %hu for item id %hu.\n", gmlv, nameid); return false; } id->flag.trade_restriction = flag; id->gm_lv_trade_override = gmlv; return true; } /** Reads item delay amounts [Paradox924X] * Structure: ,{,} */ static bool itemdb_read_itemdelay(char* str[], int columns, int current) { unsigned short nameid; int delay; struct item_data *id; nameid = atoi(str[0]); if( ( id = itemdb_exists(nameid) ) == NULL ) { ShowWarning("itemdb_read_itemdelay: Invalid item id %hu.\n", nameid); return false; } delay = atoi(str[1]); if( delay < 0 ) { ShowWarning("itemdb_read_itemdelay: Invalid delay %d for item id %hu.\n", id->delay, nameid); return false; } id->delay = delay; if (columns == 2) id->delay_sc = SC_NONE; else if( ISDIGIT(str[2][0]) ) id->delay_sc = atoi(str[2]); else{ // Try read sc group id from const db int constant; if( !script_get_constant(trim(str[2]), &constant) ){ ShowWarning("itemdb_read_itemdelay: Invalid sc group \"%s\" for item id %hu.\n", str[2], nameid); return false; } id->delay_sc = (short)constant; } return true; } /** Reads item stacking restrictions * Structure: ,, */ static bool itemdb_read_stack(char* fields[], int columns, int current) { unsigned short nameid, amount; unsigned int type; struct item_data* id; nameid = (unsigned short)strtoul(fields[0], NULL, 10); if( ( id = itemdb_exists(nameid) ) == NULL ) { ShowWarning("itemdb_read_stack: Unknown item id '%hu'.\n", nameid); return false; } if( !itemdb_isstackable2(id) ) { ShowWarning("itemdb_read_stack: Item id '%hu' is not stackable.\n", nameid); return false; } amount = (unsigned short)strtoul(fields[1], NULL, 10); type = strtoul(fields[2], NULL, 10); if( !amount ) {// ignore return true; } id->stack.amount = amount; id->stack.inventory = (type&1)!=0; id->stack.cart = (type&2)!=0; id->stack.storage = (type&4)!=0; id->stack.guildstorage = (type&8)!=0; return true; } /** Reads items allowed to be sold in buying stores * */ static bool itemdb_read_buyingstore(char* fields[], int columns, int current) { unsigned short nameid; struct item_data* id; nameid = atoi(fields[0]); if( ( id = itemdb_exists(nameid) ) == NULL ) { ShowWarning("itemdb_read_buyingstore: Invalid item id %hu.\n", nameid); return false; } if( !itemdb_isstackable2(id) ) { ShowWarning("itemdb_read_buyingstore: Non-stackable item id %hu cannot be enabled for buying store.\n", nameid); return false; } id->flag.buyingstore = true; return true; } /** Item usage restriction (item_nouse.txt) * ,, */ static bool itemdb_read_nouse(char* fields[], int columns, int current) { unsigned short nameid, flag, override; struct item_data* id; nameid = atoi(fields[0]); if( ( id = itemdb_exists(nameid) ) == NULL ) { ShowWarning("itemdb_read_nouse: Invalid item id %hu.\n", nameid); return false; } flag = atoi(fields[1]); override = atoi(fields[2]); id->item_usage.flag = flag; id->item_usage.override = override; return true; } /** Misc Item flags * , * &1 - As dead branch item * &2 - As item container * &4 - GUID item, cannot be stacked even same or stackable item */ static bool itemdb_read_flag(char* fields[], int columns, int current) { unsigned short nameid = atoi(fields[0]); uint8 flag; bool set; struct item_data *id; if (!(id = itemdb_exists(nameid))) { ShowError("itemdb_read_flag: Invalid item item with id %hu\n", nameid); return true; } flag = abs(atoi(fields[1])); set = atoi(fields[1]) > 0; if (flag&1) id->flag.dead_branch = set ? 1 : 0; if (flag&2) id->flag.group = set ? 1 : 0; if (flag&4 && itemdb_isstackable2(id)) id->flag.guid = set ? 1 : 0; if (flag&8) id->flag.bindOnEquip = true; if (flag&16) id->flag.broadcast = 1; return true; } /** * @return: amount of retrieved entries. **/ static int itemdb_combo_split_atoi (char *str, int *val) { int i; for (i=0; i,<{ script }> **/ static void itemdb_read_combos(const char* basedir, bool silent) { uint32 lines = 0, count = 0; char line[1024]; char path[256]; FILE* fp; sprintf(path, "%s/%s", basedir, "item_combo_db.txt"); if ((fp = fopen(path, "r")) == NULL) { if(silent==0) ShowError("itemdb_read_combos: File not found \"%s\".\n", path); return; } // process rows one by one while(fgets(line, sizeof(line), fp)) { char *str[2], *p; lines++; if (line[0] == '/' && line[1] == '/') continue; memset(str, 0, sizeof(str)); p = line; p = trim(p); if (*p == '\0') continue;// empty line if (!strchr(p,',')) { /* is there even a single column? */ ShowError("itemdb_read_combos: Insufficient columns in line %d of \"%s\", skipping.\n", lines, path); continue; } str[0] = p; p = strchr(p,','); *p = '\0'; p++; str[1] = p; p = strchr(p,','); p++; if (str[1][0] != '{') { ShowError("itemdb_read_combos(#1): Invalid format (Script column) in line %d of \"%s\", skipping.\n", lines, path); continue; } /* no ending key anywhere (missing \}\) */ if ( str[1][strlen(str[1])-1] != '}' ) { ShowError("itemdb_read_combos(#2): Invalid format (Script column) in line %d of \"%s\", skipping.\n", lines, path); continue; } else { int items[MAX_ITEMS_PER_COMBO]; int v = 0, retcount = 0; struct item_data * id = NULL; int idx = 0; if((retcount = itemdb_combo_split_atoi(str[0], items)) < 2) { ShowError("itemdb_read_combos: line %d of \"%s\" doesn't have enough items to make for a combo (min:2), skipping.\n", lines, path); continue; } /* validate */ for(v = 0; v < retcount; v++) { if( !itemdb_exists(items[v]) ) { ShowError("itemdb_read_combos: line %d of \"%s\" contains unknown item ID %d, skipping.\n", lines, path,items[v]); break; } } /* failed at some item */ if( v < retcount ) continue; id = itemdb_exists(items[0]); idx = id->combos_count; /* first entry, create */ if( id->combos == NULL ) { CREATE(id->combos, struct item_combo*, 1); id->combos_count = 1; } else { RECREATE(id->combos, struct item_combo*, ++id->combos_count); } CREATE(id->combos[idx],struct item_combo,1); id->combos[idx]->nameid = (unsigned short*)aMalloc( retcount * sizeof(unsigned short) ); id->combos[idx]->count = retcount; id->combos[idx]->script = parse_script(str[1], path, lines, 0); id->combos[idx]->id = count; id->combos[idx]->isRef = false; /* populate ->nameid field */ for( v = 0; v < retcount; v++ ) { id->combos[idx]->nameid[v] = items[v]; } /* populate the children to refer to this combo */ for( v = 1; v < retcount; v++ ) { struct item_data * it; int index; it = itemdb_exists(items[v]); index = it->combos_count; if( it->combos == NULL ) { CREATE(it->combos, struct item_combo*, 1); it->combos_count = 1; } else { RECREATE(it->combos, struct item_combo*, ++it->combos_count); } CREATE(it->combos[index],struct item_combo,1); /* we copy previously alloc'd pointers and just set it to reference */ memcpy(it->combos[index],id->combos[idx],sizeof(struct item_combo)); /* we flag this way to ensure we don't double-dealloc same data */ it->combos[index]->isRef = true; } uidb_put(itemdb_combo,id->combos[idx]->id,id->combos[idx]); } count++; } fclose(fp); ShowStatus("Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n",count,path); return; } /** * Process Roulette items */ bool itemdb_parse_roulette_db(void) { int i, j; uint32 count = 0; // retrieve all rows from the item database if (SQL_ERROR == Sql_Query(mmysql_handle, "SELECT * FROM `%s`", db_roulette_table)) { Sql_ShowDebug(mmysql_handle); return false; } for (i = 0; i < MAX_ROULETTE_LEVEL; i++) rd.items[i] = 0; for (i = 0; i < MAX_ROULETTE_LEVEL; i++) { int k, limit = MAX_ROULETTE_COLUMNS - i; for (k = 0; k < limit && SQL_SUCCESS == Sql_NextRow(mmysql_handle); k++) { char* data; unsigned short item_id, amount; int level, flag; Sql_GetData(mmysql_handle, 1, &data, NULL); level = atoi(data); Sql_GetData(mmysql_handle, 2, &data, NULL); item_id = atoi(data); Sql_GetData(mmysql_handle, 3, &data, NULL); amount = atoi(data); Sql_GetData(mmysql_handle, 4, &data, NULL); flag = atoi(data); if (!itemdb_exists(item_id)) { ShowWarning("itemdb_parse_roulette_db: Unknown item ID '%hu' in level '%d'\n", item_id, level); continue; } if (amount < 1) { ShowWarning("itemdb_parse_roulette_db: Unsupported amount '%hu' for item ID '%hu' in level '%d'\n", amount, item_id, level); continue; } if (flag < 0 || flag > 1) { ShowWarning("itemdb_parse_roulette_db: Unsupported flag '%d' for item ID '%hu' in level '%d'\n", flag, item_id, level); continue; } j = rd.items[i]; RECREATE(rd.nameid[i], unsigned short, ++rd.items[i]); RECREATE(rd.qty[i], unsigned short, rd.items[i]); RECREATE(rd.flag[i], int, rd.items[i]); rd.nameid[i][j] = item_id; rd.qty[i][j] = amount; rd.flag[i][j] = flag; ++count; } } // free the query result Sql_FreeResult(mmysql_handle); for (i = 0; i < MAX_ROULETTE_LEVEL; i++) { int limit = MAX_ROULETTE_COLUMNS - i; if (rd.items[i] == limit) continue; if (rd.items[i] > limit) { ShowWarning("itemdb_parse_roulette_db: level %d has %d items, only %d supported, capping...\n", i + 1, rd.items[i], limit); rd.items[i] = limit; continue; } /** this scenario = rd.items[i] < limit **/ ShowWarning("itemdb_parse_roulette_db: Level %d has %d items, %d are required. Filling with Apples...\n", i + 1, rd.items[i], limit); rd.items[i] = limit; RECREATE(rd.nameid[i], unsigned short, rd.items[i]); RECREATE(rd.qty[i], unsigned short, rd.items[i]); RECREATE(rd.flag[i], int, rd.items[i]); for (j = 0; j < MAX_ROULETTE_COLUMNS - i; j++) { if (rd.qty[i][j]) continue; rd.nameid[i][j] = ITEMID_APPLE; rd.qty[i][j] = 1; rd.flag[i][j] = 0; } } ShowStatus("Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, db_roulette_table); return true; } /** * Free Roulette items */ static void itemdb_roulette_free(void) { int i; for (i = 0; i < MAX_ROULETTE_LEVEL; i++) { if (rd.nameid[i]) aFree(rd.nameid[i]); if (rd.qty[i]) aFree(rd.qty[i]); if (rd.flag[i]) aFree(rd.flag[i]); rd.nameid[i] = NULL; rd.qty[i] = NULL; rd.flag[i] = NULL; rd.items[i] = 0; } } /*====================================== * Applies gender restrictions according to settings. [Skotlex] *======================================*/ static char itemdb_gendercheck(struct item_data *id) { if (id->nameid == WEDDING_RING_M) //Grom Ring return 1; if (id->nameid == WEDDING_RING_F) //Bride Ring return 0; if (id->look == W_MUSICAL && id->type == IT_WEAPON) //Musical instruments are always male-only return 1; if (id->look == W_WHIP && id->type == IT_WEAPON) //Whips are always female-only return 0; return (battle_config.ignore_items_gender) ? 2 : id->sex; } /** * [RRInd] * For backwards compatibility, in Renewal mode, MATK from weapons comes from the atk slot * We use a ':' delimiter which, if not found, assumes the weapon does not provide any matk. **/ static void itemdb_re_split_atoi(char *str, int *val1, int *val2) { int i, val[2]; for (i=0; i<2; i++) { if (!str) break; val[i] = atoi(str); str = strchr(str,':'); if (str) *str++=0; } if( i == 0 ) { *val1 = *val2 = 0; return;//no data found } if( i == 1 ) {//Single Value *val1 = val[0]; *val2 = 0; return; } //We assume we have 2 values. *val1 = val[0]; *val2 = val[1]; return; } /** * Processes one itemdb entry */ static bool itemdb_parse_dbrow(char** str, const char* source, int line, int scriptopt) { /* +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+-------------+---------------+-----------------+--------------+-------------+------------+------+--------+--------------+----------------+ | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+-------------+---------------+-----------------+--------------+-------------+------------+------+--------+--------------+----------------+ | id | name_english | name_japanese | type | price_buy | price_sell | weight | attack | defence | range | slots | equip_jobs | equip_upper | equip_genders | equip_locations | weapon_level | equip_level | refineable | view | script | equip_script | unequip_script | +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+-------------+---------------+-----------------+--------------+-------------+------------+------+--------+--------------+----------------+ */ unsigned short nameid; struct item_data* id; if( atoi(str[0]) <= 0 || atoi(str[0]) >= MAX_ITEMID || atoi(str[0]) == dummy_item->nameid ) { ShowWarning("itemdb_parse_dbrow: Invalid id %d in line %d of \"%s\", skipping.\n", atoi(str[0]), line, source); return false; } nameid = atoi(str[0]); //ID,Name,Jname,Type,Price,Sell,Weight,ATK,DEF,Range,Slot,Job,Job Upper,Gender,Loc,wLV,eLV,refineable,View if (!(id = itemdb_exists(nameid))) id = itemdb_create_item(nameid); safestrncpy(id->name, str[1], sizeof(id->name)); safestrncpy(id->jname, str[2], sizeof(id->jname)); id->type = atoi(str[3]); if( id->type < 0 || id->type == IT_UNKNOWN || id->type == IT_UNKNOWN2 || ( id->type > IT_SHADOWGEAR && id->type < IT_CASH ) || id->type >= IT_MAX ) {// catch invalid item types ShowWarning("itemdb_parse_dbrow: Invalid item type %d for item %hu. IT_ETC will be used.\n", id->type, nameid); id->type = IT_ETC; } if (id->type == IT_DELAYCONSUME) { //Items that are consumed only after target confirmation id->type = IT_USABLE; id->flag.delay_consume = 1; } else //In case of an itemdb reload and the item type changed. id->flag.delay_consume = 0; //When a particular price is not given, we should base it off the other one //(it is important to make a distinction between 'no price' and 0z) if ( str[4][0] ) id->value_buy = atoi(str[4]); else id->value_buy = atoi(str[5]) * 2; if ( str[5][0] ) id->value_sell = atoi(str[5]); else id->value_sell = id->value_buy / 2; /* if ( !str[4][0] && !str[5][0]) { ShowWarning("itemdb_parse_dbrow: No buying/selling price defined for item %hu (%s), using 20/10z\n", nameid, id->jname); id->value_buy = 20; id->value_sell = 10; } else */ if (id->value_buy/124. < id->value_sell/75.) ShowWarning("itemdb_parse_dbrow: Buying/Selling [%d/%d] price of item %hu (%s) allows Zeny making exploit through buying/selling at discounted/overcharged prices!\n", id->value_buy, id->value_sell, nameid, id->jname); id->weight = atoi(str[6]); #ifdef RENEWAL itemdb_re_split_atoi(str[7],&id->atk,&id->matk); #else id->atk = atoi(str[7]); #endif id->def = atoi(str[8]); id->range = atoi(str[9]); id->slot = atoi(str[10]); if (id->slot > MAX_SLOTS) { ShowWarning("itemdb_parse_dbrow: Item %hu (%s) specifies %d slots, but the server only supports up to %d. Using %d slots.\n", nameid, id->jname, id->slot, MAX_SLOTS, MAX_SLOTS); id->slot = MAX_SLOTS; } itemdb_jobid2mapid(id->class_base, (unsigned int)strtoul(str[11],NULL,0)); id->class_upper = atoi(str[12]); id->sex = atoi(str[13]); id->equip = atoi(str[14]); if (!id->equip && itemdb_isequip2(id)) { ShowWarning("Item %hu (%s) is an equipment with no equip-field! Making it an etc item.\n", nameid, id->jname); id->type = IT_ETC; } if( id->type != IT_SHADOWGEAR && id->equip&EQP_SHADOW_GEAR ) { ShowWarning("Item %hu (%s) have invalid equipment slot! Making it an etc item.\n", nameid, id->jname); id->type = IT_ETC; } id->wlv = cap_value(atoi(str[15]), REFINE_TYPE_ARMOR, REFINE_TYPE_MAX); itemdb_re_split_atoi(str[16],&id->elv,&id->elvmax); id->flag.no_refine = atoi(str[17]) ? 0 : 1; //FIXME: verify this id->look = atoi(str[18]); id->flag.available = 1; id->view_id = 0; id->sex = itemdb_gendercheck(id); //Apply gender filtering. if (id->script) { script_free_code(id->script); id->script = NULL; } if (id->equip_script) { script_free_code(id->equip_script); id->equip_script = NULL; } if (id->unequip_script) { script_free_code(id->unequip_script); id->unequip_script = NULL; } if (*str[19]) id->script = parse_script(str[19], source, line, scriptopt); if (*str[20]) id->equip_script = parse_script(str[20], source, line, scriptopt); if (*str[21]) id->unequip_script = parse_script(str[21], source, line, scriptopt); if (!id->nameid) { id->nameid = nameid; uidb_put(itemdb, nameid, id); } return true; } /** * Read item from item db * item_db2 overwriting item_db */ static int itemdb_readdb(void){ const char* filename[] = { DBPATH"item_db.txt", DBIMPORT"/item_db.txt" }; int fi; for( fi = 0; fi < ARRAYLENGTH(filename); ++fi ) { uint32 lines = 0, count = 0; char line[1024]; char path[256]; FILE* fp; sprintf(path, "%s/%s", db_path, filename[fi]); fp = fopen(path, "r"); if( fp == NULL ) { ShowWarning("itemdb_readdb: File not found \"%s\", skipping.\n", path); continue; } // process rows one by one while(fgets(line, sizeof(line), fp)) { char *str[32], *p; int i; lines++; if(line[0] == '/' && line[1] == '/') continue; memset(str, 0, sizeof(str)); p = line; while( ISSPACE(*p) ) ++p; if( *p == '\0' ) continue;// empty line for( i = 0; i < 19; ++i ) { str[i] = p; p = strchr(p,','); if( p == NULL ) break;// comma not found *p = '\0'; ++p; } if( p == NULL ) { ShowError("itemdb_readdb: Insufficient columns in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); continue; } // Script if( *p != '{' ) { ShowError("itemdb_readdb: Invalid format (Script column) in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); continue; } str[19] = p; p = strstr(p+1,"},"); if( p == NULL ) { ShowError("itemdb_readdb: Invalid format (Script column) in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); continue; } p[1] = '\0'; p += 2; // OnEquip_Script if( *p != '{' ) { ShowError("itemdb_readdb: Invalid format (OnEquip_Script column) in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); continue; } str[20] = p; p = strstr(p+1,"},"); if( p == NULL ) { ShowError("itemdb_readdb: Invalid format (OnEquip_Script column) in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); continue; } p[1] = '\0'; p += 2; // OnUnequip_Script (last column) if( *p != '{' ) { ShowError("itemdb_readdb: Invalid format (OnUnequip_Script column) in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); continue; } str[21] = p; if ( str[21][strlen(str[21])-2] != '}' ) { /* lets count to ensure it's not something silly e.g. a extra space at line ending */ int v, lcurly = 0, rcurly = 0; for( v = 0; v < strlen(str[21]); v++ ) { if( str[21][v] == '{' ) lcurly++; else if ( str[21][v] == '}' ) rcurly++; } if( lcurly != rcurly ) { ShowError("itemdb_readdb: Mismatching curly braces in line %d of \"%s\" (item with id %d), skipping.\n", lines, path, atoi(str[0])); continue; } } if (!itemdb_parse_dbrow(str, path, lines, 0)) continue; count++; } fclose(fp); ShowStatus("Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, path); } return 0; } /** * Read item_db table */ static int itemdb_read_sqldb(void) { const char* item_db_name[] = { #ifdef RENEWAL item_db_re_db, #else item_db_db, #endif item_db2_db }; int fi; for( fi = 0; fi < ARRAYLENGTH(item_db_name); ++fi ) { uint32 lines = 0, count = 0; // retrieve all rows from the item database if( SQL_ERROR == Sql_Query(mmysql_handle, "SELECT * FROM `%s`", item_db_name[fi]) ) { Sql_ShowDebug(mmysql_handle); continue; } // process rows one by one while( SQL_SUCCESS == Sql_NextRow(mmysql_handle) ) {// wrap the result into a TXT-compatible format char* str[22]; char* dummy = ""; int i; ++lines; for( i = 0; i < 22; ++i ) { Sql_GetData(mmysql_handle, i, &str[i], NULL); if( str[i] == NULL ) str[i] = dummy; // get rid of NULL columns } if (!itemdb_parse_dbrow(str, item_db_name[fi], lines, SCRIPT_IGNORE_EXTERNAL_BRACKETS)) continue; ++count; } // free the query result Sql_FreeResult(mmysql_handle); ShowStatus("Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, item_db_name[fi]); } return 0; } /** Check if the item is restricted by item_noequip.txt * @param id Item that will be checked * @param m Map ID * @return true: can't be used; false: can be used */ bool itemdb_isNoEquip(struct item_data *id, uint16 m) { if (!id->flag.no_equip) return false; /* on restricted maps the item is consumed but the effect is not used */ if ((!map_flag_vs(m) && id->flag.no_equip&1) || // Normal (map[m].flag.pvp && id->flag.no_equip&2) || // PVP (map_flag_gvg(m) && id->flag.no_equip&4) || // GVG (map[m].flag.battleground && id->flag.no_equip&8) || // Battleground (map[m].flag.restricted && id->flag.no_equip&(8*map[m].zone)) // Zone restriction ) return true; return false; } /** * Check if item is available in spellbook_db or not * @param nameid * @return True if item is spellbook; False if not */ bool itemdb_is_spellbook2(unsigned short nameid) { unsigned char i; if (!nameid || !itemdb_exists(nameid) || !skill_spellbook_count) return false; ARR_FIND(0, MAX_SKILL_SPELLBOOK_DB, i, skill_spellbook_db[i].nameid == nameid); return i == MAX_SKILL_SPELLBOOK_DB ? false : true; } /** * Read all item-related databases */ static void itemdb_read(void) { int i; const char* dbsubpath[] = { "", "/"DBIMPORT, }; if (db_use_sqldbs) itemdb_read_sqldb(); else itemdb_readdb(); for(i=0; iscript ) script_free_code(self->script); if( self->equip_script ) script_free_code(self->equip_script); if( self->unequip_script ) script_free_code(self->unequip_script); if( self->combos_count ) { int i; for( i = 0; i < self->combos_count; i++ ) { if( !self->combos[i]->isRef ) { aFree(self->combos[i]->nameid); if (self->combos[i]->script) script_free_code(self->combos[i]->script); } aFree(self->combos[i]); } aFree(self->combos); } #if defined(DEBUG) // trash item memset(self, 0xDD, sizeof(struct item_data)); #endif // free self aFree(self); } /** * @see DBApply */ static int itemdb_final_sub(DBKey key, DBData *data, va_list ap) { struct item_data *id = (struct item_data *)db_data2ptr(data); destroy_item_data(id); return 0; } static int itemdb_group_free(DBKey key, DBData *data, va_list ap) { struct s_item_group_db *group = (struct s_item_group_db *)db_data2ptr(data); uint8 j; if (!group) return 0; if (group->must_qty) aFree(group->must); group->must_qty = 0; for (j = 0; j < MAX_ITEMGROUP_RANDGROUP; j++) { if (!group->random[j].data_qty || !(&group->random[j])) continue; aFree(group->random[j].data); group->random[j].data_qty = 0; } aFree(group); return 0; } /** * Reload Item DB */ void itemdb_reload(void) { struct s_mapiterator* iter; struct map_session_data* sd; int i,d,k; itemdb_group->clear(itemdb_group, itemdb_group_free); itemdb->clear(itemdb, itemdb_final_sub); db_clear(itemdb_combo); if (battle_config.feature_roulette) itemdb_roulette_free(); // read new data itemdb_read(); cashshop_reloaddb(); if (battle_config.feature_roulette) itemdb_parse_roulette_db(); //Epoque's awesome @reloaditemdb fix - thanks! [Ind] //- Fixes the need of a @reloadmobdb after a @reloaditemdb to re-link monster drop data for( i = 0; i < MAX_MOB_DB; i++ ) { struct mob_db *entry; if( !((i < MOBID_TREAS01 || i > MOBID_TREAS40) && (i < MOBID_TREAS41 || i > MOBID_TREAS49)) ) continue; entry = mob_db(i); for(d = 0; d < MAX_MOB_DROP; d++) { struct item_data *id; if( !entry->dropitem[d].nameid ) continue; id = itemdb_search(entry->dropitem[d].nameid); for (k = 0; k < MAX_SEARCH; k++) { if (id->mob[k].chance <= entry->dropitem[d].p) break; } if (k == MAX_SEARCH) continue; if (id->mob[k].id != i) memmove(&id->mob[k+1], &id->mob[k], (MAX_SEARCH-k-1)*sizeof(id->mob[0])); id->mob[k].chance = entry->dropitem[d].p; id->mob[k].id = i; } } // readjust itemdb pointer cache for each player iter = mapit_geteachpc(); for( sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter); sd = (struct map_session_data*)mapit_next(iter) ) { memset(sd->item_delay, 0, sizeof(sd->item_delay)); // reset item delays pc_setinventorydata(sd); pc_check_available_item(sd); // Check for invalid(ated) items. /* clear combo bonuses */ if( sd->combos.count ) { aFree(sd->combos.bonus); aFree(sd->combos.id); aFree(sd->combos.pos); sd->combos.bonus = NULL; sd->combos.id = NULL; sd->combos.pos = NULL; sd->combos.count = 0; if( pc_load_combo(sd) > 0 ) status_calc_pc(sd, SCO_FORCE); } } mapit_free(iter); } /** * Finalizing Item DB */ void do_final_itemdb(void) { db_destroy(itemdb_combo); itemdb_group->destroy(itemdb_group, itemdb_group_free); itemdb->destroy(itemdb, itemdb_final_sub); destroy_item_data(dummy_item); if (battle_config.feature_roulette) itemdb_roulette_free(); } /** * Initializing Item DB */ void do_init_itemdb(void) { itemdb = uidb_alloc(DB_OPT_BASE); itemdb_combo = uidb_alloc(DB_OPT_BASE); itemdb_group = uidb_alloc(DB_OPT_BASE); itemdb_create_dummy(); itemdb_read(); if (battle_config.feature_roulette) itemdb_parse_roulette_db(); }