浏览代码

Cleans up the skill unit group storage (#6195)

* Fixes #2217.
* Adjusts the storage for skill unit group from DBMap to unordered_map.
* Removes the MAX_SKILLUNITGROUP limit.
* Cleans up memory management to utilize shared_ptr.
Thanks to @Lemongrass3110!
Co-authored-by: Lemongrass3110 <lemongrass@kstp.at>
Aleos 3 年之前
父节点
当前提交
a84e731f79
共有 9 个文件被更改,包括 250 次插入244 次删除
  1. 4 7
      src/map/battle.cpp
  2. 3 7
      src/map/chat.cpp
  3. 12 12
      src/map/guild.cpp
  4. 1 1
      src/map/script.cpp
  5. 177 182
      src/map/skill.cpp
  6. 23 19
      src/map/skill.hpp
  7. 15 15
      src/map/status.cpp
  8. 13 0
      src/map/unit.cpp
  9. 2 1
      src/map/unit.hpp

+ 4 - 7
src/map/battle.cpp

@@ -468,7 +468,7 @@ int64 battle_attr_fix(struct block_list *src, struct block_list *target, int64 d
 	if( target && target->type == BL_SKILL ) {
 		if( atk_elem == ELE_FIRE && battle_getcurrentskill(target) == GN_WALLOFTHORN ) {
 			struct skill_unit *su = (struct skill_unit*)target;
-			struct skill_unit_group *sg;
+			std::shared_ptr<s_skill_unit_group> sg;
 			struct block_list *src2;
 
 			if( !su || !su->alive || (sg = su->group) == NULL || !sg || sg->val3 == -1 ||
@@ -1133,7 +1133,7 @@ bool battle_status_block_damage(struct block_list *src, struct block_list *targe
 
 	// ATK_BLOCK Type
 	if ((sce = sc->data[SC_SAFETYWALL]) && (flag&(BF_SHORT | BF_MAGIC)) == BF_SHORT) {
-		skill_unit_group *group = skill_id2group(sce->val3);
+		std::shared_ptr<s_skill_unit_group> group = skill_id2group(sce->val3);
 
 		if (group) {
 			d->dmg_lv = ATK_BLOCK;
@@ -8032,11 +8032,8 @@ enum damage_lv battle_weapon_attack(struct block_list* src, struct block_list* t
 					if( BL_PC&battle_config.land_skill_limit &&
 						(maxcount = skill_get_maxcount(r_skill, r_lv)) > 0
 					  ) {
-						int v;
-						for(v=0;v<MAX_SKILLUNITGROUP && sd->ud.skillunit[v] && maxcount;v++) {
-							if(sd->ud.skillunit[v]->skill_id == r_skill)
-								maxcount--;
-						}
+						unit_skillunit_maxcount(sd->ud, r_skill, maxcount);
+
 						if( maxcount == 0 )
 							type = -1;
 					}

+ 3 - 7
src/map/chat.cpp

@@ -217,20 +217,16 @@ int chat_leavechat(struct map_session_data* sd, bool kicked)
 		cd->usersd[i] = cd->usersd[i+1];
 
 	if( cd->users == 0 && cd->owner->type == BL_PC ) { // Delete empty chatroom
-		struct skill_unit* unit;
-		struct skill_unit_group* group;
-
 		clif_clearchat(cd, 0);
 		db_destroy(cd->kick_list);
 		map_deliddb(&cd->bl);
 		map_delblock(&cd->bl);
 		map_freeblock(&cd->bl);
 
-		unit = map_find_skill_unit_oncell(&sd->bl, sd->bl.x, sd->bl.y, AL_WARP, NULL, 0);
-		group = (unit != NULL) ? unit->group : NULL;
+		skill_unit *unit = map_find_skill_unit_oncell(&sd->bl, sd->bl.x, sd->bl.y, AL_WARP, nullptr, 0);
 
-		if (group != NULL)
-			ext_skill_unit_onplace(unit, &sd->bl, group->tick);
+		if (unit != nullptr && unit->group != nullptr)
+			ext_skill_unit_onplace(unit, &sd->bl, unit->group->tick);
 
 		return 1;
 	}

+ 12 - 12
src/map/guild.cpp

@@ -1523,13 +1523,15 @@ int guild_skillupack(int guild_id,uint16 skill_id,uint32 account_id) {
 }
 
 void guild_guildaura_refresh(struct map_session_data *sd, uint16 skill_id, uint16 skill_lv) {
-	struct skill_unit_group* group = NULL;
-	sc_type type = status_skill2sc(skill_id);
 	if( !(battle_config.guild_aura&(is_agit_start()?2:1)) &&
 			!(battle_config.guild_aura&(map_flag_gvg2(sd->bl.m)?8:4)) )
 		return;
 	if( !skill_lv )
 		return;
+
+	std::shared_ptr<s_skill_unit_group> group;
+	sc_type type = status_skill2sc(skill_id);
+
 	if( sd->sc.data[type] && (group = skill_id2group(sd->sc.data[type]->val4)) ) {
 		skill_delunitgroup(group);
 		status_change_end(&sd->bl,type,INVALID_TIMER);
@@ -2058,24 +2060,22 @@ int guild_break(struct map_session_data *sd,char *name) {
 
 	/* Regardless of char server allowing it, we clear the guild master's auras */
 	if ((ud = unit_bl2ud(&sd->bl))) {
-		int count = 0;
-		struct skill_unit_group *group[4];
+		std::vector<std::shared_ptr<s_skill_unit_group>> group;
 
-		for(i = 0; i < MAX_SKILLUNITGROUP && ud->skillunit[i]; i++) {
-			switch(ud->skillunit[i]->skill_id) {
+		for (const auto su : ud->skillunits) {
+			switch (su->skill_id) {
 				case GD_LEADERSHIP:
 				case GD_GLORYWOUNDS:
 				case GD_SOULCOLD:
 				case GD_HAWKEYES:
-					if(count == 4)
-						ShowWarning("guild_break: '%s' got more than 4 guild aura instances! (%d)\n",sd->status.name,ud->skillunit[i]->skill_id);
-					else
-						group[count++] = ud->skillunit[i];
+					group.push_back(su);
 					break;
 			}
 		}
-		for (i = 0; i < count; i++)
-			skill_delunitgroup(group[i]);
+
+		for (auto it = group.begin(); it != group.end(); it++) {
+			skill_delunitgroup(*it);
+		}
 	}
 
 #ifdef BOUND_ITEMS

+ 1 - 1
src/map/script.cpp

@@ -19412,7 +19412,7 @@ BUILDIN_FUNC(warpportal)
 	unsigned short mapindex;
 	int tpx;
 	int tpy;
-	struct skill_unit_group* group;
+	std::shared_ptr<s_skill_unit_group> group;
 	struct block_list* bl;
 
 	bl = map_id2bl(st->oid);

+ 177 - 182
src/map/skill.cpp

@@ -67,7 +67,6 @@ using namespace rathena;
 static uint16 skilldb_id2idx[(UINT16_MAX + 1)];	/// Skill ID to Index lookup: skill_index = skill_get_index(skill_id) - [FWI] 20160423 the whole index thing should be removed.
 static uint16 skill_num = 1;			 		/// Skill count, also as last index
 
-static struct eri *skill_unit_ers = NULL; //For handling skill_unit's [Skotlex]
 static struct eri *skill_timer_ers = NULL; //For handling skill_timerskills [Skotlex]
 static DBMap* bowling_db = NULL; // int mob_id -> struct mob_data*
 
@@ -326,12 +325,12 @@ int skill_tree_get_max(uint16 skill_id, int b_class)
 
 int skill_frostjoke_scream(struct block_list *bl,va_list ap);
 int skill_attack_area(struct block_list *bl,va_list ap);
-struct skill_unit_group *skill_locate_element_field(struct block_list *bl); // [Skotlex]
+std::shared_ptr<s_skill_unit_group> skill_locate_element_field(struct block_list *bl); // [Skotlex]
 int skill_graffitiremover(struct block_list *bl, va_list ap); // [Valaris]
 int skill_greed(struct block_list *bl, va_list ap);
 static int skill_cell_overlap(struct block_list *bl, va_list ap);
 static int skill_trap_splash(struct block_list *bl, va_list ap);
-struct skill_unit_group_tickset *skill_unitgrouptickset_search(struct block_list *bl,struct skill_unit_group *sg,t_tick tick);
+struct skill_unit_group_tickset *skill_unitgrouptickset_search(struct block_list *bl,std::shared_ptr<s_skill_unit_group> sg,t_tick tick);
 static int skill_unit_onplace(struct skill_unit *src,struct block_list *bl,t_tick tick);
 int skill_unit_onleft(uint16 skill_id, struct block_list *bl,t_tick tick);
 static int skill_unit_effect(struct block_list *bl,va_list ap);
@@ -488,10 +487,8 @@ bool skill_pos_maxcount_check(struct block_list *src, int16 x, int16 y, uint16 s
 		return false;
 	}
 	if (type&battle_config.land_skill_limit && (maxcount = skill_get_maxcount(skill_id, skill_lv)) > 0) {
-		for (int i = 0; i < MAX_SKILLUNITGROUP && ud->skillunit[i] && maxcount; i++) {
-			if (ud->skillunit[i]->skill_id == skill_id)
-				maxcount--;
-		}
+		unit_skillunit_maxcount(*ud, skill_id, maxcount);
+
 		if (maxcount == 0) {
 			if (sd && display_failure)
 				clif_skill_fail(sd, skill_id, USESKILL_FAIL_LEVEL, 0);
@@ -4445,14 +4442,16 @@ int skill_cleartimerskill (struct block_list *src)
 	return 1;
 }
 static int skill_active_reverberation(struct block_list *bl, va_list ap) {
-	struct skill_unit *su = (TBL_SKILL*)bl;
-	struct skill_unit_group *sg = NULL;
+	skill_unit *su = (skill_unit*)bl;
 
 	nullpo_ret(su);
 
 	if (bl->type != BL_SKILL)
 		return 0;
-	if (su->alive && (sg = su->group) && sg->skill_id == NPC_REVERBERATION) {
+
+	std::shared_ptr<s_skill_unit_group> sg = su->group;
+
+	if (su->alive && sg && sg->skill_id == NPC_REVERBERATION) {
 		map_foreachinallrange(skill_trap_splash, bl, skill_get_splash(sg->skill_id, sg->skill_lv), sg->bl_flag, bl, gettick());
 		su->limit = DIFF_TICK(gettick(), sg->tick);
 		sg->unit_id = UNT_USED_TRAPS;
@@ -5876,8 +5875,8 @@ int skill_castend_damage_id (struct block_list* src, struct block_list *bl, uint
 			if (tsc && tsc->data[SC__SHADOWFORM] && rnd() % 100 < 100 - tsc->data[SC__SHADOWFORM]->val1 * 10) // [100 - (Skill Level x 10)] %
 				status_change_end(bl, SC__SHADOWFORM, INVALID_TIMER); // Should only end, no damage dealt.
 		} else {
-			struct skill_unit *su = NULL;
-			struct skill_unit_group* sg;
+			skill_unit *su = BL_CAST(BL_SKILL, bl);
+			std::shared_ptr<s_skill_unit_group> sg;
 
 			if (su && (sg = su->group) && skill_get_inf2(sg->skill_id, INF2_ISTRAP)) {
 				if( !(sg->unit_id == UNT_USED_TRAPS || (sg->unit_id == UNT_ANKLESNARE && sg->val2 != 0 )) )
@@ -8965,12 +8964,10 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
 	case MA_REMOVETRAP:
 	case HT_REMOVETRAP:
 		{
-			struct skill_unit* su;
-			struct skill_unit_group* sg = NULL;
+			skill_unit* su = BL_CAST(BL_SKILL, bl);
+			std::shared_ptr<s_skill_unit_group> sg;
 			std::shared_ptr<s_skill_db> skill_group;
 
-			su = BL_CAST(BL_SKILL, bl);
-
 			// Mercenaries can remove any trap
 			// Players can only remove their own traps or traps on Vs maps.
 			if( su && (sg = su->group) && (src->type == BL_MER || sg->src_id == src->id || map_flag_vs(bl->m)) && ( skill_group = skill_db.find(sg->skill_id) ) && skill_group->inf2[INF2_ISTRAP] )
@@ -12104,7 +12101,7 @@ int skill_castend_pos2(struct block_list* src, int x, int y, uint16 skill_id, ui
 	struct map_session_data* sd;
 	struct status_change* sc;
 	struct status_change_entry *sce;
-	struct skill_unit_group* sg;
+	std::shared_ptr<s_skill_unit_group> sg;
 	enum sc_type type;
 	int i;
 
@@ -12186,7 +12183,7 @@ int skill_castend_pos2(struct block_list* src, int x, int y, uint16 skill_id, ui
 	case SA_DELUGE:
 	case SA_VIOLENTGALE:
 	{	//Does not consumes if the skill is already active. [Skotlex]
-		struct skill_unit_group *sg2;
+		std::shared_ptr<s_skill_unit_group> sg2;
 		if ((sg2= skill_locate_element_field(src)) != NULL && ( sg2->skill_id == SA_VOLCANO || sg2->skill_id == SA_DELUGE || sg2->skill_id == SA_VIOLENTGALE ))
 		{
 			if (sg2->limit - DIFF_TICK(gettick(), sg2->tick) > 0)
@@ -12715,9 +12712,9 @@ int skill_castend_pos2(struct block_list* src, int x, int y, uint16 skill_id, ui
 		break;
 
 	case SC_FEINTBOMB: {
-			struct skill_unit_group *group = skill_unitsetting(src,skill_id,skill_lv,x,y,0); // Set bomb on current Position
+			std::shared_ptr<s_skill_unit_group> group = skill_unitsetting(src,skill_id,skill_lv,x,y,0); // Set bomb on current Position
 
-			if( group == NULL || group->unit == NULL ) {
+			if( group == nullptr || group->unit == nullptr ) {
 				clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
 				return 1;
 			}
@@ -12770,16 +12767,15 @@ int skill_castend_pos2(struct block_list* src, int x, int y, uint16 skill_id, ui
 		}
 		break;
 	case GN_FIRE_EXPANSION: {
-		int i_su;
 		struct unit_data *ud = unit_bl2ud(src);
 
 		if( !ud ) break;
 
-		for(i_su = 0; i_su < MAX_SKILLUNITGROUP && ud->skillunit[i_su]; i_su++) {
-			struct skill_unit *su = ud->skillunit[i_su]->unit;
-			struct skill_unit_group *sg = ud->skillunit[i_su]->unit->group;
+		for (const auto itsu : ud->skillunits) {
+			skill_unit *su = itsu->unit;
+			std::shared_ptr<s_skill_unit_group> sg = itsu->unit->group;
 
-			if (ud->skillunit[i_su]->skill_id == GN_DEMONIC_FIRE && distance_xy(x, y, su->bl.x, su->bl.y) < 4) {
+			if (itsu->skill_id == GN_DEMONIC_FIRE && distance_xy(x, y, su->bl.x, su->bl.y) < 4) {
 				switch (skill_lv) {
 					case 1: {
 							// TODO:
@@ -12987,7 +12983,7 @@ int skill_castend_map (struct map_session_data *sd, uint16 skill_id, const char
 	case AL_WARP:
 		{
 			const struct point *p[4];
-			struct skill_unit_group *group;
+			std::shared_ptr<s_skill_unit_group> group;
 			int i, lv, wx, wy;
 			int maxcount=0;
 			int x,y;
@@ -13005,11 +13001,9 @@ int skill_castend_map (struct map_session_data *sd, uint16 skill_id, const char
 			p[3] = &sd->status.memo_point[2];
 
 			if((maxcount = skill_get_maxcount(skill_id, sd->menuskill_val)) > 0) {
-				for(i=0;i<MAX_SKILLUNITGROUP && sd->ud.skillunit[i] && maxcount;i++) {
-					if(sd->ud.skillunit[i]->skill_id == skill_id)
-						maxcount--;
-				}
-				if(!maxcount) {
+				unit_skillunit_maxcount(sd->ud, skill_id, maxcount);
+
+				if (maxcount == 0) {
 					clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
 					skill_failed(sd);
 					return 0;
@@ -13042,7 +13036,7 @@ int skill_castend_map (struct map_session_data *sd, uint16 skill_id, const char
 			skill_consume_requirement(sd,sd->menuskill_id,lv,2);
 			sd->skillitem = sd->skillitemlv = sd->skillitem_keep_requirement = 0; // Clear data that's skipped in 'skill_castend_pos' [Inkfish]
 
-			if((group=skill_unitsetting(&sd->bl,skill_id,lv,wx,wy,0))==NULL) {
+			if((group=skill_unitsetting(&sd->bl,skill_id,lv,wx,wy,0))==nullptr) {
 				skill_failed(sd);
 				return 0;
 			}
@@ -13116,10 +13110,10 @@ int skill_dance_overlap(struct skill_unit* unit, int flag)
 static bool skill_dance_switch(struct skill_unit* unit, int flag)
 {
 	static int prevflag = 1;  // by default the backup is empty
-	static struct skill_unit_group backup;
-	struct skill_unit_group* group;
+	static s_skill_unit_group backup;
+	std::shared_ptr<s_skill_unit_group> group;
 
-	if( unit == NULL || (group = unit->group) == NULL )
+	if( unit == nullptr || (group = unit->group) == nullptr )
 		return false;
 
 	//val2&(1 << UF_ENSEMBLE) is a hack to indicate dissonance
@@ -13174,11 +13168,11 @@ static bool skill_dance_switch(struct skill_unit* unit, int flag)
  * @param y Position y
  * @param flag &1: Used to determine when the skill 'morphs' (Warp portal becomes active, or Fire Pillar becomes active)
  *		xx_METEOR: flag &1 contains if the unit can cause curse, flag is also the duration of the unit in milliseconds
- * @return skill_unit_group
+ * @return s_skill_unit_group
  */
-struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_id, uint16 skill_lv, int16 x, int16 y, int flag)
+std::shared_ptr<s_skill_unit_group> skill_unitsetting(struct block_list *src, uint16 skill_id, uint16 skill_lv, int16 x, int16 y, int flag)
 {
-	struct skill_unit_group *group;
+	std::shared_ptr<s_skill_unit_group> group;
 	int i, val1 = 0, val2 = 0, val3 = 0;
 	t_tick limit;
 	int link_group_id = 0;
@@ -13193,7 +13187,7 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_
 	bool hidden = false;
 	struct map_data *mapdata;
 
-	nullpo_retr(NULL, src);
+	nullpo_retr(nullptr, src);
 
 	std::shared_ptr<s_skill_db> skill = skill_db.find(skill_id);
 
@@ -13232,10 +13226,10 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_
 			limit=2000;
 		else // previous implementation (not used anymore)
 		{	//Warp Portal morphing to active mode, extract relevant data from src. [Skotlex]
-			if( src->type != BL_SKILL ) return NULL;
+			if( src->type != BL_SKILL ) return nullptr;
 			group = ((TBL_SKILL*)src)->group;
 			src = map_id2bl(group->src_id);
-			if( !src ) return NULL;
+			if( !src ) return nullptr;
 			val2 = group->val2; //Copy the (x,y) position you warp to
 			val3 = group->val3; //as well as the mapindex to warp to.
 		}
@@ -13258,7 +13252,7 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_
 		break;
 	case WZ_FIREPILLAR:
 		if( map_getcell(src->m, x, y, CELL_CHKLANDPROTECTOR) )
-			return NULL;
+			return nullptr;
 		if((flag&1)!=0)
 			limit=1000;
 		val1=skill_lv+2;
@@ -13311,8 +13305,9 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_
 	case SA_VIOLENTGALE:
 	case SC_CHAOSPANIC:
 	{
-		struct skill_unit_group *old_sg;
-		if ((old_sg = skill_locate_element_field(src)) != NULL)
+		std::shared_ptr<s_skill_unit_group> old_sg = skill_locate_element_field(src);
+
+		if (old_sg != nullptr)
 		{	//HelloKitty confirmed that these are interchangeable,
 			//so you can change element and not consume gemstones.
 			if ((
@@ -13460,7 +13455,7 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_
 		}
 	case GC_POISONSMOKE:
 		if( !(sc && sc->data[SC_POISONINGWEAPON]) )
-			return NULL;
+			return nullptr;
 		val2 = sc->data[SC_POISONINGWEAPON]->val2; // Type of Poison
 		val3 = sc->data[SC_POISONINGWEAPON]->val1;
 		limit = skill_get_time(skill_id, skill_lv);
@@ -13490,7 +13485,7 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_
 	case SO_WIND_INSIGNIA:
 	case SO_EARTH_INSIGNIA:
 		if( map_getcell(src->m, x, y, CELL_CHKLANDPROTECTOR) )
-			return NULL;
+			return nullptr;
 		break;
 	case SO_CLOUD_KILL:
 	case NPC_CLOUD_KILL:
@@ -13551,7 +13546,11 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_
 	}
 
 	// Init skill unit group
-	nullpo_retr(NULL, (group = skill_initunitgroup(src,layout->count,skill_id,skill_lv,(flag & 1 ? skill->unit_id2 : skill->unit_id)+subunt, limit, interval)));
+	group = skill_initunitgroup(src, layout->count, skill_id, skill_lv, (flag & 1 ? skill->unit_id2 : skill->unit_id) + subunt, limit, interval);
+
+	if (group == nullptr)
+		return nullptr;
+
 	group->val1 = val1;
 	group->val2 = val2;
 	group->val3 = val3;
@@ -13687,7 +13686,7 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_
 		if( !alive )
 			continue;
 
-		nullpo_retr(NULL, (unit = skill_initunit(group,i,ux,uy,unit_val1,unit_val2,hidden)));
+		nullpo_retr(nullptr, (unit = skill_initunit(group,i,ux,uy,unit_val1,unit_val2,hidden)));
 		unit->limit = limit;
 		unit->range = range;
 
@@ -13705,7 +13704,7 @@ struct skill_unit_group *skill_unitsetting(struct block_list *src, uint16 skill_
 	if (!group->alive_count)
 	{	//No cells? Something that was blocked completely by Land Protector?
 		skill_delunitgroup(group);
-		return NULL;
+		return nullptr;
 	}
 
 	//success, unit created.
@@ -13736,7 +13735,7 @@ void ext_skill_unit_onplace(struct skill_unit *unit, struct block_list *bl, t_ti
  */
 static int skill_unit_onplace(struct skill_unit *unit, struct block_list *bl, t_tick tick)
 {
-	struct skill_unit_group *sg;
+	
 	struct block_list *ss; // Actual source that cast the skill unit
 	struct status_change *sc;
 	struct status_change_entry *sce;
@@ -13750,7 +13749,11 @@ static int skill_unit_onplace(struct skill_unit *unit, struct block_list *bl, t_
 	if(bl->prev == NULL || !unit->alive || status_isdead(bl))
 		return 0;
 
-	nullpo_ret(sg = unit->group);
+	std::shared_ptr<s_skill_unit_group> sg = unit->group;
+
+	if (sg == nullptr)
+		return 0;
+
 	nullpo_ret(ss = map_id2bl(sg->src_id));
 
 	tstatus = status_get_status_data(bl);
@@ -14078,7 +14081,6 @@ static int skill_unit_onplace(struct skill_unit *unit, struct block_list *bl, t_
  */
 int skill_unit_onplace_timer(struct skill_unit *unit, struct block_list *bl, t_tick tick)
 {
-	struct skill_unit_group *sg;
 	struct block_list *ss;
 	TBL_PC* tsd;
 	struct status_data *tstatus;
@@ -14094,7 +14096,11 @@ int skill_unit_onplace_timer(struct skill_unit *unit, struct block_list *bl, t_t
 	if (bl->prev == NULL || !unit->alive || status_isdead(bl))
 		return 0;
 
-	nullpo_ret(sg = unit->group);
+	std::shared_ptr<s_skill_unit_group> sg = unit->group;
+
+	if (sg == nullptr)
+		return 0;
+
 	nullpo_ret(ss = map_id2bl(sg->src_id));
 
 	tsd = BL_CAST(BL_PC, bl);
@@ -14800,14 +14806,18 @@ int skill_unit_onplace_timer(struct skill_unit *unit, struct block_list *bl, t_t
  */
 int skill_unit_onout(struct skill_unit *src, struct block_list *bl, t_tick tick)
 {
-	struct skill_unit_group *sg;
 	struct status_change *sc;
 	struct status_change_entry *sce;
 	enum sc_type type;
 
 	nullpo_ret(src);
 	nullpo_ret(bl);
-	nullpo_ret(sg=src->group);
+
+	std::shared_ptr<s_skill_unit_group> sg = src->group;
+
+	if (sg == nullptr)
+		return 0;
+
 	sc = status_get_sc(bl);
 	type = status_skill2sc(sg->skill_id);
 	sce = (sc && type != -1)?sc->data[type]:NULL;
@@ -15008,7 +15018,6 @@ int skill_unit_onleft(uint16 skill_id, struct block_list *bl, t_tick tick)
 static int skill_unit_effect(struct block_list* bl, va_list ap)
 {
 	struct skill_unit* unit = va_arg(ap,struct skill_unit*);
-	struct skill_unit_group* group = unit->group;
 	t_tick tick = va_arg(ap,t_tick);
 	unsigned int flag = va_arg(ap,unsigned int);
 	uint16 skill_id;
@@ -15018,7 +15027,10 @@ static int skill_unit_effect(struct block_list* bl, va_list ap)
 	if( (!unit->alive && !(flag&4)) || bl->prev == NULL )
 		return 0;
 
-	nullpo_ret(group);
+	std::shared_ptr<s_skill_unit_group> group = unit->group;
+
+	if (group == nullptr)
+		return 0;
 
 	if( !(flag&8) ) {
 		dissonance = skill_dance_switch(unit, 0);
@@ -15058,10 +15070,12 @@ static int skill_unit_effect(struct block_list* bl, va_list ap)
  */
 int64 skill_unit_ondamaged(struct skill_unit *unit, int64 damage)
 {
-	struct skill_unit_group *sg;
-
 	nullpo_ret(unit);
-	nullpo_ret(sg = unit->group);
+
+	std::shared_ptr<s_skill_unit_group> sg = unit->group;
+
+	if (sg == nullptr)
+		return 0;
 
 	switch( sg->unit_id ) {
 		case UNT_BLASTMINE:
@@ -17848,18 +17862,17 @@ int skill_attack_area(struct block_list *bl, va_list ap)
  */
 int skill_clear_group(struct block_list *bl, int flag)
 {
-	struct unit_data *ud = NULL;
-	struct skill_unit_group *group[MAX_SKILLUNITGROUP];
-	int i, count = 0;
-
 	nullpo_ret(bl);
 
-	if (!(ud = unit_bl2ud(bl)))
+	unit_data *ud = unit_bl2ud(bl);
+
+	if (ud == nullptr)
 		return 0;
 
-	// All groups to be deleted are first stored on an array since the array elements shift around when you delete them. [Skotlex]
-	for (i = 0; i < MAX_SKILLUNITGROUP && ud->skillunit[i]; i++) {
-		switch (ud->skillunit[i]->skill_id) {
+	size_t count = 0;
+
+	for (auto it = ud->skillunits.begin(); it != ud->skillunits.end(); it++) {
+		switch ((*it)->skill_id) {
 			case SA_DELUGE:
 			case SA_VOLCANO:
 			case SA_VIOLENTGALE:
@@ -17869,47 +17882,52 @@ int skill_clear_group(struct block_list *bl, int flag)
 			case SC_CHAOSPANIC:
 			case MH_POISON_MIST:
 			case MH_LAVA_SLIDE:
-				if (flag&1)
-					group[count++] = ud->skillunit[i];
+				if (flag & 1) {
+					skill_delunitgroup(*it);
+					count++;
+				}
 				break;
 			case SO_CLOUD_KILL:
 			case NPC_CLOUD_KILL:
-				if( flag&4 )
-					group[count++] = ud->skillunit[i];
+				if (flag & 4) {
+					skill_delunitgroup(*it);
+					count++;
+				}
 				break;
 			case SO_WARMER:
-				if( flag&8 )
-					group[count++] = ud->skillunit[i];
+				if (flag & 8) {
+					skill_delunitgroup(*it);
+					count++;
+				}
 				break;
 			default:
-				if (flag&2 && skill_get_inf2(ud->skillunit[i]->skill_id, INF2_ISTRAP))
-					group[count++] = ud->skillunit[i];
+				if (flag & 2 && skill_get_inf2((*it)->skill_id, INF2_ISTRAP)) {
+					skill_delunitgroup(*it);
+					count++;
+				}
 				break;
 		}
-
 	}
-	for (i = 0; i < count; i++)
-		skill_delunitgroup(group[i]);
-	return count;
+
+	return static_cast<int>(count);
 }
 
 /**
  * Returns the first element field found [Skotlex]
  * @param bl
- * @return skill_unit_group
+ * @return s_skill_unit_group
  */
-struct skill_unit_group *skill_locate_element_field(struct block_list *bl)
+std::shared_ptr<s_skill_unit_group> skill_locate_element_field(struct block_list *bl)
 {
-	struct unit_data *ud = NULL;
-	int i;
-
 	nullpo_ret(bl);
 
-	if (!(ud = unit_bl2ud(bl)))
-		return NULL;
+	unit_data *ud = unit_bl2ud(bl);
+
+	if (ud == nullptr)
+		return nullptr;
 
-	for (i = 0; i < MAX_SKILLUNITGROUP && ud->skillunit[i]; i++) {
-		switch (ud->skillunit[i]->skill_id) {
+	for (const auto su : ud->skillunits) {
+		switch (su->skill_id) {
 			case SA_DELUGE:
 			case SA_VOLCANO:
 			case SA_VIOLENTGALE:
@@ -17921,10 +17939,10 @@ struct skill_unit_group *skill_locate_element_field(struct block_list *bl)
 			case SC_CHAOSPANIC:
 			case MH_POISON_MIST:
 			case MH_LAVA_SLIDE:
-				return ud->skillunit[i];
+				return su;
 		}
 	}
-	return NULL;
+	return nullptr;
 }
 
 /// Graffiti cleaner [Valaris]
@@ -17977,7 +17995,7 @@ int skill_detonator(struct block_list *bl, va_list ap)
 	if (unit == nullptr)
 		return 0;
 
-	skill_unit_group *group = unit->group;
+	std::shared_ptr<s_skill_unit_group> group = unit->group;
 
 	if (group == nullptr || group->src_id != src->id)
 		return 0;
@@ -18198,7 +18216,6 @@ static int skill_trap_splash(struct block_list *bl, va_list ap)
 	struct block_list *src = va_arg(ap,struct block_list *);
 	struct skill_unit *unit = NULL;
 	t_tick tick = va_arg(ap,t_tick);
-	struct skill_unit_group *sg;
 	struct block_list *ss; //Skill src bl
 
 	nullpo_ret(src);
@@ -18208,7 +18225,11 @@ static int skill_trap_splash(struct block_list *bl, va_list ap)
 	if (!unit || !unit->alive || bl->prev == NULL)
 		return 0;
 
-	nullpo_ret(sg = unit->group);
+	std::shared_ptr<s_skill_unit_group> sg = unit->group;
+
+	if (sg == nullptr)
+		return 0;
+
 	nullpo_ret(ss = map_id2bl(sg->src_id));
 
 	if (battle_check_target(src,bl,sg->target_flag) <= 0)
@@ -18574,13 +18595,15 @@ void skill_getareachar_skillunit_visibilty_single(struct skill_unit *su, struct
  * @param val1
  * @param val2
  */
-struct skill_unit *skill_initunit(struct skill_unit_group *group, int idx, int x, int y, int val1, int val2, bool hidden)
+struct skill_unit *skill_initunit(std::shared_ptr<s_skill_unit_group> group, int idx, int x, int y, int val1, int val2, bool hidden)
 {
-	struct skill_unit *unit;
+	if (group == nullptr || group->unit == nullptr)
+		return nullptr;
+
+	skill_unit *unit = &group->unit[idx];
 
-	nullpo_retr(NULL, group);
-	nullpo_retr(NULL, group->unit); // crash-protection against poor coding
-	nullpo_retr(NULL, (unit = &group->unit[idx]));
+	if (unit == nullptr)
+		return nullptr;
 
 	if( map_getcell(map_id2bl(group->src_id)->m, x, y, CELL_CHKMAELSTROM) )
 		return unit;
@@ -18639,8 +18662,6 @@ struct skill_unit *skill_initunit(struct skill_unit_group *group, int idx, int x
  */
 int skill_delunit(struct skill_unit* unit)
 {
-	struct skill_unit_group *group;
-
 	nullpo_ret(unit);
 
 	if( !unit->alive )
@@ -18648,7 +18669,10 @@ int skill_delunit(struct skill_unit* unit)
 
 	unit->alive = 0;
 
-	nullpo_ret(group = unit->group);
+	std::shared_ptr<s_skill_unit_group> group = unit->group;
+
+	if (group == nullptr)
+		return 0;
 
 	if( group->state.song_dance&0x1 ) //Cancel dissonance effect.
 		skill_dance_overlap(unit, 0);
@@ -18714,11 +18738,11 @@ int skill_delunit(struct skill_unit* unit)
 }
 
 
-static DBMap* skillunit_group_db = NULL; /// Skill unit group DB. Key int group_id -> struct skill_unit_group*
+static std::unordered_map<int, std::shared_ptr<s_skill_unit_group>> skillunit_group_db; /// Skill unit group DB. Key int group_id -> struct s_skill_unit_group*
 
-/// Returns the target skill_unit_group or NULL if not found.
-struct skill_unit_group* skill_id2group(int group_id) {
-	return (struct skill_unit_group*)idb_get(skillunit_group_db, group_id);
+/// Returns the target s_skill_unit_group or nullptr if not found.
+std::shared_ptr<s_skill_unit_group> skill_id2group(int group_id) {
+	return util::umap_find(skillunit_group_db, group_id);
 }
 
 static int skill_unit_group_newid = MAX_SKILL; /// Skill Unit Group ID
@@ -18755,46 +18779,28 @@ static int skill_get_new_group_id(void)
  * @param unit_id Unit ID (see skill.hpp::e_skill_unit_id)
  * @param limit Lifetime for skill unit, uses skill_get_time(skill_id, skill_lv)
  * @param interval Time interval
- * @return skill_unit_group
+ * @return s_skill_unit_group
  */
-struct skill_unit_group* skill_initunitgroup(struct block_list* src, int count, uint16 skill_id, uint16 skill_lv, int unit_id, t_tick limit, int interval)
+std::shared_ptr<s_skill_unit_group> skill_initunitgroup(struct block_list* src, int count, uint16 skill_id, uint16 skill_lv, int unit_id, t_tick limit, int interval)
 {
-	struct unit_data* ud = unit_bl2ud( src );
-	struct skill_unit_group* group;
-	int i;
+	nullpo_retr(nullptr, src);
 
-	if(!(skill_id && skill_lv)) return 0;
+	unit_data *ud = unit_bl2ud(src);
 
-	nullpo_retr(NULL, src);
-	nullpo_retr(NULL, ud);
+	nullpo_retr(nullptr, ud);
 
-	// Find a free spot to store the new unit group
-	// TODO: Make this flexible maybe by changing this fixed array?
-	ARR_FIND( 0, MAX_SKILLUNITGROUP, i, ud->skillunit[i] == NULL );
-	if(i == MAX_SKILLUNITGROUP) {
-		// Array is full, make room by discarding oldest group
-		int j = 0;
-		t_tick maxdiff = 0, tick = gettick();
-		for(i = 0; i < MAX_SKILLUNITGROUP && ud->skillunit[i];i++){
-			t_tick x = DIFF_TICK(tick,ud->skillunit[i]->tick);
-			if(x > maxdiff){
-				maxdiff = x;
-				j = i;
-			}
-		}
-		skill_delunitgroup(ud->skillunit[j]);
-		// Since elements must have shifted, we use the last slot.
-		i = MAX_SKILLUNITGROUP-1;
-	}
+	if (skill_id == 0 || skill_lv == 0)
+		return 0;
+
+	auto group = std::make_shared<s_skill_unit_group>();
 
-	group             = ers_alloc(skill_unit_ers, struct skill_unit_group);
 	group->src_id     = src->id;
 	group->party_id   = status_get_party_id(src);
 	group->guild_id   = status_get_guild_id(src);
 	group->bg_id      = bg_team_get_id(src);
 	group->group_id   = skill_get_new_group_id();
 	group->link_group_id = 0;
-	group->unit       = (struct skill_unit *)aCalloc(count,sizeof(struct skill_unit));
+	group->unit       = (skill_unit *)aCalloc(count, sizeof(skill_unit));
 	group->unit_count = count;
 	group->alive_count = 0;
 	group->val1       = 0;
@@ -18807,12 +18813,13 @@ struct skill_unit_group* skill_initunitgroup(struct block_list* src, int count,
 	group->limit      = limit;
 	group->interval   = interval;
 	group->tick       = gettick();
-	group->valstr     = NULL;
+	group->valstr     = nullptr;
+
+	ud->skillunits.push_back(group);
 
-	ud->skillunit[i] = group;
+	// Stores this new group
+	skillunit_group_db.insert({ group->group_id, group });
 
-	// Stores this new group to DBMap
-	idb_put(skillunit_group_db, group->group_id, group);
 	return group;
 }
 
@@ -18823,14 +18830,14 @@ struct skill_unit_group* skill_initunitgroup(struct block_list* src, int count,
  * @param line
  * @param *func
  */
-int skill_delunitgroup_(struct skill_unit_group *group, const char* file, int line, const char* func)
+int skill_delunitgroup_(std::shared_ptr<s_skill_unit_group> group, const char* file, int line, const char* func)
 {
 	struct block_list* src;
 	struct unit_data *ud;
-	short i, j;
+	short i;
 	int link_group_id;
 
-	if( group == NULL ) {
+	if( group == nullptr ) {
 		ShowDebug("skill_delunitgroup: group is NULL (source=%s:%d, %s)! Please report this! (#3504)\n", file, line, func);
 		return 0;
 	}
@@ -18962,28 +18969,14 @@ int skill_delunitgroup_(struct skill_unit_group *group, const char* file, int li
 		group->valstr = NULL;
 	}
 
-	idb_remove(skillunit_group_db, group->group_id);
-	map_freeblock(&group->unit->bl); // schedules deallocation of whole array (HACK)
-	group->unit = NULL;
-	group->group_id = 0;
-	group->unit_count = 0;
-
 	link_group_id = group->link_group_id;
-	group->link_group_id = 0;
 
-	// locate this group, swap with the last entry and delete it
-	ARR_FIND( 0, MAX_SKILLUNITGROUP, i, ud->skillunit[i] == group );
-	ARR_FIND( i, MAX_SKILLUNITGROUP, j, ud->skillunit[j] == NULL );
-	 j--;
-	if( i < MAX_SKILLUNITGROUP ) {
-		ud->skillunit[i] = ud->skillunit[j];
-		ud->skillunit[j] = NULL;
-		ers_free(skill_unit_ers, group);
-	} else
+	if (skillunit_group_db.erase(group->group_id) != 1)
 		ShowError("skill_delunitgroup: Group not found! (src_id: %d skill_id: %d)\n", group->src_id, group->skill_id);
 
 	if(link_group_id) {
-		struct skill_unit_group* group_cur = skill_id2group(link_group_id);
+		std::shared_ptr<s_skill_unit_group> group_cur = skill_id2group(link_group_id);
+
 		if(group_cur)
 			skill_delunitgroup(group_cur);
 	}
@@ -18997,13 +18990,15 @@ int skill_delunitgroup_(struct skill_unit_group *group, const char* file, int li
  */
 void skill_clear_unitgroup(struct block_list *src)
 {
-	struct unit_data *ud;
-
 	nullpo_retv(src);
-	nullpo_retv((ud = unit_bl2ud(src)));
 
-	while (ud->skillunit[0])
-		skill_delunitgroup(ud->skillunit[0]);
+	unit_data *ud = unit_bl2ud(src);
+
+	nullpo_retv(ud);
+
+	for (auto it = ud->skillunits.begin(); it != ud->skillunits.end(); it++) {
+		skill_delunitgroup(*it);
+	}
 }
 
 /**
@@ -19013,7 +19008,7 @@ void skill_clear_unitgroup(struct block_list *src)
  * @param tick
  * @return skill_unit_group_tickset if found
  */
-struct skill_unit_group_tickset *skill_unitgrouptickset_search(struct block_list *bl, struct skill_unit_group *group, t_tick tick)
+struct skill_unit_group_tickset *skill_unitgrouptickset_search(struct block_list *bl, std::shared_ptr<s_skill_unit_group> group, t_tick tick)
 {
 	int i, j = -1, s, id;
 	struct unit_data *ud;
@@ -19059,7 +19054,6 @@ struct skill_unit_group_tickset *skill_unitgrouptickset_search(struct block_list
 int skill_unit_timer_sub_onplace(struct block_list* bl, va_list ap)
 {
 	struct skill_unit* unit = va_arg(ap,struct skill_unit *);
-	struct skill_unit_group* group = NULL;
 	t_tick tick = va_arg(ap,t_tick);
 
 	nullpo_ret(unit);
@@ -19067,7 +19061,10 @@ int skill_unit_timer_sub_onplace(struct block_list* bl, va_list ap)
 	if( !unit->alive || bl->prev == NULL )
 		return 0;
 
-	nullpo_ret(group = unit->group);
+	std::shared_ptr<s_skill_unit_group> group = unit->group;
+
+	if (group == nullptr)
+		return 0;
 
 	std::shared_ptr<s_skill_db> skill = skill_db.find(group->skill_id);
 
@@ -19088,7 +19085,6 @@ int skill_unit_timer_sub_onplace(struct block_list* bl, va_list ap)
 static int skill_unit_timer_sub(DBKey key, DBData *data, va_list ap)
 {
 	struct skill_unit* unit = (struct skill_unit*)db_data2ptr(data);
-	struct skill_unit_group* group = NULL;
 	t_tick tick = va_arg(ap,t_tick);
 	bool dissonance;
 	struct block_list* bl = &unit->bl;
@@ -19098,7 +19094,10 @@ static int skill_unit_timer_sub(DBKey key, DBData *data, va_list ap)
 	if( !unit->alive )
 		return 0;
 
-	nullpo_ret(group = unit->group);
+	std::shared_ptr<s_skill_unit_group> group = unit->group;
+
+	if (group == nullptr)
+		return 0;
 
 	// Check for expiration
 	if( !group->state.guildaura && (DIFF_TICK(tick,group->tick) >= group->limit || DIFF_TICK(tick,group->tick) >= unit->limit) )
@@ -19370,7 +19369,6 @@ static int skill_unit_temp[20];  // temporary storage for tracking skill unit sk
 int skill_unit_move_sub(struct block_list* bl, va_list ap)
 {
 	struct skill_unit* unit = (struct skill_unit *)bl;
-	struct skill_unit_group* group = NULL;
 
 	struct block_list* target = va_arg(ap,struct block_list*);
 	t_tick tick = va_arg(ap,t_tick);
@@ -19385,7 +19383,10 @@ int skill_unit_move_sub(struct block_list* bl, va_list ap)
 	if( !unit->alive || target->prev == NULL )
 		return 0;
 
-	nullpo_ret(group = unit->group);
+	std::shared_ptr<s_skill_unit_group> group = unit->group;
+
+	if (group == nullptr)
+		return 0;
 
 	if( flag&1 && ( group->skill_id == PF_SPIDERWEB || group->skill_id == GN_THORNS_TRAP ) )
 		return 0; // Fiberlock is never supposed to trigger on skill_unit_move. [Inkfish]
@@ -19536,7 +19537,7 @@ void skill_unit_move_unit(struct block_list *bl, int dx, int dy) {
  * @param dx
  * @param dy
  */
-void skill_unit_move_unit_group(struct skill_unit_group *group, int16 m, int16 dx, int16 dy)
+void skill_unit_move_unit_group(std::shared_ptr<s_skill_unit_group> group, int16 m, int16 dx, int16 dy)
 {
 	int i, j;
 	t_tick tick = gettick();
@@ -20727,13 +20728,12 @@ int skill_changematerial(struct map_session_data *sd, int n, unsigned short *ite
  */
 static int skill_destroy_trap(struct block_list *bl, va_list ap)
 {
-	struct skill_unit *su = (struct skill_unit *)bl;
-	struct skill_unit_group *sg = NULL;
-	t_tick tick;
+	skill_unit *su = (struct skill_unit *)bl;
 
 	nullpo_ret(su);
 
-	tick = va_arg(ap, t_tick);
+	std::shared_ptr<s_skill_unit_group> sg;
+	t_tick tick = va_arg(ap, t_tick);
 
 	if (su->alive && (sg = su->group) && skill_get_inf2(sg->skill_id, INF2_ISTRAP)) {
 		switch( sg->unit_id ) {
@@ -20934,8 +20934,8 @@ void skill_usave_add(struct map_session_data *sd, uint16 skill_id, uint16 skill_
  */
 void skill_usave_trigger(struct map_session_data *sd)
 {
-	struct skill_usave *sus = NULL;
-	struct skill_unit_group *group = NULL;
+	skill_usave *sus;
+	std::shared_ptr<s_skill_unit_group> group;
 
 	if (!(sus = static_cast<skill_usave *>(idb_get(skillusave_db,sd->status.char_id))))
 		return;
@@ -23074,14 +23074,11 @@ void do_init_skill(void)
 {
 	skill_readdb();
 
-	skillunit_group_db = idb_alloc(DB_OPT_BASE);
 	skillunit_db = idb_alloc(DB_OPT_BASE);
 	skillusave_db = idb_alloc(DB_OPT_RELEASE_DATA);
 	bowling_db = idb_alloc(DB_OPT_BASE);
-	skill_unit_ers = ers_new(sizeof(struct skill_unit_group),"skill.cpp::skill_unit_ers",ERS_CACHE_OPTIONS);
 	skill_timer_ers  = ers_new(sizeof(struct skill_timerskill),"skill.cpp::skill_timer_ers",ERS_CACHE_OPTIONS);
 
-	ers_chunk_size(skill_unit_ers, 150);
 	ers_chunk_size(skill_timer_ers, 150);
 
 	add_timer_func_list(skill_unit_timer,"skill_unit_timer");
@@ -23102,10 +23099,8 @@ void do_final_skill(void)
 	reading_spellbook_db.clear();
 	skill_arrow_db.clear();
 
-	db_destroy(skillunit_group_db);
 	db_destroy(skillunit_db);
 	db_destroy(skillusave_db);
 	db_destroy(bowling_db);
-	ers_destroy(skill_unit_ers);
 	ers_destroy(skill_timer_ers);
 }

+ 23 - 19
src/map/skill.hpp

@@ -24,7 +24,7 @@ enum e_battle_check_target : uint32;
 struct map_session_data;
 struct homun_data;
 struct skill_unit;
-struct skill_unit_group;
+struct s_skill_unit_group;
 struct status_change_entry;
 
 #define MAX_SKILL_PRODUCE_DB	282 /// Max Produce DB
@@ -340,9 +340,19 @@ struct skill_timerskill {
 	int flag;
 };
 
-#define MAX_SKILLUNITGROUP 25 /// Maximum skill unit group (for same skill each source)
+/// Skill unit
+struct skill_unit {
+	struct block_list bl;
+	std::shared_ptr<s_skill_unit_group> group; /// Skill group reference
+	t_tick limit;
+	int val1, val2;
+	short range;
+	bool alive;
+	bool hidden;
+};
+
 /// Skill unit group
-struct skill_unit_group {
+struct s_skill_unit_group {
 	int src_id; /// Caster ID/RID, if player is account_id
 	int party_id; /// Party ID
 	int guild_id; /// Guild ID
@@ -369,17 +379,11 @@ struct skill_unit_group {
 		unsigned song_dance : 2; //0x1 Song/Dance, 0x2 Ensemble
 		unsigned guildaura : 1; // Guild Aura
 	} state;
-};
 
-/// Skill unit
-struct skill_unit {
-	struct block_list bl;
-	struct skill_unit_group *group; /// Skill group reference
-	t_tick limit;
-	int val1, val2;
-	short range;
-	unsigned alive : 1;
-	unsigned hidden : 1;
+	~s_skill_unit_group() {
+		if (this->unit)
+			map_freeblock(&this->unit->bl); // schedules deallocation of whole array (HACK)
+	}
 };
 
 #define MAX_SKILLUNITGROUPTICKSET 25
@@ -556,12 +560,12 @@ short skill_blown(struct block_list* src, struct block_list* target, char count,
 int skill_break_equip(struct block_list *src,struct block_list *bl, unsigned short where, int rate, int flag);
 int skill_strip_equip(struct block_list *src,struct block_list *bl, unsigned short where, int rate, int lv, int time);
 // Skills unit
-struct skill_unit_group *skill_id2group(int group_id);
-struct skill_unit_group *skill_unitsetting(struct block_list* src, uint16 skill_id, uint16 skill_lv, int16 x, int16 y, int flag);
-struct skill_unit *skill_initunit (struct skill_unit_group *group, int idx, int x, int y, int val1, int val2, bool hidden);
+std::shared_ptr<s_skill_unit_group> skill_id2group(int group_id);
+std::shared_ptr<s_skill_unit_group> skill_unitsetting(struct block_list* src, uint16 skill_id, uint16 skill_lv, int16 x, int16 y, int flag);
+struct skill_unit *skill_initunit (std::shared_ptr<s_skill_unit_group> group, int idx, int x, int y, int val1, int val2, bool hidden);
 int skill_delunit(struct skill_unit *unit);
-struct skill_unit_group *skill_initunitgroup(struct block_list* src, int count, uint16 skill_id, uint16 skill_lv, int unit_id, t_tick limit, int interval);
-int skill_delunitgroup_(struct skill_unit_group *group, const char* file, int line, const char* func);
+std::shared_ptr<s_skill_unit_group> skill_initunitgroup(struct block_list* src, int count, uint16 skill_id, uint16 skill_lv, int unit_id, t_tick limit, int interval);
+int skill_delunitgroup_(std::shared_ptr<s_skill_unit_group> group, const char* file, int line, const char* func);
 #define skill_delunitgroup(group) skill_delunitgroup_(group,__FILE__,__LINE__,__func__)
 void skill_clear_unitgroup(struct block_list *src);
 int skill_clear_group(struct block_list *bl, int flag);
@@ -591,7 +595,7 @@ bool skill_pos_maxcount_check(struct block_list *src, int16 x, int16 y, uint16 s
 
 int skill_check_pc_partner(struct map_session_data *sd, uint16 skill_id, uint16 *skill_lv, int range, int cast_flag);
 int skill_unit_move(struct block_list *bl,t_tick tick,int flag);
-void skill_unit_move_unit_group( struct skill_unit_group *group, int16 m,int16 dx,int16 dy);
+void skill_unit_move_unit_group( std::shared_ptr<s_skill_unit_group> group, int16 m,int16 dx,int16 dy);
 void skill_unit_move_unit(struct block_list *bl, int dx, int dy);
 
 int skill_sit(struct map_session_data *sd, bool sitting);

+ 15 - 15
src/map/status.cpp

@@ -2356,7 +2356,8 @@ int status_damage(struct block_list *src,struct block_list *target,int64 dhp, in
 			}
 #ifndef RENEWAL
 			if ((sce=sc->data[SC_GRAVITATION]) && sce->val3 == BCT_SELF) {
-				struct skill_unit_group* sg = skill_id2group(sce->val4);
+				std::shared_ptr<s_skill_unit_group> sg = skill_id2group(sce->val4);
+
 				if (sg) {
 					skill_delunitgroup(sg);
 					sce->val4 = 0;
@@ -13422,7 +13423,7 @@ int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const
 			return 0;
 		if (type == SC_SPIDERWEB) {
 			//Delete the unit group first to expire found in the status change
-			struct skill_unit_group *group = NULL, *group2 = NULL;
+			std::shared_ptr<s_skill_unit_group> group, group2;
 			t_tick tick = gettick();
 			int pos = 1;
 			if (sce->val2)
@@ -13620,16 +13621,11 @@ int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const
 				}
 
 				if(sce->val2) { // Erase associated land skill
-					struct skill_unit_group *group;
-					group = skill_id2group(sce->val2);
-					if( group == NULL ) {
-						ShowDebug("status_change_end: SC_DANCING is missing skill unit group (val1=%d, val2=%d, val3=%d, val4=%d, timer=%d, tid=%d, char_id=%d, map=%s, x=%d, y=%d). Please report this!\n",
-							sce->val1, sce->val2, sce->val3, sce->val4, sce->timer, tid,
-							sd ? sd->status.char_id : 0,
-							mapindex_id2name(map_id2index(bl->m)), bl->x, bl->y);
-					}
+					std::shared_ptr<s_skill_unit_group> group = skill_id2group(sce->val2);
+
 					sce->val2 = 0;
-					skill_delunitgroup(group);
+					if (group)
+						skill_delunitgroup(group);
 				}
 
 				if((sce->val1&0xFFFF) == CG_MOONLIT)
@@ -13712,7 +13708,8 @@ int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const
 			break;
 		case SC_GOSPEL:
 			if (sce->val3) { // Clear the group.
-				struct skill_unit_group* group = skill_id2group(sce->val3);
+				std::shared_ptr<s_skill_unit_group> group = skill_id2group(sce->val3);
+
 				sce->val3 = 0;
 				if (group)
 					skill_delunitgroup(group);
@@ -13725,7 +13722,8 @@ int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const
 			break;
 		case SC_BASILICA: // Clear the skill area. [Skotlex]
 				if (sce->val3 && sce->val4 == bl->id) {
-					struct skill_unit_group* group = skill_id2group(sce->val3);
+					std::shared_ptr<s_skill_unit_group> group = skill_id2group(sce->val3);
+
 					sce->val3 = 0;
 					if (group)
 						skill_delunitgroup(group);
@@ -13739,7 +13737,8 @@ int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const
 		case SC__MANHOLE:
 		case SC_BANDING:
 			if (sce->val4) { // Clear the group.
-				struct skill_unit_group* group = skill_id2group(sce->val4);
+				std::shared_ptr<s_skill_unit_group> group = skill_id2group(sce->val4);
+
 				sce->val4 = 0;
 				if( group ) // Might have been cleared before status ended, e.g. land protector
 					skill_delunitgroup(group);
@@ -13825,7 +13824,8 @@ int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const
 		case SC_NEUTRALBARRIER_MASTER:
 		case SC_STEALTHFIELD_MASTER:
 			if( sce->val2 ) {
-				struct skill_unit_group* group = skill_id2group(sce->val2);
+				std::shared_ptr<s_skill_unit_group> group = skill_id2group(sce->val2);
+
 				sce->val2 = 0;
 				if( group ) // Might have been cleared before status ended, e.g. land protector
 					skill_delunitgroup(group);

+ 13 - 0
src/map/unit.cpp

@@ -2910,6 +2910,19 @@ void unit_dataset(struct block_list *bl)
 	ud->canmove_tick   = gettick();
 }
 
+/**
+ * Returns the remaining max amount of skill units per object for a specific skill
+ * @param ud: Unit data
+ * @param skill_id: Skill to search for
+ * @param maxcount: Maximum amount of placeable units
+ */
+void unit_skillunit_maxcount(unit_data& ud, uint16 skill_id, int& maxcount) {
+	for (const auto su : ud.skillunits) {
+		if (su->skill_id == skill_id && --maxcount == 0 )
+			break;
+	}
+}
+
 /**
  * Gets the number of units attacking another unit
  * @param bl: Object to check amount of targets

+ 2 - 1
src/map/unit.hpp

@@ -23,7 +23,7 @@ struct unit_data {
 	struct block_list *bl; ///link to owner object BL_PC|BL_MOB|BL_PET|BL_NPC|BL_HOM|BL_MER|BL_ELEM
 	struct walkpath_data walkpath;
 	struct skill_timerskill *skilltimerskill[MAX_SKILLTIMERSKILL];
-	struct skill_unit_group *skillunit[MAX_SKILLUNITGROUP];
+	std::vector<std::shared_ptr<s_skill_unit_group>> skillunits;
 	struct skill_unit_group_tickset skillunittick[MAX_SKILLUNITGROUPTICKSET];
 	short attacktarget_lv;
 	short to_x, to_y;
@@ -156,6 +156,7 @@ int unit_set_target(struct unit_data* ud, int target_id);
 
 // unit_data
 void unit_dataset(struct block_list *bl);
+void unit_skillunit_maxcount(unit_data& ud, uint16 skill_id, int& maxcount);
 
 // Remove unit
 struct unit_data* unit_bl2ud(struct block_list *bl);