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

Merge pull request #143 from rathena/feature/separated_item_guid

Corrected `guid` implementation of item package in 51074a0. See http://rathena.org/board/topic/99743-item-guid/
Cydh Ramdh 10 лет назад
Родитель
Сommit
7295bdc925

+ 1 - 0
db/import-tmpl/item_flag.txt

@@ -4,4 +4,5 @@
 // <Flag>:
 // 1 - As Dead Branch item (will be logged at `branchlog` table and cannot be used at 'nobranch' mapflag)
 // 2 - As item group container, check player's inventory and weight before consumed
+// 4 - GUID item, cannot be stacked even same or stackable item
 // NOTE: For removing flag by import file, use "-" to remove the flag. Example, 604,-1 will removes flag 1 from Branch_Of_Dead_Tree

+ 1 - 0
db/pre-re/item_flag.txt

@@ -4,6 +4,7 @@
 // <Flag>:
 // 1 - As Dead Branch item (will be logged at `branchlog` table and cannot be used at 'nobranch' mapflag)
 // 2 - As item group container, check player's inventory and weight before consumed
+// 4 - GUID item, cannot be stacked even same or stackable item
 // NOTE: For removing flag by import file, use "-" to remove the flag. Example, 604,-1 will removes flag 1 from Branch_Of_Dead_Tree
 
 // Logged as Dead Branch item

+ 266 - 0
db/re/item_flag.txt

@@ -4,6 +4,7 @@
 // <Flag>:
 // 1 - As Dead Branch item (will be logged at `branchlog` table and cannot be used at 'nobranch' mapflag)
 // 2 - As item group container, check player's inventory and weight before consumed
+// 4 - GUID item, cannot be stacked even same or stackable item
 // NOTE: For removing flag by import file, use "-" to remove the flag. Example, 604,-1 will removes flag 1 from Branch_Of_Dead_Tree
 
 // Logged as Dead Branch item
@@ -213,11 +214,19 @@
 13909,2 //MVP_Hunt_Box
 13910,2 //Brewing_Box
 13911,2 //Christmas_Pet_Scroll
+13925,2 //Lucky_Scroll08
+13945,2 //Br_SwordPackage
+13946,2 //Br_MagePackage
+13947,2 //Br_AcolPackage
+13948,2 //Br_ArcherPackage
+13949,2 //Br_MerPackage
+13950,2 //Br_ThiefPackage
 13953,2 //All_In_One_Ring_Box
 13989,2 //Acidbomb_10_Box
 14001,2 //Basic_Siege_Supply_Box
 14002,2 //Adv_Siege_Supply_Box
 14003,2 //Elite_Siege_Supply_Box
+14229,2 //Sakura_Scroll
 14242,2 //Beholder_Ring_Box
 14243,2 //Hallow_Ring_Box
 14244,2 //Clamorous_Ring_Box
@@ -242,6 +251,7 @@
 14469,2 //Ox_Tail_Scroll
 14596,2 //Pierre_Treasurebox
 14613,2 //RWC_Scroll_2012
+14624,2 //Blue_Scroll
 14626,2 //Indigo_Scroll
 14643,2 //Violet_Scroll
 14664,2 //Bi_Hwang_Scroll
@@ -366,3 +376,259 @@
 22558,2 //Lucky_Bag
 22669,2 //HALLOWEEN_G_BOX
 22685,2 //Solo_Christmas_Gift
+
+// GUID
+12915,4 //Aspersio_5_Scroll_Box
+12923,4 //Pet_Egg_Scroll_Box1
+12924,4 //Pet_Egg_Scroll_Box2
+12925,4 //Pet_Egg_Scroll1
+12926,4 //Pet_Egg_Scroll2
+12929,4 //Pet_Egg_Scroll_Box3
+12930,4 //Pet_Egg_Scroll_Box4
+12931,4 //Pet_Egg_Scroll_Box5
+12932,4 //Pet_Egg_Scroll3
+12933,4 //Pet_Egg_Scroll4
+12934,4 //Pet_Egg_Scroll5
+12935,4 //Infiltrator_Box
+12936,4 //Muramasa_Box
+12937,4 //Excalibur_Box
+12938,4 //Combat_Knife_Box
+12939,4 //Counter_Dagger_Box
+12940,4 //Kaiser_Knuckle_Box
+12941,4 //Pole_Axe_Box
+12942,4 //Mighty_Staff_Box
+12943,4 //Right_Epsilon_Box
+12944,4 //Balistar_Box
+12945,4 //Diary_Of_Great_Sage_Box
+12946,4 //Asura_Box
+12947,4 //Apple_Of_Archer_Box
+12948,4 //Bunny_Band_Box
+12949,4 //Sahkkat_Box
+12950,4 //Lord_Circlet_Box
+12951,4 //Elven_Ears_Box
+12952,4 //Steel_Flower_Box
+12953,4 //Critical_Ring_Box
+12954,4 //Earring_Box
+12955,4 //Ring_Box
+12956,4 //Necklace_Box
+12957,4 //Glove_Box
+12958,4 //Brooch_Box
+12959,4 //Rosary_Box
+12960,4 //Safety_Ring_Box
+12961,4 //Vesper_Core01_Box
+12962,4 //Vesper_Core02_Box
+12963,4 //Vesper_Core03_Box
+12964,4 //Vesper_Core04_Box
+12983,4 //Pet_Egg_Scroll_Box6
+12984,4 //Pet_Egg_Scroll_Box7
+12985,4 //Pet_Egg_Scroll_Box8
+12986,4 //Pet_Egg_Scroll_Box9
+12987,4 //Pet_Egg_Scroll_Box10
+12988,4 //Pet_Egg_Scroll_Box11
+12989,4 //Pet_Egg_Scroll6
+12990,4 //Pet_Egg_Scroll7
+12991,4 //Pet_Egg_Scroll8
+12992,4 //Pet_Egg_Scroll9
+12993,4 //Pet_Egg_Scroll10
+12994,4 //Pet_Egg_Scroll11
+13543,4 //CP_Helm_Scroll_Box
+13544,4 //CP_Shield_Scroll_Box
+13545,4 //CP_Armor_Scroll_Box
+13546,4 //CP_Weapon_Scroll_Box
+13547,4 //Repair_Scroll_Box
+13617,4 //Super_Pet_Egg1
+13618,4 //Super_Pet_Egg2
+13619,4 //Super_Pet_Egg3
+13620,4 //Super_Pet_Egg4
+13630,4 //Super_Card_Pet_Egg1
+13631,4 //Super_Card_Pet_Egg2
+13632,4 //Super_Card_Pet_Egg3
+13633,4 //Super_Card_Pet_Egg4
+13634,4 //Vigorgra_Package1
+13635,4 //Vigorgra_Package2
+13636,4 //Vigorgra_Package3
+13637,4 //Vigorgra_Package4
+13638,4 //Vigorgra_Package5
+13639,4 //Vigorgra_Package6
+13640,4 //Vigorgra_Package7
+13641,4 //Vigorgra_Package8
+13642,4 //Vigorgra_Package9
+13643,4 //Vigorgra_Package10
+13644,4 //Vigorgra_Package11
+13645,4 //Vigorgra_Package12
+13701,4 //Pet_Egg_Scroll12
+13702,4 //Pet_Egg_Scroll13
+13703,4 //Pet_Egg_Scroll14
+13704,4 //Super_Pet_Egg5
+13705,4 //Super_Pet_Egg6
+13706,4 //Super_Pet_Egg7
+13707,4 //Super_Pet_Egg8
+13708,4 //Pet_Egg_Scroll_E
+13725,4 //Ramen_Hat_Box
+13773,4 //Fire_Brand_Box
+13845,4 //Mysterious_Travel_Sack1
+13846,4 //Mysterious_Travel_Sack2
+13847,4 //Mysterious_Travel_Sack3
+13848,4 //Mysterious_Travel_Sack4
+13871,4 //Magician_Card_Box
+13872,4 //Acolyte_Card_Box
+13873,4 //Archer_Card_Box
+13874,4 //Swordman_Card_Box
+13875,4 //Thief_Card_Box
+13876,4 //Merchant_Card_Box
+13905,4 //Hard_Core_Set_Box
+13906,4 //Kitty_Set_Box
+13907,4 //Soft_Core_Set_Box
+13908,4 //Deviruchi_Set_Box
+13909,4 //MVP_Hunt_Box
+13910,4 //Brewing_Box
+13911,4 //Christmas_Pet_Scroll
+13925,4 //Lucky_Scroll08
+13945,4 //Br_SwordPackage
+13946,4 //Br_MagePackage
+13947,4 //Br_AcolPackage
+13948,4 //Br_ArcherPackage
+13949,4 //Br_MerPackage
+13950,4 //Br_ThiefPackage
+13953,4 //All_In_One_Ring_Box
+13989,4 //Acidbomb_10_Box
+14001,4 //Basic_Siege_Supply_Box
+14002,4 //Adv_Siege_Supply_Box
+14003,4 //Elite_Siege_Supply_Box
+14229,4 //Sakura_Scroll
+14242,4 //Beholder_Ring_Box
+14243,4 //Hallow_Ring_Box
+14244,4 //Clamorous_Ring_Box
+14245,4 //Chemical_Ring_Box
+14246,4 //Insecticide_Ring_Box
+14247,4 //Fisher_Ring_Box
+14248,4 //Decussate_Ring_Box
+14249,4 //Bloody_Ring_Box
+14250,4 //Satanic_Ring_Box
+14251,4 //Dragoon_Ring_Box
+14296,4 //Angel_Scroll
+14297,4 //Devil_Scroll
+14298,4 //Surprise_Scroll
+14306,4 //RWC_Special_Scroll
+14307,4 //RWC_Limited_Scroll
+14316,4 //July7_Scroll
+14317,4 //Bacsojin_Scroll
+14345,4 //Animal_Scroll
+14363,4 //Heart_Scroll
+14408,4 //New_Year_Scroll
+14466,4 //Valentine_Pledge_Box
+14469,4 //Ox_Tail_Scroll
+16245,4 //Tw_April_Scroll
+16304,4 //Evil_Incarnation
+16371,4 //Tw_Aug_Scroll
+16372,4 //F_Clover_Box_Mouth
+16374,4 //Mouth_Bubble_Gum_Box
+16385,4 //F_Clover_Box_Mouth2
+16386,4 //F_Clover_Box_Mouth4
+16389,4 //BGum_Box_In_Mouth2
+16390,4 //BGum_Box_In_Mouth4
+16409,4 //Tw_Sep_Scroll
+16446,4 //Tw_October_Scroll
+16456,4 //My_Scroll1
+16457,4 //Tw_Nov_Scroll
+16466,4 //My_Scroll2
+16542,4 //Xmas_Bless
+16555,4 //Pr_Reset_Stone_Box
+16556,4 //FPr_Reset_Stone_Box
+16562,4 //Majestic_Devil_Scroll
+16576,4 //Illusion_Nothing
+16638,4 //Life_Ribbon_Box
+16639,4 //Life_Ribbon_Box2
+16640,4 //Life_Ribbon_Box3
+16652,4 //Flame_Light
+16666,4 //Magic_Candy_Box10
+16673,4 //Libra_Scroll
+16675,4 //Splash_Scroll
+16681,4 //BR_Independence_Scroll
+16682,4 //Boarding_Halter_Box
+16687,4 //RWC2010_SuitcaseA
+16688,4 //RWC2010_SuitcaseB
+16741,4 //Hairtail_Box1
+16742,4 //Hairtail_Box2
+16743,4 //Spearfish_Box1
+16744,4 //Spearfish_Box2
+16745,4 //Saurel_Box1
+16746,4 //Saurel_Box2
+16747,4 //Tuna_Box1
+16748,4 //Tuna_Box2
+16749,4 //Malang_Crab_Box1
+16750,4 //Malang_Crab_Box2
+16751,4 //Brindle_Eel_Box1
+16752,4 //Brindle_Eel_Box2
+16757,4 //Hallo_Scroll
+16760,4 //Umbala_Spirit_Box2
+16761,4 //F_Umbala_Spirit_Box2
+16763,4 //Ptotection_Seagod_Box2
+16764,4 //Ptotection_Seagod_Box3
+16765,4 //Octo_Hstick_Box
+16766,4 //Octo_Hstick_Box2
+16767,4 //Octo_Hstick_Box3
+16770,4 //Silvervine_Fruit_Box10
+16771,4 //Silvervine_Fruit_Box40
+16774,4 //Asgard_Scroll
+16775,4 //Sagittarius_Scroll
+16826,4 //Sagittarius_Scr_Box
+16972,4 //Weather_Report_Box
+16974,4 //Comin_Actor_Box
+16976,4 //Hen_Set_Box
+16979,4 //Silvervine_Fruit_Box4
+16990,4 //Sagittar_Diadem_Scroll
+16991,4 //Sagittar_Di_Scroll_Box
+16996,4 //Capri_Crown_Scroll
+16997,4 //Capri_Crown_Scroll_Box
+17011,4 //Capricon_Di_Scroll
+17012,4 //Capricon_Di_Scroll_Box
+17013,4 //Malang_Woe_Encard_Box
+17016,4 //Aquarius_Diadem_Scroll
+17017,4 //Aquarius_Di_Scroll_Box
+17020,4 //Tw_Nov_Scroll2
+17021,4 //Summer_Scroll3
+17022,4 //Super_Pet_Egg1_2
+17023,4 //Super_Pet_Egg4_2
+17024,4 //Lovely_Aquarius_Scroll
+17025,4 //Lovely_Aquarius_Box
+17026,4 //Boitata_Scroll
+17028,4 //Pisces_Diadem_Scroll
+17029,4 //Pisces_Diadem_Box
+17035,4 //Energetic_Pisces_Scroll
+17036,4 //Energetic_Pisces_Box
+17050,4 //Aries_Scroll
+17051,4 //Aries_Scroll_Box
+17062,4 //Taurus_Diadem_Scroll
+17063,4 //Taurus_Di_Scroll_Box
+17077,4 //Taurus_Crown_Scroll
+17078,4 //Taurus_Crown_Scroll_Box
+17082,4 //Gemi_Diadem_Scroll
+17083,4 //Gemi_Diadem_Scroll_Box
+17107,4 //Gemi_Crown_Scroll
+17108,4 //Gemi_Crown_Scroll_Box
+17138,4 //Ms_Cancer_Scroll
+17139,4 //RWC_Super_Scroll
+17140,4 //Leo_Scroll
+17141,4 //Ms_Virgo_Scroll
+17143,4 //Ms_Scorpio_Scroll
+17156,4 //TCG_Card_Scroll
+17165,4 //Challenge_Kit
+17209,4 //Tw_Rainbow_Scroll
+17210,4 //Tw_Red_Scroll
+17211,4 //Tw_Orange_Scroll
+17212,4 //Tw_Yellow_Scroll
+17233,4 //Scroll_Of_Death
+17234,4 //Scroll_Of_Life
+17235,4 //Scroll_Of_Magic
+17236,4 //Scroll_Of_Thews
+17237,4 //Scroll_Of_Darkness
+17238,4 //Scroll_Of_Holiness
+17239,4 //Horned_Scroll
+17240,4 //Mercury_Scroll
+17251,4 //C_Wing_Of_Fly_3Day_Box
+17252,4 //RWC_2012_Set_Box
+17256,4 //Good_Student_Gift_Box
+17257,4 //Bad_Student_Gift_Box
+17262,4 //Ex_Def_Potion_Box
+22558,4 //Lucky_Bag

+ 1 - 1
db/re/item_package.txt

@@ -1,7 +1,7 @@
 // Item Package Database
 //
 // Structure of Database:
-// GroupID,ItemID,Rate{,Amount,Random,isAnnounced,Duration,isNamed,isBound}
+// GroupID,ItemID,Rate{,Amount,Random,isAnnounced,Duration,GUID,isBound,isNamed}
 
 IG_Special_Box,Wrapped_Mask,3,1,1
 IG_Special_Box,Poison_Bottle,10,2,1

+ 13 - 2
doc/item_group.txt

@@ -29,10 +29,12 @@ accessed in each.
 +===============+====================+================+
 | Duration      |       no           |      YES       |
 +===============+====================+================+
-| isNamed       |       no           |      YES       |
+| GUID          |       no           |      YES       |
 +===============+====================+================+
 | isBound       |       no           |      YES       |
 +===============+====================+================+
+| isNamed       |       no           |      YES       |
++===============+====================+================+
 
 ---------------------------------------
 
@@ -117,7 +119,12 @@ Duration: Makes the item a rental item, which will be expire in the given amount
 
 ---------------------------------------
 
-isNamed: Inscribes the item with the obtainer's name.
+GUID: Makes the given item(s) with Unique ID. Item will be stacked ONLY each group
+      when it obtained. Cannot be stacked with same item, even it's stackable item.
+	  Example, there is Box (just call it Apple_Box) that contains 3x Apples with
+	  GUID = 1. When Apples appear it will stack for each 3 even another 3x Apples
+	  are appeared by same box. So it will be filled in inventory as:
+	          3x Apples | 3x Apples | so on... | nx Apples (normal)
 
 ---------------------------------------
 
@@ -126,6 +133,10 @@ isBound: Binds the obtained item.
 
 ---------------------------------------
 
+isNamed: Inscribes the item with the obtainer's name.
+
+---------------------------------------
+
 Supports to import other file, usage:
 import: db/path/filename.txt
 

+ 11 - 0
doc/script_commands.txt

@@ -2841,6 +2841,17 @@ Returns value from equipped item slot in the indicated slot (0, 1, 2, or 3).
 This function returns CARD ID, 255,254,-255 (for card 0, if the item is produced).
 It's useful for when you want to check whether an item contains cards or if it's signed.
 
+---------------------------------------
+
+*mergeitem({<item_id>});
+*mergeitem({"<item name>"});
+
+Merge all stackable items that separated by GUID flags
+(either by flag 4 item_flag.txt or GUID  in item_group).
+If no item ID/name given, all possible items in player's inventory will be merged.
+
+Example, just see the NPC 'npc/re/other/merge_item.txt'.
+
 ---------------------------------------
 //
 2,1.- End of item-related commands.

+ 3 - 2
npc/re/other/item_merge.txt

@@ -3,7 +3,7 @@
 //===== By: ==================================================
 //= Euphy
 //===== Current Version: =====================================
-//= 1.0
+//= 1.1
 //===== Compatible With: =====================================
 //= rAthena Project
 //===== Description: =========================================
@@ -12,6 +12,7 @@
 //= inventory.
 //===== Additional Comments: =================================
 //= 1.0 First version, currently useless/disabled.
+//= 1.1 Implemented the 'mergeitem' script command. [Cydh]
 //============================================================
 
 prontera,146,95,3	script	Mergician#pron	64,{
@@ -55,7 +56,7 @@ prontera,146,95,3	script	Mergician#pron	64,{
 		next;
 		switch(select("Merrrrge!:Don't follow what he says.")) {
 		case 1:
-//			MergeItem
+			mergeitem;
 			mes "[Mergician]";
 			mes "Merge just heard your wish and let it be realised!";
 			mes "Open your inventory to check the miracle!";

+ 1 - 1
npc/re/scripts_athena.conf

@@ -90,7 +90,7 @@ npc: npc/re/merchants/shops.txt
 
 // --------------------------- Others ---------------------------
 npc: npc/re/other/bulletin_boards.txt
-//npc: npc/re/other/item_merge.txt
+npc: npc/re/other/item_merge.txt
 npc: npc/re/other/mail.txt
 npc: npc/re/other/mercenary_rent.txt
 npc: npc/re/other/pvp.txt

+ 8 - 0
src/config/core.h

@@ -64,6 +64,14 @@
 /// Uncomment to enable the job base HP/SP table (job_basehpsp_db.txt)
 //#define HP_SP_TABLES
 
+/// Enable separated item by `guid`. [Cydh]
+/// See db/[pre-]re/item_flag.txt and doc/item_group.txt for the `guid` explanation.
+/// NOTE:
+///    If this feature is disabled "in the middle" of your game, the separated is still
+///    separated in inventory, storage, or guild storage until player move the item
+///    to/from storage/inventory to restack them.
+#define ENABLE_ITEM_GUID
+
 /// Uncomment to enable VIP system.
 //#define VIP_ENABLE
 

+ 0 - 1
src/map/atcommand.c

@@ -1271,7 +1271,6 @@ ACMD_FUNC(item)
 				item_tmp.nameid = item_id;
 				item_tmp.identify = 1;
 				item_tmp.bound = bound;
-
 				if ((flag = pc_additem(sd, &item_tmp, get_count, LOG_TYPE_COMMAND)))
 					clif_additem(sd, 0, 0, flag);
 			}

+ 8 - 2
src/map/clif.c

@@ -13207,8 +13207,14 @@ void clif_parse_GM_Item_Monster(int fd, struct map_session_data *sd)
 		StringBuf_Init(&command);
 		if( !itemdb_isstackable2(id) ) //Nonstackable
 			StringBuf_Printf(&command, "%citem2 %d 1 0 0 0 0 0 0 0", atcommand_symbol, id->nameid);
-		else
-			StringBuf_Printf(&command, "%citem %d 20", atcommand_symbol, id->nameid);
+		else {
+#ifdef ENABLE_ITEM_GUID
+			if (id->flag.guid)
+				StringBuf_Printf(&command, "%citem %d 1", atcommand_symbol, id->nameid);
+			else
+#endif
+				StringBuf_Printf(&command, "%citem %d 20", atcommand_symbol, id->nameid);
+		}
 		is_atcommand(fd, sd, StringBuf_Value(&command), 1);
 		StringBuf_Destroy(&command);
 		return;

+ 29 - 16
src/map/itemdb.c

@@ -13,6 +13,7 @@
 #include "cashshop.h"
 #include "script.h" // item script processing
 #include "pc.h"     // W_MUSICAL, W_WHIP
+#include "intif.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -198,12 +199,17 @@ static void itemdb_pc_get_itemgroup_sub(struct map_session_data *sd, struct s_it
 	// Do loop for non-stackable item
 	for (i = 0; i < data->amount; i++) {
 		char flag = 0;
+#ifdef ENABLE_ITEM_GUID
+		tmp.unique_id = data->GUID ? pc_generate_unique_id(sd) : 0; // Generate UID
+#endif
 		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)
+		else if (!flag && data->isAnnounced) {
 			char output[CHAT_SIZE_MAX];
 			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);
+
+			//! TODO: Move this broadcast to proper packet
+			intif_broadcast(output, strlen(output) + 1, BC_DEFAULT);
 			//clif_broadcast_obtain_special_item();
 		}
 		if (itemdb_isstackable(data->nameid))
@@ -438,17 +444,17 @@ bool itemdb_isequip2(struct item_data *id)
 */
 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;
-  }
+	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;
+	}
 }
 
 
@@ -557,7 +563,7 @@ static bool itemdb_read_itemavail(char* str[], int columns, int current) {
 }
 
 /** Read item group data
-* Structure: GroupID,ItemID,Rate{,Amount,isMust,isAnnounced,Duration,isNamed,isBound}
+* Structure: GroupID,ItemID,Rate{,Amount,isMust,isAnnounced,Duration,GUID,isBound,isNamed}
 */
 static void itemdb_read_itemgroup_sub(const char* filename, bool silent)
 {
@@ -574,7 +580,7 @@ static void itemdb_read_itemgroup_sub(const char* filename, bool silent)
 		int group_id = -1;
 		unsigned int j, prob = 1;
 		uint8 rand_group = 1;
-		char *str[9], *p;
+		char *str[10], *p;
 		struct s_item_group_random *random = NULL;
 		struct s_item_group_db *group = NULL;
 		struct s_item_group_entry entry;
@@ -658,8 +664,11 @@ static void itemdb_read_itemgroup_sub(const char* filename, bool silent)
 		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.isNamed = atoi(str[7]);
+#ifdef ENABLE_ITEM_GUID
+		if (str[7] != NULL) entry.GUID = atoi(str[7]);
+#endif
 		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);
@@ -894,6 +903,7 @@ static bool itemdb_read_nouse(char* fields[], int columns, int current) {
 * <item_id>,<flag>
 * &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]);
@@ -911,6 +921,9 @@ static bool itemdb_read_flag(char* fields[], int columns, int current) {
 
 	if (flag&1) id->flag.dead_branch = set ? 1 : 0;
 	if (flag&2) id->flag.group = set ? 1 : 0;
+#ifdef ENABLE_ITEM_GUID
+	if (flag&4 && itemdb_isstackable2(id)) id->flag.guid = set ? 1 : 0;
+#endif
 
 	return true;
 }

+ 2 - 0
src/map/itemdb.h

@@ -345,6 +345,7 @@ struct s_item_group_entry
 		duration, /// Duration if item as rental item (in minutes)
 		amount; /// Amount of item will be obtained
 	bool isAnnounced, /// Broadcast if player get this item
+		GUID, /// Gives Unique ID for items in each box opened
 		isNamed; /// Named the item (if possible)
 	char bound; /// Makes the item as bound item (according to bound type)
 };
@@ -414,6 +415,7 @@ struct item_data
 		unsigned buyingstore : 1;
 		unsigned dead_branch : 1; // As dead branch item. Logged at `branchlog` table and cannot be used at 'nobranch' mapflag [Cydh]
 		unsigned group : 1; // As item group container [Cydh]
+		unsigned guid : 1; // This item always be attached with GUID and make it as bound item! [Cydh]
 	} flag;
 	struct {// item stacking limitation
 		unsigned short amount;

+ 26 - 9
src/map/pc.c

@@ -4289,13 +4289,23 @@ char pc_additem(struct map_session_data *sd,struct item *item,int amount,e_log_p
 
 	i = MAX_INVENTORY;
 
+#ifdef ENABLE_ITEM_GUID
+	if (id->flag.guid && !item->unique_id)
+		item->unique_id = pc_generate_unique_id(sd);
+#endif
+
 	// Stackable | Non Rental
 	if( itemdb_isstackable2(id) && item->expire_time == 0 ) {
 		for( i = 0; i < MAX_INVENTORY; i++ ) {
 			if( sd->status.inventory[i].nameid == item->nameid &&
-			    sd->status.inventory[i].bound == item->bound &&
-			    sd->status.inventory[i].expire_time == 0 &&
-			    memcmp(&sd->status.inventory[i].card, &item->card, sizeof(item->card)) == 0 ) {
+				sd->status.inventory[i].bound == item->bound &&
+				sd->status.inventory[i].expire_time == 0 &&
+#ifdef ENABLE_ITEM_GUID
+				sd->status.inventory[i].unique_id == item->unique_id &&
+#endif
+				memcmp(&sd->status.inventory[i].card, &item->card, sizeof(item->card)) == 0
+				)
+			{
 				if( amount > MAX_AMOUNT - sd->status.inventory[i].amount || ( id->stack.inventory && amount > id->stack.amount - sd->status.inventory[i].amount ) )
 					return ADDITEM_OVERAMOUNT;
 				sd->status.inventory[i].amount += amount;
@@ -4324,7 +4334,7 @@ char pc_additem(struct map_session_data *sd,struct item *item,int amount,e_log_p
 		clif_additem(sd,i,amount,0);
 	}
 	if( !itemdb_isstackable2(id) && !item->unique_id )
-		sd->status.inventory[i].unique_id = pc_generate_unique_id(sd);
+		item->unique_id = pc_generate_unique_id(sd);
 	log_pick_pc(sd, log_type, amount, &sd->status.inventory[i]);
 
 	sd->weight += w;
@@ -4858,11 +4868,17 @@ unsigned char pc_cart_additem(struct map_session_data *sd,struct item *item,int
 	i = MAX_CART;
 	if( itemdb_isstackable2(data) && !item->expire_time )
 	{
-		ARR_FIND( 0, MAX_CART, i,
-			sd->status.cart[i].nameid == item->nameid && sd->status.cart[i].bound == item->bound &&
-			sd->status.cart[i].card[0] == item->card[0] && sd->status.cart[i].card[1] == item->card[1] &&
-			sd->status.cart[i].card[2] == item->card[2] && sd->status.cart[i].card[3] == item->card[3] );
-	};
+		for (i = 0; i < MAX_CART; i++) {
+			if (sd->status.cart[i].nameid == item->nameid
+				&& sd->status.cart[i].bound == item->bound
+#ifdef ENABLE_ITEM_GUID
+				&& sd->status.cart[i].unique_id == item->unique_id
+#endif
+				&& memcmp(sd->status.cart[i].card, item->card, sizeof(item->card)) == 0
+				)
+				break;
+		}
+	}
 
 	if( i < MAX_CART )
 	{// item already in cart, stack it
@@ -11274,6 +11290,7 @@ bool pc_is_same_equip_index(enum equip_index eqi, short *equip_index, short inde
  * @return A generated Unique item ID
  */
 uint64 pc_generate_unique_id(struct map_session_data *sd) {
+	nullpo_ret(sd);
 	return ((uint64)sd->status.char_id << 32) | sd->status.uniqueitem_counter++;
 }
 

+ 92 - 7
src/map/script.c

@@ -6497,20 +6497,21 @@ BUILDIN_FUNC(getitem)
 	struct script_data *data;
 	unsigned char flag = 0;
 	const char* command = script_getfuncname(st);
+	struct item_data *id = NULL;
 
-	data=script_getdata(st,2);
+	data = script_getdata(st,2);
 	get_val(st,data);
 	if( data_isstring(data) ) {// "<item name>"
 		const char *name = conv_str(st,data);
-		struct item_data *item_data = itemdb_searchname(name);
-		if( item_data == NULL ){
+		id = itemdb_searchname(name);
+		if( id == NULL ){
 			ShowError("buildin_getitem: Nonexistant item %s requested.\n", name);
 			return SCRIPT_CMD_FAILURE; //No item created.
 		}
-		nameid = item_data->nameid;
+		nameid = id->nameid;
 	} else if( data_isint(data) ) {// <item id>
 		nameid = conv_num(st,data);
-		if( !itemdb_exists(nameid) ){
+		if( !(id = itemdb_exists(nameid)) ){
 			ShowError("buildin_getitem: Nonexistant item %d requested.\n", nameid);
 			return SCRIPT_CMD_FAILURE; //No item created.
 		}
@@ -6548,7 +6549,7 @@ BUILDIN_FUNC(getitem)
 		return SCRIPT_CMD_SUCCESS;
 
 	//Check if it's stackable.
-	if (!itemdb_isstackable(nameid))
+	if (!itemdb_isstackable2(id))
 		get_count = 1;
 	else
 		get_count = amount;
@@ -6666,7 +6667,7 @@ BUILDIN_FUNC(getitem2)
 		item_tmp.bound = bound;
 
 		//Check if it's stackable.
-		if (!itemdb_isstackable(nameid))
+		if (!itemdb_isstackable2(item_data))
 			get_count = 1;
 		else
 			get_count = amount;
@@ -19111,6 +19112,89 @@ BUILDIN_FUNC(countspiritball) {
 	return SCRIPT_CMD_SUCCESS;
 }
 
+/** Merges separated stackable items because of guid
+* mergeitem {<item_id>};
+* mergeitem {"<item name>"};
+* @param item Item ID/Name for merging specific item (Optional)
+* @author [Cydh]
+*/
+BUILDIN_FUNC(mergeitem) {
+	struct map_session_data *sd = script_rid2sd(st);
+	struct item *items;
+	uint16 i, count = 0;
+	int nameid = 0;
+
+	if (!sd)
+		return SCRIPT_CMD_SUCCESS;
+
+	if (script_hasdata(st, 2)) {
+		struct script_data *data = script_getdata(st, 2);
+		struct item_data *id;
+		get_val(st, data);
+
+		if (data_isstring(data)) {// "<item name>"
+			const char *name = conv_str(st,data);
+			if (!(id = itemdb_searchname(name))) {
+				ShowError("buildin_mergeitem: Nonexistant item %s requested.\n", name);
+				script_pushint(st, count);
+				return SCRIPT_CMD_FAILURE;
+			}
+			nameid = id->nameid;
+		}
+		else if (data_isint(data)) {// <item id>
+			nameid = conv_num(st,data);
+			if (!(id = itemdb_exists(nameid))) {
+				ShowError("buildin_mergeitem: Nonexistant item %d requested.\n", nameid);
+				script_pushint(st, count);
+				return SCRIPT_CMD_FAILURE;
+			}
+		}
+	}
+
+	for (i = 0; i < MAX_INVENTORY; i++) {
+		struct item *it = &sd->status.inventory[i];
+		if (!it || !it->unique_id || it->expire_time || !itemdb_isstackable(it->nameid))
+			continue;
+		if ((!nameid || (nameid == it->nameid))) {
+			uint8 k;
+			if (!count) {
+				CREATE(items, struct item, 1);
+				memcpy(&items[count++], it, sizeof(struct item));
+				pc_delitem(sd, i, it->amount, 0, 0, LOG_TYPE_NPC);
+				continue;
+			}
+			for (k = 0; k < count; k++) {
+				// Find Match
+				if (&items[k] && items[k].nameid == it->nameid && items[k].bound == it->bound && memcmp(items[k].card, it->card, sizeof(it->card)) == 0) {
+					items[k].amount += it->amount;
+					pc_delitem(sd, i, it->amount, 0, 0, LOG_TYPE_NPC);
+					break;
+				}
+			}
+			if (k >= count) {
+				// New entry
+				RECREATE(items, struct item, count+1);
+				memcpy(&items[count++], it, sizeof(struct item));
+				pc_delitem(sd, i, it->amount, 0, 0, LOG_TYPE_NPC);
+			}
+		}
+	}
+
+	// Retrieve the items
+	for (i = 0; i < count; i++) {
+		uint8 flag = 0;
+		if (!&items[i])
+			continue;
+		items[i].id = 0;
+		items[i].unique_id = 0;
+		if ((flag = pc_additem(sd, &items[i], items[i].amount, LOG_TYPE_NPC)))
+			clif_additem(sd, i, items[i].amount, flag);
+	}
+	aFree(items);
+	script_pushint(st, count);
+	return SCRIPT_CMD_SUCCESS;
+}
+
 #include "../custom/script.inc"
 
 // declarations that were supposed to be exported from npc_chat.c
@@ -19653,6 +19737,7 @@ struct script_function buildin_func[] = {
 	BUILDIN_DEF(addspiritball,"ii?"),
 	BUILDIN_DEF(delspiritball,"i?"),
 	BUILDIN_DEF(countspiritball,"?"),
+	BUILDIN_DEF(mergeitem,"?"),
 
 #include "../custom/script_def.inc"
 

+ 7 - 2
src/map/storage.c

@@ -131,7 +131,7 @@ int storage_storageopen(struct map_session_data *sd)
 }
 
 /**
- * check if 2 item a and b have same values
+ * Check if 2 item a and b have same values
  * @param a : item 1
  * @param b : item 2
  * @return 1:same, 0:are different
@@ -143,7 +143,12 @@ int compare_item(struct item *a, struct item *b)
 		a->refine == b->refine &&
 		a->attribute == b->attribute &&
 		a->expire_time == b->expire_time &&
-		a->bound == b->bound ) {
+		a->bound == b->bound
+#ifdef ENABLE_ITEM_GUID
+		&& a->unique_id == b->unique_id
+#endif
+		)
+	{
 		int i;
 
 		for (i = 0; i < MAX_SLOTS && (a->card[i] == b->card[i]); i++);