Pārlūkot izejas kodu

Fixed bugreport:309 combos may now stack properly. moved all combo processing out of item bonuses and made it's own processing scheme, which is by far more efficient cpu-wise although it requires a little more memory, instead of checking for combo items whenever a status effect is turned on/off it only checks when equipping/un-equipping a item (and on login). Special Thanks to GreenBox, Kenpachi, Skotlex and Trojal

git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@16508 54d463be-8e91-2dee-dedb-b68131a5f0ec
shennetsind 13 gadi atpakaļ
vecāks
revīzija
a7a1f81e56
8 mainītis faili ar 330 papildinājumiem un 120 dzēšanām
  1. 10 16
      src/map/battle.c
  2. 3 5
      src/map/clif.c
  3. 88 81
      src/map/itemdb.c
  4. 9 4
      src/map/itemdb.h
  5. 193 7
      src/map/pc.c
  6. 9 0
      src/map/pc.h
  7. 10 1
      src/map/status.c
  8. 8 6
      src/map/unit.c

+ 10 - 16
src/map/battle.c

@@ -3126,34 +3126,28 @@ static struct Damage battle_calc_weapon_attack(struct block_list *src,struct blo
 			return wd;
 	}
 
-	if (sd)
-	{
-		if (!flag.rh && flag.lh) 
-		{	//Move lh damage to the rh
+	if (sd) {
+		if (!flag.rh && flag.lh) {	//Move lh damage to the rh
 			wd.damage = wd.damage2;
 			wd.damage2 = 0;
 			flag.rh=1;
 			flag.lh=0;
-		} else if(flag.rh && flag.lh)
-		{	//Dual-wield
-			if (wd.damage)
-			{
-				if( skill = pc_checkskill(sd,AS_RIGHT) )
+		} else if(flag.rh && flag.lh) {	//Dual-wield
+			if (wd.damage) {
+				if( (skill = pc_checkskill(sd,AS_RIGHT)) )
 					wd.damage = wd.damage * (50 + (skill * 10))/100;
-				else if( skill = pc_checkskill(sd,KO_RIGHT) )
+				else if( (skill = pc_checkskill(sd,KO_RIGHT)) )
 					wd.damage = wd.damage * (70 + (skill * 10))/100;
 				if(wd.damage < 1) wd.damage = 1;
 			}
-			if (wd.damage2)
-			{
-				if( skill = pc_checkskill(sd,AS_LEFT) )
+			if (wd.damage2) {
+				if( (skill = pc_checkskill(sd,AS_LEFT)) )
 					wd.damage2 = wd.damage2 * (30 + (skill * 10))/100;
-				else if( skill = pc_checkskill(sd,KO_LEFT) )
+				else if( (skill = pc_checkskill(sd,KO_LEFT)) )
 					wd.damage2 = wd.damage2 * (50 + (skill * 10))/100;
 				if(wd.damage2 < 1) wd.damage2 = 1;
 			}
-		} else if(sd->status.weapon == W_KATAR && !skill_num)
-		{ //Katars (offhand damage only applies to normal attacks, tested on Aegis 10.2)
+		} else if(sd->status.weapon == W_KATAR && !skill_num) { //Katars (offhand damage only applies to normal attacks, tested on Aegis 10.2)
 			skill = pc_checkskill(sd,TF_DOUBLE);
 			wd.damage2 = wd.damage * (1 + (skill * 2))/100;
 

+ 3 - 5
src/map/clif.c

@@ -9220,8 +9220,7 @@ void clif_parse_LoadEndAck(int fd,struct map_session_data *sd)
 
 		if (sd->sc.option&OPTION_RIDING)
 			clif_status_load(&sd->bl, SI_RIDING, 1);
-
-		if (sd->sc.option&OPTION_WUGRIDER)
+		else if (sd->sc.option&OPTION_WUGRIDER)
 			clif_status_load(&sd->bl, SI_WUGRIDER, 1);
 
 		if(sd->status.manner < 0)
@@ -9241,12 +9240,11 @@ void clif_parse_LoadEndAck(int fd,struct map_session_data *sd)
 		if(merc_is_hom_active(sd->hd))
 			merc_hom_init_timers(sd->hd);
 
-		if (night_flag && map[sd->bl.m].flag.nightenabled)
-		{
+		if (night_flag && map[sd->bl.m].flag.nightenabled) {
 			sd->state.night = 1;
 			clif_status_load(&sd->bl, SI_NIGHT, 1);
 		}
-
+				
 		// Notify everyone that this char logged in [Skotlex].
 		map_foreachpc(clif_friendslist_toggle_sub, sd->status.account_id, sd->status.char_id, 1);
 

+ 88 - 81
src/map/itemdb.c

@@ -761,7 +761,7 @@ int itemdb_combo_split_atoi (char *str, int *val) {
 /**
  * <combo{:combo{:combo:{..}}}>,<{ script }>
  **/
-void itemdb_read_combos(DBMap* item_combo_db) {
+void itemdb_read_combos() {
 	uint32 lines = 0, count = 0;
 	char line[1024];
 	
@@ -821,38 +821,73 @@ void itemdb_read_combos(DBMap* item_combo_db) {
 		} else {
 			int items[MAX_ITEMS_PER_COMBO];
 			int v = 0, retcount = 0;
-			struct item_combo * ic = NULL;
-			char combo_out[2048];
-			int len = 0, slen = 0;
-			bool exists = false;
+			struct item_data * id = NULL;
+			int idx = 0;
 			
 			if((retcount = itemdb_combo_split_atoi(str[0], items)) < 2) {
 				ShowError("itemdb_read_combos: line %d of \"%s\" doesn't have enough items to make for a combo (min:2), skipping.\n", lines, path);
 				continue;
 			}
 			
-			if ((ic = idb_get(item_combo_db, items[0])) != NULL) {
-				slen = strlen(ic->script);
-				exists = true;
-			} else {
-				CREATE(ic, struct item_combo, 1);
+			/* validate */
+			for(v = 0; v < retcount; v++) {
+				if( !itemdb_exists(items[v]) ) {
+					ShowError("itemdb_read_combos: line %d of \"%s\" contains unknown item ID %d, skipping.\n", lines, path,items[v]);
+					break;
+				}
 			}
+			/* failed at some item */
+			if( v < retcount )
+				continue;
+
+			id = itemdb_exists(items[0]);
 			
-			len += sprintf(combo_out + len, "if(isequipped(");
+			idx = id->combos_count;
 			
-			/* we skip the first for its not a required check */
-			for(v = 1; v < retcount; v++) {
-				len += sprintf(combo_out + len, "%d,", items[v]);
+			/* first entry, create */
+			if( id->combos == NULL ) {
+				CREATE(id->combos, struct item_combo*, 1);
+				id->combos_count = 1;
+			} else {
+				RECREATE(id->combos, struct item_combo*, ++id->combos_count);
 			}
 			
-			len += sprintf(combo_out + len - 1, "))%s", str[1]);
-						
-			safestrncpy(ic->script + slen, combo_out, len);
+			CREATE(id->combos[idx],struct item_combo,1);
 			
-			if (!exists) {
-				ic->nameid = items[0];
-				idb_put(item_combo_db, items[0], ic);
+			id->combos[idx]->nameid = aMalloc( retcount * sizeof(unsigned short) );
+			id->combos[idx]->count = retcount;
+			id->combos[idx]->script = parse_script(str[1], path, lines, 0);
+			id->combos[idx]->id = count;
+			id->combos[idx]->isRef = false;
+			/* populate ->nameid field */
+			for( v = 0; v < retcount; v++ ) {
+				id->combos[idx]->nameid[v] = items[v];
 			}
+			
+			/* populate the children to refer to this combo */
+			for( v = 1; v < retcount; v++ ) {
+				struct item_data * it = NULL;
+				int index;
+				
+				it = itemdb_exists(items[v]);
+				
+				index = it->combos_count;
+				
+				if( it->combos == NULL ) {
+					CREATE(it->combos, struct item_combo*, 1);
+					it->combos_count = 1;
+				} else {
+					RECREATE(it->combos, struct item_combo*, ++it->combos_count);
+				}
+				
+				CREATE(it->combos[index],struct item_combo,1);
+				
+				/* we copy previously alloc'd pointers and just set it to reference */
+				memcpy(it->combos[index],id->combos[idx],sizeof(struct item_combo));
+				/* we flag this way to ensure we don't double-dealloc same data */
+				it->combos[index]->isRef = true;
+			}
+			
 		}
 		
 		count++;
@@ -915,8 +950,7 @@ void itemdb_re_split_atoi(char *str, int *atk, int *matk) {
 /*==========================================
  * processes one itemdb entry
  *------------------------------------------*/
-static bool itemdb_parse_dbrow(char** str, const char* source, int line, int scriptopt, char *comboScript)
-{
+static bool itemdb_parse_dbrow(char** str, const char* source, int line, int scriptopt) {
 	/*
 		+----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+-------------+---------------+-----------------+--------------+-------------+------------+------+--------+--------------+----------------+
 		| 00 |      01      |       02      |  03  |     04    |     05     |   06   |   07   |    08   |   09  |   10  |     11     |      12     |       13      |        14       |      15      |      16     |     17     |  18  |   19   |      20      |        21      |
@@ -1013,42 +1047,21 @@ static bool itemdb_parse_dbrow(char** str, const char* source, int line, int scr
 	id->view_id = 0;
 	id->sex = itemdb_gendercheck(id); //Apply gender filtering.
 
-	if (id->script)
-	{
+	if (id->script) {
 		script_free_code(id->script);
 		id->script = NULL;
 	}
-	if (id->equip_script)
-	{
+	if (id->equip_script) {
 		script_free_code(id->equip_script);
 		id->equip_script = NULL;
 	}
-	if (id->unequip_script)
-	{
+	if (id->unequip_script) {
 		script_free_code(id->unequip_script);
 		id->unequip_script = NULL;
 	}
 
-	if (*str[19]) {
-		char *scriptCode = str[19];
-
-		if ( comboScript ) {
-			char *script1 = str[19];
-
-			while (*script1++ != '{');
-
-			scriptCode = (char *)aMalloc(strlen(script1) + strlen(comboScript) + 2); // +2 = {\0
-			sprintf(scriptCode, "{%s%s", comboScript, script1);
-		}
-
-		id->script = parse_script(scriptCode, source, line, scriptopt);
-		
-		if( comboScript )
-			aFree(scriptCode);
-
-	} else if ( comboScript )
-		id->script = parse_script(comboScript, source, line, scriptopt);
-	
+	if (*str[19])
+		id->script = parse_script(str[19], source, line, scriptopt);
 	if (*str[20])
 		id->equip_script = parse_script(str[20], source, line, scriptopt);
 	if (*str[21])
@@ -1067,9 +1080,6 @@ static int itemdb_readdb(void)
 		"item_db2.txt" };
 
 	int fi;
-	DBMap* item_combo_db = idb_alloc(DB_OPT_RELEASE_DATA);
-	
-	itemdb_read_combos(item_combo_db);
 
 	for( fi = 0; fi < ARRAYLENGTH(filename); ++fi ) {
 		uint32 lines = 0, count = 0;
@@ -1090,8 +1100,6 @@ static int itemdb_readdb(void)
 		{
 			char *str[32], *p;
 			int i;
-			struct item_combo *ic = NULL;
-			char *script2 = NULL;
 			lines++;
 			if(line[0] == '/' && line[1] == '/')
 				continue;
@@ -1165,15 +1173,8 @@ static int itemdb_readdb(void)
 				continue;
 			}
 
-			if ((ic = idb_get(item_combo_db, atoi(str[0])))) {
-				script2 = ic->script;
-			}
-			
-			if (!itemdb_parse_dbrow(str, path, lines, 0, script2))
+			if (!itemdb_parse_dbrow(str, path, lines, 0))
 				continue;
-
-			if( script2 != NULL )
-				idb_remove(item_combo_db,atoi(str[0]));
 			
 			count++;
 		}
@@ -1183,22 +1184,6 @@ static int itemdb_readdb(void)
 		ShowStatus("Done reading '"CL_WHITE"%lu"CL_RESET"' entries in '"CL_WHITE"%s"CL_RESET"'.\n", count, filename[fi]);
 	}
 
-	if( db_size(item_combo_db) ) {
-		DBIterator * iter = db_iterator(item_combo_db);
-		struct item_combo * ic = NULL;
-		int icount = 1;
-		/* non-processed entries */
-		ShowWarning("item_combo_db: There are %d unused entries in the file (combo(s) with non-available item IDs)\n",db_size(item_combo_db));
-		
-		for( ic = dbi_first(iter); dbi_exists(iter); ic = dbi_next(iter) ) {
-			ShowWarning("item_combo_db(%d): (ID:%d) \"%s\" combo unused\n",icount++,ic->nameid,ic->script);
-		}
-
-		dbi_destroy(iter);
-	}
-	
-	db_destroy(item_combo_db);
-
 	return 0;
 }
 
@@ -1238,7 +1223,7 @@ static int itemdb_read_sqldb(void)
 				if( str[i] == NULL ) str[i] = dummy; // get rid of NULL columns
 			}
 
-			if (!itemdb_parse_dbrow(str, item_db_name[fi], lines, SCRIPT_IGNORE_EXTERNAL_BRACKETS, NULL))
+			if (!itemdb_parse_dbrow(str, item_db_name[fi], lines, SCRIPT_IGNORE_EXTERNAL_BRACKETS))
 				continue;
 			++count;
 		}
@@ -1261,7 +1246,8 @@ static void itemdb_read(void) {
 		itemdb_read_sqldb();
 	else
 		itemdb_readdb();
-
+	
+	itemdb_read_combos();
 	itemdb_read_itemgroup();
 	sv_readdb(db_path, "item_avail.txt",         ',', 2, 2, -1, &itemdb_read_itemavail);
 	sv_readdb(db_path, DBPATH"item_noequip.txt", ',', 2, 2, -1, &itemdb_read_noequip);
@@ -1287,6 +1273,17 @@ static void destroy_item_data(struct item_data* self, int free_self)
 		script_free_code(self->equip_script);
 	if( self->unequip_script )
 		script_free_code(self->unequip_script);
+	if( self->combos_count ) {
+		int i;
+		for( i = 0; i < self->combos_count; i++ ) {
+			if( !self->combos[i]->isRef ) {
+				aFree(self->combos[i]->nameid);
+				script_free_code(self->combos[i]->script);
+			}
+			aFree(self->combos[i]);
+		}
+		aFree(self->combos);
+	}
 #if defined(DEBUG)
 	// trash item
 	memset(self, 0xDD, sizeof(struct item_data));
@@ -1358,10 +1355,20 @@ void itemdb_reload(void)
 
 	// readjust itemdb pointer cache for each player
 	iter = mapit_geteachpc();
-	for( sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter); sd = (struct map_session_data*)mapit_next(iter) )
-	{
+	for( sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter); sd = (struct map_session_data*)mapit_next(iter) ) {
 		memset(sd->item_delay, 0, sizeof(sd->item_delay));  // reset item delays
 		pc_setinventorydata(sd);
+		/* clear combo bonuses */
+		if( sd->combos.count ) {
+			aFree(sd->combos.bonus);
+			aFree(sd->combos.id);
+			sd->combos.bonus = NULL;
+			sd->combos.id = NULL;
+			sd->combos.count = 0;
+			if( pc_load_combo(sd) > 0 )
+				status_calc_pc(sd,0);
+		}
+
 	}
 	mapit_free(iter);
 }

+ 9 - 4
src/map/itemdb.h

@@ -129,8 +129,7 @@ struct item_data {
 		unsigned autoequip: 1;
 		unsigned buyingstore : 1;
 	} flag;
-	struct
-	{// item stacking limitation
+	struct {// item stacking limitation
 		unsigned short amount;
 		unsigned int inventory:1;
 		unsigned int cart:1;
@@ -138,6 +137,9 @@ struct item_data {
 		unsigned int guildstorage:1;
 	} stack;
 	short gm_lv_trade_override;	//GM-level to override trade_restriction
+	/* bugreport:309 */
+	struct item_combo **combos;
+	unsigned char combos_count;
 };
 
 struct item_group {
@@ -146,8 +148,11 @@ struct item_group {
 };
 
 struct item_combo {
-	char script[2048]; /* combo script */
-	short nameid;/* id of the first */
+	struct script_code *script;
+	unsigned short *nameid;/* nameid array */
+	unsigned char count;
+	unsigned short id;/* id of this combo */
+	bool isRef;/* whether this struct is a reference or the master */
 };
 
 struct item_data* itemdb_searchname(const char *name);

+ 193 - 7
src/map/pc.c

@@ -1220,6 +1220,8 @@ int pc_reg_received(struct map_session_data *sd)
 	map_delnickdb(sd->status.char_id, sd->status.name);
 	if (!chrif_auth_finished(sd))
 		ShowError("pc_reg_received: Failed to properly remove player %d:%d from logging db!\n", sd->status.account_id, sd->status.char_id);
+	
+	pc_load_combo(sd);
 
 	status_calc_pc(sd,1);
 	chrif_scdata_request(sd->status.account_id, sd->status.char_id);
@@ -7859,12 +7861,158 @@ int pc_cleareventtimer(struct map_session_data *sd)
 		}
 	return 0;
 }
+/* called when a item with combo is worn */
+int pc_checkcombo(struct map_session_data *sd, struct item_data *data ) {
+	int i, j, k, z;
+	int index, idx, success = 0;
 
-//
-// ? ”õ•¨
-//
+	for( i = 0; i < data->combos_count; i++ ) {
+		
+		/* ensure this isn't a duplicate combo */
+		if( sd->combos.bonus != NULL ) {
+			int x;
+			ARR_FIND( 0, sd->combos.count, x, sd->combos.id[x] == data->combos[i]->id );
+
+			/* found a match, skip this combo */
+			if( x < sd->combos.count )
+				continue;
+		}
+		
+		for( j = 0; j < data->combos[i]->count; j++ ) {
+			int id = data->combos[i]->nameid[j];
+			bool found = false;
+			
+			for( k = 0; k < EQI_MAX; k++ ) {
+				index = sd->equip_index[k];
+				if( index < 0 ) continue;
+				if( k == EQI_HAND_R   &&  sd->equip_index[EQI_HAND_L] == index ) continue;
+				if( k == EQI_HEAD_MID &&  sd->equip_index[EQI_HEAD_LOW] == index ) continue;
+				if( k == EQI_HEAD_TOP && (sd->equip_index[EQI_HEAD_MID] == index || sd->equip_index[EQI_HEAD_LOW] == index) ) continue;
+				
+				if(!sd->inventory_data[index])
+					continue;
+				
+				if ( itemdb_type(id) != IT_CARD ) {
+					if ( sd->inventory_data[index]->nameid != id )
+						continue;
+
+					found = true;
+					break;
+				} else { //Cards
+					if ( sd->inventory_data[index]->slot == 0 || itemdb_isspecial(sd->status.inventory[index].card[0]) )
+						continue;
+					
+					for (z = 0; z < sd->inventory_data[index]->slot; z++) {
+
+						if (sd->status.inventory[index].card[z] != id)
+							continue;
+												
+						// We have found a match
+						found = true;
+						break;
+					}
+				}
+
+			}
+
+			if( !found )
+				break;/* we haven't found all the ids for this combo, so we can return */
+		}
+		
+		/* means we broke out of the count loop w/o finding all ids, we can move to the next combo */
+		if( j < data->combos[i]->count )
+			continue;
+		
+		/* we got here, means all items in the combo are matching */
+		
+		idx = sd->combos.count;
+		
+		if( sd->combos.bonus == NULL ) {
+			CREATE(sd->combos.bonus, struct script_code *, 1);
+			CREATE(sd->combos.id, unsigned short, 1);
+			sd->combos.count = 1;
+		} else {
+			RECREATE(sd->combos.bonus, struct script_code *, ++sd->combos.count);
+			RECREATE(sd->combos.id, unsigned short, sd->combos.count);
+		}
+				
+		/* we simply copy the pointer */
+		sd->combos.bonus[idx] = data->combos[i]->script;
+		/* save this combo's id */
+		sd->combos.id[idx] = data->combos[i]->id;
+		
+		success++;
+	}
+	return success;
+}
+
+/* called when a item with combo is removed */
+void pc_removecombo(struct map_session_data *sd, struct item_data *data ) {
+	int i;
+	
+	if( sd->combos.bonus == NULL )
+		return;/* nothing to do here, player has no combos */
+	for( i = 0; i < data->combos_count; i++ ) {
+		/* check if this combo exists in this user */
+		int x = 0, cursor = 0, j;
+		ARR_FIND( 0, sd->combos.count, x, sd->combos.id[x] == data->combos[i]->id );
+		/* no match, skip this combo */
+		if( !(x < sd->combos.count) )
+			continue;
+
+		sd->combos.bonus[x] = NULL;
+		sd->combos.id[x] = 0;
+		
+		for( j = 0, cursor = 0; j < sd->combos.count; j++ ) {
+			if( sd->combos.bonus[j] == NULL )
+				continue;
+			
+			if( cursor != j ) {
+				sd->combos.bonus[cursor] = sd->combos.bonus[j];
+				sd->combos.id[cursor]    = sd->combos.id[j];
+			}
+			
+			cursor++;
+		}
+
+		/* it's empty, we can clear all the memory */
+		if( (sd->combos.count = cursor) == 0 ) {
+			aFree(sd->combos.bonus);
+			aFree(sd->combos.id);
+			sd->combos.bonus = NULL;
+			sd->combos.id = NULL;
+			return; /* we also can return at this point for we have no more combos to check */
+		}
+
+	}
+	
+}
+int pc_load_combo(struct map_session_data *sd) {
+	int i, ret = 0;
+	for( i = 0; i < EQI_MAX; i++ ) {
+		struct item_data *id = NULL;
+		int idx = sd->equip_index[i];
+		if( sd->equip_index[i] < 0 || !(id = sd->inventory_data[idx] ) )
+			continue;
+		if( id->combos_count )
+			ret += pc_checkcombo(sd,id);
+		if(!itemdb_isspecial(sd->status.inventory[idx].card[0])) {
+			struct item_data *data;
+			int j;
+			for( j = 0; j < id->slot; j++ ) {
+				if (!sd->status.inventory[idx].card[j])
+					continue;
+				if ( ( data = itemdb_exists(sd->status.inventory[idx].card[j]) ) != NULL ) {
+					if( data->combos_count )
+						ret += pc_checkcombo(sd,data);
+				}
+			}
+		}
+	}
+	return ret;
+}
 /*==========================================
- * ƒAƒCƒeƒ€‚ð?”õ‚·‚é
+ * Attempt to equip item in inventory index 'n' in the EQP_ 'req_pos'
  *------------------------------------------*/
 int pc_equipitem(struct map_session_data *sd,int n,int req_pos)
 {
@@ -8019,15 +8167,32 @@ int pc_equipitem(struct map_session_data *sd,int n,int req_pos)
 	}
 
 	pc_checkallowskill(sd); //Check if status changes should be halted.
-
-
+	
+	/* check for combos (MUST be before status_calc_pc) */
+	if ( id ) {
+		struct item_data *data;
+		if( id->combos_count )
+			pc_checkcombo(sd,id);
+		if(itemdb_isspecial(sd->status.inventory[n].card[0]))
+			; //No cards
+		else {
+			for( i = 0; i < id->slot; i++ ) {
+				if (!sd->status.inventory[n].card[i])
+					continue;
+				if ( ( data = itemdb_exists(sd->status.inventory[n].card[i]) ) != NULL ) {
+					if( data->combos_count )
+						pc_checkcombo(sd,data);
+				}
+			}
+		}
+	}
+		
 	status_calc_pc(sd,0);
 	if (flag) //Update skill data
 		clif_skillinfoblock(sd);
 
 	//OnEquip script [Skotlex]
 	if (id) {
-		int i;
 		struct item_data *data;
 		if (id->equip_script)
 			run_script(id->equip_script,0,sd->bl.id,fake_nd->bl.id);
@@ -8150,6 +8315,27 @@ int pc_unequipitem(struct map_session_data *sd,int n,int flag)
 	sd->status.inventory[n].equip=0;
 
 	if(flag&1) {
+
+		/* check for combos (MUST be before status_calc_pc) */
+		if ( sd->inventory_data[n] ) {
+			struct item_data *data;
+
+			if( sd->inventory_data[n]->combos_count )
+				pc_removecombo(sd,sd->inventory_data[n]);
+			if(itemdb_isspecial(sd->status.inventory[n].card[0]))
+				; //No cards
+			else {
+				for( i = 0; i < sd->inventory_data[n]->slot; i++ ) {
+					if (!sd->status.inventory[n].card[i])
+						continue;
+					if ( ( data = itemdb_exists(sd->status.inventory[n].card[i]) ) != NULL ) {
+						if( data->combos_count )
+							pc_removecombo(sd,data);
+					}
+				}
+			}
+		}
+		
 		pc_checkallowskill(sd);
 		status_calc_pc(sd,0);
 	}

+ 9 - 0
src/map/pc.h

@@ -454,6 +454,12 @@ struct map_session_data {
 	unsigned int npc_idle_tick;
 #endif
 
+	struct {
+		struct script_code **bonus;/* the script */
+		unsigned short *id;/* array of combo ids */
+		unsigned char count;
+	} combos;
+	
 	/**
 	 * Guarantees your friend request is legit (for bugreport:4629)
 	 **/
@@ -909,4 +915,7 @@ void pc_overheat(struct map_session_data *sd, int val);
 int pc_banding(struct map_session_data *sd, short skill_lv);
 
 void pc_itemcd_do(struct map_session_data *sd, bool load);
+
+int pc_load_combo(struct map_session_data *sd);
+
 #endif /* _PC_H_ */

+ 10 - 1
src/map/status.c

@@ -2507,7 +2507,16 @@ int status_calc_pc_(struct map_session_data* sd, bool first)
 				return 1;
 		}
 	}
-
+	
+	/* we've got combos to process */
+	if( sd->combos.count ) {
+		for( i = 0; i < sd->combos.count; i++ ) {
+			run_script(sd->combos.bonus[i],0,sd->bl.id,0);
+			if (!calculating) //Abort, run_script retriggered this.
+				return 1;
+		}
+	}
+	
 	//Store equipment script bonuses 
 	memcpy(sd->param_equip,sd->param_bonus,sizeof(sd->param_equip));
 	memset(sd->param_bonus, 0, sizeof(sd->param_bonus));

+ 8 - 6
src/map/unit.c

@@ -2254,14 +2254,12 @@ int unit_free(struct block_list *bl, clr_type clrtype)
 			pc_inventory_rental_clear(sd);
 			pc_delspiritball(sd,sd->spiritball,1);
 
-			if( sd->reg )
-			{	//Double logout already freed pointer fix... [Skotlex]
+			if( sd->reg ) {	//Double logout already freed pointer fix... [Skotlex]
 				aFree(sd->reg);
 				sd->reg = NULL;
 				sd->reg_num = 0;
 			}
-			if( sd->regstr )
-			{
+			if( sd->regstr ) {
 				int i;
 				for( i = 0; i < sd->regstr_num; ++i )
 					if( sd->regstr[i].data )
@@ -2270,12 +2268,16 @@ int unit_free(struct block_list *bl, clr_type clrtype)
 				sd->regstr = NULL;
 				sd->regstr_num = 0;
 			}
-			if( sd->st && sd->st->state != RUN )
-			{// free attached scripts that are waiting
+			if( sd->st && sd->st->state != RUN ) {// free attached scripts that are waiting
 				script_free_state(sd->st);
 				sd->st = NULL;
 				sd->npc_id = 0;
 			}
+			if( sd->combos.count ) {
+				aFree(sd->combos.bonus);
+				aFree(sd->combos.id);
+				sd->combos.count = 0;
+			}
 			break;
 		}
 		case BL_PET: