diff --git a/db/const.txt b/db/const.txt index 653760d7650..287c7eda728 100644 --- a/db/const.txt +++ b/db/const.txt @@ -2732,3 +2732,5 @@ ARCWANDCLAN 2 GOLDENMACECLAN 3 CROSSBOWCLAN 4 JUMPINGCLAN 5 + +RDMOPTG_Crimson_Weapon 1 diff --git a/db/import-tmpl/item_randomopt_group.txt b/db/import-tmpl/item_randomopt_group.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/db/import-tmpl/mob_drop.txt b/db/import-tmpl/mob_drop.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/db/pre-re/item_randomopt_group.txt b/db/pre-re/item_randomopt_group.txt new file mode 100644 index 00000000000..d7d68d4584f --- /dev/null +++ b/db/pre-re/item_randomopt_group.txt @@ -0,0 +1 @@ +// ,,,,{,,,,,,,,,,,,} diff --git a/db/pre-re/mob_drop.txt b/db/pre-re/mob_drop.txt new file mode 100644 index 00000000000..a840a49b47b --- /dev/null +++ b/db/pre-re/mob_drop.txt @@ -0,0 +1,17 @@ +// Monster Drop Database +// Add drop item to monster +// +// Structure: +// ,,{,,} +// +// : Monster ID. See db/[pre-]re/mob_db.txt +// : Item ID. +// : 1 = 0.01% +// 100 = 1% +// 10000 = 100% +// Just like rate in mob_db.txt, adjusted by battle_config. +// To remove original drop from monster, use 0 as rate. +// Optional: +// : If set, the dropped item will be modified by Random Option Group based on db/[pre-]re/item_randomopt_group.txt +// : 1 - The item is protected from steal. +// 2 - As MVP Reward diff --git a/db/re/item_randomopt_group.txt b/db/re/item_randomopt_group.txt new file mode 100644 index 00000000000..673e0f482d1 --- /dev/null +++ b/db/re/item_randomopt_group.txt @@ -0,0 +1,13 @@ +// ,,,,{,,,,,,,,,,,,} + +// Crimson Weapon +RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_NOTHING,0,0 +RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_WATER,0,0 +RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_GROUND,0,0 +RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_FIRE,0,0 +RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_WIND,0,0 +RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_POISON,0,0 +RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_SAINT,0,0 +RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_DARKNESS,0,0 +RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_TELEKINESIS,0,0 +RDMOPTG_Crimson_Weapon,1,RDMOPT_WEAPON_ATTR_UNDEAD,0,0 diff --git a/db/re/mob_drop.txt b/db/re/mob_drop.txt new file mode 100644 index 00000000000..cd45806656b --- /dev/null +++ b/db/re/mob_drop.txt @@ -0,0 +1,123 @@ +// Monster Drop Database +// Add drop item to monster +// +// Structure: +// ,,{,,} +// +// : Monster ID. See db/[pre-]re/mob_db.txt +// : Item ID. +// : 1 = 0.01% +// 100 = 1% +// 10000 = 100% +// Just like rate in mob_db.txt, adjusted by battle_config. +// To remove original drop from monster, use 0 as rate. +// Optional: +// : If set, the dropped item will be modified by Random Option Group based on db/[pre-]re/item_randomopt_group.txt +// : 1 - The item is protected from steal. +// 2 - As MVP Reward + +1063,1102,100,RDMOPTG_None // LUNATIC +2770,1102,500,RDMOPTG_None // C2_LUNATIC +2771,1102,500,RDMOPTG_None // C3_LUNATIC +2072,1839,50,RDMOPTG_Crimson_Weapon // JAGUAR +1584,21015,50,RDMOPTG_Crimson_Weapon // TAMRUAN +2639,21015,250,RDMOPTG_Crimson_Weapon // C4_TAMRUAN +1154,13454,50,RDMOPTG_Crimson_Weapon // PASANA +1154,28705,50,RDMOPTG_Crimson_Weapon // PASANA +2719,13454,250,RDMOPTG_Crimson_Weapon // C1_PASANA +2719,28705,250,RDMOPTG_Crimson_Weapon // C1_PASANA +1117,28604,50,RDMOPTG_Crimson_Weapon // EVIL_DRUID +1517,16040,50,RDMOPTG_Crimson_Weapon // LI_ME_MANG_RYANG +2071,28007,50,RDMOPTG_Crimson_Weapon // HEADLESS_MULE +2778,16040,250,RDMOPTG_Crimson_Weapon // C5_LI_ME_MANG_RYANG +2838,28604,50,RDMOPTG_Crimson_Weapon // C5_EVIL_DRUID +1613,13127,50,RDMOPTG_None // METALING +1386,28705,50,RDMOPTG_Crimson_Weapon // SLEEPER +2655,28705,250,RDMOPTG_Crimson_Weapon // C5_SLEEPER +2656,28705,250,RDMOPTG_Crimson_Weapon // C1_SLEEPER +2755,13127,250,RDMOPTG_None // C2_METALING +2756,13127,250,RDMOPTG_None // C3_METALING +1631,1839,50,RDMOPTG_Crimson_Weapon // CHUNG_E_ +1215,1443,50,RDMOPTG_Crimson_Weapon // STEM_WORM +2641,1443,250,RDMOPTG_Crimson_Weapon // C1_STEM_WORM +1404,1939,50,RDMOPTG_Crimson_Weapon // MIYABI_NINGYO +1628,13127,50,RDMOPTG_None // MOLE +1619,28705,50,RDMOPTG_Crimson_Weapon // PORCELLIO +2700,28705,250,RDMOPTG_Crimson_Weapon // C2_PORCELLIO +2745,13127,250,RDMOPTG_None // C2_MOLE +2746,1939,250,RDMOPTG_Crimson_Weapon // C3_MIYABI_NINGYO +1102,2009,50,RDMOPTG_None // BATHORY +1155,16040,50,RDMOPTG_Crimson_Weapon // PETIT +2714,16040,250,RDMOPTG_Crimson_Weapon // C1_PETIT +2715,16040,250,RDMOPTG_Crimson_Weapon // C2_PETIT +2885,2009,250,RDMOPTG_None // C4_BATHORY +2199,28705,50,RDMOPTG_Crimson_Weapon // SIORAVA +1143,16040,50,RDMOPTG_Crimson_Weapon // MARIONETTE +1413,1995,50,RDMOPTG_Crimson_Weapon // WILD_GINSENG +2761,16040,250,RDMOPTG_Crimson_Weapon // C3_MARIONETTE +1320,1498,50,RDMOPTG_Crimson_Weapon // OWL_DUKE +1320,2025,50,RDMOPTG_None // OWL_DUKE +1316,16040,50,RDMOPTG_Crimson_Weapon // SOLIDER +2647,16040,250,RDMOPTG_Crimson_Weapon // C2_SOLIDER +2721,1498,250,RDMOPTG_Crimson_Weapon // C3_OWL_DUKE +2721,2025,250,RDMOPTG_None // C3_OWL_DUKE +1408,1839,50,RDMOPTG_Crimson_Weapon // BLOOD_BUTTERFLY +2883,1839,250,RDMOPTG_Crimson_Weapon // C1_BLOOD_BUTTERFLY +1257,28007,50,RDMOPTG_Crimson_Weapon // INJUSTICE +2792,28007,250,RDMOPTG_Crimson_Weapon // C4_INJUSTICE +1302,21015,50,RDMOPTG_Crimson_Weapon // DARK_ILLUSION +1416,1939,50,RDMOPTG_Crimson_Weapon // WICKED_NYMPH +1416,1995,50,RDMOPTG_Crimson_Weapon // WICKED_NYMPH +2617,1939,250,RDMOPTG_Crimson_Weapon // C5_WICKED_NYMPH +2617,1995,250,RDMOPTG_Crimson_Weapon // C5_WICKED_NYMPH +1405,13327,50,RDMOPTG_Crimson_Weapon // TENGU +1030,1498,50,RDMOPTG_Crimson_Weapon // ANACONDAQ +2904,1498,250,RDMOPTG_Crimson_Weapon // C4_ANACONDAQ +1205,13454,50,RDMOPTG_Crimson_Weapon // EXECUTIONER +1135,28106,50,RDMOPTG_Crimson_Weapon // KOBOLD_3 +1106,28705,50,RDMOPTG_Crimson_Weapon // DESERT_WOLF +1259,1498,250,RDMOPTG_Crimson_Weapon // GRYPHON +1310,28106,50,RDMOPTG_Crimson_Weapon // MAJORUROS +2767,28106,250,RDMOPTG_Crimson_Weapon // C4_MAJORUROS +1736,1839,50,RDMOPTG_Crimson_Weapon // ALIOT +1296,16040,50,RDMOPTG_Crimson_Weapon // KOBOLD_LEADER +1204,28705,50,RDMOPTG_Crimson_Weapon // TIRFING +1204,13454,50,RDMOPTG_Crimson_Weapon // TIRFING +1993,1443,50,RDMOPTG_Crimson_Weapon // NAGA +1390,1939,50,RDMOPTG_Crimson_Weapon // VIOLY +2621,1939,250,RDMOPTG_Crimson_Weapon // C5_VIOLY +2622,1939,250,RDMOPTG_Crimson_Weapon // C1_VIOLY +2623,1939,250,RDMOPTG_Crimson_Weapon // C2_VIOLY +1295,18130,50,RDMOPTG_None // OWL_BARON +1303,2025,50,RDMOPTG_None // GIANT_HONET +2821,2025,250,RDMOPTG_None // C3_GIANT_HONET +1702,21015,50,RDMOPTG_Crimson_Weapon // RETRIBUTION +2353,28106,50,RDMOPTG_Crimson_Weapon // N_MINOROUS +2684,21015,250,RDMOPTG_Crimson_Weapon // C4_RETRIBUTION +2685,21015,250,RDMOPTG_Crimson_Weapon // C5_RETRIBUTION +2686,21015,250,RDMOPTG_Crimson_Weapon // C1_RETRIBUTION +1219,21015,50,RDMOPTG_Crimson_Weapon // KNIGHT_OF_ABYSS +1703,1939,50,RDMOPTG_Crimson_Weapon // SOLACE +2650,1939,250,RDMOPTG_Crimson_Weapon // C5_SOLACE +2041,28705,50,RDMOPTG_Crimson_Weapon // MYSTELTAINN +2041,13454,50,RDMOPTG_Crimson_Weapon // MYSTELTAINN +2041,21015,50,RDMOPTG_Crimson_Weapon // MYSTELTAINN +1830,18130,50,RDMOPTG_None // BOW_GUARDIAN +1653,28705,50,RDMOPTG_Crimson_Weapon // WHIKEBAIN +1655,1839,50,RDMOPTG_Crimson_Weapon // EREND +1655,16040,50,RDMOPTG_Crimson_Weapon // EREND +1657,2009,50,RDMOPTG_None // RAWREL +1829,21015,50,RDMOPTG_Crimson_Weapon // SWORD_GUARDIAN +2692,2009,250,RDMOPTG_None // C3_RAWREL +1654,13454,50,RDMOPTG_Crimson_Weapon // ARMAIA +1654,28106,50,RDMOPTG_Crimson_Weapon // ARMAIA +1656,1939,50,RDMOPTG_Crimson_Weapon // KAVAC +1656,18130,50,RDMOPTG_None // KAVAC +1652,13454,50,RDMOPTG_Crimson_Weapon // YGNIZEM +1652,21015,50,RDMOPTG_Crimson_Weapon // YGNIZEM +1290,28705,50,RDMOPTG_Crimson_Weapon // SKELETON_GENERAL +2658,28705,250,RDMOPTG_Crimson_Weapon // C3_SKELETON_GENERAL +2659,28705,250,RDMOPTG_Crimson_Weapon // C4_SKELETON_GENERAL +1658,21015,500,RDMOPTG_Crimson_Weapon // B_YGNIZEM +1301,16040,50,RDMOPTG_Crimson_Weapon // AM_MUT +2362,28604,50,RDMOPTG_Crimson_Weapon // N_AMON_RA diff --git a/src/common/mmo.h b/src/common/mmo.h index d5247ef2c7e..33a8ba7e039 100644 --- a/src/common/mmo.h +++ b/src/common/mmo.h @@ -183,6 +183,12 @@ struct quest { enum quest_state state; ///< Current quest state }; +struct s_item_randomoption { + short id; + short value; + char param; +}; + struct item { int id; unsigned short nameid; @@ -192,11 +198,7 @@ struct item { char refine; char attribute; unsigned short card[MAX_SLOTS]; - struct { - short id; - short value; - char param; - } option[MAX_ITEM_RDM_OPT]; // max of 5 random options can be supported. + struct s_item_randomoption option[MAX_ITEM_RDM_OPT]; // max of 5 random options can be supported. unsigned int expire_time; char favorite, bound; uint64 unique_id; diff --git a/src/map/atcommand.c b/src/map/atcommand.c index 85c3c627622..1a6ff6e60c8 100644 --- a/src/map/atcommand.c +++ b/src/map/atcommand.c @@ -7181,7 +7181,7 @@ ACMD_FUNC(mobinfo) clif_displaymessage(fd, msg_txt(sd,1245)); // Drops: strcpy(atcmd_output, " "); j = 0; - for (i = 0; i < MAX_MOB_DROP; i++) { + for (i = 0; i < MAX_MOB_DROP_TOTAL; i++) { int droprate; if (mob->dropitem[i].nameid <= 0 || mob->dropitem[i].p < 1 || (item_data = itemdb_exists(mob->dropitem[i].nameid)) == NULL) continue; @@ -7218,7 +7218,7 @@ ACMD_FUNC(mobinfo) strcpy(atcmd_output, msg_txt(sd,1248)); // MVP Items: mvpremain = 100.0; //Remaining drop chance for official mvp drop mode j = 0; - for (i = 0; i < MAX_MVP_DROP; i++) { + for (i = 0; i < MAX_MVP_DROP_TOTAL; i++) { if (mob->mvpitem[i].nameid <= 0 || (item_data = itemdb_exists(mob->mvpitem[i].nameid)) == NULL) continue; //Because if there are 3 MVP drops at 50%, the first has a chance of 50%, the second 25% and the third 12.5% diff --git a/src/map/itemdb.c b/src/map/itemdb.c index c238170662a..13760048b30 100644 --- a/src/map/itemdb.c +++ b/src/map/itemdb.c @@ -18,6 +18,7 @@ static DBMap *itemdb; /// Item DB static DBMap *itemdb_combo; /// Item Combo DB static DBMap *itemdb_group; /// Item Group DB static DBMap *itemdb_randomopt; /// Random option DB +static DBMap *itemdb_randomopt_group; /// Random option group DB struct item_data *dummy_item; /// This is the default dummy item used for non-existant items. [Skotlex] @@ -1649,6 +1650,79 @@ static bool itemdb_read_randomopt(const char* basedir, bool silent) { return true; } +/** + * Clear Item Random Option Group from memory + * @author [Cydh] + **/ +static int itemdb_randomopt_group_free(DBKey key, DBData *data, va_list ap) { + struct s_random_opt_group *g = (struct s_random_opt_group *)db_data2ptr(data); + if (!g) + return 0; + if (g->entries) + aFree(g->entries); + g->entries = NULL; + aFree(g); + return 1; +} + +/** + * Get Item Random Option Group from itemdb_randomopt_group MapDB + * @param id Random Option Group + * @return Random Option Group data or NULL if not found + * @author [Cydh] + **/ +struct s_random_opt_group *itemdb_randomopt_group_exists(int id) { + return (struct s_random_opt_group *)uidb_get(itemdb_randomopt_group, id); +} + +/** + * Read Item Random Option Group from db file + * @author [Cydh] + **/ +static bool itemdb_read_randomopt_group(char* str[], int columns, int current) { + int id = 0, i; + unsigned short rate = (unsigned short)strtoul(str[1], NULL, 10); + struct s_random_opt_group *g = NULL; + + if (!script_get_constant(str[0], &id)) { + ShowError("itemdb_read_randomopt_group: Invalid ID for Random Option Group '%s'.\n", str[0]); + return false; + } + + if ((columns-2)%3 != 0) { + ShowError("itemdb_read_randomopt_group: Invalid column entries '%d'.\n", columns); + return false; + } + + if (!(g = (struct s_random_opt_group *)uidb_get(itemdb_randomopt_group, id))) { + CREATE(g, struct s_random_opt_group, 1); + g->id = id; + g->total = 0; + g->entries = NULL; + uidb_put(itemdb_randomopt_group, g->id, g); + } + + RECREATE(g->entries, struct s_random_opt_group_entry, g->total + rate); + + for (i = g->total; i < (g->total + rate); i++) { + int j, k; + memset(&g->entries[i].option, 0, sizeof(g->entries[i].option)); + for (j = 0, k = 2; k < columns && j < MAX_ITEM_RDM_OPT; k+=3) { + int randid = 0; + if (!script_get_constant(str[k], &randid) || !itemdb_randomopt_exists(randid)) { + ShowError("itemdb_read_randomopt_group: Invalid random group id '%s' in column %d!\n", str[k], k+1); + continue; + } + g->entries[i].option[j].id = randid; + g->entries[i].option[j].value = (short)strtoul(str[k+1], NULL, 10); + g->entries[i].option[j].param = (char)strtoul(str[k+2], NULL, 10); + j++; + } + } + g->total += rate; + return true; +} + /** * Read all item-related databases */ @@ -1700,6 +1774,7 @@ static void itemdb_read(void) { sv_readdb(dbsubpath2, "item_delay.txt", ',', 2, 3, -1, &itemdb_read_itemdelay, i); sv_readdb(dbsubpath2, "item_buyingstore.txt", ',', 1, 1, -1, &itemdb_read_buyingstore, i); sv_readdb(dbsubpath2, "item_flag.txt", ',', 2, 2, -1, &itemdb_read_flag, i); + sv_readdb(dbsubpath2, "item_randomopt_group.txt", ',', 5, 2+5*MAX_ITEM_RDM_OPT, -1, &itemdb_read_randomopt_group, i); aFree(dbsubpath1); aFree(dbsubpath2); } @@ -1793,6 +1868,7 @@ void itemdb_reload(void) { itemdb_group->clear(itemdb_group, itemdb_group_free); itemdb_randomopt->clear(itemdb_randomopt, itemdb_randomopt_free); + itemdb_randomopt_group->clear(itemdb_randomopt_group, itemdb_randomopt_group_free); itemdb->clear(itemdb, itemdb_final_sub); db_clear(itemdb_combo); if (battle_config.feature_roulette) @@ -1810,7 +1886,7 @@ void itemdb_reload(void) { for( i = 0; i < MAX_MOB_DB; i++ ) { struct mob_db *entry = mob_db(i); - for(d = 0; d < MAX_MOB_DROP; d++) { + for(d = 0; d < MAX_MOB_DROP_TOTAL; d++) { struct item_data *id; if( !entry->dropitem[d].nameid ) continue; @@ -1861,6 +1937,7 @@ void do_final_itemdb(void) { db_destroy(itemdb_combo); itemdb_group->destroy(itemdb_group, itemdb_group_free); itemdb_randomopt->destroy(itemdb_randomopt, itemdb_randomopt_free); + itemdb_randomopt_group->destroy(itemdb_randomopt_group, itemdb_randomopt_group_free); itemdb->destroy(itemdb, itemdb_final_sub); destroy_item_data(dummy_item); if (battle_config.feature_roulette) @@ -1875,6 +1952,7 @@ void do_init_itemdb(void) { itemdb_combo = uidb_alloc(DB_OPT_BASE); itemdb_group = uidb_alloc(DB_OPT_BASE); itemdb_randomopt = uidb_alloc(DB_OPT_BASE); + itemdb_randomopt_group = uidb_alloc(DB_OPT_BASE); itemdb_create_dummy(); itemdb_read(); diff --git a/src/map/itemdb.h b/src/map/itemdb.h index 0ed0feaa9d3..c2d443e65d3 100644 --- a/src/map/itemdb.h +++ b/src/map/itemdb.h @@ -460,6 +460,23 @@ struct s_random_opt_data struct script_code *script; }; +/// Enum for Random Option Group, for checking in source +enum Random_Option_Group { + RDMOPTG_None = 0, +}; + +/// Struct for random option group entry +struct s_random_opt_group_entry { + struct s_item_randomoption option[MAX_ITEM_RDM_OPT]; +}; + +/// Struct for Random Option Group +struct s_random_opt_group { + uint8 id; + struct s_random_opt_group_entry *entries; + uint16 total; +}; + struct item_data* itemdb_searchname(const char *name); int itemdb_searchname_array(struct item_data** data, int size, const char *str); struct item_data* itemdb_search(unsigned short nameid); @@ -536,6 +553,7 @@ uint16 itemdb_get_randgroupitem_count(uint16 group_id, uint8 sub_group, unsigned bool itemdb_parse_roulette_db(void); struct s_random_opt_data *itemdb_randomopt_exists(short id); +struct s_random_opt_group *itemdb_randomopt_group_exists(int id); void itemdb_reload(void); diff --git a/src/map/mob.c b/src/map/mob.c index 13aed85c4f0..fcb175ebdd2 100644 --- a/src/map/mob.c +++ b/src/map/mob.c @@ -1945,16 +1945,36 @@ static int mob_ai_hard(int tid, unsigned int tick, int id, intptr_t data) return 0; } +/** + * Set random option for item when dropped from monster + * @param itm Item data + * @param mobdrop Drop data + * @author [Cydh] + **/ +void mob_setdropitem_option(struct item *itm, struct s_mob_drop *mobdrop) { + struct s_random_opt_group *g = NULL; + if (!itm || !mobdrop || mobdrop->randomopt_group == RDMOPTG_None) + return; + if ((g = itemdb_randomopt_group_exists(mobdrop->randomopt_group)) && g->total) { + int r = rnd()%g->total; + if (&g->entries[r]) { + memcpy(&itm->option, &g->entries[r], sizeof(itm->option)); + return; + } + } +} + /*========================================== * Initializes the delay drop structure for mob-dropped items. *------------------------------------------*/ -static struct item_drop* mob_setdropitem(unsigned short nameid, int qty, unsigned short mob_id) +static struct item_drop* mob_setdropitem(struct s_mob_drop *mobdrop, int qty, unsigned short mob_id) { struct item_drop *drop = ers_alloc(item_drop_ers, struct item_drop); memset(&drop->item_data, 0, sizeof(struct item)); - drop->item_data.nameid = nameid; + drop->item_data.nameid = mobdrop->nameid; drop->item_data.amount = qty; - drop->item_data.identify = itemdb_isidentified(nameid); + drop->item_data.identify = itemdb_isidentified(mobdrop->nameid); + mob_setdropitem_option(&drop->item_data, mobdrop); drop->mob_id = mob_id; drop->next = NULL; return drop; @@ -2531,7 +2551,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) dlist->third_charid = (third_sd ? third_sd->status.char_id : 0); dlist->item = NULL; - for (i = 0; i < MAX_MOB_DROP; i++) { + for (i = 0; i < MAX_MOB_DROP_TOTAL; i++) { if (md->db->dropitem[i].nameid <= 0) continue; if ( !(it = itemdb_exists(md->db->dropitem[i].nameid)) ) @@ -2602,7 +2622,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) continue; } - ditem = mob_setdropitem(md->db->dropitem[i].nameid, 1, md->mob_id); + ditem = mob_setdropitem(&md->db->dropitem[i], 1, md->mob_id); //A Rare Drop Global Announce by Lupus if( mvp_sd && drop_rate <= battle_config.rare_drop_announce ) { @@ -2618,7 +2638,10 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) // Ore Discovery [Celest] if (sd == mvp_sd && pc_checkskill(sd,BS_FINDINGORE)>0 && battle_config.finding_ore_rate/10 >= rnd()%10000) { - ditem = mob_setdropitem(itemdb_searchrandomid(IG_FINDINGORE,1), 1, md->mob_id); + struct s_mob_drop mobdrop; + memset(&mobdrop, 0, sizeof(struct s_mob_drop)); + mobdrop.nameid = itemdb_searchrandomid(IG_FINDINGORE,1); + ditem = mob_setdropitem(&mobdrop, 1, md->mob_id); mob_item_drop(md, dlist, ditem, 0, battle_config.finding_ore_rate/10, homkillonly); } @@ -2627,6 +2650,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) uint16 dropid = 0; for (i = 0; i < ARRAYLENGTH(sd->add_drop); i++) { + struct s_mob_drop mobdrop; if (!&sd->add_drop[i] || (!sd->add_drop[i].nameid && !sd->add_drop[i].group)) continue; if ((sd->add_drop[i].race < RC_NONE_ && sd->add_drop[i].race == -md->mob_id) || //Race < RC_NONE_, use mob_id @@ -2647,8 +2671,10 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) if (rnd()%10000 >= drop_rate) continue; dropid = (sd->add_drop[i].nameid > 0) ? sd->add_drop[i].nameid : itemdb_searchrandomid(sd->add_drop[i].group,1); + memset(&mobdrop, 0, sizeof(struct s_mob_drop)); + mobdrop.nameid = dropid; - mob_item_drop(md, dlist, mob_setdropitem(dropid,1,md->mob_id), 0, drop_rate, homkillonly); + mob_item_drop(md, dlist, mob_setdropitem(&mobdrop,1,md->mob_id), 0, drop_rate, homkillonly); } } @@ -2707,47 +2733,37 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) if( !(map[m].flag.nomvploot || type&1) ) { //Order might be random depending on item_drop_mvp_mode config setting - int mdrop_id[MAX_MVP_DROP]; - int mdrop_p[MAX_MVP_DROP]; + struct s_mob_drop mdrop[MAX_MVP_DROP_TOTAL]; - memset(mdrop_id,0,MAX_MVP_DROP*sizeof(int)); - memset(mdrop_p,0,MAX_MVP_DROP*sizeof(int)); + memset(&mdrop,0,sizeof(mdrop)); if(battle_config.item_drop_mvp_mode == 1) { //Random order - for(i = 0; i < MAX_MVP_DROP; i++) { + for(i = 0; i < MAX_MVP_DROP_TOTAL; i++) { while( 1 ) { - uint8 va = rnd()%MAX_MVP_DROP; - if (mdrop_id[va] == 0) { - if (md->db->mvpitem[i].nameid > 0) { - mdrop_id[va] = md->db->mvpitem[i].nameid; - mdrop_p[va] = md->db->mvpitem[i].p; - } - else - mdrop_id[va] = -1; + uint8 va = rnd()%MAX_MVP_DROP_TOTAL; + if (mdrop[va].nameid == 0) { + if (md->db->mvpitem[i].nameid > 0) + memcpy(&mdrop[va],&md->db->mvpitem[i],sizeof(mdrop[va])); break; } } } } else { //Normal order - for(i = 0; i < MAX_MVP_DROP; i++) { - if (md->db->mvpitem[i].nameid > 0) { - mdrop_id[i] = md->db->mvpitem[i].nameid; - mdrop_p[i] = md->db->mvpitem[i].p; - } - else - mdrop_id[i] = -1; + for(i = 0; i < MAX_MVP_DROP_TOTAL; i++) { + if (md->db->mvpitem[i].nameid > 0) + memcpy(&mdrop[i],&md->db->mvpitem[i],sizeof(mdrop[i])); } } - for(i = 0; i < MAX_MVP_DROP; i++) { + for(i = 0; i < MAX_MVP_DROP_TOTAL; i++) { struct item_data *i_data; - if(mdrop_id[i] <= 0 || !itemdb_exists(mdrop_id[i])) + if(mdrop[i].nameid <= 0 || !(i_data = itemdb_exists(mdrop[i].nameid))) continue; - temp = mdrop_p[i]; + temp = mdrop[i].p; if (temp != 10000) { if(temp <= 0 && !battle_config.drop_rate0item) temp = 1; @@ -2756,13 +2772,11 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) } memset(&item,0,sizeof(item)); - item.nameid=mdrop_id[i]; + item.nameid=mdrop[i].nameid; item.identify= itemdb_isidentified(item.nameid); clif_mvp_item(mvp_sd,item.nameid); log_mvp[0] = item.nameid; - i_data = itemdb_exists(item.nameid); - //A Rare MVP Drop Global Announce by Lupus if(temp<=battle_config.rare_drop_announce) { char message[128]; @@ -2771,6 +2785,8 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) intif_broadcast(message,strlen(message)+1,BC_DEFAULT); } + mob_setdropitem_option(&item, &mdrop[i]); + if((temp = pc_additem(mvp_sd,&item,1,LOG_TYPE_PICKDROP_PLAYER)) != 0) { clif_additem(mvp_sd,0,0,temp); map_addflooritem(&item,1,mvp_sd->bl.m,mvp_sd->bl.x,mvp_sd->bl.y,mvp_sd->status.char_id,(second_sd?second_sd->status.char_id:0),(third_sd?third_sd->status.char_id:0),1,0); @@ -4702,6 +4718,81 @@ static bool mob_readdb_itemratio(char* str[], int columns, int current) return true; } +/** + * Read additional monster drop from db file + * @author [Cydh] + **/ +static bool mob_readdb_drop(char* str[], int columns, int current) { + unsigned short mobid, nameid; + int rate, i, size, flag = 0; + struct mob_db *mob; + struct s_mob_drop *drop; + + mobid = atoi(str[0]); + if ((mob = mob_db(mobid)) == mob_dummy) { + ShowError("mob_readdb_drop: Invalid monster with ID %s.\n", str[0]); + return false; + } + + nameid = atoi(str[1]); + if (itemdb_exists(nameid) == NULL) { + ShowWarning("mob_readdb_drop: Invalid item id %s.\n", str[1]); + return false; + } + + rate = atoi(str[2]); + if (columns > 4 && (flag = atoi(str[4])) == 2) { + drop = mob->mvpitem; + size = ARRAYLENGTH(mob->mvpitem); + } + else { + drop = mob->dropitem; + size = ARRAYLENGTH(mob->dropitem); + } + + if (rate == 0) { + for (i = 0; i < size; i++) { + if (drop[i].nameid == nameid) { + memset(&drop[i], 0, sizeof(struct s_mob_drop)); + ShowInfo("mob_readdb_drop: Removed item '%hu' from monster '%hu'.\n", nameid, mobid); + return true; + } + } + } + else { + for (i = 0; i < size; i++) { + if (drop[i].nameid == 0) + break; + } + if (i == size) { + ShowError("mob_readdb_drop: Cannot add item '%hu' to monster '%hu'. Max drop reached '%d'.\n", nameid, mobid, size); + return true; + } + + drop[i].nameid = nameid; + drop[i].p = rate; + drop[i].steal_protected = (flag) ? 1 : 0; + drop[i].randomopt_group = 0; + + if (columns > 3) { + int randomopt_group = -1; + if (!script_get_constant(trim(str[3]), &randomopt_group)) { + ShowError("mob_readdb_drop: Invalid 'randopt_groupid' '%s' for monster '%hu'.\n", str[3], mobid); + return false; + } + if (randomopt_group == RDMOPTG_None) + return true; + if (!itemdb_randomopt_group_exists(randomopt_group)) { + ShowError("mob_readdb_drop: 'randopt_groupid' '%s' cannot be found in DB for monster '%hu'.\n", str[3], mobid); + return false; + } + drop[i].randomopt_group = randomopt_group; + } + } + + return true; +} + /** * Free drop ratio data **/ @@ -4732,7 +4823,7 @@ static void mob_drop_ratio_adjust(void){ mob_id = i; - for( j = 0; j < MAX_MVP_DROP; j++ ){ + for( j = 0; j < MAX_MVP_DROP_TOTAL; j++ ){ nameid = mob->mvpitem[j].nameid; rate = mob->mvpitem[j].p; @@ -4769,7 +4860,7 @@ static void mob_drop_ratio_adjust(void){ mob->mvpitem[j].p = rate; } - for( j = 0; j < MAX_MOB_DROP; j++ ){ + for( j = 0; j < MAX_MOB_DROP_TOTAL; j++ ){ unsigned short ratemin, ratemax; bool is_treasurechest; @@ -5034,6 +5125,7 @@ static void mob_load(void) sv_readdb(dbsubpath2, "mob_boss.txt", ',', 4, 4, -1, &mob_readdb_group, i ); sv_readdb(dbsubpath1, "mob_pouch.txt", ',', 4, 4, -1, &mob_readdb_group, i ); sv_readdb(dbsubpath1, "mob_classchange.txt", ',', 4, 4, -1, &mob_readdb_group, i ); + sv_readdb(dbsubpath2, "mob_drop.txt", ',', 3, 5, -1, &mob_readdb_drop, i ); aFree(dbsubpath1); aFree(dbsubpath2); diff --git a/src/map/mob.h b/src/map/mob.h index 271c33419a0..b451823801c 100644 --- a/src/map/mob.h +++ b/src/map/mob.h @@ -19,6 +19,10 @@ //The number of drops all mobs have and the max drop-slot that the steal skill will attempt to steal from. #define MAX_MOB_DROP 10 #define MAX_MVP_DROP 3 +#define MAX_MOB_DROP_ADD 5 +#define MAX_MVP_DROP_ADD 2 +#define MAX_MOB_DROP_TOTAL (MAX_MOB_DROP+MAX_MOB_DROP_ADD) +#define MAX_MVP_DROP_TOTAL (MAX_MVP_DROP+MAX_MVP_DROP_ADD) #define MAX_STEAL_DROP 7 #define MAX_RACE2_MOBS 100 @@ -137,6 +141,14 @@ struct s_mob_lootitem { unsigned short mob_id; ///< ID of monster that dropped the item }; +/// Struct for monster's drop item +struct s_mob_drop { + unsigned short nameid; + int p; + uint8 randomopt_group; + unsigned steal_protected : 1; +}; + struct mob_db { char sprite[NAME_LENGTH],name[NAME_LENGTH],jname[NAME_LENGTH]; unsigned int base_exp,job_exp; @@ -144,14 +156,7 @@ struct mob_db { short range2,range3; enum e_race2 race2; // celest unsigned short lv; - struct { - unsigned short nameid; - int p; - } dropitem[MAX_MOB_DROP]; - struct { - unsigned short nameid; - int p; - } mvpitem[MAX_MVP_DROP]; + struct s_mob_drop dropitem[MAX_MOB_DROP_TOTAL], mvpitem[MAX_MVP_DROP_TOTAL]; struct status_data status; struct view_data vd; unsigned int option; @@ -358,6 +363,8 @@ int mvptomb_delayspawn(int tid, unsigned int tick, int id, intptr_t data); void mvptomb_create(struct mob_data *md, char *killer, time_t time); void mvptomb_destroy(struct mob_data *md); +void mob_setdropitem_option(struct item *itm, struct s_mob_drop *mobdrop); + #define CHK_MOBSIZE(size) ((size) >= SZ_SMALL && (size) < SZ_MAX) /// Check valid Monster Size #endif /* _MOB_H_ */ diff --git a/src/map/pc.c b/src/map/pc.c index 5bfad2b4b20..1312f8bfdf3 100755 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -5248,7 +5248,7 @@ int pc_steal_item(struct map_session_data *sd,struct block_list *bl, uint16 skil // Try dropping one item, in the order from first to last possible slot. // Droprate is affected by the skill success rate. for( i = 0; i < MAX_STEAL_DROP; i++ ) - if( md->db->dropitem[i].nameid > 0 && itemdb_exists(md->db->dropitem[i].nameid) && rnd() % 10000 < md->db->dropitem[i].p * rate/100. ) + if( md->db->dropitem[i].nameid > 0 && md->db->dropitem[i].steal_protected && itemdb_exists(md->db->dropitem[i].nameid) && rnd() % 10000 < md->db->dropitem[i].p * rate/100. ) break; if( i == MAX_STEAL_DROP ) return 0; @@ -5258,6 +5258,7 @@ int pc_steal_item(struct map_session_data *sd,struct block_list *bl, uint16 skil tmp_item.nameid = itemid; tmp_item.amount = 1; tmp_item.identify = itemdb_isidentified(itemid); + mob_setdropitem_option(&tmp_item, &md->db->dropitem[i]); flag = pc_additem(sd,&tmp_item,1,LOG_TYPE_PICKDROP_PLAYER); //TODO: Should we disable stealing when the item you stole couldn't be added to your inventory? Perhaps players will figure out a way to exploit this behaviour otherwise? diff --git a/src/map/script.c b/src/map/script.c index b72500fd9c6..6c2ad04429a 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -9841,7 +9841,7 @@ BUILDIN_FUNC(getmobdrops) mob = mob_db(class_); - for( i = 0; i < MAX_MOB_DROP; i++ ) + for( i = 0; i < MAX_MOB_DROP_TOTAL; i++ ) { if( mob->dropitem[i].nameid < 1 ) continue; @@ -16524,7 +16524,7 @@ BUILDIN_FUNC(addmonsterdrop) if(mob) { //We got a valid monster, check for available drop slot unsigned char i, c = 0; - for(i = 0; i < MAX_MOB_DROP; i++) { + for(i = 0; i < MAX_MOB_DROP_TOTAL; i++) { if(mob->dropitem[i].nameid) { if(mob->dropitem[i].nameid == item_id) { //If it equals item_id we update that drop c = i; @@ -16579,7 +16579,7 @@ BUILDIN_FUNC(delmonsterdrop) if(mob) { //We got a valid monster, check for item drop on monster unsigned char i; - for(i = 0; i < MAX_MOB_DROP; i++) { + for(i = 0; i < MAX_MOB_DROP_TOTAL; i++) { if(mob->dropitem[i].nameid == item_id) { mob->dropitem[i].nameid = 0; mob->dropitem[i].p = 0; diff --git a/src/map/script_constants.h b/src/map/script_constants.h index b757db21620..7ac8d97927c 100644 --- a/src/map/script_constants.h +++ b/src/map/script_constants.h @@ -3168,6 +3168,9 @@ export_constant(MOBG_Red_Pouch_Of_Surprise); export_constant(MOBG_ClassChange); + /* Item Random Option Group */ + export_constant(RDMOPTG_None); + /* random option attributes */ export_constant(ROA_ID); export_constant(ROA_VALUE); diff --git a/vcproj-10/map-server.vcxproj b/vcproj-10/map-server.vcxproj index 90752722d15..27cab8f6a80 100644 --- a/vcproj-10/map-server.vcxproj +++ b/vcproj-10/map-server.vcxproj @@ -323,6 +323,7 @@ + @@ -338,6 +339,7 @@ + diff --git a/vcproj-12/map-server.vcxproj b/vcproj-12/map-server.vcxproj index c322aea9b18..a2b0efc877a 100644 --- a/vcproj-12/map-server.vcxproj +++ b/vcproj-12/map-server.vcxproj @@ -327,6 +327,7 @@ + @@ -342,6 +343,7 @@ + diff --git a/vcproj-13/map-server.vcxproj b/vcproj-13/map-server.vcxproj index 8cbdfe835c6..ecbc84856e6 100644 --- a/vcproj-13/map-server.vcxproj +++ b/vcproj-13/map-server.vcxproj @@ -327,6 +327,7 @@ + @@ -342,6 +343,7 @@ + diff --git a/vcproj-14/map-server.vcxproj b/vcproj-14/map-server.vcxproj index 60420561482..9a741b60cd8 100644 --- a/vcproj-14/map-server.vcxproj +++ b/vcproj-14/map-server.vcxproj @@ -325,6 +325,7 @@ + @@ -340,6 +341,7 @@ +