Просмотр исходного кода

* Changed itemgroup from array to DBMap
* Removed MAX_ITEMGROUP limit
* Changed player bonus fixed array
* Minor changes

Signed-off-by: Cydh Ramdh <house.bad@gmail.com>

Cydh Ramdh 11 лет назад
Родитель
Сommit
7f4a7ba26f
12 измененных файлов с 352 добавлено и 227 удалено
  1. 1 1
      db/re/item_db.txt
  2. 1 1
      db/re/item_misc.txt
  3. 12 12
      db/re/item_package.txt
  4. 1 1
      sql-files/item_db_re.sql
  5. 3 2
      src/map/cashshop.c
  6. 133 127
      src/map/itemdb.c
  7. 28 26
      src/map/itemdb.h
  8. 104 5
      src/map/pc.c
  9. 16 2
      src/map/pc.h
  10. 50 49
      src/map/skill.c
  11. 2 1
      src/map/status.c
  12. 1 0
      src/map/unit.c

+ 1 - 1
db/re/item_db.txt

@@ -2598,7 +2598,7 @@
 4567,Alphoccio_Card,Alphoccio Card,6,20,,10,,,,,,,,4,,,,,{ bonus bFlee,10; if(BaseJob==Job_Bard) { bonus bMaxHPrate,10; bonus bMaxSPrate,5;} },{},{}
 4568,Celia_Card,Celia Card,6,20,,10,,,,,,,,4,,,,,{ bonus bFlee,10; skill "SA_ABRACADABRA",1; },{},{}
 4569,Chen_Card,Chen Card,6,20,,10,,,,,,,,4,,,,,{ bonus bFlee,10; skill "MO_CALLSPIRITS",2; },{},{}
-4570,Flamel_Card,Flamel Card,6,20,,10,,,,,,,,4,,,,,{ bonus bFlee,10; bonus2 bAddItemHealRate,70,200; },{},{}
+4570,Flamel_Card,Flamel Card,6,20,,10,,,,,,,,4,,,,,{ bonus bFlee,10; bonus2 bAddItemHealRate,IG_Flamel_Card,200; },{},{}
 4571,Gertie_Card,Gertie Card,6,20,,10,,,,,,,,4,,,,,{ bonus bFlee,10; skill "RG_CLOSECONFINE",1; },{},{}
 4572,Randel_Card,Randel Card,6,20,,10,,,,,,,,4,,,,,{ bonus bFlee,10; skill "CR_AUTOGUARD",3; },{},{}
 4573,Trentini_Card,Trentini Card,6,20,,10,,,,,,,,4,,,,,{ bonus bFlee,10; if(BaseJob==Job_Dancer) { bonus bMaxHPrate,10; bonus bMaxSPrate,5;} },{},{}

+ 1 - 1
db/re/item_misc.txt

@@ -706,7 +706,7 @@
 61,1473,1	// Wizardy_Staff
 61,1474,1	// Gae_Bolg
 61,1477,1	// Spectral_Spear
-61,1478,1	// Ahlspiess,
+61,1478,1	// Ahlspiess
 61,1479,1	// Spectral_Spear_
 61,1480,1	// Gae_Bolg_
 61,1481,1	// Zephyrus_

+ 12 - 12
db/re/item_package.txt

@@ -1149,24 +1149,24 @@ IG_Ox_Tail_Scroll,Eyes_Of_Ifrit,5,1,1,1,0,1
 IG_Ox_Tail_Scroll,Majoruros_Horn,100,1,1,1,0,1
 IG_Ox_Tail_Scroll,Sealed_D_Lord_Card,5,1,1,1,0,1
 
-IG_Buddah_Scroll,Mental_Potion,1,3,1,0,0,0
+IG_Buddah_Scroll,Mental_Potion,0,3,0,0,0,0
 IG_Buddah_Scroll,Bubble_Gum_Box,1400,1,1,0,0,0
 IG_Buddah_Scroll,Str_Dish_Box,1400,1,1,0,0,0
 IG_Buddah_Scroll,Megaphone_Box,1400,1,1,0,0,0
-IG_Buddah_Scroll,Battle_Manual_Box,1352,1,,0,0,0,0
+IG_Buddah_Scroll,Battle_Manual_Box,1352,1,1,0,0,0,0
 IG_Buddah_Scroll,Token_Of_Siegfried_Box,1250,1,1,0,0,0
 IG_Buddah_Scroll,Shadow_Armor_S_Box10,1400,1,1,0,0,0
 IG_Buddah_Scroll,Guyak_Pudding,1400,10,1,0,0,0
-IG_Buddah_Scroll,Poker_Card_In_Mouth,30,1,0,0,0,0
-IG_Buddah_Scroll,Sleeping_Kitty_Cat,100,1,0,0,0,0
-IG_Buddah_Scroll,18600,10,1,0,0,0,0
-IG_Buddah_Scroll,Guarantee_Weapon_5Up,70,1,0,0,0,0
-IG_Buddah_Scroll,Red_Hood,100,0,1,0,0,0
-IG_Buddah_Scroll,Spirit_Of_Chung_E,20,1,0,0,0,0
-IG_Buddah_Scroll,Guarantee_Weapon_9Up,5,1,0,0,0,0
-IG_Buddah_Scroll,Sealed_Samurai_Card,3,1,0,0,0,0
-IG_Buddah_Scroll,Kirin_Wing,10,1,0,0,0,0
-IG_Buddah_Scroll,Unbreak_Weap_Box,50,1,0,0,0,0
+IG_Buddah_Scroll,Poker_Card_In_Mouth,30,1,1,1,0,0
+IG_Buddah_Scroll,Sleeping_Kitty_Cat,100,1,1,1,0,0
+IG_Buddah_Scroll,18600,10,1,1,1,0,0
+IG_Buddah_Scroll,Guarantee_Weapon_5Up,70,1,1,1,0,0
+IG_Buddah_Scroll,Red_Hood,100,1,1,1,0,0
+IG_Buddah_Scroll,Spirit_Of_Chung_E,20,1,1,1,0,0
+IG_Buddah_Scroll,Guarantee_Weapon_9Up,5,1,1,1,0,0
+IG_Buddah_Scroll,Sealed_Samurai_Card,3,1,1,1,0,0
+IG_Buddah_Scroll,Kirin_Wing,10,1,1,1,0,0
+IG_Buddah_Scroll,Unbreak_Weap_Box,50,1,1,1,0,0
 
 IG_Evil_Incarnation,Dead_Tree_Branch_Box2,224,1,1,0,0,1
 IG_Evil_Incarnation,Guyak_Pudding,124,10,1,0,0,1

+ 1 - 1
sql-files/item_db_re.sql

@@ -2629,7 +2629,7 @@ REPLACE INTO `item_db_re` VALUES (4566,'Gypsy_Trentini_Card','Gypsy Trentini Car
 REPLACE INTO `item_db_re` VALUES (4567,'Alphoccio_Card','Alphoccio Card',6,20,NULL,10,NULL,NULL,NULL,NULL,NULL,NULL,NULL,4,NULL,NULL,NULL,NULL,'bonus bFlee,10; if(BaseJob==Job_Bard) { bonus bMaxHPrate,10; bonus bMaxSPrate,5;}',NULL,NULL);
 REPLACE INTO `item_db_re` VALUES (4568,'Celia_Card','Celia Card',6,20,NULL,10,NULL,NULL,NULL,NULL,NULL,NULL,NULL,4,NULL,NULL,NULL,NULL,'bonus bFlee,10; skill "SA_ABRACADABRA",1;',NULL,NULL);
 REPLACE INTO `item_db_re` VALUES (4569,'Chen_Card','Chen Card',6,20,NULL,10,NULL,NULL,NULL,NULL,NULL,NULL,NULL,4,NULL,NULL,NULL,NULL,'bonus bFlee,10; skill "MO_CALLSPIRITS",2;',NULL,NULL);
-REPLACE INTO `item_db_re` VALUES (4570,'Flamel_Card','Flamel Card',6,20,NULL,10,NULL,NULL,NULL,NULL,NULL,NULL,NULL,4,NULL,NULL,NULL,NULL,'bonus bFlee,10; bonus2 bAddItemHealRate,70,200;',NULL,NULL);
+REPLACE INTO `item_db_re` VALUES (4570,'Flamel_Card','Flamel Card',6,20,NULL,10,NULL,NULL,NULL,NULL,NULL,NULL,NULL,4,NULL,NULL,NULL,NULL,'bonus bFlee,10; bonus2 bAddItemHealRate,IG_Flamel_Card,200;',NULL,NULL);
 REPLACE INTO `item_db_re` VALUES (4571,'Gertie_Card','Gertie Card',6,20,NULL,10,NULL,NULL,NULL,NULL,NULL,NULL,NULL,4,NULL,NULL,NULL,NULL,'bonus bFlee,10; skill "RG_CLOSECONFINE",1;',NULL,NULL);
 REPLACE INTO `item_db_re` VALUES (4572,'Randel_Card','Randel Card',6,20,NULL,10,NULL,NULL,NULL,NULL,NULL,NULL,NULL,4,NULL,NULL,NULL,NULL,'bonus bFlee,10; skill "CR_AUTOGUARD",3;',NULL,NULL);
 REPLACE INTO `item_db_re` VALUES (4573,'Trentini_Card','Trentini Card',6,20,NULL,10,NULL,NULL,NULL,NULL,NULL,NULL,NULL,4,NULL,NULL,NULL,NULL,'bonus bFlee,10; if(BaseJob==Job_Dancer) { bonus bMaxHPrate,10; bonus bMaxSPrate,5;}',NULL,NULL);

+ 3 - 2
src/map/cashshop.c

@@ -67,6 +67,7 @@ static int cashshop_parse_dbrow( char** str, const char* source, int line ){
 /*
  * Reads database from TXT format,
  * parses lines and sends them to parse_dbrow.
+ * TODO: Change to sv_readdb
  */
 static void cashshop_read_db_txt( void ){
 	const char* filename[] = { DBPATH"item_cash_db.txt", DBIMPORT"/item_cash_db.txt" };
@@ -87,7 +88,7 @@ static void cashshop_read_db_txt( void ){
 		}
 
 		while( fgets( line, sizeof( line ), fp ) ){
-			char *str[3], *p;
+			char *str[2], *p;
 			int i;
 			lines++;
 
@@ -130,7 +131,7 @@ static void cashshop_read_db_txt( void ){
 
 		fclose(fp);
 
-		ShowStatus( "Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, filename[fi] );
+		ShowStatus( "Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, path );
 	}
 }
 

+ 133 - 127
src/map/itemdb.c

@@ -19,18 +19,20 @@
 #include <string.h>
 
 static struct item_data* itemdb_array[MAX_ITEMDB];
-static DBMap*            itemdb_other;// int nameid -> struct item_data*
-
-static struct s_item_group_db itemgroup_db[MAX_ITEMGROUP];
-
 struct item_data dummy_item; //This is the default dummy item used for non-existant items. [Skotlex]
 
+static DBMap* itemdb_other;// int nameid -> struct item_data*
 static DBMap *itemdb_combo;
+static DBMap *itemdb_group;
 
 DBMap * itemdb_get_combodb(){
 	return itemdb_combo;
 }
 
+DBMap * itemdb_get_groupdb() {
+	return itemdb_group;
+}
+
 /**
  * Search for item name
  * name = item alias, so we should find items aliases first. if not found then look for "jname" (full name)
@@ -150,9 +152,10 @@ int itemdb_searchname_array(struct item_data** data, int size, const char *str)
 */
 unsigned short itemdb_searchrandomid(uint16 group_id, uint8 sub_group)
 {
+	struct s_item_group_db *group = (struct s_item_group_db *) idb_get(itemdb_group, group_id);
 	if (sub_group)
 		sub_group -= 1;
-	if (!group_id || group_id >= MAX_ITEMGROUP || !&itemgroup_db[group_id]) {
+	if (!group) {
 		ShowError("itemdb_searchrandomid: Invalid group id %d\n", group_id);
 		return UNKNOWN_ITEM_ID;
 	}
@@ -160,8 +163,8 @@ unsigned short itemdb_searchrandomid(uint16 group_id, uint8 sub_group)
 		ShowError("itemdb_searchrandomid: Invalid sub_group %d\n", sub_group+1);
 		return UNKNOWN_ITEM_ID;
 	}
-	if (&itemgroup_db[group_id].random[sub_group] && itemgroup_db[group_id].random[sub_group].data_qty)
-		return itemgroup_db[group_id].random[sub_group].data[rand()%itemgroup_db[group_id].random[sub_group].data_qty].nameid;
+	if (&group->random[sub_group] && group->random[sub_group].data_qty)
+		return group->random[sub_group].data[rand()%group->random[sub_group].data_qty].nameid;
 
 	ShowError("itemdb_searchrandomid: No item entries for group id %d and sub group %d\n", group_id, sub_group+1);
 	return UNKNOWN_ITEM_ID;
@@ -177,9 +180,11 @@ unsigned short itemdb_searchrandomid(uint16 group_id, uint8 sub_group)
 */
 uint16 itemdb_get_randgroupitem_count(uint16 group_id, uint8 sub_group, uint16 nameid) {
 	uint16 i, amt = 1;
+	struct s_item_group_db *group = (struct s_item_group_db *) idb_get(itemdb_group, group_id);
+	
 	if (sub_group)
 		sub_group -= 1;
-	if (!group_id || group_id >= MAX_ITEMGROUP || !&itemgroup_db[group_id]) {
+	if (!group) {
 		ShowError("itemdb_get_randgroupitem_count: Invalid group id %d\n", group_id);
 		return amt;
 	}
@@ -187,11 +192,12 @@ uint16 itemdb_get_randgroupitem_count(uint16 group_id, uint8 sub_group, uint16 n
 		ShowError("itemdb_get_randgroupitem_count: Invalid sub_group id %d\n", group_id+1);
 		return amt;
 	}
-	if (!(&itemgroup_db[group_id].random[sub_group]) || !itemgroup_db[group_id].random[sub_group].data_qty)
+	if (!(&group->random[sub_group]) || !group->random[sub_group].data_qty)
 		return amt;
-	ARR_FIND(0,itemgroup_db[group_id].random[sub_group].data_qty,i,itemgroup_db[group_id].random[sub_group].data[i].nameid == nameid);
-	if (i < itemgroup_db[group_id].random[sub_group].data_qty)
-		amt = itemgroup_db[group_id].random[sub_group].data[i].amount;
+	for (i = 0; i < group->random[sub_group].data_qty; i++) {
+		if (group->random[sub_group].data[i].nameid == nameid)
+			return group->random[sub_group].data[i].amount;
+	}
 	return amt;
 }
 
@@ -201,37 +207,37 @@ uint16 itemdb_get_randgroupitem_count(uint16 group_id, uint8 sub_group, uint16 n
 * @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, uint16 group_id, struct s_item_group *group) {
+static void itemdb_pc_get_itemgroup_sub(struct map_session_data *sd, struct s_item_group_entry *data) {
 	uint16 i;
 	struct item tmp;
 
-	nullpo_retv(group);
+	nullpo_retv(data);
 
-	memset(&tmp,0,sizeof(tmp));
+	memset(&tmp, 0, sizeof(tmp));
 
-	tmp.nameid = group->nameid;
-	tmp.amount = (itemdb_isstackable(group->nameid)) ? group->amount : 1;
-	tmp.bound = group->bound;
+	tmp.nameid = data->nameid;
+	tmp.amount = (itemdb_isstackable(data->nameid)) ? data->amount : 1;
+	tmp.bound = data->bound;
 	tmp.identify = 1;
-	tmp.expire_time = (group->duration) ? (unsigned int)(time(NULL) + group->duration*60) : 0;
-	if (group->isNamed) {
-		tmp.card[0] = itemdb_isequip(group->nameid) ? CARD0_FORGE : CARD0_CREATE;
+	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);
 	}
-	//Do loop for non-stackable item
-	for (i = 0; i < group->amount; i++) {
-		int flag;
-		if ((flag = pc_additem(sd,&tmp,tmp.amount,LOG_TYPE_SCRIPT)))
-			clif_additem(sd,0,0,flag);
-		else if (!flag && group->isAnnounced) { ///TODO: Move this broadcast to proper behavior (it should on at different packet)
+	// Do loop for non-stackable item
+	for (i = 0; i < data->amount; i++) {
+		char flag;
+		if ((flag = pc_additem(sd, &tmp, tmp.amount, LOG_TYPE_SCRIPT)))
+			clif_additem(sd, 0, 0, flag);
+		else if (!flag && data->isAnnounced) { ///TODO: Move this broadcast to proper behavior (it should on at different packet)
 			char output[CHAT_SIZE_MAX];
-			sprintf(output,msg_txt(NULL,717),sd->status.name,itemdb_jname(group->nameid),itemdb_jname(sd->itemid));
-			clif_broadcast(&sd->bl,output,strlen(output),BC_DEFAULT,ALL_CLIENT);
+			sprintf(output, msg_txt(NULL, 717), sd->status.name, itemdb_jname(data->nameid), itemdb_jname(sd->itemid));
+			clif_broadcast(&sd->bl, output, strlen(output), BC_DEFAULT, ALL_CLIENT);
 			//clif_broadcast_obtain_special_item();
 		}
-		if (itemdb_isstackable(group->nameid))
+		if (itemdb_isstackable(data->nameid))
 			break;
 	}
 }
@@ -244,53 +250,36 @@ static void itemdb_pc_get_itemgroup_sub(struct map_session_data *sd, uint16 grou
 */
 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_id || group_id >= MAX_ITEMGROUP) {
+	if (!(group = (struct s_item_group_db *) idb_get(itemdb_group, group_id))) {
 		ShowError("itemdb_pc_get_itemgroup: Invalid group id '%d' specified.",group_id);
 		return 2;
 	}
 	
-	//Get the 'must' item(s)
-	if (itemgroup_db[group_id].must_qty)
-		for (i = 0; i < itemgroup_db[group_id].must_qty; i++)
-			if (&itemgroup_db[group_id].must[i] && itemdb_exists(itemgroup_db[group_id].must[i].nameid))
-				itemdb_pc_get_itemgroup_sub(sd,group_id,&itemgroup_db[group_id].must[i]);
+	// 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
+	// Get the 'random' item each random group
 	for (i = 0; i < MAX_ITEMGROUP_RANDGROUP; i++) {
 		uint16 rand;
-		if (!(&itemgroup_db[group_id].random[i]) || !itemgroup_db[group_id].random[i].data_qty) //Skip empty random group
+		if (!(&group->random[i]) || !group->random[i].data_qty) //Skip empty random group
 			continue;
-		rand = rnd()%itemgroup_db[group_id].random[i].data_qty;
-		//Woops, why is the data empty? Every check should be done when load the item group! So this is bad day for the player :P
-		if (!&itemgroup_db[group_id].random[i].data[rand] || !itemgroup_db[group_id].random[i].data[rand].nameid) {
+		rand = rnd()%group->random[i].data_qty;
+		if (!(&group->random[i].data[rand]) || !group->random[i].data[rand].nameid)
 			continue;
-		}
-		if (itemdb_exists(itemgroup_db[group_id].random[i].data[rand].nameid))
-			itemdb_pc_get_itemgroup_sub(sd,group_id,&itemgroup_db[group_id].random[i].data[rand]);
+		itemdb_pc_get_itemgroup_sub(sd,&group->random[i].data[rand]);
 	}
 
 	return 0;
 }
 
-/*==========================================
- * Calculates total item-group related bonuses for the given item
- *------------------------------------------*/
-int itemdb_group_bonus(struct map_session_data* sd, int itemid)
-{
-	int bonus = 0, i, j;
-	for (i=0; i < MAX_ITEMGROUP; i++) {
-		if (!sd->itemgrouphealrate[i])
-			continue;
-		ARR_FIND(0,itemgroup_db[i].random[0].data_qty,j,itemgroup_db[i].random[0].data[j].nameid == itemid );
-		if( j < itemgroup_db[i].random[0].data_qty )
-			bonus += sd->itemgrouphealrate[i];
-	}
-	return bonus;
-}
-
 /// Searches for the item_data.
 /// Returns the item_data or NULL if it does not exist.
 struct item_data* itemdb_exists(int nameid)
@@ -672,11 +661,13 @@ static void itemdb_read_itemgroup_sub(const char* filename, bool silent)
 	}
 	
 	while (fgets(line,sizeof(line),fp)) {
-		uint16 nameid;
-		int j, group_id, prob = 1, amt = 1, rand_group = 1, announced = 0, dur = 0, named = 0, bound = 0;
-		char *str[3], *p;
+		int group_id = -1;
+		unsigned int j, prob = 1;
+		uint16 nameid, amt = 1, dur = 0;
+		char *str[9], *p, rand_group = 1, announced = 0, named = 0, bound = 0;
+		struct s_item_group_random *random = NULL;
+		struct s_item_group_db *group = NULL;
 		bool found = false;
-		struct s_item_group_random *random;
 
 		ln++;
 		if (line[0] == '/' && line[1] == '/')
@@ -692,44 +683,45 @@ static void itemdb_read_itemgroup_sub(const char* filename, bool silent)
 			}
 		}
 		memset(str,0,sizeof(str));
-		for (j = 0, p = line; j < 3 && p;j++) {
+		for (j = 0, p = line; j < 9 && p;j++) {
 			str[j] = p;
-			if (j == 2)
-				sscanf(str[j],"%d,%d,%d,%d,%d,%d,%d",&prob,&amt,&rand_group,&announced,&dur,&named,&bound);
 			p = strchr(p,',');
 			if (p) *p++=0;
 		}
-		if (str[0] == NULL)
+		if (str[0] == NULL) //Empty Group ID
 			continue;
 		if (j < 3) {
-			if (j > 1) //Or else it barks on blank lines...
+			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;
 		}
 
-		//Checking group_id
+		// 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 || group_id >= MAX_ITEMGROUP) {
-			ShowWarning("itemdb_read_itemgroup: Cannot save '%s' because invalid group id or group db is overflow in %s:%d\n", str[0], filename, ln);
+		else // Try reads group id by const
+			script_get_constant(trim(str[0]), &group_id);
+
+		if (group_id < 0) {
+			ShowWarning("itemdb_read_itemgroup: Invlaid Group ID '%s' (%s:%d)\n", str[0], filename, ln);
 			continue;
 		}
 
-		//Checking sub group
+		// 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);
+			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 && prob < 1) {
-			ShowWarning("itemdb_read_itemgroup: Invalid probaility for group '%s' sub: %d in %s:%d\n", str[0], rand_group, filename, ln);
+		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
+		// Checking item
 		trim(str[1]);
 		if (ISDIGIT(str[1][0]) && itemdb_exists((nameid = atoi(str[1]))))
 			found = true;
@@ -742,27 +734,40 @@ static void itemdb_read_itemgroup_sub(const char* filename, bool silent)
 			continue;
 		}
 
-		amt = cap_value(amt,1,MAX_AMOUNT);
-		dur = cap_value(dur,0,UINT16_MAX);
-		bound = cap_value(bound,0,4);
+		if (str[3] != NULL) amt = cap_value(atoi(str[3]),1,MAX_AMOUNT);
+		if (str[5] != NULL) announced = atoi(str[5]);
+		if (str[6] != NULL) dur = cap_value(atoi(str[6]),0,UINT16_MAX);
+		if (str[7] != NULL) named = atoi(str[7]);
+		if (str[8] != NULL) bound = cap_value(atoi(str[8]),0,4);
+
+		found = true;
+		if (!(group = (struct s_item_group_db *) idb_get(itemdb_group, group_id))) {
+			found = false;
+			CREATE(group, struct s_item_group_db, 1);
+			group->id = group_id;
+		}
 
-		//Must item (rand_group == 0), place it here
+		// Must item (rand_group == 0), place it here
 		if (!rand_group) {
-			uint16 idx = itemgroup_db[group_id].must_qty;
+			uint16 idx = group->must_qty;
 			if (!idx)
-				CREATE(itemgroup_db[group_id].must,struct s_item_group,1);
+				CREATE(group->must, struct s_item_group_entry, 1);
 			else
-				RECREATE(itemgroup_db[group_id].must,struct s_item_group,idx+1);
-
-			itemgroup_db[group_id].must[idx].nameid = nameid;
-			itemgroup_db[group_id].must[idx].amount = amt;
-			itemgroup_db[group_id].must[idx].isAnnounced = announced;
-			itemgroup_db[group_id].must[idx].duration = dur;
-			itemgroup_db[group_id].must[idx].isNamed = named;
-			itemgroup_db[group_id].must[idx].bound = bound;
-			itemgroup_db[group_id].must_qty++;
-			//If 'must' item isn't set as random item, skip the next process
+				RECREATE(group->must, struct s_item_group_entry, idx+1);
+
+			group->must[idx].nameid = nameid;
+			group->must[idx].amount = amt;
+			group->must[idx].isAnnounced = announced;
+			group->must[idx].duration = dur;
+			group->must[idx].isNamed = named;
+			group->must[idx].bound = bound;
+			group->must_qty++;
+			// If 'must' item isn't set as random item, skip the next process
 			if (!prob) {
+				if (!found) {
+					idb_put(itemdb_group, group->id, group);
+					itemdb_itemgroup_count++;
+				}
 				entries++;
 				continue;
 			}
@@ -771,15 +776,16 @@ static void itemdb_read_itemgroup_sub(const char* filename, bool silent)
 		else
 			rand_group -= 1;
 
-		random = &itemgroup_db[group_id].random[rand_group];
+		random = &group->random[rand_group];
 
-		//Check, if the entry for this random group already created or not
+		// Check, if the entry for this random group already created or not
 		if (!random->data_qty) {
-			CREATE(random->data,struct s_item_group,prob);
+			CREATE(random->data, struct s_item_group_entry, prob);
 			random->data_qty = 0;
 		}
 		else
-			RECREATE(random->data,struct s_item_group,random->data_qty+prob);
+			RECREATE(random->data, struct s_item_group_entry, random->data_qty+prob);
+
 		//Now put the entry to its rand_group
 		for (j = random->data_qty; j < random->data_qty+prob; j++) {
 			random->data[j].nameid = nameid;
@@ -790,6 +796,11 @@ static void itemdb_read_itemgroup_sub(const char* filename, bool silent)
 			random->data[j].bound = bound;
 		}
 		random->data_qty += prob;
+
+		if (!found) {
+			idb_put(itemdb_group, group->id, group);
+			itemdb_itemgroup_count++;
+		}
 		entries++;
 	}
 	fclose(fp);
@@ -1590,7 +1601,7 @@ static void itemdb_read(void) {
 	else
 		itemdb_readdb();
 
-	memset(&itemgroup_db, 0, sizeof(itemgroup_db));
+	itemdb_itemgroup_count = 0;
 	
 	for(i=0; i<ARRAYLENGTH(dbsubpath); i++){
 		uint8 n1 = strlen(db_path)+strlen(dbsubpath[i])+1;
@@ -1675,6 +1686,24 @@ static int itemdb_final_sub(DBKey key, DBData *data, va_list ap)
 	return 0;
 }
 
+static int itemdb_group_free(DBKey key, DBData *data, va_list ap) {
+	struct s_item_group_db *group = 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
 */
@@ -1689,19 +1718,7 @@ void itemdb_reload(void) {
 		if( itemdb_array[i] )
 			destroy_item_data(itemdb_array[i], true);
 
-	for (i = 0; i < MAX_ITEMGROUP; i++) {
-		uint8 j;
-		if (!(&itemgroup_db[i]))
-			continue;
-		if (itemgroup_db[i].must_qty)
-			aFree(itemgroup_db[i].must);
-		for (j = 0; j < MAX_ITEMGROUP_RANDGROUP; j++) {
-			if (!(&itemgroup_db[i].random[j]) || !itemgroup_db[i].random[j].data_qty)
-				continue;
-			aFree(itemgroup_db[i].random[j].data);
-		}
-	}
-
+	itemdb_group->clear(itemdb_group, itemdb_group_free);
 	itemdb_other->clear(itemdb_other, itemdb_final_sub);
 	db_clear(itemdb_combo);
 
@@ -1769,20 +1786,8 @@ void do_final_itemdb(void) {
 	for( i = 0; i < ARRAYLENGTH(itemdb_array); ++i )
 		if( itemdb_array[i] )
 			destroy_item_data(itemdb_array[i], true);
-
-	for (i = 0; i < MAX_ITEMGROUP; i++) {
-		uint8 j;
-		if (!(&itemgroup_db[i]))
-			continue;
-		if (itemgroup_db[i].must_qty)
-			aFree(itemgroup_db[i].must);
-		for (j = 0; j < MAX_ITEMGROUP_RANDGROUP; j++) {
-			if (!(&itemgroup_db[i].random[j]) || !itemgroup_db[i].random[j].data_qty)
-				continue;
-			aFree(itemgroup_db[i].random[j].data);
-		}
-	}
-
+	
+	itemdb_group->destroy(itemdb_group, itemdb_group_free);
 	itemdb_other->destroy(itemdb_other, itemdb_final_sub);
 	destroy_item_data(&dummy_item, false);
 	db_destroy(itemdb_combo);
@@ -1795,6 +1800,7 @@ void do_init_itemdb(void) {
 	memset(itemdb_array, 0, sizeof(itemdb_array));
 	itemdb_other = idb_alloc(DB_OPT_BASE);
 	itemdb_combo = idb_alloc(DB_OPT_BASE);
+	itemdb_group = idb_alloc(DB_OPT_BASE);
 	create_dummy_data(); //Dummy data item.
 	
 	itemdb_read();

+ 28 - 26
src/map/itemdb.h

@@ -23,8 +23,6 @@
 #define IG_FINDINGORE 6
 #define IG_POTION 37
 
-#define MAX_ITEMGROUP 420 ///The max. item group count (increase this when needed). TODO: Remove this limit and use dynamic size or DBMap if needed
-
 #define MAX_ITEMGROUP_RANDGROUP 4	///Max group for random item (increase this when needed). TODO: Remove this limit and use dynamic size if needed
 
 #define CARD0_FORGE 0x00FF
@@ -318,6 +316,33 @@ struct item_combo {
 	bool isRef;/* whether this struct is a reference or the master */
 };
 
+
+/// Struct of item group entry
+struct s_item_group_entry {
+	uint16 nameid, /// Item ID
+		duration; /// Duration if item as rental item (in minutes)
+	uint16 amount; /// Amount of item will be obtained
+	bool isAnnounced, /// Broadcast if player get this item
+		isNamed; /// Named the item (if possible)
+	char bound; /// Makes the item as bound item (according to bound type)
+};
+
+/// Struct of random group
+struct s_item_group_random {
+	struct s_item_group_entry *data;
+	uint16 data_qty;
+};
+
+/// Struct of item group that will be used for db
+struct s_item_group_db {
+	uint16 id;
+	struct s_item_group_entry *must;
+	uint16 must_qty;
+	struct s_item_group_random random[MAX_ITEMGROUP_RANDGROUP]; //! TODO: Move this fixed array to dynamic size if needed.
+};
+
+static uint16 itemdb_itemgroup_count; /// Number of Item Groups
+
 ///Main item data struct
 struct item_data {
 	uint16 nameid;
@@ -384,29 +409,6 @@ struct item_data {
 	unsigned char combos_count;
 };
 
-/* Struct of item group entry */
-struct s_item_group {
-	uint16 nameid, ///item id
-		duration; ///duration if item as rental item (in minute)
-	uint16 amount; ///amount of item will be obtained
-	bool isAnnounced, ///broadcast if player get this item
-		isNamed; ///named the item (if possible)
-	char bound; ///makes the item as bound item (according to bound type)
-};
-
-/* Struct of random group */
-struct s_item_group_random {
-	struct s_item_group *data;
-	uint16 data_qty;
-};
-
-/* Struct of item group that will be used for db */
-struct s_item_group_db {
-	struct s_item_group *must;
-	uint16 must_qty;
-	struct s_item_group_random random[MAX_ITEMGROUP_RANDGROUP]; //! TODO: Move this fixed array to dynamic size if needed.
-};
-
 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_load(int nameid);
@@ -440,7 +442,6 @@ struct item_data* itemdb_exists(int nameid);
 const char* itemdb_typename(enum item_types type);
 const char *itemdb_typename_ammo (enum e_item_ammo ammo);
 
-int itemdb_group_bonus(struct map_session_data* sd, int itemid);
 unsigned short itemdb_searchrandomid(uint16 group_id, uint8 sub_group);
 
 #define itemdb_value_buy(n) itemdb_search(n)->value_buy
@@ -479,6 +480,7 @@ char itemdb_pc_get_itemgroup(uint16 group_id, struct map_session_data *sd);
 uint16 itemdb_get_randgroupitem_count(uint16 group_id, uint8 sub_group, uint16 nameid);
 
 DBMap * itemdb_get_combodb();
+DBMap * itemdb_get_groupdb();
 
 void itemdb_reload(void);
 

+ 104 - 5
src/map/pc.c

@@ -66,6 +66,7 @@ int day_timer_tid = INVALID_TIMER;
 int night_timer_tid = INVALID_TIMER;
 
 struct eri *pc_sc_display_ers = NULL;
+struct eri *pc_itemgrouphealrate_ers = NULL;
 int pc_expiration_tid = INVALID_TIMER;
 
 struct fame_list smith_fame_list[MAX_FAME_LIST];
@@ -2154,6 +2155,56 @@ int pc_bonus_subele(struct map_session_data* sd, unsigned char ele, short rate,
 	return 0;
 }
 
+/** Add item group heal rate bonus to player
+* @param sd Player
+* @param group_id Item Group ID
+* @param rate
+* @author Cydh
+*/
+void pc_itemgrouphealrate(struct map_session_data *sd, uint16 group_id, short rate) {
+	struct s_pc_itemgrouphealrate *entry;
+	uint8 i;
+
+	for (i = 0; i < sd->itemgrouphealrate_count; i++) {
+		if (sd->itemgrouphealrate[i]->group_id == group_id)
+			break;
+	}
+
+	if (i != sd->itemgrouphealrate_count) {
+		sd->itemgrouphealrate[i]->rate += rate;
+		return;
+	}
+
+	if (i >= UINT8_MAX) {
+		ShowError("pc_itemgrouphealrate_add: Reached max (%d) possible bonuses for this player %d\n", UINT8_MAX);
+		return;
+	}
+
+	entry = ers_alloc(pc_itemgrouphealrate_ers, struct s_pc_itemgrouphealrate);
+	entry->group_id = group_id;
+	entry->rate = rate;
+
+	RECREATE(sd->itemgrouphealrate, struct s_pc_itemgrouphealrate *, sd->itemgrouphealrate_count+1);
+	sd->itemgrouphealrate[sd->itemgrouphealrate_count++] = entry;
+}
+
+/** Clear item group heal rate from player
+* @param sd Player
+* @author Cydh
+*/
+void pc_itemgrouphealrate_clear(struct map_session_data *sd) {
+	if (!sd || !sd->itemgrouphealrate_count)
+		return;
+	else {
+		uint8 i;
+		for( i = 0; i < sd->itemgrouphealrate_count; i++ )
+			ers_free(pc_itemgrouphealrate_ers, sd->itemgrouphealrate[i]);
+		sd->itemgrouphealrate_count = 0;
+		aFree(sd->itemgrouphealrate);
+		sd->itemgrouphealrate = NULL;
+	}
+}
+
 /*==========================================
  * Add a bonus(type) to player sd
  *------------------------------------------*/
@@ -3217,8 +3268,8 @@ int pc_bonus2(struct map_session_data *sd,int type,int type2,int val)
 	case SP_ADD_ITEM_HEAL_RATE:
 		if(sd->state.lr_flag == 2)
 			break;
-		if (type2 < MAX_ITEMGROUP) {	//Group bonus
-			sd->itemgrouphealrate[type2] += val;
+		if (type2 < itemdb_itemgroup_count) {	//Group bonus
+			pc_itemgrouphealrate(sd, type2, val);
 			break;
 		}
 		//Standard item bonus.
@@ -7616,7 +7667,7 @@ int pc_itemheal(struct map_session_data *sd,int itemid, int hp,int sp)
 		//All item bonuses.
 		bonus += sd->bonus.itemhealrate2;
 		//Item Group bonuses
-		bonus += bonus*itemdb_group_bonus(sd, itemid)/100;
+		bonus += bonus*pc_get_itemgroup_bonus(sd, itemid)/100;
 		//Individual item bonuses.
 		for(i = 0; i < ARRAYLENGTH(sd->itemhealrate) && sd->itemhealrate[i].nameid; i++)
 		{
@@ -10758,16 +10809,63 @@ short pc_maxparameter(struct map_session_data *sd, enum e_params param) {
 		((class_&JOBL_UPPER) ? battle_config.max_trans_parameter : battle_config.max_parameter)));
 }
 
+
+/**
+* Calculates total item-group related bonuses for the given item
+* @param sd Player
+* @param nameid Item ID
+* @return Heal rate
+**/
+short pc_get_itemgroup_bonus(struct map_session_data* sd, uint16 nameid) {
+	short bonus = 0;
+	uint8 i;
+
+	if (!sd->itemgrouphealrate_count)
+		return bonus;
+	for (i = 0; i < sd->itemgrouphealrate_count; i++) {
+		uint16 group_id = sd->itemgrouphealrate[i]->group_id, j;
+		struct s_item_group_db *group = (struct s_item_group_db *) idb_get(itemdb_get_groupdb(), group_id);
+		if (!group)
+			continue;
+		
+		for (j = 0; j < group->random[0].data_qty; j++) {
+			if (group->random[0].data[j].nameid == nameid) {
+				bonus += sd->itemgrouphealrate[i]->rate;
+				break;
+			}
+		}
+	}
+	return bonus;
+}
+
+/**
+* Calculates total item-group related bonuses for the given item group
+* @param sd Player
+* @param group_id Item Group ID
+* @return Heal rate
+**/
+short pc_get_itemgroup_bonus_group(struct map_session_data* sd, uint16 group_id) {
+	short bonus = 0;
+	uint8 i;
+
+	if (!sd->itemgrouphealrate_count)
+		return bonus;
+	for (i = 0; i < sd->itemgrouphealrate_count; i++) {
+		if (sd->itemgrouphealrate[i]->group_id == group_id)
+			return sd->itemgrouphealrate[i]->rate;
+	}
+	return bonus;
+}
+
 /*==========================================
  * pc Init/Terminate
  *------------------------------------------*/
 void do_final_pc(void) {
-
 	db_destroy(itemcd_db);
-
 	do_final_pc_groups();
 
 	ers_destroy(pc_sc_display_ers);
+	ers_destroy(pc_itemgrouphealrate_ers);
 }
 
 void do_init_pc(void) {
@@ -10808,4 +10906,5 @@ void do_init_pc(void) {
 	do_init_pc_groups();
 
 	pc_sc_display_ers = ers_new(sizeof(struct sc_display_entry), "pc.c:pc_sc_display_ers", ERS_OPT_NONE);
+	pc_itemgrouphealrate_ers = ers_new(sizeof(struct s_pc_itemgrouphealrate), "pc.c:pc_itemgrouphealrate_ers", ERS_OPT_NONE);
 }

+ 16 - 2
src/map/pc.h

@@ -149,6 +149,12 @@ enum npc_timeout_type {
 	NPCT_WAIT  = 2,
 };
 
+/// Item Group heal rate struct
+struct s_pc_itemgrouphealrate {
+	uint16 group_id; /// Item Group ID
+	short rate; /// Rate
+};
+
 struct map_session_data {
 	struct block_list bl;
 	struct unit_data ud;
@@ -323,7 +329,6 @@ struct map_session_data {
 	int ignore_mdef_by_race[RC_MAX];
 	int ignore_mdef_by_class[CLASS_MAX];
 	int ignore_def_by_race[RC_MAX];
-	int itemgrouphealrate[MAX_ITEMGROUP];
 	short sp_gain_race[RC_MAX];
 	short sp_gain_race_attack[RC_MAX];
 	short hp_gain_race_attack[RC_MAX];
@@ -595,13 +600,18 @@ struct map_session_data {
 		int16 icon;
 		int tid;
 	} bonus_script[MAX_PC_BONUS_SCRIPT];
+	
+	struct s_pc_itemgrouphealrate **itemgrouphealrate; /// List of Item Group Heal rate bonus
+	uint8 itemgrouphealrate_count; /// Number of rate bonuses
 
 	/* Expiration Timer ID */
 	int expiration_tid;
 	time_t expiration_time;
 };
 
-struct eri *pc_sc_display_ers;
+struct eri *pc_sc_display_ers; /// Player's SC display table
+struct eri *pc_itemgrouphealrate_ers; /// Player's Item Group Heal Rate table
+
 /* Global Expiration Timer ID */
 extern int pc_expiration_tid;
 
@@ -1105,6 +1115,10 @@ void pc_bonus_script_clear(struct map_session_data *sd, uint16 flag);
 
 void pc_cell_basilica(struct map_session_data *sd);
 
+void pc_itemgrouphealrate_clear(struct map_session_data *sd);
+short pc_get_itemgroup_bonus(struct map_session_data* sd, uint16 nameid);
+short pc_get_itemgroup_bonus_group(struct map_session_data* sd, uint16 group_id);
+
 #if defined(RENEWAL_DROP) || defined(RENEWAL_EXP)
 int pc_level_penalty_mod(struct map_session_data *sd, int mob_level, uint32 mob_class, int type);
 #endif

+ 50 - 49
src/map/skill.c

@@ -6965,66 +6965,67 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
 	}
 
 	case AM_BERSERKPITCHER:
-	case AM_POTIONPITCHER: {
-		int i,hp = 0,sp = 0;
-		if( dstmd && dstmd->mob_id == MOBID_EMPERIUM ) {
-			map_freeblock_unlock();
-			return 1;
-		}
-		if( sd ) {
-			int x,bonus=100;
-			struct skill_condition require = skill_get_requirement(sd, skill_id, skill_lv);
-			x = skill_lv%11 - 1;
-			i = pc_search_inventory(sd, require.itemid[x]);
-			if (i < 0 || require.itemid[x] <= 0) {
-				clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
-				map_freeblock_unlock();
-				return 1;
-			}
-				if (sd->inventory_data[i] == NULL || sd->status.inventory[i].amount < require.amount[x]) {
-				clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
+	case AM_POTIONPITCHER: 
+		{
+			int i,hp = 0,sp = 0;
+			if( dstmd && dstmd->mob_id == MOBID_EMPERIUM ) {
 				map_freeblock_unlock();
 				return 1;
 			}
-			if( skill_id == AM_BERSERKPITCHER ) {
-				if( dstsd && dstsd->status.base_level < (unsigned int)sd->inventory_data[i]->elv ) {
+			if( sd ) {
+				int x,bonus=100;
+				struct skill_condition require = skill_get_requirement(sd, skill_id, skill_lv);
+				x = skill_lv%11 - 1;
+				i = pc_search_inventory(sd, require.itemid[x]);
+				if (i < 0 || require.itemid[x] <= 0) {
 					clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
 					map_freeblock_unlock();
 					return 1;
 				}
-			}
-			potion_flag = 1;
-			potion_hp = potion_sp = potion_per_hp = potion_per_sp = 0;
-			potion_target = bl->id;
-			run_script(sd->inventory_data[i]->script,0,sd->bl.id,0);
-			potion_flag = potion_target = 0;
-			if( sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_ALCHEMIST )
-				bonus += sd->status.base_level;
-			if( potion_per_hp > 0 || potion_per_sp > 0 ) {
-				hp = tstatus->max_hp * potion_per_hp / 100;
-				hp = hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000;
-				if( dstsd ) {
-					sp = dstsd->status.max_sp * potion_per_sp / 100;
-					sp = sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000;
+					if (sd->inventory_data[i] == NULL || sd->status.inventory[i].amount < require.amount[x]) {
+					clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
+					map_freeblock_unlock();
+					return 1;
 				}
-			} else {
-				if( potion_hp > 0 ) {
-					hp = potion_hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000;
-					hp = hp * (100 + (tstatus->vit<<1)) / 100;
-					if( dstsd )
-						hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100;
+				if( skill_id == AM_BERSERKPITCHER ) {
+					if( dstsd && dstsd->status.base_level < (unsigned int)sd->inventory_data[i]->elv ) {
+						clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
+						map_freeblock_unlock();
+						return 1;
+					}
 				}
-				if( potion_sp > 0 ) {
-					sp = potion_sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000;
-					sp = sp * (100 + (tstatus->int_<<1)) / 100;
-					if( dstsd )
-						sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10) / 100;
+				potion_flag = 1;
+				potion_hp = potion_sp = potion_per_hp = potion_per_sp = 0;
+				potion_target = bl->id;
+				run_script(sd->inventory_data[i]->script,0,sd->bl.id,0);
+				potion_flag = potion_target = 0;
+				if( sd->sc.data[SC_SPIRIT] && sd->sc.data[SC_SPIRIT]->val2 == SL_ALCHEMIST )
+					bonus += sd->status.base_level;
+				if( potion_per_hp > 0 || potion_per_sp > 0 ) {
+					hp = tstatus->max_hp * potion_per_hp / 100;
+					hp = hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000;
+					if( dstsd ) {
+						sp = dstsd->status.max_sp * potion_per_sp / 100;
+						sp = sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000;
+					}
+				} else {
+					if( potion_hp > 0 ) {
+						hp = potion_hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000;
+						hp = hp * (100 + (tstatus->vit<<1)) / 100;
+						if( dstsd )
+							hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100;
+					}
+					if( potion_sp > 0 ) {
+						sp = potion_sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)*bonus/10000;
+						sp = sp * (100 + (tstatus->int_<<1)) / 100;
+						if( dstsd )
+							sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10) / 100;
+					}
 				}
-			}
 
-				if (sd->itemgrouphealrate[IG_POTION]>0) {
-					hp += hp * sd->itemgrouphealrate[IG_POTION] / 100;
-					sp += sp * sd->itemgrouphealrate[IG_POTION] / 100;
+				if ((bonus = pc_get_itemgroup_bonus_group(sd, IG_POTION))) {
+					hp += hp * bonus / 100;
+					sp += sp * bonus / 100;
 				}
 
 				if( (i = pc_skillheal_bonus(sd, skill_id)) ) {

+ 2 - 1
src/map/status.c

@@ -2866,7 +2866,6 @@ int status_calc_pc_(struct map_session_data* sd, enum e_status_calc_opt opt)
 		+ sizeof(sd->ignore_def_by_race)
 		+ sizeof(sd->ignore_mdef_by_race)
 		+ sizeof(sd->ignore_mdef_by_class)
-		+ sizeof(sd->itemgrouphealrate)
 		+ sizeof(sd->sp_gain_race)
 		+ sizeof(sd->sp_gain_race_attack)
 		+ sizeof(sd->hp_gain_race_attack)
@@ -2938,6 +2937,8 @@ int status_calc_pc_(struct map_session_data* sd, enum e_status_calc_opt opt)
 	pc_delautobonus(sd,sd->autobonus2,ARRAYLENGTH(sd->autobonus2),true);
 	pc_delautobonus(sd,sd->autobonus3,ARRAYLENGTH(sd->autobonus3),true);
 
+	pc_itemgrouphealrate_clear(sd);
+
 	npc_script_event(sd, NPCE_STATCALC);
 
 	// Parse equipment

+ 1 - 0
src/map/unit.c

@@ -2664,6 +2664,7 @@ int unit_free(struct block_list *bl, clr_type clrtype)
 				sd->quest_log = NULL;
 				sd->num_quests = sd->avail_quests = 0;
 			}
+			pc_itemgrouphealrate_clear(sd);
 			break;
 		}
 		case BL_PET: