瀏覽代碼

Corrects status icon timer after relog
* Fixes #4984.
* Resolves status icon timers not properly showing the difference of remaining/total time when a player relogs.
* Fixes status icon timers going into negative values.
Thanks to @attackjom!

aleos 4 年之前
父節點
當前提交
da8d77bfc1
共有 10 個文件被更改,包括 248 次插入231 次删除
  1. 1 0
      sql-files/main.sql
  2. 2 0
      sql-files/upgrades/upgrade_20200803.sql
  3. 1 1
      src/char/char.cpp
  4. 9 8
      src/char/char_mapif.cpp
  5. 1 0
      src/common/mmo.hpp
  6. 2 1
      src/map/chrif.cpp
  7. 57 75
      src/map/clif.cpp
  8. 2 1
      src/map/clif.hpp
  9. 171 145
      src/map/status.cpp
  10. 2 0
      src/map/status.hpp

+ 1 - 0
sql-files/main.sql

@@ -910,6 +910,7 @@ CREATE TABLE IF NOT EXISTS `sc_data` (
   `account_id` int(11) unsigned NOT NULL,
   `char_id` int(11) unsigned NOT NULL,
   `type` smallint(11) unsigned NOT NULL,
+  `tick_total` bigint(20) NOT NULL,
   `tick` bigint(20) NOT NULL,
   `val1` int(11) NOT NULL default '0',
   `val2` int(11) NOT NULL default '0',

+ 2 - 0
sql-files/upgrades/upgrade_20200803.sql

@@ -0,0 +1,2 @@
+ALTER TABLE `sc_data` ADD COLUMN `tick_total` BIGINT(20) NOT NULL AFTER `tick`;
+UPDATE `sc_data` SET `tick_total` = `tick`;

+ 1 - 1
src/char/char.cpp

@@ -2338,7 +2338,7 @@ bool char_checkdb(void){
 		return false;
 	}
 	//checking scdata_db
-	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT  `account_id`,`char_id`,`type`,`tick`,`val1`,`val2`,`val3`,`val4`"
+	if( SQL_ERROR == Sql_Query(sql_handle, "SELECT  `account_id`,`char_id`,`type`,`tick`,`tick_total`,`val1`,`val2`,`val3`,`val4`"
 		" FROM `%s` LIMIT 1;", schema_config.scdata_db) ){
 		Sql_ShowDebug(sql_handle);
 		return false;

+ 9 - 8
src/char/char_mapif.cpp

@@ -279,7 +279,7 @@ int chmapif_parse_askscdata(int fd){
 		int aid, cid;
 		aid = RFIFOL(fd,2);
 		cid = RFIFOL(fd,6);
-		if( SQL_ERROR == Sql_Query(sql_handle, "SELECT type, tick, val1, val2, val3, val4 from `%s` WHERE `account_id` = '%d' AND `char_id`='%d'",
+		if( SQL_ERROR == Sql_Query(sql_handle, "SELECT type, tick, tick_total, val1, val2, val3, val4 from `%s` WHERE `account_id` = '%d' AND `char_id`='%d'",
 			schema_config.scdata_db, aid, cid) )
 		{
 			Sql_ShowDebug(sql_handle);
@@ -299,10 +299,11 @@ int chmapif_parse_askscdata(int fd){
 			{
 				Sql_GetData(sql_handle, 0, &data, NULL); scdata.type = atoi(data);
 				Sql_GetData(sql_handle, 1, &data, NULL); scdata.tick = strtoll( data, nullptr, 10 );
-				Sql_GetData(sql_handle, 2, &data, NULL); scdata.val1 = atoi(data);
-				Sql_GetData(sql_handle, 3, &data, NULL); scdata.val2 = atoi(data);
-				Sql_GetData(sql_handle, 4, &data, NULL); scdata.val3 = atoi(data);
-				Sql_GetData(sql_handle, 5, &data, NULL); scdata.val4 = atoi(data);
+				Sql_GetData(sql_handle, 2, &data, NULL); scdata.tick_total = strtoll(data, nullptr, 10);
+				Sql_GetData(sql_handle, 3, &data, NULL); scdata.val1 = atoi(data);
+				Sql_GetData(sql_handle, 4, &data, NULL); scdata.val2 = atoi(data);
+				Sql_GetData(sql_handle, 5, &data, NULL); scdata.val3 = atoi(data);
+				Sql_GetData(sql_handle, 6, &data, NULL); scdata.val4 = atoi(data);
 				memcpy(WFIFOP(fd, 14+count*sizeof(struct status_change_data)), &scdata, sizeof(struct status_change_data));
 			}
 			if (count >= 50)
@@ -942,14 +943,14 @@ int chmapif_parse_save_scdata(int fd){
 			int i;
 
 			StringBuf_Init(&buf);
-			StringBuf_Printf(&buf, "INSERT INTO `%s` (`account_id`, `char_id`, `type`, `tick`, `val1`, `val2`, `val3`, `val4`) VALUES ", schema_config.scdata_db);
+			StringBuf_Printf(&buf, "INSERT INTO `%s` (`account_id`, `char_id`, `type`, `tick`, `tick_total`, `val1`, `val2`, `val3`, `val4`) VALUES ", schema_config.scdata_db);
 			for( i = 0; i < count; ++i )
 			{
 				memcpy (&data, RFIFOP(fd, 14+i*sizeof(struct status_change_data)), sizeof(struct status_change_data));
 				if( i > 0 )
 					StringBuf_AppendStr(&buf, ", ");
-				StringBuf_Printf(&buf, "('%d','%d','%hu','%" PRtf "','%ld','%ld','%ld','%ld')", aid, cid,
-					data.type, data.tick, data.val1, data.val2, data.val3, data.val4);
+				StringBuf_Printf(&buf, "('%d','%d','%hu','%" PRtf "','%" PRtf "','%ld','%ld','%ld','%ld')", aid, cid,
+					data.type, data.tick, data.tick_total, data.val1, data.val2, data.val3, data.val4);
 			}
 			if( SQL_ERROR == Sql_QueryStr(sql_handle, StringBuf_Value(&buf)) )
 				Sql_ShowDebug(sql_handle);

+ 1 - 0
src/common/mmo.hpp

@@ -340,6 +340,7 @@ struct status_change_data {
 	unsigned short type; //SC_type
 	long val1, val2, val3, val4;
 	t_tick tick; //Remaining duration.
+	t_tick tick_total; // Total duration
 };
 
 #define MAX_BONUS_SCRIPT_LENGTH 512

+ 2 - 1
src/map/chrif.cpp

@@ -1330,6 +1330,7 @@ int chrif_save_scdata(struct map_session_data *sd) { //parses the sc_data of the
 				data.tick = 0; //Negative tick does not necessarily mean that sc has expired
 		} else
 			data.tick = INFINITE_TICK; //Infinite duration
+		data.tick_total = sc->data[i]->tick_total;
 		data.type = i;
 		data.val1 = sc->data[i]->val1;
 		data.val2 = sc->data[i]->val2;
@@ -1413,7 +1414,7 @@ int chrif_load_scdata(int fd) {
 	for (i = 0; i < count; i++) {
 		struct status_change_data *data = (struct status_change_data*)RFIFOP(fd,14 + i*sizeof(struct status_change_data));
 
-		status_change_start(NULL,&sd->bl, (sc_type)data->type, 10000, data->val1, data->val2, data->val3, data->val4, data->tick, SCSTART_NOAVOID|SCSTART_NOTICKDEF|SCSTART_LOADED|SCSTART_NORATEDEF);
+		status_change_start_sub(NULL,&sd->bl, (sc_type)data->type, 10000, data->val1, data->val2, data->val3, data->val4, data->tick, data->tick_total, SCSTART_NOAVOID|SCSTART_NOTICKDEF|SCSTART_LOADED|SCSTART_NORATEDEF);
 	}
 
 	pc_scdata_received(sd);

+ 57 - 75
src/map/clif.cpp

@@ -6126,17 +6126,17 @@ void clif_cooking_list( struct map_session_data *sd, int trigger, uint16 skill_i
 /// 0196 <index>.W <id>.L <state>.B (ZC_MSG_STATE_CHANGE) [used for ending status changes and starting them on non-pc units (when needed)]
 /// 043f <index>.W <id>.L <state>.B <remain msec>.L { <val>.L }*3 (ZC_MSG_STATE_CHANGE2) [used exclusively for starting statuses on pcs]
 /// 0983 <index>.W <id>.L <state>.B <total msec>.L <remain msec>.L { <val>.L }*3 (ZC_MSG_STATE_CHANGE3) (PACKETVER >= 20120618)
-/// @param bl Sends packet to clients around this object
-/// @param id ID of object that has this effect
-/// @param type Status icon see enum efst_types
-/// @param flag 1:Active, 0:Deactive
-/// @param tick Duration in ms
-/// @param val1
-/// @param val2
-/// @param val3
-void clif_status_change_sub(struct block_list *bl, int id, int type, int flag, t_tick tick, int val1, int val2, int val3, enum send_target target_type)
+/// @param bl: Sends packet to clients around this object
+/// @param type: Status icon (see efst_types)
+/// @param flag: 1:Active, 0:Inactive
+/// @param tick_total: Total duration in ms
+/// @param tick: Remaining duration in ms
+/// @param val1: Value 1
+/// @param val2: Value 2
+/// @param val3: Value 3
+void clif_status_change_sub(struct block_list *bl, int type, int flag, t_tick tick_total, t_tick tick, int val1, int val2, int val3)
 {
-	unsigned char buf[32];
+	nullpo_retv(bl);
 
 	if (type == EFST_BLANK)  //It shows nothing on the client...
 		return;
@@ -6144,79 +6144,61 @@ void clif_status_change_sub(struct block_list *bl, int id, int type, int flag, t
 	if (type == EFST_POSTDELAY && tick == 0)
 		return;
 
-	nullpo_retv(bl);
+	if (!(status_type2relevant_bl_types(type) & bl->type)) // only send status changes that actually matter to the client
+		return;
 
-	// Statuses with an infinite duration, but still needs a duration sent to display properly.
-	if (type == EFST_LUNARSTANCE || type == EFST_UNIVERSESTANCE || type == EFST_SUNSTANCE || type == EFST_STARSTANCE)
-		tick = 200;
+	packet_status_change p = { 0 };
+	map_session_data *sd = BL_CAST(BL_PC, bl);
 
-#if PACKETVER >= 20120618
-	if (flag && battle_config.display_status_timers)
-		WBUFW(buf,0) = 0x983;
-	else
-#elif PACKETVER >= 20090121
-	if (flag && battle_config.display_status_timers)
-		WBUFW(buf,0) = 0x43f;
-	else
-#endif
-		WBUFW(buf,0) = 0x196;
-	WBUFW(buf,2) = type;
-	WBUFL(buf,4) = id;
-	WBUFB(buf,8) = flag;
-#if PACKETVER >= 20120618
-	if (flag && battle_config.display_status_timers) {
-		if (tick <= 0)
-			tick = 9999; // this is indeed what official servers do
+	if (battle_config.display_status_timers > 0) {
+		// Statuses with an infinite duration, but still needs a duration sent to display properly.
+		switch (type) {
+			case EFST_LUNARSTANCE:
+			case EFST_UNIVERSESTANCE:
+			case EFST_SUNSTANCE:
+			case EFST_STARSTANCE:
+				tick = 200;
+				break;
+			default:
+				if (tick < 0)
+					tick = 9999; // this is indeed what official servers do
+				break;
+		}
 
-		WBUFL(buf,9) = client_tick(tick);/* at this stage remain and total are the same value I believe */
-		WBUFL(buf,13) = client_tick(tick);
-		WBUFL(buf,17) = val1;
-		WBUFL(buf,21) = val2;
-		WBUFL(buf,25) = val3;
-	}
-#elif PACKETVER >= 20090121
-	if (flag && battle_config.display_status_timers) {
-		if (tick <= 0)
-			tick = 9999; // this is indeed what official servers do
+		p.PacketType = status_changeType;
+	} else
+		p.PacketType = sc_notickType;
+	p.index = type;
+	p.AID = bl->id;
+	p.state = (uint8)flag;
 
-		WBUFL(buf,9) = client_tick(tick);
-		WBUFL(buf,13) = val1;
-		WBUFL(buf,17) = val2;
-		WBUFL(buf,21) = val3;
+#if PACKETVER >= 20120618
+	if (battle_config.display_status_timers > 0)
+		p.Total = client_tick(tick_total);
+#endif
+#if PACKETVER >= 20090121
+	if (battle_config.display_status_timers > 0) {
+		p.Left = client_tick(tick);
+		p.val1 = val1;
+		p.val2 = val2;
+		p.val3 = val3;
 	}
 #endif
-	clif_send(buf, packet_len(WBUFW(buf,0)), bl, target_type);
+
+	clif_send(&p, sizeof(p), bl, (sd && sd->status.option & OPTION_INVISIBLE) ? SELF : AREA);
 }
 
 /* Sends status effect to clients around the bl
- * @param bl Object that has the effect
- * @param type Status icon see enum efst_types
- * @param flag 1:Active, 0:Deactive
- * @param tick Duration in ms
- * @param val1
- * @param val2
- * @param val3
+ * @param bl: Object that has the effect
+ * @param type: Status icon (see efst_types)
+ * @param flag: 1:Active, 0:Inactive
+ * @param tick_total: Total duration in ms
+ * @param val1: Value 1
+ * @param val2: Value 2
+ * @param val3: Value 3
  */
-void clif_status_change(struct block_list *bl, int type, int flag, t_tick tick, int val1, int val2, int val3) {
-	struct map_session_data *sd = NULL;
-
-	if (type == EFST_BLANK)  //It shows nothing on the client...
-		return;
-
-	if (type == EFST_POSTDELAY && tick == 0)
-		return;
-
-	if (type == EFST_ILLUSION && !battle_config.display_hallucination) // Disable Hallucination.
-		return;
-
-	nullpo_retv(bl);
-
-	sd = BL_CAST(BL_PC, bl);
-
-	if (!(status_type2relevant_bl_types(type)&bl->type)) // only send status changes that actually matter to the client
-		return;
-
-	clif_status_change_sub(bl, bl->id, type, flag, tick, val1, val2, val3, ((sd ? (pc_isinvisible(sd) ? SELF : AREA) : AREA_WOS)));
+void clif_status_change(struct block_list *bl, int type, int flag, t_tick tick_total, int val1, int val2, int val3) {
+	clif_status_change_sub(bl, type, flag, tick_total, tick_total, val1, val2, val3);
 }
 
 /**
@@ -6267,13 +6249,13 @@ void clif_efst_status_change_sub(struct block_list *tbl, struct block_list *bl,
 #if PACKETVER > 20120418
 			clif_efst_status_change(tbl, bl->id, AREA_WOS, StatusIconChangeTable[type], tick, sc_display[i]->val1, sc_display[i]->val2, sc_display[i]->val3);
 #else
-			clif_status_change_sub(tbl, bl->id, StatusIconChangeTable[type], 1, tick, sc_display[i]->val1, sc_display[i]->val2, sc_display[i]->val3, AREA_WOS);
+			clif_status_change_sub(tbl, StatusIconChangeTable[type], 1, tick, tick, sc_display[i]->val1, sc_display[i]->val2, sc_display[i]->val3, AREA_WOS);
 #endif
 		}else{
 #if PACKETVER > 20120418
 			clif_efst_status_change(tbl, bl->id, target, StatusIconChangeTable[type], tick, sc_display[i]->val1, sc_display[i]->val2, sc_display[i]->val3);
 #else
-			clif_status_change_sub(tbl, bl->id, StatusIconChangeTable[type], 1, tick, sc_display[i]->val1, sc_display[i]->val2, sc_display[i]->val3, target);
+			clif_status_change_sub(tbl, StatusIconChangeTable[type], 1, tick, tick, sc_display[i]->val1, sc_display[i]->val2, sc_display[i]->val3, target);
 #endif
 		}
 	}

+ 2 - 1
src/map/clif.hpp

@@ -728,7 +728,8 @@ void clif_bladestop(struct block_list *src, int dst_id, int active);
 void clif_changemapcell(int fd, int16 m, int x, int y, int type, enum send_target target);
 
 #define clif_status_load(bl, type, flag) clif_status_change((bl), (type), (flag), 0, 0, 0, 0)
-void clif_status_change(struct block_list *bl, int type, int flag, t_tick tick, int val1, int val2, int val3);
+void clif_status_change(struct block_list *bl, int type, int flag, t_tick tick_total, int val1, int val2, int val3);
+void clif_status_change_sub(struct block_list *bl, int type, int flag, t_tick tick_total, t_tick tick, int val1, int val2, int val3);
 void clif_efst_status_change(struct block_list *bl, int tid, enum send_target target, int type, t_tick tick, int val1, int val2, int val3);
 void clif_efst_status_change_sub(struct block_list *tbl, struct block_list *bl, enum send_target target);
 

+ 171 - 145
src/map/status.cpp

@@ -9099,18 +9099,21 @@ void status_display_remove(struct block_list *bl, enum sc_type type) {
 }
 
 /**
- * Applies SC defense to a given status change
- * This function also determines whether or not the status change will be applied
+ * Applies a Status Change for a given amount of time
  * @param src: Source of the status change [PC|MOB|HOM|MER|ELEM|NPC]
  * @param bl: Target of the status change (See: enum sc_type)
  * @param type: Status change (SC_*)
  * @param rate: Initial percentage rate of affecting bl (0~10000)
- * @param val1~4: Depends on type of status change
- * @param tick: Initial duration that the status change affects bl
+ * @param val1: Depends on type of status change
+ * @param val2: Depends on type of status change
+ * @param val3: Depends on type of status change
+ * @param val4: Depends on type of status change
+ * @param duration: Remaining duration
+ * @param duration_total: Initial duration
  * @param flag: Value which determines what parts to calculate. See e_status_change_start_flags
  * @return adjusted duration based on flag values
  */
-int status_change_start(struct block_list* src, struct block_list* bl,enum sc_type type,int rate,int val1,int val2,int val3,int val4,t_tick duration,unsigned char flag) {
+int status_change_start_sub(struct block_list* src, struct block_list* bl,enum sc_type type,int rate,int val1,int val2,int val3,int val4,t_tick duration,t_tick duration_total,unsigned char flag) {
 	struct map_session_data *sd = NULL;
 	struct status_change* sc;
 	struct status_change_entry* sce;
@@ -9207,14 +9210,14 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		}
 	}
 
-	// Adjust tick according to status resistances
+	// Adjust duration_total according to status resistances
 	if( !(flag&(SCSTART_NOAVOID|SCSTART_LOADED)) ) {
-		duration = status_get_sc_def(src, bl, type, rate, duration, flag);
-		if( !duration )
+		duration_total = status_get_sc_def(src, bl, type, rate, duration_total, flag);
+		if( duration_total == 0 )
 			return 0;
 	}
 
-	int tick = (int)duration;
+	int tick_total = static_cast<int>(duration_total);
 
 	sd = BL_CAST(BL_PC, bl);
 	vd = status_get_viewdata(bl);
@@ -9417,7 +9420,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			}
 			if (!opt_flag) return 0;
 		}
-		if (tick == 1) return 1; // Minimal duration: Only strip without causing the SC
+		if (tick_total == 1) return 1; // Minimal duration: Only strip without causing the SC
 	break;
 	case SC_STRIPSHIELD:
 		if( val2 == 1 ) val2 = 0; // GX effect. Do not take shield off..
@@ -9431,7 +9434,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 				return 0;
 			pc_unequipitem(sd,i,3);
 		}
-		if (tick == 1) return 1; // Minimal duration: Only strip without causing the SC
+		if (tick_total == 1) return 1; // Minimal duration: Only strip without causing the SC
 	break;
 	case SC_STRIPARMOR:
 		if (sd && !(flag&SCSTART_LOADED)) {
@@ -9443,7 +9446,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 				return 0;
 			pc_unequipitem(sd,i,3);
 		}
-		if (tick == 1) return 1; // Minimal duration: Only strip without causing the SC
+		if (tick_total == 1) return 1; // Minimal duration: Only strip without causing the SC
 	break;
 	case SC_STRIPHELM:
 		if (sd && !(flag&SCSTART_LOADED)) {
@@ -9455,7 +9458,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 				return 0;
 			pc_unequipitem(sd,i,3);
 		}
-		if (tick == 1) return 1; // Minimal duration: Only strip without causing the SC
+		if (tick_total == 1) return 1; // Minimal duration: Only strip without causing the SC
 	break;
 	case SC_MERC_FLEEUP:
 	case SC_MERC_ATKUP:
@@ -9533,7 +9536,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			if( i < 0 )
 				return 0;
 		}
-		if (tick == 1) return 1; // Minimal duration: Only strip without causing the SC
+		if (tick_total == 1) return 1; // Minimal duration: Only strip without causing the SC
 	break;
 	case SC_TOXIN:
 	case SC_PARALYSE:
@@ -10237,7 +10240,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_SPRITEMABLE:
 		case SC_CLAN_INFO:
 		case SC_DAILYSENDMAILCNT:
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 
 		case SC_KEEPING:
@@ -10245,7 +10248,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			unit_data *ud = unit_bl2ud(bl);
 
 			if (ud)
-				ud->attackabletime = ud->canact_tick = ud->canmove_tick = gettick() + tick;
+				ud->attackabletime = ud->canact_tick = ud->canmove_tick = gettick() + tick_total;
 		}
 			break;
 		case SC_DECREASEAGI:
@@ -10267,29 +10270,29 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 					int i;
 					for( i = 0; i < MAX_DEVOTION; i++ ) {
 						if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) )
-							status_change_start(src,&tsd->bl, type, 10000, val1, val2, val3, val4, tick, SCSTART_NOAVOID|SCSTART_NOICON);
+							status_change_start(src,&tsd->bl, type, 10000, val1, val2, val3, val4, tick_total, SCSTART_NOAVOID|SCSTART_NOICON);
 					}
 				}
 				else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) )
-					status_change_start(src,&tsd->bl, type, 10000, val1, val2, val3, val4, tick, SCSTART_NOAVOID|SCSTART_NOICON);
+					status_change_start(src,&tsd->bl, type, 10000, val1, val2, val3, val4, tick_total, SCSTART_NOAVOID|SCSTART_NOICON);
 			}
 			if( val4 )
-				tick = INFINITE_TICK;
+				tick_total = INFINITE_TICK;
 			break;
 		case SC_AUTOBERSERK:
 			if (status->hp < status->max_hp>>2 &&
 				(!sc->data[SC_PROVOKE] || sc->data[SC_PROVOKE]->val2==0))
 					sc_start4(src,bl,SC_PROVOKE,100,10,1,0,0,60000);
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 		case SC_SIGNUMCRUCIS:
 			val2 = 10 + 4*val1; // Def reduction
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			clif_emotion(bl, ET_SWEAT);
 			break;
 		case SC_MAXIMIZEPOWER:
-			tick_time = val2 = tick>0?tick:60000;
-			tick = INFINITE_TICK; // Duration sent to the client should be infinite
+			tick_time = val2 = tick_total>0?tick_total:60000;
+			tick_total = INFINITE_TICK; // Duration sent to the client should be infinite
 			break;
 		case SC_EDP:
 			val2 = val1 + 2; // Chance to Poison enemies.
@@ -10300,8 +10303,8 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 				uint16 poison_level = pc_checkskill(sd, GC_RESEARCHNEWPOISON);
 
 				if (poison_level > 0) {
-					tick += 30000; // Base of 30 seconds
-					tick += poison_level * 15 * 1000; // Additional 15 seconds per level
+					tick_total += 30000; // Base of 30 seconds
+					tick_total += poison_level * 15 * 1000; // Additional 15 seconds per level
 				}
 			}
 			break;
@@ -10329,7 +10332,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_SACRIFICE:
 			val2 = 5; // Lasts 5 hits
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 		case SC_ENCPOISON:
 			val2= 250+50*val1; // Poisoning Chance (2.5+0.5%) in 1/10000 rate
@@ -10366,11 +10369,11 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 					int i;
 					for( i = 0; i < MAX_DEVOTION; i++ ) {
 						if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) )
-							status_change_start(src,&tsd->bl, type, 10000, val1, val2, 0, 1, tick, SCSTART_NOAVOID|SCSTART_NOICON);
+							status_change_start(src,&tsd->bl, type, 10000, val1, val2, 0, 1, tick_total, SCSTART_NOAVOID|SCSTART_NOICON);
 					}
 				}
 				else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) )
-					status_change_start(src,&tsd->bl, type, 10000, val1, val2, 0, 1, tick, SCSTART_NOAVOID|SCSTART_NOICON);
+					status_change_start(src,&tsd->bl, type, 10000, val1, val2, 0, 1, tick_total, SCSTART_NOAVOID|SCSTART_NOICON);
 			}
 			break;
 		case SC_STRIPWEAPON:
@@ -10475,9 +10478,9 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			// val3 : Brings the skill_lv (merged into val1 here)
 			// val4 : Partner
 			if (val1 == CG_MOONLIT)
-				clif_status_change(bl,EFST_MOON,1,tick,0, 0, 0);
+				clif_status_change(bl,EFST_MOON,1,tick_total,0, 0, 0);
 			val1|= (val3<<16);
-			val3 = tick/1000; // Tick duration
+			val3 = tick_total/1000; // Tick duration
 			tick_time = 1000; // [GodLesZ] tick time
 			break;
 #ifndef RENEWAL
@@ -10552,7 +10555,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			// mmocharstatus.manner, each negative point results in 1 minute with this status activated.
 			// This is done this way because the message that the client displays is hardcoded, and only
 			// shows how many minutes are remaining. [Panikon]
-			tick = 60000;
+			tick_total = 60000;
 			val1 = battle_config.manner_system; // Mute filters.
 			if (sd) {
 				clif_changestatus(sd,SP_MANNER,sd->status.manner);
@@ -10562,8 +10565,8 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 
 		case SC_STONE:
 			val3 = max(val3, 100); // Incubation time
-			val4 = max(tick-val3, 100); // Petrify time
-			tick = val3;
+			val4 = max(tick_total-val3, 100); // Petrify time
+			tick_total = val3;
 			calc_flag = 0; // Actual status changes take effect on petrified state.
 			break;
 
@@ -10580,32 +10583,32 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_BLEEDING:
 		case SC_BURNING:
 			tick_time = status_get_sc_interval(type);
-			val4 = tick - tick_time; // Remaining time
+			val4 = tick_total - tick_time; // Remaining time
 			break;
 		case SC_TOXIN:
 			if (val3 == 1) // Target
 				tick_time = status_get_sc_interval(type);
 			else // Caster
 				tick_time = 1000;
-			val4 = tick - tick_time; // Remaining time
+			val4 = tick_total - tick_time; // Remaining time
 			break;
 		case SC_DEATHHURT:
 			if (val3 == 1)
 				break;
 			tick_time = status_get_sc_interval(type);
-			val4 = tick - tick_time; // Remaining time
+			val4 = tick_total - tick_time; // Remaining time
 		case SC_LEECHESEND:
 			if (val3 == 0)
 				break;
 			tick_time = status_get_sc_interval(type);
-			val4 = tick - tick_time; // Remaining time
+			val4 = tick_total - tick_time; // Remaining time
 			break;
 		case SC_PYREXIA:
 			if (val3 == 1) { // Target
 				// Causes blind for duration of pyrexia, unreducable and unavoidable, but can be healed with e.g. green potion
-				status_change_start(src, bl, SC_BLIND, 10000, val1, 0, 0, 0, tick, SCSTART_NOAVOID | SCSTART_NOTICKDEF | SCSTART_NORATEDEF);
+				status_change_start(src, bl, SC_BLIND, 10000, val1, 0, 0, 0, tick_total, SCSTART_NOAVOID | SCSTART_NOTICKDEF | SCSTART_NORATEDEF);
 				tick_time = status_get_sc_interval(type);
-				val4 = tick - tick_time; // Remaining time
+				val4 = tick_total - tick_time; // Remaining time
 			} else // Caster
 				val2 = 15; // CRIT % and ATK % increase
 			break;
@@ -10616,7 +10619,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_MAGICMUSHROOM:
 			if (val3 == 1) { // Target
 				tick_time = status_get_sc_interval(type);
-				val4 = tick - tick_time; // Remaining time
+				val4 = tick_total - tick_time; // Remaining time
 			} else // Caster
 				val2 = 10; // After-cast delay % reduction
 			break;
@@ -10632,7 +10635,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			// val2 = seconds between heals
 			// val4 = total of heals
 			if( val2 < 1 ) val2 = 1;
-			if( (val4 = tick/(val2 * 1000)) < 1 )
+			if( (val4 = tick_total/(val2 * 1000)) < 1 )
 				val4 = 1;
 			tick_time = val2 * 1000; // [GodLesZ] tick time
 			break;
@@ -10646,17 +10649,17 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 				}
 				val1 = boss_md->bl.id;
 				tick_time = 1000; // [GodLesZ] tick time
-				val4 = tick / tick_time;
+				val4 = tick_total / tick_time;
 			}
 			break;
 		case SC_HIDING:
-			val2 = tick/1000;
+			val2 = tick_total/1000;
 			tick_time = 1000; // [GodLesZ] tick time
 			val3 = 0; // Unused, previously speed adjustment
 			val4 = val1+3; // Seconds before SP substraction happen.
 			break;
 		case SC_CHASEWALK:
-			val2 = tick>0?tick:10000; // Interval at which SP is drained.
+			val2 = tick_total>0?tick_total:10000; // Interval at which SP is drained.
 			val3 = 35 - 5 * val1; // Speed adjustment.
 			if (sc->data[SC_SPIRIT] && sc->data[SC_SPIRIT]->val2 == SL_ROGUE)
 				val3 -= 40;
@@ -10666,8 +10669,8 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_CLOAKING:
 			if (!sd) // Monsters should be able to walk with no penalties. [Skotlex]
 				val1 = 10;
-			tick_time = val2 = tick>0?tick:60000; // SP consumption rate.
-			tick = INFINITE_TICK; // Duration sent to the client should be infinite
+			tick_time = val2 = tick_total>0?tick_total:60000; // SP consumption rate.
+			tick_total = INFINITE_TICK; // Duration sent to the client should be infinite
 			val3 = 0; // Unused, previously walk speed adjustment
 			// val4&1 signals the presence of a wall.
 			// val4&2 makes cloak not end on normal attacks [Skotlex]
@@ -10681,7 +10684,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_RUWACH:
 		case SC_SIGHTBLASTER:
 			val3 = skill_get_splash(val2, val1); // Val2 should bring the skill-id.
-			val2 = tick/20;
+			val2 = tick_total/20;
 			tick_time = 20; // [GodLesZ] tick time
 			break;
 
@@ -10698,11 +10701,11 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 					if( sd ) {
 						for( i = 0; i < MAX_DEVOTION; i++ ) {
 							if( sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])) )
-								status_change_start(src,&tsd->bl, type, 10000, val1, val2, 0, 0, tick, SCSTART_NOAVOID|SCSTART_NOICON);
+								status_change_start(src,&tsd->bl, type, 10000, val1, val2, 0, 0, tick_total, SCSTART_NOAVOID|SCSTART_NOICON);
 						}
 					}
 					else if( bl->type == BL_MER && ((TBL_MER*)bl)->devotion_flag && (tsd = ((TBL_MER*)bl)->master) )
-						status_change_start(src,&tsd->bl, type, 10000, val1, val2, 0, 0, tick, SCSTART_NOAVOID|SCSTART_NOICON);
+						status_change_start(src,&tsd->bl, type, 10000, val1, val2, 0, 0, tick_total, SCSTART_NOAVOID|SCSTART_NOICON);
 				}
 			}
 			break;
@@ -10718,7 +10721,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 					int i;
 					for (i = 0; i < MAX_DEVOTION; i++) { // See if there are devoted characters, and pass the status to them. [Skotlex]
 						if (sd->devotion[i] && (tsd = map_id2sd(sd->devotion[i])))
-							status_change_start(src,&tsd->bl,type,10000,val1,val2,val3,val4,tick,SCSTART_NOAVOID);
+							status_change_start(src,&tsd->bl,type,10000,val1,val2,val3,val4,tick_total,SCSTART_NOAVOID);
 					}
 				}
 			}
@@ -10732,8 +10735,8 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			}
 			val2 = 12; // SP cost
 			tick_time = 10000; // Decrease at 10secs intervals.
-			val3 = tick / tick_time;
-			tick = INFINITE_TICK; // Duration sent to the client should be infinite
+			val3 = tick_total / tick_time;
+			tick_total = INFINITE_TICK; // Duration sent to the client should be infinite
 			break;
 		case SC_PARRYING:
 		    val2 = 20 + val1*3; // Block Chance
@@ -10750,20 +10753,20 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 
 		case SC_BERSERK:
 			if( val3 == SC__BLOODYLUST )
-				sc_start(src,bl,(sc_type)val3,100,val1,tick);
+				sc_start(src,bl,(sc_type)val3,100,val1,tick_total);
 			else
-				sc_start4(src,bl, SC_ENDURE, 100,10,0,0,1, tick);
+				sc_start4(src,bl, SC_ENDURE, 100,10,0,0,1, tick_total);
 			// HP healing is performing after the calc_status call.
 			// Val2 holds HP penalty
 			if (!val4) val4 = skill_get_time2(status_sc2skill(type),val1);
 			if (!val4) val4 = 10000; // Val4 holds damage interval
-			val3 = tick/val4; // val3 holds skill duration
+			val3 = tick_total/val4; // val3 holds skill duration
 			tick_time = val4; // [GodLesZ] tick time
 			break;
 
 		case SC_GOSPEL:
 			if(val4 == BCT_SELF) {	// Self effect
-				val2 = tick/10000;
+				val2 = tick_total/10000;
 				tick_time = 10000; // [GodLesZ] tick time
 				status_change_clear_buffs(bl, SCCB_BUFFS|SCCB_DEBUFFS|SCCB_CHEM_PROTECT); // Remove buffs/debuffs
 			}
@@ -10828,12 +10831,12 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_REJECTSWORD:
 			val2 = 15*val1; // Reflect chance
 			val3 = 3; // Reflections
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 
 		case SC_MEMORIZE:
 			val2 = 5; // Memorized casts.
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 
 #ifndef RENEWAL
@@ -10881,11 +10884,11 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 
 			if (src2 && sc2) {
 				if (!sce2) // Start lock on caster.
-					sc_start4(src2,src2,SC_CLOSECONFINE,100,val1,1,0,0,tick+1000);
+					sc_start4(src2,src2,SC_CLOSECONFINE,100,val1,1,0,0,tick_total+1000);
 				else { // Increase count of locked enemies and refresh time.
 					(sce2->val2)++;
 					delete_timer(sce2->timer, status_change_timer);
-					sce2->timer = add_timer(gettick()+tick+1000, status_change_timer, src2->id, SC_CLOSECONFINE);
+					sce2->timer = add_timer(gettick()+tick_total+1000, status_change_timer, src2->id, SC_CLOSECONFINE);
 				}
 			} else // Status failed.
 				return 0;
@@ -10920,13 +10923,13 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			// val4: TK: Combo time
 			struct unit_data *ud = unit_bl2ud(bl);
 			if ( ud && (!val3 || val3 == 2) ) {
-				tick += 300 * battle_config.combo_delay_rate/100;
-				ud->attackabletime = gettick()+tick;
+				tick_total += 300 * battle_config.combo_delay_rate/100;
+				ud->attackabletime = gettick()+tick_total;
 				if( !val3 )
-					unit_set_walkdelay(bl, gettick(), tick, 1);
+					unit_set_walkdelay(bl, gettick(), tick_total, 1);
 			}
 			val3 = 0;
-			val4 = tick;
+			val4 = tick_total;
 			break;
 		}
 		case SC_EARTHSCROLL:
@@ -10939,7 +10942,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			// Note: this int64 value is stored in two separate int32 variables (FIXME)
 			val3 = (int)(currenttick & 0x00000000ffffffffLL);
 			val4 = (int)((currenttick & 0xffffffff00000000LL) >> 32);
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 		}
 		case SC_KAAHI:
@@ -10954,7 +10957,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_TRICKDEAD:
 			if (vd) vd->dead_sit = 1;
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 		case SC_CONCENTRATE:
 			val2 = 2 + val1;
@@ -10985,7 +10988,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 					val3 = (val2) ? 300 : 200; // Aspd increase
 				}
 				if (s_sd && pc_checkskill(s_sd, BS_HILTBINDING) > 0)
-					tick += tick / 10; //If caster has Hilt Binding, duration increases by 10%
+					tick_total += tick_total / 10; //If caster has Hilt Binding, duration increases by 10%
 			}
 			break;
 		case SC_CONCENTRATION:
@@ -10997,7 +11000,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			val4 = 5*val1; // Def reduction
 #endif
 			val3 = 10*val1; // Hit Increase
-			sc_start(src, bl, SC_ENDURE, 100, 1, tick); // Level 1 Endure effect
+			sc_start(src, bl, SC_ENDURE, 100, 1, tick_total); // Level 1 Endure effect
 			break;
 		case SC_ANGELUS:
 			val2 = 5*val1; // def increase
@@ -11070,7 +11073,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_JAILED:
 			// Val1 is duration in minutes. Use INT_MAX to specify 'unlimited' time.
-			tick = val1>0?1000:250;
+			tick_total = val1>0?1000:250;
 			if (sd) {
 				if (sd->mapindex != val2) {
 					int pos =  (bl->x&0xFFFF)|(bl->y<<16), // Current Coordinates
@@ -11100,7 +11103,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_SWOO:
 			if(status_has_mode(status,MD_STATUS_IMMUNE))
-				tick /= 5; // !TODO: Reduce skill's duration. But for how long?
+				tick_total /= 5; // !TODO: Reduce skill's duration. But for how long?
 			break;
 		case SC_ARMOR:
 			// NPC_DEFENDER:
@@ -11236,7 +11239,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			}
  			break;
 		case SC_ABUNDANCE:
-			val4 = tick / 10000;
+			val4 = tick_total / 10000;
 			tick_time = 10000; // [GodLesZ] tick time
 			break;
 		case SC_GIANTGROWTH:
@@ -11249,7 +11252,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 
 		/* Arch Bishop */
 		case SC_RENOVATIO:
-			val4 = tick / 5000;
+			val4 = tick_total / 5000;
 			tick_time = 5000;
 			break;
 		case SC_SECRAMENT:
@@ -11260,13 +11263,13 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_WEAPONBLOCKING:
 			val2 = 10 + 2 * val1; // Chance
-			val4 = tick / 5000;
+			val4 = tick_total / 5000;
 			tick_time = 5000; // [GodLesZ] tick time
 			break;
 		case SC_OBLIVIONCURSE:
 			if (val3 == 0)
 				break;
-			val4 = tick / 3000;
+			val4 = tick_total / 3000;
 			tick_time = 3000; // [GodLesZ] tick time
 			break;
 		case SC_CLOAKINGEXCEED:
@@ -11300,7 +11303,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_SPHERE_5:
 			if( !sd )
 				return 0;	// Should only work on players.
-			val4 = tick / 1000;
+			val4 = tick_total / 1000;
 			if( val4 < 1 )
 				val4 = 1;
 			tick_time = 1000; // [GodLesZ] tick time
@@ -11315,7 +11318,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_ELECTRICSHOCKER:
 		case SC_CRYSTALIZE:
-			val4 = tick / 1000;
+			val4 = tick_total / 1000;
 			if( val4 < 1 )
 				val4 = 1;
 			tick_time = 1000; // [GodLesZ] tick time
@@ -11323,13 +11326,13 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_MEIKYOUSISUI:
 			val2 = val1 * 2; // % HP each sec
 			val3 = val1; // % SP each sec
-			val4 = tick / 1000;
+			val4 = tick_total / 1000;
 			if( val4 < 1 )
 				val4 = 1;
 			tick_time = 1000;
 			break;
 		case SC_CAMOUFLAGE:
-			val4 = tick/1000;
+			val4 = tick_total/1000;
 			tick_time = 1000; // [GodLesZ] tick time
 			break;
 		case SC_WUGDASH:
@@ -11339,7 +11342,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			// Note: this int64 value is stored in two separate int32 variables (FIXME)
 			val3 = (int)(currenttick&0x00000000ffffffffLL);
 			val4 = (int)((currenttick&0xffffffff00000000LL)>>32);
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 		}
 		case SC__SHADOWFORM:
@@ -11347,7 +11350,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 				struct map_session_data * s_sd = map_id2sd(val2);
 				if( s_sd )
 					s_sd->shadowform_id = bl->id;
-				val4 = tick / 1000;
+				val4 = tick_total / 1000;
 				tick_time = 1000; // [GodLesZ] tick time
 			}
 			break;
@@ -11358,8 +11361,8 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC__INVISIBILITY:
 			val2 = 50 - 10 * val1; // ASPD
 			val3 = 20 * val1; // CRITICAL
-			val4 = tick / 1000;
-			tick = INFINITE_TICK; // Duration sent to the client should be infinite
+			val4 = tick_total / 1000;
+			tick_total = INFINITE_TICK; // Duration sent to the client should be infinite
 			tick_time = 1000; // [GodLesZ] tick time
 			break;
 		case SC__ENERVATION:
@@ -11396,14 +11399,14 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 				default: rand_eff = SC_POISON; break;
 			}
 			val2 = 10 * val1; // Crit and Flee2 Reduction
-			status_change_start(src,bl,rand_eff,10000,val1,0,(rand_eff == SC_POISON ? src->id : 0),0,tick,SCSTART_NOTICKDEF|SCSTART_NORATEDEF);
+			status_change_start(src,bl,rand_eff,10000,val1,0,(rand_eff == SC_POISON ? src->id : 0),0,tick_total,SCSTART_NOTICKDEF|SCSTART_NORATEDEF);
 			break;
 		}
 		case SC__WEAKNESS:
 			val2 = 10 * val1;
 			// Bypasses coating protection and MADO
-			sc_start(src,bl,SC_STRIPWEAPON,100,val1,tick);
-			sc_start(src,bl,SC_STRIPSHIELD,100,val1,tick);
+			sc_start(src,bl,SC_STRIPWEAPON,100,val1,tick_total);
+			sc_start(src,bl,SC_STRIPSHIELD,100,val1,tick_total);
 			break;
 		case SC_GN_CARTBOOST:
 			if( val1 < 3 )
@@ -11419,21 +11422,21 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_STRIKING:
 			// val2 = watk bonus already calc
 			val3 = 6 - val1;// spcost = 6 - level (lvl1:5 ... lvl 5: 1)
-			val4 = tick / 1000;
+			val4 = tick_total / 1000;
 			tick_time = 1000; // [GodLesZ] tick time
 			break;
 		case SC_WARMER:
-			val4 = tick / 3000;
-			tick = INFINITE_TICK; // Duration sent to the client should be infinite
+			val4 = tick_total / 3000;
+			tick_total = INFINITE_TICK; // Duration sent to the client should be infinite
 			tick_time = 3000;
 			break;
 		case SC_BLOODSUCKER:
-			val4 = tick / 1000;
+			val4 = tick_total / 1000;
 			tick_time = 1000; // [GodLesZ] tick time
 			break;
 		case SC_HELLS_PLANT:
 			tick_time = 333;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			break;
 		case SC_SWINGDANCE:
 			val3 = 3 * val1 + val2; // Walk speed and aspd reduction.
@@ -11452,11 +11455,11 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			val2 = 5 + 5 * val1;
 			break;
 		case SC_VOICEOFSIREN:
-			val4 = tick / 2000;
+			val4 = tick_total / 2000;
 			tick_time = 2000; // [GodLesZ] tick time
 			break;
 		case SC_DEEPSLEEP:
-			val4 = tick / 2000;
+			val4 = tick_total / 2000;
 			tick_time = 2000; // [GodLesZ] tick time
 			break;
 		case SC_SIRCLEOFNATURE:
@@ -11469,7 +11472,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_SATURDAYNIGHTFEVER:
 			if (!val4) val4 = skill_get_time2(status_sc2skill(type),val1);
 			if (!val4) val4 = 3000;
-			val3 = tick/val4;
+			val3 = tick_total/val4;
 			tick_time = val4; // [GodLesZ] tick time
 			break;
 		case SC_GLOOMYDAY:
@@ -11514,13 +11517,13 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_REFLECTDAMAGE:
 			val2 = 15 + 5 * val1; // Reflect amount
 			val3 = val1*5 + 25; // Number of reflects
-			val4 = tick/1000; // Number of SP cycles (duration)
+			val4 = tick_total/1000; // Number of SP cycles (duration)
 			tick_time = 1000; // [GodLesZ] tick time
 			break;
 		case SC_FORCEOFVANGUARD:
 			val2 = 8 + 12 * val1; // Chance
 			val3 = 5 + 2 * val1; // Max rage counters
-			tick = INFINITE_TICK; // Endless duration in the client
+			tick_total = INFINITE_TICK; // Endless duration in the client
 			tick_time = 10000; // [GodLesZ] tick time
 			break;
 		case SC_EXEEDBREAK:
@@ -11542,13 +11545,13 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			tick_time = 5000; // [GodLesZ] tick time
 			break;
 		case SC_MAGNETICFIELD:
-			val3 = tick / 1000;
+			val3 = tick_total / 1000;
 			tick_time = 1000; // [GodLesZ] tick time
 			break;
 		case SC_INSPIRATION:
 			val2 = (sd?sd->status.job_level:50);
 			val3 = status_get_lv(bl) / 10 + val2 / 5; //All stat bonus
-			val4 = tick / 5000;
+			val4 = tick_total / 5000;
 			tick_time = 5000; // [GodLesZ] tick time
 			status_change_clear_buffs(bl, SCCB_BUFFS|SCCB_DEBUFFS); // Remove buffs/debuffs
 			break;
@@ -11559,7 +11562,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			val1 = (sd?sd->status.job_level:2)/2 + 40 + 5 * val1;
 			break;
 		case SC_RAISINGDRAGON:
-			val3 = tick / 5000;
+			val3 = tick_total / 5000;
 			tick_time = 5000; // [GodLesZ] tick time
 			break;
 		case SC_GT_ENERGYGAIN:
@@ -11681,16 +11684,16 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_TEARGAS:
 			val2 = status_get_max_hp(bl) * 5 / 100; // Drain 5% HP
-			val4 = tick / 2000;
+			val4 = tick_total / 2000;
 			tick_time = 2000;
 			break;
 		case SC_TEARGAS_SOB:
-			val4 = tick / 3000;
+			val4 = tick_total / 3000;
 			tick_time = 3000;
 			break;
 		case SC_STOMACHACHE:
 			val2 = 8; // SP consume.
-			val4 = tick / 10000;
+			val4 = tick_total / 10000;
 			tick_time = 10000; // [GodLesZ] tick time
 			break;
 		case SC_PROMOTE_HEALTH_RESERCH:
@@ -11723,7 +11726,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			val2 = 20; // Damage increase bonus
 			val3 = val1 * 2;
 			tick_time = 1000;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			break;
 		case SC_ZANGETSU:
 			if( status_get_hp(bl) % 2 == 0 )
@@ -11758,7 +11761,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_ANGRIFFS_MODUS:
 			val2 = 50 + 20 * val1; // atk bonus
 			val3 = 25 + 10 * val1; // Flee reduction.
-			val4 = tick/1000; // hp/sp reduction timer
+			val4 = tick_total/1000; // hp/sp reduction timer
 			tick_time = 1000;
 			break;
 		case SC_GOLDENE_FERSE:
@@ -11794,15 +11797,15 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			val2 = 10 * val1; // aspd reduction %
 			val3 = min((( 200 * val1 ) * status_get_lv(src)) / 150, 1000); // dmg reduction linear. upto a maximum of 1000 [iRO Wiki]
 			if(sc->data[SC_PARALYSIS])
-				sc_start(src,bl, SC_ENDURE, 100, val1, tick); // Start endure for same duration
+				sc_start(src,bl, SC_ENDURE, 100, val1, tick_total); // Start endure for same duration
 			break;
 		case SC_STYLE_CHANGE:
-			tick = INFINITE_TICK; // Infinite duration
+			tick_total = INFINITE_TICK; // Infinite duration
 			break;
 		case SC_CBC:
 			val3 = 10; // Drain sp % dmg
-			val4 = tick/1000; // dmg each sec
-			tick = 1000;
+			val4 = tick_total/1000; // dmg each sec
+			tick_total = 1000;
 			break;
 		case SC_EQC:
 			val2 = 5 * val1; // def % reduc
@@ -11824,17 +11827,17 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			val2 = ( val1 == 1 ? 6 : 6 - val1 );
 			val3 = 20; //+% AllStats
 			tick_time = 1000;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			break;
 		case SC_REBOUND:
 			tick_time = 2000;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			clif_emotion(bl, ET_SWEAT);
 			break;
 		case SC_KINGS_GRACE:
 			val2 = 3 + val1; //HP Recover rate
 			tick_time = 1000;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			break;
 		case SC_TELEKINESIS_INTENSE:
 			val2 = 10 * val1; // sp consum / casttime reduc %
@@ -11848,7 +11851,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			val2 = 5 * val1; // maxhp bonus
 			val3 = 80 + 20 * val1; // healing
 			tick_time = 1000;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			break;
 		case SC_FLASHCOMBO:
 			val2 = 20 * val1 + 20; // atk bonus
@@ -11858,8 +11861,8 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_UNLIMIT:
 			val2 = 50 * val1;
-			status_change_start(bl, bl, SC_DEFSET, 10000, 1, 0, 0, 0, tick, SCSTART_NOTICKDEF);
-			status_change_start(bl, bl, SC_MDEFSET, 10000, 1, 0, 0, 0, tick, SCSTART_NOTICKDEF);
+			status_change_start(bl, bl, SC_DEFSET, 10000, 1, 0, 0, 0, tick_total, SCSTART_NOTICKDEF);
+			status_change_start(bl, bl, SC_MDEFSET, 10000, 1, 0, 0, 0, tick_total, SCSTART_NOTICKDEF);
 			break;
 		case SC_MONSTER_TRANSFORM:
 		case SC_ACTIVE_MONSTER_TRANSFORM:
@@ -11887,19 +11890,19 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_OVERHEAT:
 		case SC_OVERHEAT_LIMITPOINT:
 		case SC_STEALTHFIELD:
-			tick_time = tick;
-			tick = INFINITE_TICK;
+			tick_time = tick_total;
+			tick_total = INFINITE_TICK;
 			break;
 		case SC_STEALTHFIELD_MASTER:
 			tick_time = val3 = 2000 + 1000 * val1;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			break;
 		case SC_VACUUM_EXTREME:
 			// Suck target at n second, only if the n second is lower than the duration
 			// Does not suck targets on no-knockback maps
-			if (val4 < tick && unit_blown_immune(bl, 0x9) == UB_KNOCKABLE) {
+			if (val4 < tick_total && unit_blown_immune(bl, 0x9) == UB_KNOCKABLE) {
 				tick_time = val4;
-				val4 = tick - tick_time;
+				val4 = tick_total - tick_time;
 			} else
 				val4 = 0;
 			break;
@@ -11908,11 +11911,11 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_WIND_INSIGNIA:
 		case SC_EARTH_INSIGNIA:
 			tick_time = 5000;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			break;
 		case SC_NEUTRALBARRIER:
 			val2 = 10 + val1 * 5; // Def/Mdef
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 
 		/* Rebellion */
@@ -11925,7 +11928,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			// val2 = src_id
 			val3 = 10; // -10 flee
 			//Start timer to send mark on mini map
-			val4 = tick/1000;
+			val4 = tick_total/1000;
 			tick_time = 1000; // Sends every 1 seconds
 			break;
 		case SC_H_MINE:
@@ -11971,7 +11974,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 
 				val2 = (status_get_max_hp(bl) * (val1 + (b_status->dex / 25))) / status_get_max_hp(bl); // MHP% damage
 				tick_time = 1000;
-				val4 = tick / tick_time;
+				val4 = tick_total / tick_time;
 			}
 			break;
 		case SC_ARCLOUSEDASH:
@@ -12018,7 +12021,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 						val2 *= 2; // Doubles HP
 				}
 				tick_time = 10000 - ((val1 - 1) * 1000);
-				val4 = tick / tick_time;
+				val4 = tick_total / tick_time;
 			}
 			break;
 		case SC_TUNAPARTY:
@@ -12042,13 +12045,13 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		case SC_GOLDENMACECLAN:
 		case SC_CROSSBOWCLAN:
 		case SC_JUMPINGCLAN:
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			status_change_start(src,bl,SC_CLAN_INFO,10000,0,val2,0,0,INFINITE_TICK,flag);
 			break;
 		case SC_DORAM_BUF_01:
 		case SC_DORAM_BUF_02:
 			tick_time = 10000; // every 10 seconds
-			if( (val4 = tick/tick_time) < 1 )
+			if( (val4 = tick_total/tick_time) < 1 )
 				val4 = 1;
 			break;
 
@@ -12079,29 +12082,29 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_HELPANGEL:
 			tick_time = 1000;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			break;
 
 		case SC_SUNSTANCE:
 			val2 = 2 + val1; // ATK Increase
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 		case SC_LUNARSTANCE:
 			val2 = 2 + val1; // MaxHP Increase
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 		case SC_STARSTANCE:
 			val2 = 4 + 2 * val1; // ASPD Increase
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 		case SC_UNIVERSESTANCE:
 			val2 = 2 + val1; // All Stats Increase
-			tick = INFINITE_TICK;
+			tick_total = INFINITE_TICK;
 			break;
 		case SC_NEWMOON:
 			val2 = 7; // Number of Regular Attacks Until Reveal
 			tick_time = 1000;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			break;
 		case SC_FALLINGSTAR:
 			val2 = 8 + 2 * (1 + val1) / 2; // Autocast Chance
@@ -12110,7 +12113,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_CREATINGSTAR:
 			tick_time = 500;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			break;
 		case SC_LIGHTOFSUN:
 		case SC_LIGHTOFMOON:
@@ -12143,7 +12146,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_SOULUNITY:
 			tick_time = 3000;
-			val4 = tick / tick_time;
+			val4 = tick_total / tick_time;
 			break;
 		case SC_SOULDIVISION:
 			val2 = 10 * val1; // Skill Aftercast Increase
@@ -12153,9 +12156,9 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_SOULCOLLECT:
 			val2 = 5 + 3 * val2; // Max Soul Sphere's.
-			val3 = tick > 0 ? tick : 60000;
-			tick_time = tick;
-			tick = INFINITE_TICK;
+			val3 = tick_total > 0 ? tick_total : 60000;
+			tick_time = tick_total;
+			tick_total = INFINITE_TICK;
 			break;
 		case SC_SP_SHA:
 			val2 = 50; // Move speed reduction
@@ -12192,21 +12195,21 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			case SC_BLEEDING:
 			case SC_BURNING:
 			case SC_TOXIN:
-				tick_time = tick;
-				tick = tick_time + max(val4, 0);
+				tick_time = tick_total;
+				tick_total = tick_time + max(val4, 0);
 				break;
 			case SC_DEATHHURT:
 				if (val3 == 1)
 					break;
-				tick_time = tick;
-				tick = tick_time + max(val4, 0);
+				tick_time = tick_total;
+				tick_total = tick_time + max(val4, 0);
 			case SC_MAGICMUSHROOM:
 			case SC_PYREXIA:
 			case SC_LEECHESEND:
 				if (val3 == 0)
 					break;
-				tick_time = tick;
-				tick = tick_time + max(val4, 0);
+				tick_time = tick_total;
+				tick_total = tick_time + max(val4, 0);
 				break;
 			case SC_SWORDCLAN:
 			case SC_ARCWANDCLAN:
@@ -12294,7 +12297,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 	}
 
-	if (current_equip_combo_pos && tick == INFINITE_TICK) {
+	if (current_equip_combo_pos && tick_total == INFINITE_TICK) {
 		ShowWarning("sc_start: Item combo contains an INFINITE_TICK duration. Skipping bonus.\n");
 		return 0;
 	}
@@ -12406,7 +12409,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 		case SC_ITEMSCRIPT: // Shows Buff Icons
 			if (sd && val2 != EFST_BLANK)
-				clif_status_change(bl, (enum efst_types)val2, 1, tick, 0, 0, 0);
+				clif_status_change(bl, (enum efst_types)val2, 1, tick_total, 0, 0, 0);
 			break;
 	}
 
@@ -12614,6 +12617,10 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		calc_flag&=~SCB_BODY;
 	}*/
 
+	t_tick tick = duration;
+
+	if (!(flag & SCSTART_LOADED))
+		tick = tick_total; // When starting a new SC (not loading), its remaining duration is the same as the total
 	if (!(flag&SCSTART_NOICON) && !(flag&SCSTART_LOADED && StatusDisplayType[type])) {
 		int status_icon = StatusIconChangeTable[type];
 
@@ -12622,7 +12629,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			status_icon = EFST_ATTACK_PROPERTY_NOTHING + val1; // Assign status icon for older clients
 #endif
 
-		clif_status_change(bl, status_icon, 1, tick, (val_flag & 1) ? val1 : 1, (val_flag & 2) ? val2 : 0, (val_flag & 4) ? val3 : 0);
+		clif_status_change_sub(bl, status_icon, 1, tick_total, tick, (val_flag & 1) ? val1 : 1, (val_flag & 2) ? val2 : 0, (val_flag & 4) ? val3 : 0);
 	}
 
 	// Used as temporary storage for scs with interval ticks, so that the actual duration is sent to the client first.
@@ -12646,6 +12653,7 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 		sce->timer = add_timer(gettick() + tick, status_change_timer, bl->id, type);
 	else
 		sce->timer = INVALID_TIMER; // Infinite duration
+	sce->tick_total = tick_total;
 
 	if (calc_flag) {
 		if (sd) {
@@ -12757,6 +12765,24 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 	return 1;
 }
 
+/**
+ * Applies a Status Change for its full time
+ * @param src: Source of the status change [PC|MOB|HOM|MER|ELEM|NPC]
+ * @param bl: Target of the status change (See: enum sc_type)
+ * @param type: Status change (SC_*)
+ * @param rate: Initial percentage rate of affecting bl (0~10000)
+ * @param val1: Depends on type of status change
+ * @param val2: Depends on type of status change
+ * @param val3: Depends on type of status change
+ * @param val4: Depends on type of status change
+ * @param tick: Remaining duration
+ * @param flag: Value which determines what parts to calculate. See e_status_change_start_flags
+ * @return adjusted duration based on flag values
+ */
+int status_change_start(struct block_list *src, struct block_list *bl, enum sc_type type, int rate, int val1, int val2, int val3, int val4, t_tick tick, unsigned char flag) {
+	return status_change_start_sub(src, bl, type, rate, val1, val2, val3, val4, 0, tick, flag);
+}
+
 /**
  * End all statuses except those listed
  * TODO: May be useful for dispel instead resetting a list there

+ 2 - 0
src/map/status.hpp

@@ -2571,6 +2571,7 @@ struct sc_display_entry {
 struct status_change_entry {
 	int timer;
 	int val1,val2,val3,val4;
+	t_tick tick_total;
 };
 
 ///Status change
@@ -2706,6 +2707,7 @@ t_tick status_get_sc_def(struct block_list *src,struct block_list *bl, enum sc_t
 #define sc_start4(src, bl, type, rate, val1, val2, val3, val4, tick) status_change_start(src,bl,type,100*(rate),val1,val2,val3,val4,tick,SCSTART_NONE)
 
 int status_change_start(struct block_list* src, struct block_list* bl,enum sc_type type,int rate,int val1,int val2,int val3,int val4,t_tick duration,unsigned char flag);
+int status_change_start_sub(struct block_list *src, struct block_list *bl, enum sc_type type, int rate, int val1, int val2, int val3, int val4, t_tick duration, t_tick duration_total, unsigned char flag);
 int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const char* file, int line);
 #define status_change_end(bl,type,tid) status_change_end_(bl,type,tid,__FILE__,__LINE__)
 TIMER_FUNC(status_change_timer);