Pārlūkot izejas kodu

Add cooldown saving for homunculus and mercenary (#8827)

* Fixes #8518.
* Allow skill cooldowns for homunculus and mercenary to be saved when the player logs off.

---------

Co-authored-by: Lemongrass3110 <lemongrass@kstp.at>
Aleos 5 mēneši atpakaļ
vecāks
revīzija
c381e3a1eb

+ 2 - 0
conf/inter_athena.conf

@@ -123,8 +123,10 @@ auction_db: auction
 quest_db: quest
 homunculus_db: homunculus
 skill_homunculus_db: skill_homunculus
+skillcooldown_homunculus_db: skillcooldown_homunculus
 mercenary_db: mercenary
 mercenary_owner_db: mercenary_owner
+skillcooldown_mercenary_db: skillcooldown_mercenary
 elemental_db: elemental
 skillcooldown_db: skillcooldown
 bonus_script_db: bonus_script

+ 22 - 0
sql-files/main.sql

@@ -1055,6 +1055,28 @@ CREATE TABLE IF NOT EXISTS `skill_homunculus` (
   PRIMARY KEY  (`homun_id`,`id`)
 ) ENGINE=MyISAM;
 
+--
+-- Table structure for table `skillcooldown_homunculus`
+--
+
+CREATE TABLE IF NOT EXISTS `skillcooldown_homunculus` (
+  `homun_id` int(11) NOT NULL,
+  `skill` smallint(11) unsigned NOT NULL DEFAULT '0',
+  `tick` bigint(20) NOT NULL,
+  PRIMARY KEY (`homun_id`)
+) ENGINE=MyISAM;
+
+--
+-- Table structure for table `skillcooldown_mercenary`
+--
+
+CREATE TABLE IF NOT EXISTS `skillcooldown_mercenary` (
+  `mer_id` int(11) NOT NULL,
+  `skill` smallint(11) unsigned NOT NULL DEFAULT '0',
+  `tick` bigint(20) NOT NULL,
+  PRIMARY KEY (`mer_id`)
+) ENGINE=MyISAM;
+
 --
 -- Table structure for table `storage`
 --

+ 13 - 0
sql-files/upgrades/upgrade_20241216.sql

@@ -0,0 +1,13 @@
+CREATE TABLE IF NOT EXISTS `skillcooldown_homunculus` (
+  `homun_id` int(11) NOT NULL,
+  `skill` smallint(11) unsigned NOT NULL DEFAULT '0',
+  `tick` bigint(20) NOT NULL,
+  PRIMARY KEY (`homun_id`)
+) ENGINE=MyISAM;
+
+CREATE TABLE IF NOT EXISTS `skillcooldown_mercenary` (
+  `mer_id` int(11) NOT NULL,
+  `skill` smallint(11) unsigned NOT NULL DEFAULT '0',
+  `tick` bigint(20) NOT NULL,
+  PRIMARY KEY (`mer_id`)
+) ENGINE=MyISAM;

+ 19 - 2
src/char/char.cpp

@@ -2293,8 +2293,9 @@ bool char_checkdb(void){
                 schema_config.guild_expulsion_db, schema_config.guild_member_db, 
                 schema_config.guild_skill_db, schema_config.guild_position_db, schema_config.guild_storage_db,
 		schema_config.party_db, schema_config.pet_db, schema_config.friend_db, schema_config.mail_db, 
-                schema_config.auction_db, schema_config.quest_db, schema_config.homunculus_db, schema_config.skill_homunculus_db,
-                schema_config.mercenary_db, schema_config.mercenary_owner_db,
+                schema_config.auction_db, schema_config.quest_db,
+                schema_config.homunculus_db, schema_config.skill_homunculus_db, schema_config.skillcooldown_homunculus_db,
+                schema_config.mercenary_db, schema_config.mercenary_owner_db, schema_config.skillcooldown_mercenary_db,
 		schema_config.elemental_db, schema_config.skillcooldown_db, schema_config.bonus_script_db,
 		schema_config.clan_table, schema_config.clan_alliance_table, schema_config.mail_attachment_db, schema_config.achievement_table
 	};
@@ -2472,6 +2473,11 @@ bool char_checkdb(void){
 		Sql_ShowDebug(sql_handle);
 		return false;
 	}
+	//checking skillcooldown_homunculus_db
+	if (SQL_ERROR == Sql_Query(sql_handle, "SELECT  `homun_id`,`skill`,`tick` FROM `%s` LIMIT 1;", schema_config.skillcooldown_homunculus_db)) {
+		Sql_ShowDebug(sql_handle);
+		return false;
+	}
 	//checking mercenary_db
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT  `mer_id`,`char_id`,`class`,`hp`,`sp`,`kill_counter`,`life_time` FROM `%s` LIMIT 1;", schema_config.mercenary_db) ){
 		Sql_ShowDebug(sql_handle);
@@ -2484,6 +2490,11 @@ bool char_checkdb(void){
 		Sql_ShowDebug(sql_handle);
 		return false;
 	}
+	//checking skillcooldown_mercenary_db
+	if (SQL_ERROR == Sql_Query(sql_handle, "SELECT  `mer_id`,`skill`,`tick` FROM `%s` LIMIT 1;", schema_config.skillcooldown_mercenary_db)) {
+		Sql_ShowDebug(sql_handle);
+		return false;
+	}
 	//checking elemental_db
 	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT  `ele_id`,`char_id`,`class`,`mode`,`hp`,`sp`,`max_hp`,`max_sp`,"
 		"`atk1`,`atk2`,`matk`,`aspd`,`def`,`mdef`,`flee`,`hit`,`life_time` "
@@ -2614,10 +2625,14 @@ void char_sql_config_read(const char* cfgName) {
 			safestrncpy(schema_config.homunculus_db,w2,sizeof(schema_config.homunculus_db));
 		else if(!strcmpi(w1,"skill_homunculus_db"))
 			safestrncpy(schema_config.skill_homunculus_db,w2,sizeof(schema_config.skill_homunculus_db));
+		else if (!strcmpi(w1, "skillcooldown_homunculus_db"))
+			safestrncpy(schema_config.skillcooldown_homunculus_db, w2, sizeof(schema_config.skillcooldown_homunculus_db));
 		else if(!strcmpi(w1,"mercenary_db"))
 			safestrncpy(schema_config.mercenary_db,w2,sizeof(schema_config.mercenary_db));
 		else if(!strcmpi(w1,"mercenary_owner_db"))
 			safestrncpy(schema_config.mercenary_owner_db,w2,sizeof(schema_config.mercenary_owner_db));
+		else if (!strcmpi(w1, "skillcooldown_mercenary_db"))
+			safestrncpy(schema_config.skillcooldown_mercenary_db, w2, sizeof(schema_config.skillcooldown_mercenary_db));
 		else if(!strcmpi(w1,"elemental_db"))
 			safestrncpy(schema_config.elemental_db,w2,sizeof(schema_config.elemental_db));
 		else if(!strcmpi(w1,"skillcooldown_db"))
@@ -2678,8 +2693,10 @@ void char_set_default_sql(){
 	safestrncpy(schema_config.quest_db,"quest",sizeof(schema_config.quest_db));
 	safestrncpy(schema_config.homunculus_db,"homunculus",sizeof(schema_config.homunculus_db));
 	safestrncpy(schema_config.skill_homunculus_db,"skill_homunculus",sizeof(schema_config.skill_homunculus_db));
+	safestrncpy(schema_config.skillcooldown_homunculus_db,"skillcooldown_homunculus",sizeof(schema_config.skillcooldown_homunculus_db));
 	safestrncpy(schema_config.mercenary_db,"mercenary",sizeof(schema_config.mercenary_db));
 	safestrncpy(schema_config.mercenary_owner_db,"mercenary_owner",sizeof(schema_config.mercenary_owner_db));
+	safestrncpy(schema_config.skillcooldown_mercenary_db, "skillcooldown_mercenary", sizeof(schema_config.skillcooldown_mercenary_db));
 	safestrncpy(schema_config.skillcooldown_db,"skillcooldown",sizeof(schema_config.skillcooldown_db));
 	safestrncpy(schema_config.bonus_script_db,"bonus_script",sizeof(schema_config.bonus_script_db));
 	safestrncpy(schema_config.char_reg_num_table,"char_reg_num",sizeof(schema_config.char_reg_num_table));

+ 2 - 0
src/char/char.hpp

@@ -93,8 +93,10 @@ struct Schema_Config {
 	char quest_db[DB_NAME_LEN];
 	char homunculus_db[DB_NAME_LEN];
 	char skill_homunculus_db[DB_NAME_LEN];
+	char skillcooldown_homunculus_db[DB_NAME_LEN];
 	char mercenary_db[DB_NAME_LEN];
 	char mercenary_owner_db[DB_NAME_LEN];
+	char skillcooldown_mercenary_db[DB_NAME_LEN];
 	char elemental_db[DB_NAME_LEN];
 	char bonus_script_db[DB_NAME_LEN];
 	char acc_reg_num_table[DB_NAME_LEN];

+ 6 - 6
src/char/char_mapif.cpp

@@ -517,7 +517,7 @@ int32 chmapif_parse_req_saveskillcooldown(int32 fd){
 		count = RFIFOW(fd,12);
 		if( count > 0 )
 		{
-			struct skill_cooldown_data data;
+			s_skill_cooldown_data data;
 			StringBuf buf;
 			int32 i;
 
@@ -525,7 +525,7 @@ int32 chmapif_parse_req_saveskillcooldown(int32 fd){
 			StringBuf_Printf(&buf, "INSERT INTO `%s` (`account_id`, `char_id`, `skill`, `tick`) VALUES ", schema_config.skillcooldown_db);
 			for( i = 0; i < count; ++i )
 			{
-				memcpy(&data,RFIFOP(fd,14+i*sizeof(struct skill_cooldown_data)),sizeof(struct skill_cooldown_data));
+				memcpy(&data,RFIFOP(fd,14+i*sizeof(s_skill_cooldown_data)),sizeof(s_skill_cooldown_data));
 				if( i > 0 )
 					StringBuf_AppendStr(&buf, ", ");
 				StringBuf_Printf(&buf, "('%d','%d','%d','%" PRtf "')", aid, cid, data.skill_id, data.tick);
@@ -558,9 +558,9 @@ int32 chmapif_parse_req_skillcooldown(int32 fd){
 		{
 			int32 count;
 			char* data;
-			struct skill_cooldown_data scd;
+			s_skill_cooldown_data scd;
 
-			WFIFOHEAD(fd,14 + MAX_SKILLCOOLDOWN * sizeof(struct skill_cooldown_data));
+			WFIFOHEAD(fd,14 + MAX_SKILLCOOLDOWN * sizeof(s_skill_cooldown_data));
 			WFIFOW(fd,0) = 0x2b0b;
 			WFIFOL(fd,4) = aid;
 			WFIFOL(fd,8) = cid;
@@ -568,13 +568,13 @@ int32 chmapif_parse_req_skillcooldown(int32 fd){
 			{
 				Sql_GetData(sql_handle, 0, &data, nullptr); scd.skill_id = atoi(data);
 				Sql_GetData(sql_handle, 1, &data, nullptr); scd.tick = strtoll( data, nullptr, 10 );
-				memcpy(WFIFOP(fd,14+count*sizeof(struct skill_cooldown_data)), &scd, sizeof(struct skill_cooldown_data));
+				memcpy(WFIFOP(fd,14+count*sizeof(s_skill_cooldown_data)), &scd, sizeof(s_skill_cooldown_data));
 			}
 			if( count >= MAX_SKILLCOOLDOWN )
 				ShowWarning("Too many skillcooldowns for %d:%d, some of them were not loaded.\n", aid, cid);
 			if( count > 0 )
 			{
-				WFIFOW( fd, 2 ) = static_cast<int16>( 14 + count * sizeof( struct skill_cooldown_data ) );
+				WFIFOW( fd, 2 ) = static_cast<int16>( 14 + count * sizeof( s_skill_cooldown_data ) );
 				WFIFOW(fd,12) = count;
 				WFIFOSET(fd,WFIFOW(fd,2));
 				//Clear the data once loaded.

+ 74 - 15
src/char/int_homun.cpp

@@ -85,7 +85,6 @@ void mapif_homunculus_renamed(int32 fd, uint32 account_id, uint32 char_id, unsig
 
 bool mapif_homunculus_save(struct s_homunculus* hd)
 {
-	bool flag = true;
 	char esc_name[NAME_LENGTH*2+1];
 
 	Sql_EscapeStringLen(sql_handle, esc_name, hd->name, strnlen(hd->name, NAME_LENGTH));
@@ -99,7 +98,7 @@ bool mapif_homunculus_save(struct s_homunculus* hd)
 			hd->hp, hd->max_hp, hd->sp, hd->max_sp, hd->skillpts, hd->rename_flag, hd->vaporize, hd->autofeed) )
 		{
 			Sql_ShowDebug(sql_handle);
-			flag = false;
+			return false;
 		}
 		else
 		{
@@ -113,32 +112,55 @@ bool mapif_homunculus_save(struct s_homunculus* hd)
 			hd->hp, hd->max_hp, hd->sp, hd->max_sp, hd->skillpts, hd->rename_flag, hd->vaporize, hd->autofeed, hd->hom_id) )
 		{
 			Sql_ShowDebug(sql_handle);
-			flag = false;
+			return false;
 		}
 		else
 		{
 			SqlStmt stmt{ *sql_handle };
-			int32 i;
 
-			if( SQL_ERROR == stmt.Prepare("REPLACE INTO `%s` (`homun_id`, `id`, `lv`) VALUES (%d, ?, ?)", schema_config.skill_homunculus_db, hd->hom_id) )
+			// Save skills
+			if (SQL_ERROR == stmt.Prepare("REPLACE INTO `%s` (`homun_id`, `id`, `lv`) VALUES (%d, ?, ?)", schema_config.skill_homunculus_db, hd->hom_id)) {
 				SqlStmt_ShowDebug(stmt);
-			for( i = 0; i < MAX_HOMUNSKILL; ++i )
-			{
-				if( hd->hskill[i].id > 0 && hd->hskill[i].lv != 0 )
-				{
-					stmt.BindParam(0, SQLDT_USHORT, &hd->hskill[i].id, 0);
-					stmt.BindParam(1, SQLDT_USHORT, &hd->hskill[i].lv, 0);
-					if( SQL_ERROR == stmt.Execute() ){
+				return false;
+			}
+
+			for (uint16 i = 0; i < MAX_HOMUNSKILL; ++i) {
+				if (hd->hskill[i].id > 0 && hd->hskill[i].lv != 0) {
+					if (SQL_ERROR == stmt.BindParam(0, SQLDT_USHORT, &hd->hskill[i].id, 0)
+						|| SQL_ERROR == stmt.BindParam(1, SQLDT_USHORT, &hd->hskill[i].lv, 0)
+						|| SQL_ERROR == stmt.Execute()) {
 						SqlStmt_ShowDebug(stmt);
-						flag = false;
-						break;
+						return false;
 					}
 				}
 			}
+
+			// Save skill cooldowns
+			if (SQL_ERROR == stmt.Prepare("INSERT INTO `%s` (`homun_id`, `skill`, `tick`) VALUES (%d, ?, ?)", schema_config.skillcooldown_homunculus_db, hd->hom_id)) {
+				SqlStmt_ShowDebug(stmt);
+				return false;
+			}
+
+			for (uint16 i = 0; i < MAX_SKILLCOOLDOWN; ++i) {
+				if (hd->scd[i].skill_id == 0) {
+					continue;
+				}
+
+				if (hd->scd[i].tick == 0) {
+					continue;
+				}
+
+				if (SQL_ERROR == stmt.BindParam(0, SQLDT_USHORT, &hd->scd[i].skill_id, 0)
+					|| SQL_ERROR == stmt.BindParam(1, SQLDT_LONGLONG, &hd->scd[i].tick, 0)
+					|| SQL_ERROR == stmt.Execute()) {
+					SqlStmt_ShowDebug(stmt);
+					return false;
+				}
+			}
 		}
 	}
 
-	return flag;
+	return true;
 }
 
 
@@ -221,6 +243,42 @@ bool mapif_homunculus_load(int32 homun_id, struct s_homunculus* hd)
 	}
 	Sql_FreeResult(sql_handle);
 
+	// Load Homunuclus Skill Cooldown
+	if (SQL_ERROR == Sql_Query(sql_handle, "SELECT `skill`,`tick` FROM `%s` WHERE `homun_id`=%d", schema_config.skillcooldown_homunculus_db, homun_id)) {
+		Sql_ShowDebug(sql_handle);
+		return false;
+	}
+
+	uint16 count = 0;
+
+	while (SQL_SUCCESS == Sql_NextRow(sql_handle)) {
+		if (count == MAX_SKILLCOOLDOWN) {
+			ShowWarning("Too many skillcooldowns for homunculus %d, skipping.\n", homun_id);
+			break;
+		}
+
+		// Skill
+		Sql_GetData(sql_handle, 0, &data, nullptr);
+		uint16 skill_id = static_cast<uint16>(strtoul(data, nullptr, 10));
+
+		if (skill_id < HM_SKILLBASE || skill_id >= HM_SKILLBASE + MAX_HOMUNSKILL)
+			continue; // invalid skill ID
+		hd->scd[count].skill_id = skill_id;
+
+		// Tick
+		Sql_GetData(sql_handle, 1, &data, nullptr);
+		hd->scd[count].tick = strtoll(data, nullptr, 10);
+
+		count++;
+	}
+	Sql_FreeResult(sql_handle);
+
+	// Clear the data once loaded.
+	if (count > 0) {
+		if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `homun_id`='%d'", schema_config.skillcooldown_homunculus_db, homun_id))
+			Sql_ShowDebug(sql_handle);
+	}
+
 	if( charserv_config.save_log )
 		ShowInfo("Homunculus loaded (ID: %d - %s / Class: %d / CID: %d).\n", hd->hom_id, hd->name, hd->class_, hd->char_id);
 
@@ -231,6 +289,7 @@ bool mapif_homunculus_delete(int32 homun_id)
 {
 	if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `homun_id` = '%u'", schema_config.homunculus_db, homun_id)
 	||	SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `homun_id` = '%u'", schema_config.skill_homunculus_db, homun_id)
+	||	SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `homun_id` = '%u'", schema_config.skillcooldown_homunculus_db, homun_id)
 	) {
 		Sql_ShowDebug(sql_handle);
 		return false;

+ 67 - 5
src/char/int_mercenary.cpp

@@ -57,6 +57,9 @@ bool mercenary_owner_tosql(uint32 char_id, struct mmo_charstatus *status)
 
 bool mercenary_owner_delete(uint32 char_id)
 {
+	if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `mer_id` IN ( SELECT `merc_id` FROM `%s` WHERE `char_id` = '%d' )", schema_config.skillcooldown_mercenary_db, schema_config.mercenary_owner_db, char_id))
+		Sql_ShowDebug(sql_handle);
+
 	if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d'", schema_config.mercenary_owner_db, char_id) )
 		Sql_ShowDebug(sql_handle);
 
@@ -68,8 +71,6 @@ bool mercenary_owner_delete(uint32 char_id)
 
 bool mapif_mercenary_save(struct s_mercenary* merc)
 {
-	bool flag = true;
-
 	if( merc->mercenary_id == 0 )
 	{ // Create new DB entry
 		if( SQL_ERROR == Sql_Query(sql_handle,
@@ -77,7 +78,7 @@ bool mapif_mercenary_save(struct s_mercenary* merc)
 			schema_config.mercenary_db, merc->char_id, merc->class_, merc->hp, merc->sp, merc->kill_count, merc->life_time) )
 		{
 			Sql_ShowDebug(sql_handle);
-			flag = false;
+			return false;
 		}
 		else
 			merc->mercenary_id = (int32)Sql_LastInsertId(sql_handle);
@@ -87,10 +88,35 @@ bool mapif_mercenary_save(struct s_mercenary* merc)
 		schema_config.mercenary_db, merc->char_id, merc->class_, merc->hp, merc->sp, merc->kill_count, merc->life_time, merc->mercenary_id) )
 	{ // Update DB entry
 		Sql_ShowDebug(sql_handle);
-		flag = false;
+		return false;
+	}
+
+	// Save skill cooldowns
+	SqlStmt stmt{ *sql_handle };
+
+	if (SQL_ERROR == stmt.Prepare("INSERT INTO `%s` (`mer_id`, `skill`, `tick`) VALUES (%d, ?, ?)", schema_config.skillcooldown_mercenary_db, merc->mercenary_id)) {
+		SqlStmt_ShowDebug(stmt);
+		return false;
 	}
 
-	return flag;
+	for (uint16 i = 0; i < MAX_SKILLCOOLDOWN; ++i) {
+		if (merc->scd[i].skill_id == 0) {
+			continue;
+		}
+
+		if (merc->scd[i].tick == 0) {
+			continue;
+		}
+
+		if (SQL_ERROR == stmt.BindParam(0, SQLDT_USHORT, &merc->scd[i].skill_id, 0)
+			|| SQL_ERROR == stmt.BindParam(1, SQLDT_LONGLONG, &merc->scd[i].tick, 0)
+			|| SQL_ERROR == stmt.Execute()) {
+			SqlStmt_ShowDebug(stmt);
+			return false;
+		}
+	}
+
+	return true;
 }
 
 bool mapif_mercenary_load(int32 merc_id, uint32 char_id, struct s_mercenary *merc)
@@ -122,6 +148,42 @@ bool mapif_mercenary_load(int32 merc_id, uint32 char_id, struct s_mercenary *mer
 	if( charserv_config.save_log )
 		ShowInfo("Mercenary loaded (ID: %d / Class: %d / CID: %d).\n", merc->mercenary_id, merc->class_, merc->char_id);
 
+	// Load Mercenary Skill Cooldown
+	if (SQL_ERROR == Sql_Query(sql_handle, "SELECT `skill`,`tick` FROM `%s` WHERE `mer_id`=%d", schema_config.skillcooldown_mercenary_db, merc_id)) {
+		Sql_ShowDebug(sql_handle);
+		return false;
+	}
+
+	uint16 count = 0;
+
+	while (SQL_SUCCESS == Sql_NextRow(sql_handle)) {
+		if (count == MAX_SKILLCOOLDOWN) {
+			ShowWarning("Too many skillcooldowns for mercenary %d, skipping.\n", merc_id);
+			break;
+		}
+
+		// Skill
+		Sql_GetData(sql_handle, 0, &data, nullptr);
+		uint16 skill_id = static_cast<uint16>(strtoul(data, nullptr, 10));
+
+		if (skill_id < MC_SKILLBASE || skill_id >= MC_SKILLBASE + MAX_MERCSKILL)
+			continue; // invalid skill ID
+		merc->scd[count].skill_id = skill_id;
+
+		// Tick
+		Sql_GetData(sql_handle, 1, &data, nullptr);
+		merc->scd[count].tick = strtoll(data, nullptr, 10);
+
+		count++;
+	}
+	Sql_FreeResult(sql_handle);
+
+	// Clear the data once loaded.
+	if (count > 0) {
+		if (SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `mer_id`='%d'", schema_config.skillcooldown_mercenary_db, merc_id))
+			Sql_ShowDebug(sql_handle);
+	}
+
 	return true;
 }
 

+ 4 - 2
src/common/mmo.hpp

@@ -428,8 +428,8 @@ struct bonus_script_data {
 	uint8 type; ///< 0 - None, 1 - Buff, 2 - Debuff
 };
 
-struct skill_cooldown_data {
-	unsigned short skill_id;
+struct s_skill_cooldown_data {
+	uint16 skill_id;
 	t_tick tick;
 };
 
@@ -501,6 +501,7 @@ struct s_homunculus {	//[orn]
 	uint32 intimacy;	//[orn]
 	short hunger;
 	struct s_skill hskill[MAX_HOMUNSKILL]; //albator
+	s_skill_cooldown_data scd[MAX_SKILLCOOLDOWN];
 	short skillpts;
 	short level;
 	t_exp exp;
@@ -531,6 +532,7 @@ struct s_mercenary {
 	int32 hp, sp;
 	uint32 kill_count;
 	t_tick life_time;
+	s_skill_cooldown_data scd[MAX_SKILLCOOLDOWN];
 };
 
 struct s_elemental {

+ 16 - 0
src/common/utilities.hpp

@@ -31,6 +31,12 @@ int32 levenshtein( const std::string &s1, const std::string &s2 );
 
 namespace rathena {
 	namespace util {
+		/**
+		 * Determine if a key-value pair exists in the map
+		 * @param map: Map to search through
+		 * @param key: Key wanted
+		 * @return True on success or false otherwise
+		 */
 		template <typename K, typename V> bool map_exists( std::map<K,V>& map, K key ){
 			return map.find( key ) != map.end();
 		}
@@ -96,6 +102,16 @@ namespace rathena {
 			map.erase(it, map.end());
 		}
 
+		/**
+		 * Determine if a key-value pair exists in the unordered map
+		 * @param map: Unordered Map to search through
+		 * @param key: Key wanted
+		 * @return True on success or false otherwise
+		 */
+		template <typename K, typename V> bool umap_exists(std::unordered_map<K, V> &map, K key) {
+			return map.find(key) != map.end();
+		}
+
 		/**
 		 * Find a key-value pair and return the key value as a reference
 		 * @param map: Unordered Map to search through

+ 11 - 22
src/map/atcommand.cpp

@@ -1154,42 +1154,31 @@ ACMD_FUNC(resetcooltime)
 {
 	nullpo_retr(-1, sd);
 
-	for( size_t i = 0; i < ARRAYLENGTH( sd->scd ); i++ ){
-		if( sd->scd[i] != nullptr ) {
-			sprintf( atcmd_output, msg_txt( sd, 1537 ), skill_db.find( sd->scd[i]->skill_id )->name ); // Found skill '%s', unblocking...
+	if (!sd->scd.empty()) {
+		for (const auto &entry : sd->scd) {
+			sprintf( atcmd_output, msg_txt( sd, 1537 ), skill_db.find( entry.first )->name ); // Found skill '%s', unblocking...
 			clif_displaymessage( sd->fd, atcmd_output );
-
-			if (battle_config.display_status_timers)
-				clif_skill_cooldown( *sd, sd->scd[i]->skill_id, 0 );
-
-			delete_timer(sd->scd[i]->timer, skill_blockpc_end);
-			aFree(sd->scd[i]);
-			sd->scd[i] = nullptr;
 		}
+
+		skill_blockpc_clear(*sd);
 	}
 
 	if( sd->hd != nullptr && hom_is_active( sd->hd ) ){
-		for( const uint16& skill_id : sd->hd->blockskill ){
-			sprintf( atcmd_output, msg_txt( sd, 1537 ), skill_db.find( skill_id )->name ); // Found skill '%s', unblocking...
+		for (const auto &entry : sd->hd->scd) {
+			sprintf( atcmd_output, msg_txt( sd, 1537 ), skill_db.find( entry.first )->name ); // Found skill '%s', unblocking...
 			clif_displaymessage( sd->fd, atcmd_output );
-
-			if (battle_config.display_status_timers)
-				clif_skill_cooldown( *sd, skill_id, 0 );
 		}
 
-		sd->hd->blockskill.clear();
+		skill_blockhomun_clear(*sd->hd);
 	}
 
 	if( sd->md != nullptr ){
-		for( const uint16& skill_id : sd->md->blockskill ){
-			sprintf( atcmd_output, msg_txt( sd, 1537 ), skill_db.find( skill_id )->name ); // Found skill '%s', unblocking...
+		for( const auto &entry : sd->md->scd ){
+			sprintf( atcmd_output, msg_txt( sd, 1537 ), skill_db.find( entry.first )->name ); // Found skill '%s', unblocking...
 			clif_displaymessage( sd->fd, atcmd_output );
-
-			if (battle_config.display_status_timers)
-				clif_skill_cooldown( *sd, skill_id, 0 );
 		}
 
-		sd->md->blockskill.clear();
+		skill_blockmerc_clear(*sd->md);
 	}
 
 	return 0;

+ 65 - 56
src/map/chrif.cpp

@@ -284,7 +284,7 @@ int32 chrif_save(map_session_data *sd, int32 flag) {
 	if ( (flag&CSAVE_QUITTING) && sd->state.active) { //Store player data which is quitting
 		if (chrif_isconnected()) {
 			chrif_save_scdata(sd);
-			chrif_skillcooldown_save(sd);
+			chrif_skillcooldown_save(*sd);
 		}
 		if ( !(flag&CSAVE_AUTOTRADE) && !chrif_auth_logout(sd, (flag&CSAVE_QUIT) ? ST_LOGOUT : ST_MAPCHANGE) )
 			ShowError("chrif_save: Failed to set up player %d:%d for proper quitting!\n", sd->status.account_id, sd->status.char_id);
@@ -1318,45 +1318,6 @@ int32 chrif_save_scdata(map_session_data *sd) { //parses the sc_data of the play
 	return 0;
 }
 
-int32 chrif_skillcooldown_save(map_session_data *sd) {
-	int32 i, count = 0;
-	struct skill_cooldown_data data;
-	t_tick tick;
-	const struct TimerData *timer;
-
-	chrif_check(-1);
-	tick = gettick();
-
-	WFIFOHEAD(char_fd, 14 + MAX_SKILLCOOLDOWN * sizeof (struct skill_cooldown_data));
-	WFIFOW(char_fd, 0) = 0x2b15;
-	WFIFOL(char_fd, 4) = sd->status.account_id;
-	WFIFOL(char_fd, 8) = sd->status.char_id;
-	for (i = 0; i < MAX_SKILLCOOLDOWN; i++) {
-		if (!sd->scd[i])
-			continue;
-
-		if (battle_config.guild_skill_relog_type == 1 && SKILL_CHK_GUILD(sd->scd[i]->skill_id))
-			continue;
-
-		timer = get_timer(sd->scd[i]->timer);
-		if (timer == nullptr || timer->func != skill_blockpc_end || DIFF_TICK(timer->tick, tick) < 0)
-			continue;
-
-		data.tick = DIFF_TICK(timer->tick, tick);
-		data.skill_id = sd->scd[i]->skill_id;
-		memcpy(WFIFOP(char_fd, 14 + count * sizeof (struct skill_cooldown_data)), &data, sizeof (struct skill_cooldown_data));
-		count++;
-	}
-	if (count == 0)
-		return 0;
-
-	WFIFOW(char_fd, 12) = count;
-	WFIFOW( char_fd, 2 ) = static_cast<int16>( 14 + count * sizeof( struct skill_cooldown_data ) );
-	WFIFOSET(char_fd, WFIFOW(char_fd, 2));
-
-	return 0;
-}
-
 //Retrieve and load sc_data for a player. [Skotlex]
 int32 chrif_load_scdata(int32 fd) {
 
@@ -1392,29 +1353,77 @@ int32 chrif_load_scdata(int32 fd) {
 	return 0;
 }
 
-//Retrieve and load skillcooldown for a player
+/**
+ * Save player cooldown data.
+ * @param sd: Player object
+ * @return -1 on failure or 0 otherwise
+ */
+int chrif_skillcooldown_save(map_session_data &sd) {
+	chrif_check(-1);
 
-int32 chrif_skillcooldown_load(int32 fd) {
-	map_session_data *sd;
-	int32 aid, cid, i, count;
+	if (sd.scd.empty())
+		return 0;
 
-	aid = RFIFOL(fd, 4);
-	cid = RFIFOL(fd, 8);
+	t_tick tick = gettick();
 
-	sd = map_id2sd(aid);
-	if (!sd) {
-		ShowError("chrif_skillcooldown_load: Player of AID %d not found!\n", aid);
-		return -1;
+	WFIFOHEAD(char_fd, 14 + MAX_SKILLCOOLDOWN * sizeof (s_skill_cooldown_data));
+	WFIFOW(char_fd, 0) = 0x2b15;
+	WFIFOL(char_fd, 4) = sd.status.account_id;
+	WFIFOL(char_fd, 8) = sd.status.char_id;
+
+	uint16 count = 0;
+
+	for (auto entry : sd.scd) {
+		if (battle_config.guild_skill_relog_type == 1 && SKILL_CHK_GUILD(entry.first))
+			continue;
+
+		const TimerData *timer = get_timer(entry.second);
+
+		if (timer == nullptr || timer->func != skill_blockpc_end || DIFF_TICK(timer->tick, tick) < 0)
+			continue;
+
+		s_skill_cooldown_data data = {};
+
+		data.tick = DIFF_TICK(timer->tick, tick);
+		data.skill_id = entry.first;
+		memcpy(WFIFOP(char_fd, 14 + count * sizeof (s_skill_cooldown_data)), &data, sizeof (s_skill_cooldown_data));
+		count++;
 	}
-	if (sd->status.char_id != cid) {
-		ShowError("chrif_skillcooldown_load: Receiving data for account %d, char id does not matches (%d != %d)!\n", aid, sd->status.char_id, cid);
+
+	if (count == 0)
+		return 0;
+
+	WFIFOW(char_fd, 12) = count;
+	WFIFOW( char_fd, 2 ) = static_cast<int16>( 14 + count * sizeof( s_skill_cooldown_data ) );
+	WFIFOSET(char_fd, WFIFOW(char_fd, 2));
+
+	return 0;
+}
+
+/**
+ * Retrieve and load skillcooldown for a player.
+ * @param fd
+ * @return -1 on failure or 0 otherwise
+ */
+int32 chrif_skillcooldown_load(int fd) {
+	uint32 aid = RFIFOL(fd, 4);
+	map_session_data *sd = map_id2sd(aid);
+
+	// Player not found
+	if (sd == nullptr) {
+		ShowError("chrif_skillcooldown_load: Player of AID %d not found!\n", aid);
 		return -1;
 	}
-	count = RFIFOW(fd, 12); //sc_count
-	for (i = 0; i < count; i++) {
-		struct skill_cooldown_data *data = (struct skill_cooldown_data*) RFIFOP(fd, 14 + i * sizeof (struct skill_cooldown_data));
-			skill_blockpc_start(sd, data->skill_id, data->tick);
+
+	uint32 cid = RFIFOL(fd, 8);
+	int32 count = RFIFOW(fd, 12); //sc_count
+
+	for (int32 i = 0; i < count; i++) {
+		s_skill_cooldown_data *data = (s_skill_cooldown_data*) RFIFOP(fd, 14 + i * sizeof (s_skill_cooldown_data));
+
+		skill_blockpc_start(*sd, data->skill_id, data->tick);
 	}
+
 	return 0;
 }
 

+ 1 - 1
src/map/chrif.hpp

@@ -57,7 +57,7 @@ void chrif_authreq(map_session_data* sd, bool autotrade);
 void chrif_authok(int32 fd);
 int32 chrif_scdata_request(uint32 account_id, uint32 char_id);
 int32 chrif_skillcooldown_request(uint32 account_id, uint32 char_id);
-int32 chrif_skillcooldown_save(map_session_data *sd);
+int32 chrif_skillcooldown_save(map_session_data &sd);
 int32 chrif_skillcooldown_load(int32 fd);
 
 int32 chrif_save(map_session_data* sd, int32 flag);

+ 1 - 1
src/map/guild.cpp

@@ -1826,7 +1826,7 @@ void guild_block_skill(map_session_data *sd, int32 time) {
 	uint16 skill_id[] = { GD_BATTLEORDER, GD_REGENERATION, GD_RESTORE, GD_EMERGENCYCALL };
 	int32 i;
 	for (i = 0; i < 4; i++)
-		skill_blockpc_start(sd, skill_id[i], time);
+		skill_blockpc_start(*sd, skill_id[i], time);
 }
 
 /*====================================================

+ 38 - 2
src/map/homunculus.cpp

@@ -277,8 +277,7 @@ int32 hom_vaporize(map_session_data *sd, int32 flag)
 	hom_hungry_timer_delete(hd);
 	hd->homunculus.vaporize = flag ? flag : HOM_ST_REST;
 	if (battle_config.hom_delay_reset_vaporize) {
-		hd->blockskill.clear();
-		hd->blockskill.shrink_to_fit();
+		skill_blockhomun_clear(*hd);
 	}
 	status_change_clear(&hd->bl, 1);
 	clif_hominfo(sd, sd->hd, 0);
@@ -816,6 +815,26 @@ void hom_save(struct homun_data *hd)
 	//calculation on login)
 	hd->homunculus.hp = hd->battle_status.hp;
 	hd->homunculus.sp = hd->battle_status.sp;
+
+	// Clear skill cooldown array.
+	for (uint16 i = 0; i < MAX_SKILLCOOLDOWN; i++)
+		hd->homunculus.scd[i] = {};
+
+	// Store current cooldown entries.
+	uint16 count = 0;
+	t_tick tick = gettick();
+
+	for (const auto &entry : hd->scd) {
+		const TimerData *timer = get_timer(entry.second);
+
+		if (timer == nullptr || timer->func != skill_blockhomun_end || DIFF_TICK(timer->tick, tick) < 0)
+			continue;
+
+		hd->homunculus.scd[count] = { entry.first, DIFF_TICK(timer->tick, tick) };
+
+		count++;
+	}
+
 	intif_homunculus_requestsave(sd->status.account_id, &hd->homunculus);
 }
 
@@ -1039,6 +1058,8 @@ void hom_alloc(map_session_data *sd, struct s_homunculus *hom)
 	t_tick tick = gettick();
 
 	sd->hd = hd = (struct homun_data*)aCalloc(1,sizeof(struct homun_data));
+	new (sd->hd) homun_data();
+
 	hd->bl.type = BL_HOM;
 	hd->bl.id = npc_get_new_npc_id();
 
@@ -1133,6 +1154,11 @@ bool hom_call(map_session_data *sd)
 		//Warp him to master.
 		unit_warp(&hd->bl,sd->bl.m, sd->bl.x, sd->bl.y,CLR_OUTSIGHT);
 
+	// Apply any active skill cooldowns.
+	for (const auto &entry : hd->scd) {
+		skill_blockhomun_start(*hd, entry.first, entry.second);
+	}
+
 #ifdef RENEWAL
 	sc_start(&sd->bl, &sd->bl, SC_HOMUN_TIME, 100, 1, skill_get_time(AM_CALLHOMUN, 1));
 #endif
@@ -1200,6 +1226,11 @@ int32 hom_recv_data(uint32 account_id, struct s_homunculus *sh, int32 flag)
 #endif
 	}
 
+	// Apply any active skill cooldowns.
+	for (uint16 i = 0; i < ARRAYLENGTH(hd->homunculus.scd); i++) {
+		skill_blockhomun_start(*hd, hd->homunculus.scd[i].skill_id, hd->homunculus.scd[i].tick);
+	}
+
 	return 1;
 }
 
@@ -1288,6 +1319,11 @@ int32 hom_ressurect(map_session_data* sd, unsigned char per, short x, short y)
 
 	hd->ud.state.blockedmove = false;
 
+	// Apply any active skill cooldowns.
+	for (const auto &entry : hd->scd) {
+		skill_blockhomun_start(*hd, entry.first, entry.second);
+	}
+
 #ifdef RENEWAL
 	sc_start(&sd->bl, &sd->bl, SC_HOMUN_TIME, 100, 1, skill_get_time(AM_CALLHOMUN, 1));
 #endif

+ 1 - 1
src/map/homunculus.hpp

@@ -98,7 +98,7 @@ struct homun_data {
 	map_session_data *master; //pointer back to its master
 	int32 hungry_timer;	//[orn]
 	t_exp exp_next;
-	std::vector<uint16> blockskill;	// [orn]
+	std::unordered_map<uint16, int32> scd;
 };
 
 #define HOM_EVO 0x100 //256

+ 26 - 0
src/map/mercenary.cpp

@@ -237,6 +237,25 @@ void mercenary_save(s_mercenary_data *md) {
 	md->mercenary.sp = md->battle_status.sp;
 	md->mercenary.life_time = mercenary_get_lifetime(md);
 
+	// Clear skill cooldown array.
+	for (uint16 i = 0; i < MAX_SKILLCOOLDOWN; i++)
+		md->mercenary.scd[i] = {};
+	
+	// Store current cooldown entries.
+	uint16 count = 0;
+	t_tick tick = gettick();
+
+	for (const auto &entry : md->scd) {
+		const TimerData *timer = get_timer(entry.second);
+
+		if (timer == nullptr || timer->func != skill_blockmerc_end || DIFF_TICK(timer->tick, tick) < 0)
+			continue;
+
+		md->mercenary.scd[count] = { entry.first, DIFF_TICK(timer->tick, tick) };
+
+		count++;
+	}
+
 	intif_mercenary_save(&md->mercenary);
 }
 
@@ -348,6 +367,8 @@ bool mercenary_recv_data(s_mercenary *merc, bool flag)
 
 	if( !sd->md ) {
 		sd->md = md = (s_mercenary_data*)aCalloc(1,sizeof(s_mercenary_data));
+		new (sd->md) s_mercenary_data();
+
 		md->bl.type = BL_MER;
 		md->bl.id = npc_get_new_npc_id();
 		md->devotion_flag = 0;
@@ -393,6 +414,11 @@ bool mercenary_recv_data(s_mercenary *merc, bool flag)
 		clif_mercenary_skillblock(sd);
 	}
 
+	// Apply any active skill cooldowns.
+	for (uint16 i = 0; i < ARRAYLENGTH(md->mercenary.scd); i++) {
+		skill_blockmerc_start(*md, md->mercenary.scd[i].skill_id, md->mercenary.scd[i].tick);
+	}
+
 	return true;
 }
 

+ 1 - 1
src/map/mercenary.hpp

@@ -48,7 +48,7 @@ struct s_mercenary_data {
 
 	std::shared_ptr<s_mercenary_db> db;
 	s_mercenary mercenary;
-	std::vector<uint16> blockskill;
+	std::unordered_map<uint16, int32> scd;
 
 	int32 masterteleport_timer;
 	map_session_data *master;

+ 1 - 2
src/map/pc.cpp

@@ -7005,8 +7005,7 @@ enum e_setpos pc_setpos(map_session_data* sd, unsigned short mapindex, int32 x,
 			status_db.removeByStatusFlag(&sd->hd->bl, { SCF_REMOVEFROMHOMONWARP });
 
 		if (battle_config.hom_delay_reset_warp) {
-			sd->hd->blockskill.clear();
-			sd->hd->blockskill.shrink_to_fit();
+			skill_blockhomun_clear(*sd->hd);
 		}
 
 		sd->hd->bl.m = m;

+ 1 - 6
src/map/pc.hpp

@@ -183,11 +183,6 @@ public:
 
 extern CaptchaDatabase captcha_db;
 
-struct skill_cooldown_entry {
-	unsigned short skill_id;
-	int32 timer;
-};
-
 #ifdef VIP_ENABLE
 struct vip_info {
 	uint32 enabled : 1;
@@ -536,7 +531,7 @@ public:
 	uint16 skill_id_dance,skill_lv_dance;
 	uint16 skill_id_song, skill_lv_song;
 	short cook_mastery; // range: [0,1999] [Inkfish]
-	struct skill_cooldown_entry * scd[MAX_SKILLCOOLDOWN]; // Skill Cooldown
+	std::unordered_map<uint16, int32> scd; // Skill Cooldown
 	uint16 cloneskill_idx, ///Stores index of copied skill by Intimidate/Plagiarism
 		reproduceskill_idx; ///Stores index of copied skill by Reproduce
 	int32 menuskill_id, menuskill_val, menuskill_val2;

+ 140 - 120
src/map/skill.cpp

@@ -834,7 +834,7 @@ bool skill_isNotOk( uint16 skill_id, map_session_data& sd ){
 		return true;
 	}
 
-	if (skill_blockpc_get(&sd, skill_id) != -1){
+	if (util::umap_exists(sd.scd, skill_id)) {
 		clif_skill_fail( sd, skill_id, USESKILL_FAIL_SKILLINTERVAL );
 		return true;
 	}
@@ -996,7 +996,7 @@ bool skill_isNotOk_hom(struct homun_data *hd, uint16 skill_id, uint16 skill_lv)
 	if (sc != nullptr && sc->empty())
 		sc = nullptr;
 
-	if (util::vector_exists(hd->blockskill, skill_id)) {
+	if (util::umap_exists(hd->scd, skill_id)) {
 		clif_skill_fail(*sd, skill_id, USESKILL_FAIL_SKILLINTERVAL);
 		return true;
 	}
@@ -1109,7 +1109,7 @@ bool skill_isNotOk_mercenary( uint16 skill_id, s_mercenary_data& md ){
 	if (sd == nullptr)
 		return true;
 
-	if (util::vector_exists(md.blockskill, skill_id)) {
+	if (util::umap_exists(md.scd, skill_id)) {
 		clif_skill_fail(*sd, skill_id, USESKILL_FAIL_SKILLINTERVAL);
 		return true;
 	}
@@ -6591,7 +6591,7 @@ int32 skill_castend_damage_id (struct block_list* src, struct block_list *bl, ui
 				int32 cooldown = pc_get_skillcooldown(sd,pres_skill_id, pres_skill_lv);
 
 				if( cooldown > 0 )
-					skill_blockpc_start(sd, pres_skill_id, cooldown);
+					skill_blockpc_start(*sd, pres_skill_id, cooldown);
 			} else { // Summoned Balls
 				for (i = SC_SPHERE_5; i >= SC_SPHERE_1; i--) {
 					if (sc->getSCE(static_cast<sc_type>(i)) == nullptr)
@@ -10513,7 +10513,7 @@ int32 skill_castend_nodamage_id (struct block_list *src, struct block_list *bl,
 				skill_castend_nodamage_id);
 			if (sd)
 #ifdef RENEWAL
-				skill_blockpc_start(sd, skill_id, skill_get_cooldown(skill_id, skill_lv));
+				skill_blockpc_start(*sd, skill_id, skill_get_cooldown(skill_id, skill_lv));
 #else
 				guild_block_skill(sd, skill_get_time2(skill_id, skill_lv));
 #endif
@@ -10557,7 +10557,7 @@ int32 skill_castend_nodamage_id (struct block_list *src, struct block_list *bl,
 			}
 			if (sd)
 #ifdef RENEWAL
-				skill_blockpc_start(sd, skill_id, skill_get_cooldown(skill_id, skill_lv));
+				skill_blockpc_start(*sd, skill_id, skill_get_cooldown(skill_id, skill_lv));
 #else
 				guild_block_skill(sd, skill_get_time2(skill_id, skill_lv));
 #endif
@@ -11230,7 +11230,7 @@ int32 skill_castend_nodamage_id (struct block_list *src, struct block_list *bl,
 			else rate += 40 + 10 * skill_lv; // On Monsters, (40 + 10 * Skill Level) %
 
 			if( sd )
-				skill_blockpc_start(sd,skill_id,4000);
+				skill_blockpc_start(*sd,skill_id,4000);
 
 			if( !(tsc && tsc->getSCE(type)) ){
 				i = sc_start2(src,bl,type,rate,skill_lv,src->id,(src == bl)?5000:(bl->type == BL_PC)?skill_get_time(skill_id,skill_lv):skill_get_time2(skill_id, skill_lv));
@@ -12033,7 +12033,7 @@ int32 skill_castend_nodamage_id (struct block_list *src, struct block_list *bl,
 			sd->skill_id_old = skill_id;
 			elemental_action(sd->ed, bl, tick);
 			clif_skill_nodamage(src,*bl,skill_id,skill_lv);
-			skill_blockpc_start(sd, skill_id, duration);
+			skill_blockpc_start(*sd, skill_id, duration);
 		}
 		break;
 
@@ -13411,20 +13411,31 @@ TIMER_FUNC(skill_castend_id){
 
 		// Cooldown application
 		switch (src->type) {
-		case BL_PC:{
-			// Increases/Decreases cooldown of a skill by item/card bonuses.
-			int32 cooldown = pc_get_skillcooldown(sd, ud->skill_id, ud->skill_lv);
-			if (cooldown > 0)
-				skill_blockpc_start(sd, ud->skill_id, cooldown);
-		} break;
-		case BL_HOM:{
-			homun_data& hd = reinterpret_cast<homun_data&>(*src);
+			case BL_PC:
+			{
+				// Increases/Decreases cooldown of a skill by item/card bonuses.
+				int32 cooldown = pc_get_skillcooldown(sd, ud->skill_id, ud->skill_lv);
+				if (cooldown > 0)
+					skill_blockpc_start(*sd, ud->skill_id, cooldown);
+			}
+			break;
+			case BL_HOM:
+			{
+				homun_data &hd = reinterpret_cast<homun_data &>(*src);
 #ifdef RENEWAL
-			skill_blockhomun_start(&hd, ud->skill_id, skill_get_cooldown(ud->skill_id, ud->skill_lv));
+				skill_blockhomun_start(hd, ud->skill_id, skill_get_cooldown(ud->skill_id, ud->skill_lv));
 #else
-			skill_blockhomun_start(&hd, ud->skill_id, skill_get_delay(ud->skill_id, ud->skill_lv));
+				skill_blockhomun_start(hd, ud->skill_id, skill_get_delay(ud->skill_id, ud->skill_lv));
 #endif
-		} break;
+			}
+			break;
+			case BL_MER:
+			{
+				s_mercenary_data &mc = reinterpret_cast<s_mercenary_data &>(*src);
+
+				skill_blockmerc_start(mc, ud->skill_id, skill_get_cooldown(ud->skill_id, ud->skill_lv));
+			}
+			break;
 		}
 
 		if( battle_config.display_status_timers && sd )
@@ -13495,7 +13506,7 @@ TIMER_FUNC(skill_castend_id){
 				sc->getSCE(SC_SPIRIT)->val3 = 0; //Clear bounced spell check.
 #ifndef RENEWAL
 			if( sc->getSCE(SC_DANCING) && sd && skill_get_inf2(ud->skill_id, INF2_ISSONG) )
-				skill_blockpc_start(sd,BD_ADAPTATION,3000);
+				skill_blockpc_start(*sd,BD_ADAPTATION,3000);
 #endif
 		}
 
@@ -13662,7 +13673,7 @@ TIMER_FUNC(skill_castend_pos){
 			ud->canact_tick = i64max(tick + skill_delayfix(src, ud->skill_id, ud->skill_lv), ud->canact_tick - SECURITY_CASTTIME);
 		if (sd) { //Cooldown application
 			int32 cooldown = pc_get_skillcooldown(sd,ud->skill_id, ud->skill_lv);
-			if(cooldown) skill_blockpc_start(sd, ud->skill_id, cooldown);
+			if(cooldown) skill_blockpc_start(*sd, ud->skill_id, cooldown);
 		}
 		if( battle_config.display_status_timers && sd )
 			clif_status_change(src, EFST_POSTDELAY, 1, skill_delayfix(src, ud->skill_id, ud->skill_lv), 0, 0, 0);
@@ -14097,7 +14108,7 @@ int32 skill_castend_pos2(struct block_list* src, int32 x, int32 y, uint16 skill_
 			clif_skill_poseffect(src,skill_id,skill_lv,src->x,src->y,tick);
 #endif
 			if (sd)
-				skill_blockpc_start (sd, MO_EXTREMITYFIST, 2000);
+				skill_blockpc_start (*sd, MO_EXTREMITYFIST, 2000);
 		}
 		break;
 	case NJ_SHADOWJUMP:
@@ -23010,155 +23021,164 @@ static int32 skill_destroy_trap(struct block_list *bl, va_list ap)
 /*==========================================
  *
  *------------------------------------------*/
-int32 skill_blockpc_get(map_session_data *sd, int32 skillid) {
-	int32 i;
-
-	nullpo_retr(-1, sd);
-
-	ARR_FIND(0, MAX_SKILLCOOLDOWN, i, sd->scd[i] && sd->scd[i]->skill_id == skillid);
-	return (i >= MAX_SKILLCOOLDOWN) ? -1 : i;
-}
-
 TIMER_FUNC(skill_blockpc_end){
 	map_session_data *sd = map_id2sd(id);
-	int32 i = (int32)data;
 
-	if (!sd || data < 0 || data >= MAX_SKILLCOOLDOWN)
+	if (sd == nullptr)
 		return 0;
 
-	if (!sd->scd[i] || sd->scd[i]->timer != tid) {
-		ShowWarning("skill_blockpc_end: Invalid Timer or not Skill Cooldown.\n");
-		return 0;
-	}
+	sd->scd.erase(static_cast<uint16>(data));
 
-	aFree(sd->scd[i]);
-	sd->scd[i] = nullptr;
-		return 1;
+	return 1;
 }
 
 /**
- * Flags a singular skill as being blocked from persistent usage.
- * @param   sd        the player the skill delay affects
- * @param   skill_id   the skill which should be delayed
- * @param   tick      the length of time the delay should last
- * @param   load      whether this assignment is being loaded upon player login
- * @return  0 if successful, -1 otherwise
+ * Flags a singular skill as being blocked from persistent usage for a player.
+ * @param sd: The player the skill delay affects
+ * @param skill_id: The skill which should be delayed
+ * @param tick: The length of time the delay should last
+ * @return True if successful, false otherwise
  */
-int32 skill_blockpc_start(map_session_data *sd, int32 skill_id, t_tick tick) {
-	int32 i;
-
-	nullpo_retr(-1, sd);
-
-	if (!skill_id || tick < 1)
-		return -1;
+bool skill_blockpc_start(map_session_data &sd, uint16 skill_id, t_tick tick) {
+	if (!skill_db.exists(skill_id) || tick < 1)
+		return false;
 
-	ARR_FIND(0, MAX_SKILLCOOLDOWN, i, sd->scd[i] && sd->scd[i]->skill_id == skill_id);
-	if (i < MAX_SKILLCOOLDOWN) { // Skill already with cooldown
-		delete_timer(sd->scd[i]->timer, skill_blockpc_end);
-		aFree(sd->scd[i]);
-		sd->scd[i] = nullptr;
+	if (sd.scd.size() == MAX_SKILLCOOLDOWN) {
+		ShowWarning("skill_blockpc_start: Too many skillcooldowns, increase MAX_SKILLCOOLDOWN.\n");
+		return false;
 	}
 
-	ARR_FIND(0, MAX_SKILLCOOLDOWN, i, !sd->scd[i]);
-	if (i < MAX_SKILLCOOLDOWN) { // Free Slot found
-		CREATE(sd->scd[i], struct skill_cooldown_entry, 1);
-		sd->scd[i]->skill_id = skill_id;
-		sd->scd[i]->timer = add_timer(gettick() + tick, skill_blockpc_end, sd->bl.id, i);
+	// Add entry to list.
+	sd.scd[skill_id] = add_timer(gettick() + tick, skill_blockpc_end, sd.bl.id, skill_id);
 
-		if (battle_config.display_status_timers)
-			clif_skill_cooldown( *sd, skill_id, tick );
+	if (battle_config.display_status_timers)
+		clif_skill_cooldown(sd, skill_id, tick);
 
-		return 1;
-	} else {
-		ShowWarning("skill_blockpc_start: Too many skillcooldowns, increase MAX_SKILLCOOLDOWN.\n");
-		return 0;
-	}
+	return true;
 }
 
-int32 skill_blockpc_clear(map_session_data *sd) {
-	int32 i;
-
-	nullpo_ret(sd);
-
-	for (i = 0; i < MAX_SKILLCOOLDOWN; i++) {
-		if (!sd->scd[i])
-			continue;
-		delete_timer(sd->scd[i]->timer, skill_blockpc_end);
-		aFree(sd->scd[i]);
-		sd->scd[i] = nullptr;
+/**
+ * Clear skill cooldowns from player.
+ * @param sd: Player data
+ */
+void skill_blockpc_clear(map_session_data &sd) {
+	for (auto &entry : sd.scd) {
+		if (battle_config.display_status_timers)
+			clif_skill_cooldown(sd, entry.first, 0);
+		delete_timer(entry.second, skill_blockpc_end);
 	}
-	return 1;
+
+	sd.scd.clear();
 }
 
+/**
+ * Timer end for homunculus skill cooldowns.
+ */
 TIMER_FUNC(skill_blockhomun_end){
-	struct homun_data *hd = (TBL_HOM*) map_id2bl(id);
+	homun_data *hd = map_id2hd(id);
 
-	if (hd) {
-		auto skill = util::vector_get(hd->blockskill, (uint16)data);
+	if (hd == nullptr)
+		return 0;
 
-		if (skill != hd->blockskill.end())
-			hd->blockskill.erase(skill);
-	}
+	hd->scd.erase(static_cast<uint16>(data));
 
 	return 1;
 }
 
-int32 skill_blockhomun_start(struct homun_data *hd, uint16 skill_id, int32 tick)	//[orn]
+/**
+ * Flags a singular skill as being blocked from persistent usage for a homunculus.
+ * @param hd: The homunculus the skill delay affects
+ * @param skill_id: The skill which should be delayed
+ * @param tick: The length of time the delay should last
+ * @return True if successful, false otherwise
+ */
+bool skill_blockhomun_start(homun_data &hd, uint16 skill_id, t_tick tick)	//[orn]
 {
-	nullpo_retr(-1, hd);
-
-	if (!skill_db.exists(skill_id))
-		return -1;
-
-	auto skill = util::vector_get(hd->blockskill, skill_id);
+	if (!skill_db.exists(skill_id) || tick < 1)
+		return false;
 
-	if (tick < 1 && skill != hd->blockskill.end()) {
-		hd->blockskill.erase(skill);
-		return -1;
+	if (hd.scd.size() == MAX_SKILLCOOLDOWN) {
+		ShowWarning("skill_blockhomun_start: Too many skillcooldowns, increase MAX_SKILLCOOLDOWN.\n");
+		return false;
 	}
 
-	hd->blockskill.push_back(skill_id);
+	// Add entry to list.
+	hd.scd[skill_id] = add_timer(gettick() + tick, skill_blockhomun_end, hd.bl.id, skill_id);
 
 	if (battle_config.display_status_timers)
-		clif_skill_cooldown(*hd->master, skill_id, tick);
+		clif_skill_cooldown(*hd.master, skill_id, tick);
+
+	return true;
+}
+
+/**
+ * Clear skill cooldowns from homunculus.
+ * @param hd: Homunculus data
+ */
+void skill_blockhomun_clear(homun_data &hd) {
+	for (auto &entry : hd.scd) {
+		if (battle_config.display_status_timers)
+			clif_skill_cooldown(*hd.master, entry.first, 0);
+		delete_timer(entry.second, skill_blockhomun_end);
+	}
 
-	return add_timer(gettick() + tick, skill_blockhomun_end, hd->bl.id, skill_id);
+	hd.scd.clear();
 }
 
+/**
+ * Timer end for mercenary skill cooldowns.
+ */
 TIMER_FUNC(skill_blockmerc_end){
-	s_mercenary_data *md = (TBL_MER*)map_id2bl(id);
+	s_mercenary_data *mc = map_id2mc(id);
 
-	if (md) {
-		auto skill = util::vector_get(md->blockskill, (uint16)data);
+	if (mc == nullptr)
+		return 0;
 
-		if (skill != md->blockskill.end())
-			md->blockskill.erase(skill);
-	}
+	mc->scd.erase(static_cast<uint16>(data));
 
 	return 1;
 }
 
-int32 skill_blockmerc_start(s_mercenary_data *md, uint16 skill_id, int32 tick)
+/**
+ * Flags a singular skill as being blocked from persistent usage for a mercenary.
+ * @param mc: The mercenary the skill delay affects
+ * @param skill_id: The skill which should be delayed
+ * @param tick: The length of time the delay should last
+ * @return True if successful, false otherwise
+ */
+bool skill_blockmerc_start(s_mercenary_data &mc, uint16 skill_id, t_tick tick)
 {
-	nullpo_retr(-1, md);
-
-	if (!skill_db.exists(skill_id))
-		return -1;
-
-	auto skill = util::vector_get(md->blockskill, skill_id);
+	if (!skill_db.exists(skill_id) || tick < 1)
+		return false;
 
-	if (tick < 1 && skill != md->blockskill.end()) {
-		md->blockskill.erase(skill);
-		return -1;
+	if (mc.scd.size() == MAX_SKILLCOOLDOWN) {
+		ShowWarning("skill_blockpc_start: Too many skillcooldowns, increase MAX_SKILLCOOLDOWN.\n");
+		return false;
 	}
 
-	md->blockskill.push_back(skill_id);
+	// Add entry to list.
+	mc.scd[skill_id] = add_timer(gettick() + tick, skill_blockmerc_end, mc.bl.id, skill_id);
 
 	if (battle_config.display_status_timers)
-		clif_skill_cooldown(*md->master, skill_id, tick);
+		clif_skill_cooldown(*mc.master, skill_id, tick);
+
+	return true;
+}
+
+/**
+ * Clear skill cooldowns from mercenary.
+ * @param mc: Mercenary data
+ */
+void skill_blockmerc_clear(s_mercenary_data &mc) {
+	for (auto &entry : mc.scd) {
+		if (battle_config.display_status_timers)
+			clif_skill_cooldown(*mc.master, entry.first, 0);
+		delete_timer(entry.second, skill_blockmerc_end);
+	}
 
-	return add_timer(gettick() + tick, skill_blockmerc_end, md->bl.id, skill_id);
+	mc.scd.clear();
 }
+
 /**
  * Adds a new skill unit entry for this player to recast after map load
  * @param sd: Player

+ 8 - 6
src/map/skill.hpp

@@ -634,13 +634,15 @@ int32 skill_castend_nodamage_id( struct block_list *src, struct block_list *bl,u
 int32 skill_castend_damage_id( struct block_list* src, struct block_list *bl,uint16 skill_id,uint16 skill_lv,t_tick tick,int32 flag );
 int32 skill_castend_pos2( struct block_list *src, int32 x,int32 y,uint16 skill_id,uint16 skill_lv,t_tick tick,int32 flag);
 
-int32 skill_blockpc_start(map_session_data*, int32, t_tick);
-int32 skill_blockpc_get(map_session_data *sd, int32 skillid);
-int32 skill_blockpc_clear(map_session_data *sd);
+bool skill_blockpc_start(map_session_data &sd, uint16 skill_id, t_tick tick);
+void skill_blockpc_clear(map_session_data &sd);
 TIMER_FUNC(skill_blockpc_end);
-int32 skill_blockhomun_start (struct homun_data*,uint16 skill_id,int32);
-int32 skill_blockmerc_start (s_mercenary_data*,uint16 skill_id,int32);
-
+bool skill_blockhomun_start(homun_data &hd, uint16 skill_id, t_tick tick);
+void skill_blockhomun_clear(homun_data &hd);
+TIMER_FUNC(skill_blockhomun_end);
+bool skill_blockmerc_start(s_mercenary_data &mc, uint16 skill_id, t_tick tick);
+void skill_blockmerc_clear(s_mercenary_data &mc);
+TIMER_FUNC(skill_blockmerc_end);
 
 // (Epoque:) To-do: replace this macro with some sort of skill tree check (rather than hard-coded skill names)
 #define skill_ischangesex(id) ( \

+ 6 - 1
src/map/unit.cpp

@@ -3549,7 +3549,7 @@ int32 unit_free(struct block_list *bl, clr_type clrtype)
 				duel_reject(sd->duel_invite, sd);
 
 			channel_pcquit(sd,0xF); // Leave all chan
-			skill_blockpc_clear(sd); // Clear all skill cooldown related
+			skill_blockpc_clear(*sd); // Clear all skill cooldown related
 
 			// Notify friends that this char logged out. [Skotlex]
 			map_foreachpc(clif_friendslist_toggle_sub, sd->status.account_id, sd->status.char_id, 0);
@@ -3733,9 +3733,12 @@ int32 unit_free(struct block_list *bl, clr_type clrtype)
 #endif
 			}
 
+			skill_blockhomun_clear(*hd); // Clear all skill cooldown related
+
 			if( sd )
 				sd->hd = nullptr;
 			hd->master = nullptr;
+			hd->~homun_data();
 
 			skill_clear_unitgroup(bl);
 			status_change_clear(bl,1);
@@ -3757,8 +3760,10 @@ int32 unit_free(struct block_list *bl, clr_type clrtype)
 			if( sd )
 				sd->md = nullptr;
 
+			skill_blockmerc_clear(*md); // Clear all skill cooldown related
 			mercenary_contract_stop(md);
 			md->master = nullptr;
+			md->~s_mercenary_data();
 
 			skill_clear_unitgroup(bl);
 			status_change_clear(bl,1);