// Copyright (c) Copyright (c) Hercules Dev Team, licensed under GNU GPL. // Copyright (c) 2014 - 2015 Evol developers #include "common/hercules.h" #include #include #include #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 "map/itemdb.h" #include "map/npc.h" #include "map/pc.h" #include "map/quest.h" #include "ecommon/utils/strutil.h" #include "ecommon/struct/strutildata.h" #include "emap/craft.h" #include "emap/craftconf.h" #include "emap/lang.h" struct DBMap *craftvar_db = NULL; int craft_counter = 0; void do_init_craft(void) { craftvar_db = idb_alloc(DB_OPT_RELEASE_BOTH); } static void delete_craft_var(struct craft_vardata *craft) { if (!craft) return; int index; for (index = 0; index < craft_inventory_size; index ++) { struct craft_slot *slot = &craft->slots[index]; VECTOR_CLEAR(slot->items); } } static int delete_craftvar_sub(union DBKey key __attribute__ ((unused)), struct DBData *data, va_list args __attribute__ ((unused))) { struct craft_vardata *craft = DB->data2ptr(data); if (!craft) return 0; delete_craft_var(craft); return 0; } void do_final_craft(void) { craftvar_db->destroy(craftvar_db, delete_craftvar_sub); } bool craft_checkstr(TBL_PC *sd, const char *craftstr) { if (!sd || !craftstr) return false; int amounts[MAX_INVENTORY]; int f; for (f = 0; f < MAX_INVENTORY; f ++) amounts[f] = 0; struct strutil_data *craftdata = strutil_split(craftstr, '|', craft_inventory_size + 1); if (!craftdata) return false; if (craftdata->len < 1) { strutil_free(craftdata); return false; } int index; for (index = 0; index < craftdata->len; index ++) { const char *slotstr = VECTOR_INDEX(craftdata->parts, index + 1); if (!slotstr || !*slotstr) continue; struct strutil_data *slotdata = strutil_split(slotstr, ';', sd->status.inventorySize + 1); if (!slotdata) { strutil_free(slotdata); continue; } int slot; int item_id = 0; for (slot = 0; slot < slotdata->len; slot ++) { const char *itemstr = VECTOR_INDEX(slotdata->parts, slot + 1); if (!itemstr || !*itemstr) continue; int index; int amount; int cnt = sscanf(itemstr, "%d,%d", &index, &amount); if (cnt != 2) { index = atoi(itemstr); amount = 1; } if (index < 0 || index >= sd->status.inventorySize) { // wrong item index strutil_free(slotdata); strutil_free(craftdata); return false; } const int new_item = sd->status.inventory[index].nameid; if (item_id != 0 && new_item != item_id) { // different item id in same slot strutil_free(slotdata); strutil_free(craftdata); return false; } if (new_item != 0) item_id = new_item; amounts[index] += amount; if (amounts[index] > 32000) { // slot overflow strutil_free(slotdata); strutil_free(craftdata); return false; } } strutil_free(slotdata); } strutil_free(craftdata); for (f = 0; f < sd->status.inventorySize; f ++) { const int amount = amounts[f]; if (!amount) continue; if(sd->status.inventory[f].nameid == 0 || sd->status.inventory[f].amount < amount || sd->status.inventory[f].equip > 0) { return false; } } return true; } struct craft_vardata *craft_str_to_craft(TBL_PC *sd, const char *craftstr) { if (!sd || !craftstr) return false; struct strutil_data *craftdata = strutil_split(craftstr, '|', craft_inventory_size + 1); if (!craftdata) return false; if (craftdata->len < 1) { strutil_free(craftdata); return NULL; } struct craft_vardata *vardata = aCalloc(1, sizeof(struct craft_vardata)); if (!vardata) return false; vardata->entry_id = -1; int index; for (index = 0; index < craftdata->len; index ++) { const char *slotstr = VECTOR_INDEX(craftdata->parts, index + 1); if (!slotstr || !*slotstr) continue; struct strutil_data *slotdata = strutil_split(slotstr, ';', sd->status.inventorySize + 1); if (!slotdata) { strutil_free(slotdata); continue; } struct craft_slot *crslot = &vardata->slots[index]; VECTOR_INIT(crslot->items); int slot; for (slot = 0; slot < slotdata->len; slot ++) { const char *itemstr = VECTOR_INDEX(slotdata->parts, slot + 1); if (!itemstr || !*itemstr) continue; int index; int amount; int cnt = sscanf(itemstr, "%d,%d", &index, &amount); if (cnt != 2) { index = atoi(itemstr); amount = 1; } if (index < 0 || index >= sd->status.inventorySize) { // wrong item index strutil_free(slotdata); strutil_free(craftdata); return false; } VECTOR_ENSURE(crslot->items, 1, 1); VECTOR_INSERTZEROED(crslot->items, slot); struct item_pair *pair = &VECTOR_INDEX(crslot->items, slot); pair->index = index; pair->amount = amount; } strutil_free(slotdata); } strutil_free(craftdata); return vardata; } int str_to_craftvar(TBL_PC *sd, const char *craftstr) { if (!craft_checkstr(sd, craftstr)) { if (sd) ShowWarning("invalid craft: %d\n", sd->bl.id); return -1; } struct craft_vardata *craft = craft_str_to_craft(sd, craftstr); if (!craft) return -1; craft_counter ++; idb_put(craftvar_db, craft_counter, craft); return craft_counter; } void craft_dump(TBL_PC *sd, const int id) { struct craft_vardata *craft = idb_get(craftvar_db, id); if (!sd) return; if (!craft) { ShowError("Craft object with id %d not exists.\n", id); return; } int index; ShowInfo("craft dump: %d\n", id); for (index = 0; index < craft_inventory_size; index ++) { struct craft_slot *crslot = &craft->slots[index]; const int len = VECTOR_LENGTH(crslot->items); int slot; if (len > 0) ShowInfo(" index: %d\n", index); for (slot = 0; slot < len; slot ++) { struct item_pair *pair = &VECTOR_INDEX(crslot->items, slot); const int invIndex = pair->index; if (invIndex >= 0 && invIndex < sd->status.inventorySize && sd->status.inventory[invIndex].nameid) { const int item_id = sd->status.inventory[invIndex].nameid; const struct item_data *i_data = itemdb->search(item_id); if (i_data) { ShowInfo(" item: %d (%s,%d), %d\n", invIndex, lang_pctrans(i_data->jname, sd), item_id, pair->amount); } else { ShowInfo(" item: %d, %d\n", invIndex, pair->amount); } } else { ShowInfo(" item: %d, %d\n", invIndex, pair->amount); } } } } void craft_delete(const int id) { struct craft_vardata *craft = idb_get(craftvar_db, id); if (!craft) { ShowError("Craft object with id %d not exists.\n", id); return; } delete_craft_var(craft); idb_remove(craftvar_db, id); } struct craft_slot *craft_get_slot(const int id, const int slot) { struct craft_vardata *craft = idb_get(craftvar_db, id); if (!craft) { ShowError("Craft object with id %d not exists.\n", id); return NULL; } if (slot < 0 || slot > craft_inventory_size) { ShowError("Wrong slot %d for craft with id %d.\n", slot, id); return NULL; } return &craft->slots[slot]; } bool craft_validate(TBL_PC *sd, const int id) { struct craft_vardata *craft = idb_get(craftvar_db, id); if (!sd) return false; if (!craft) { ShowError("Craft object with id %d not exists.\n", id); return false; } int amounts[MAX_INVENTORY]; int f; for (f = 0; f < MAX_INVENTORY; f ++) amounts[f] = 0; int index; for (index = 0; index < craft_inventory_size; index ++) { struct craft_slot *crslot = &craft->slots[index]; const int len = VECTOR_LENGTH(crslot->items); int slot; for (slot = 0; slot < len; slot ++) { struct item_pair *pair = &VECTOR_INDEX(crslot->items, slot); const int invIndex = pair->index; if (invIndex < 0 || invIndex >= sd->status.inventorySize || !sd->status.inventory[invIndex].nameid || sd->status.inventory[invIndex].amount <= 0 || sd->status.inventory[invIndex].equip > 0) { return false; } amounts[invIndex] += pair->amount; } } for (f = 0; f < sd->status.inventorySize; f ++) { const int amount = amounts[f]; if (!amount) continue; if(sd->status.inventory[f].nameid == 0 || sd->status.inventory[f].amount < amount || sd->status.inventory[f].equip > 0) { return false; } } return true; } static int find_inventory_item(TBL_PC *sd, const int id, const int amount) { int i; if (!sd) return -1; for (i = 0; i < sd->status.inventorySize; i++) { if (sd->status.inventory[i].nameid == id && sd->status.inventory[i].amount >= amount && sd->status.inventory[i].equip == 0) { return i; } } return -1; } static int find_inventory_equipped_item(TBL_PC *sd, const int id) { int i; if (!sd) return -1; for (i = 0; i < sd->status.inventorySize; i++) { if (sd->status.inventory[i].nameid == id && sd->status.inventory[i].amount > 0 && sd->status.inventory[i].equip > 0) { return i; } } return -1; } static int find_local_inventory_item(TBL_PC *sd, struct item_pair *local_inventory, const int id) { int i; if (!sd || !local_inventory) return -1; for (i = 0; i < sd->status.inventorySize; i++) { struct item_pair *pair = &local_inventory[i]; if (pair->index == id && pair->amount >= 0) { return i; } } return -1; } static bool check_items_collection(TBL_PC *sd, struct item_pair *local_inventory, struct craft_items_collection *vector) { if (!sd || !local_inventory) return false; int len = VECTOR_LENGTH(*vector); int i; if (len > 0) { for (i = 0; i < len; i ++) { struct item_pair2 *itemPair = &VECTOR_INDEX(*vector, i); int needAmount = itemPair->amount; while (needAmount > 0) { const int index = find_local_inventory_item(sd, local_inventory, itemPair->index); if (index < 0) return false; struct item_pair *localPair = &local_inventory[index]; if (needAmount > localPair->amount) { needAmount -= localPair->amount; localPair->index = 0; localPair->amount = 0; } else { localPair->amount -= needAmount; needAmount = 0; if (localPair->amount == 0) localPair->index = 0; } } } } return true; } static bool check_equips(TBL_PC *sd, struct craft_items_collection *vector) { int len = VECTOR_LENGTH(*vector); int i; if (len > 0) { for (i = 0; i < len; i ++) { struct item_pair2 *itemPair = &VECTOR_INDEX(*vector, i); if (find_inventory_equipped_item(sd, itemPair->index) < 0) return false; } } return true; } static bool check_skills(TBL_PC *sd, struct craft_items_collection *vector) { int len = VECTOR_LENGTH(*vector); int i; if (!sd) return false; if (len > 0) { for (i = 0; i < len; i ++) { struct item_pair2 *itemPair = &VECTOR_INDEX(*vector, i); const int index = skill->get_index(itemPair->index); if (!index) return false; if (sd->status.skill[index].lv < itemPair->amount) return false; } } return true; } static bool check_quests(TBL_PC *sd, struct craft_items_collection *vector) { int len = VECTOR_LENGTH(*vector); int i; if (!sd) return false; if (len > 0) { for (i = 0; i < len; i ++) { struct item_pair2 *itemPair = &VECTOR_INDEX(*vector, i); int n; ARR_FIND(0, sd->avail_quests, n, sd->quest_log[n].quest_id == itemPair->index); if (n == sd->avail_quests) return false; if (sd->quest_log[n].count[0] < itemPair->amount) return false; } } return true; } static bool check_inventories(TBL_PC *sd __attribute__ ((unused)), struct craft_db_entry *entry, struct item_pair *craft_inventory) { if (!entry || !craft_inventory) return false; int inv_count = VECTOR_LENGTH(entry->inventories); bool correct = true; int f; for (f = 0; f < inv_count; f ++) { struct craft_db_inventory *entry_inventory = &VECTOR_INDEX(entry->inventories, f); int i; for (i = 0; i < craft_inventory_size; i ++) { struct item_pair *invItem = &craft_inventory[i]; struct item_pair *entryItem = &entry_inventory->items[i]; if (invItem->index != entryItem->index || invItem->amount < entryItem->amount) { // items not same or amount too small, skipping correct = false; continue; } } if (correct) { entry->selected_inventory = entry_inventory; return true; } correct = true; } return false; } static void simplify_craftvar(TBL_PC *sd, struct item_pair *craft_inventory, struct craft_vardata *craft) { int i; if (!craft || !craft_inventory) return; // combine different slots from inventory var into one slot with id and amount for (i = 0; i < craft_inventory_size; i ++) { struct item_pair *invPair = &craft_inventory[i]; invPair->index = 0; invPair->amount = 0; struct craft_slot *slot = &craft->slots[i]; const int len = VECTOR_LENGTH(slot->items); int f; for (f = 0; f < len; f ++) { struct item_pair *pair = &VECTOR_INDEX(slot->items, f); const int itemIndex = pair->index; const int itemId = sd->status.inventory[itemIndex].nameid; // additional check for craft var if (invPair->index != 0 && invPair->index != itemId) continue; invPair->index = itemId; invPair->amount += sd->status.inventory[itemIndex].amount; } } } static void init_inventory_copy(TBL_PC *sd, struct item_pair *local_inventory) { int f; if (!sd || !local_inventory) return; for (f = 0; f < sd->status.inventorySize; f ++) { const int id = sd->status.inventory[f].nameid; const int amount = sd->status.inventory[f].amount; struct item_pair *pair = &local_inventory[f]; if(id > 0 && amount > 0 && sd->status.inventory[f].equip == 0) { pair->index = id; pair->amount = amount; } else { pair->index = 0; pair->amount = 0; } } } static bool apply_craft_inventory(struct craft_db_inventory *entry_inventory, struct craft_vardata *craft, struct item_pair *local_inventory) { int f; if (!entry_inventory || !craft || !local_inventory) return false; for (f = 0; f < craft_inventory_size; f ++) { struct item_pair *entryItem = &entry_inventory->items[f]; int needAmount = entryItem->amount; struct craft_slot *craftSlot = &craft->slots[f]; int i; int len = VECTOR_LENGTH(craftSlot->items); for (i = 0; i < len && needAmount; i ++) { struct item_pair *craftItem = &VECTOR_INDEX(craftSlot->items, i); if (needAmount > craftItem->amount) { local_inventory[craftItem->index].amount -= craftItem->amount; needAmount -= craftItem->amount; } else { local_inventory[craftItem->index].amount -= needAmount; needAmount = 0; } if (local_inventory[craftItem->index].amount == 0) local_inventory[craftItem->index].index = 0; } if (needAmount > 0) return false; } return true; } static int craft_get_recipe(TBL_PC *sd, struct craft_vardata *craft, struct item_pair *inventory, const int flag) { if (!sd || !craft || !inventory) return -1; struct craft_db_entry *best_entry = NULL; struct craft_db_entry *entry; struct item_pair local_inventory[MAX_INVENTORY]; // id, amount init_inventory_copy(sd, &local_inventory[0]); struct DBIterator* iter = db_iterator(craftconf_db); for (entry = dbi_first(iter); dbi_exists(iter); entry = dbi_next(iter)) { //ShowInfo("check recipes: %d\n", entry->id); if ((flag && !(entry->flag & flag)) || sd->status.zeny < entry->price || sd->status.base_level < entry->level) { continue; } struct item_pair temp_inventory[MAX_INVENTORY]; // id, amount memcpy(&temp_inventory[0], &local_inventory[0], MAX_INVENTORY * sizeof(struct item_pair)); //ShowInfo("base correct\n"); if (!check_inventories(sd, entry, inventory)) continue; //ShowInfo("inventories correct\n"); if (!apply_craft_inventory(entry->selected_inventory, craft, &temp_inventory[0])) continue; //ShowInfo("apply craft correct\n"); if (!check_items_collection(sd, &temp_inventory[0], &entry->delete_items)) continue; //ShowInfo("delete_items correct\n"); if (!check_items_collection(sd, &temp_inventory[0], &entry->required_items)) continue; //ShowInfo("required_items correct\n"); if (!check_equips(sd, &entry->required_equips)) continue; //ShowInfo("required_equips correct\n"); if (!check_skills(sd, &entry->required_skills)) continue; //ShowInfo("required_quests correct\n"); if (!check_quests(sd, &entry->required_quests)) continue; //ShowInfo("found\n"); if (best_entry == NULL || entry->priority > best_entry->priority || entry->id < best_entry->id) { best_entry = entry; } } dbi_destroy(iter); return best_entry ? best_entry->id : -1; } int craft_find_entry(TBL_PC *sd, const int craftvar, const int flag) { if (!sd) return -1; struct craft_vardata *craft = idb_get(craftvar_db, craftvar); if (!craft) { ShowError("Craft object with id %d not exists.\n", craftvar); return -1; } struct item_pair craft_inventory[craft_inventory_size]; simplify_craftvar(sd, &craft_inventory[0], craft); const int recipe = craft_get_recipe(sd, craft, &craft_inventory[0], flag); craft->entry_id = recipe; //ShowInfo("found recipe: %d\n", recipe); return recipe; } static bool craft_delete_items(TBL_PC *sd, struct craft_items_collection *vector) { if (!sd || !vector) return false; int len = VECTOR_LENGTH(*vector); int i; if (len > 0) { for (i = 0; i < len; i ++) { struct item_pair2 *itemPair = &VECTOR_INDEX(*vector, i); const int index = find_inventory_item(sd, itemPair->index, itemPair->amount); if (!index) return false; pc->delitem(sd, index, itemPair->amount, 2, 8, LOG_TYPE_PRODUCE); } } return true; } static bool craft_create_items(TBL_PC *sd, struct craft_db_entry *entry) { if (!sd || !entry) return false; const int vars = VECTOR_LENGTH(entry->create_items); struct craft_items_collection *vector = &VECTOR_INDEX(entry->create_items, (rand() % (vars * 10)) / 10); int len = VECTOR_LENGTH(*vector); int i; if (len > 0) { struct item it; for (i = 0; i < len; i ++) { struct item_pair2 *itemPair = &VECTOR_INDEX(*vector, i); memset(&it, 0, sizeof(it)); it.nameid = itemPair->index; it.amount = itemPair->amount; it.identify = 1; for (int f = 0; f < MAX_SLOTS; f ++) it.card[f] = itemPair->cards[f]; pc->additem(sd, &it, itemPair->amount, LOG_TYPE_PRODUCE); } } return true; } static bool craft_delete_inventory(TBL_PC *sd, struct craft_vardata *craft, struct craft_db_entry *entry) { if (!sd || !craft || !entry) return false; struct craft_db_inventory *entry_inventory = entry->selected_inventory; if (!entry_inventory) return false; int f; for (f = 0; f < craft_inventory_size; f ++) { struct item_pair *entryItem = &entry_inventory->items[f]; int needAmount = entryItem->amount; struct craft_slot *craftSlot = &craft->slots[f]; int i; int len = VECTOR_LENGTH(craftSlot->items); for (i = 0; i < len && needAmount; i ++) { struct item_pair *craftItem = &VECTOR_INDEX(craftSlot->items, i); if (needAmount > craftItem->amount) { pc->delitem(sd, craftItem->index, craftItem->amount, 2, 8, LOG_TYPE_PRODUCE); needAmount -= craftItem->amount; } else { pc->delitem(sd, craftItem->index, needAmount, 2, 8, LOG_TYPE_PRODUCE); needAmount = 0; } } if (needAmount > 0) { ShowError("Craft broken amount. Probably exploit in use: '" CL_WHITE"%s"CL_RESET"'" " (AID/CID: '"CL_WHITE"%d/%d"CL_RESET"'.\n", sd->status.name, sd->status.account_id, sd->status.char_id); return false; } } return true; } bool craft_use(TBL_PC *sd, const int id) { struct craft_vardata *craft = idb_get(craftvar_db, id); if (!sd) return false; if (!craft) { ShowError("Craft object with id %d not exists.\n", id); return false; } struct craft_db_entry *entry = idb_get(craftconf_db, craft->entry_id); if (!entry) { ShowError("Craft config entry with id %d not exists.\n", craft->entry_id); return false; } if (!craft_delete_inventory(sd, craft, entry)) return false; if (!craft_delete_items(sd, &entry->delete_items)) return false; if (sd->status.zeny < entry->price) { ShowError("Craft broken zeny. Probably exploit in use: '" CL_WHITE"%s"CL_RESET"'" " (AID/CID: '"CL_WHITE"%d/%d"CL_RESET"'.\n", sd->status.name, sd->status.account_id, sd->status.char_id); return false; } sd->status.zeny -= entry->price; craft_create_items(sd, entry); clif->updatestatus(sd, SP_ZENY); clif->updatestatus(sd, SP_WEIGHT); return true; } int craft_get_entry_code(TBL_PC *sd __attribute__ ((unused)), const int id) { struct craft_db_entry *entry = idb_get(craftconf_db, id); if (!entry) { ShowError("Craft config entry with id %d not exists.\n", id); return -1; } return entry->return_code; }