Browse Source

Bonus Script clean up
* Removed fixed array size (MAX_PC_BONUS_SCRIPT) of bonus_script on players.
* Changed fixed script string on player's bonus_script to StringBuf, reduce memory usage for each connected player.
* Added new flags to expand duplicated script and flag to allow duplicate script.
* Reduced buf lenght from 1024 to 512 on bonus_script_data (used for saving/loading bonus_script from char-server). Still, MAX_PC_BONUS_SCRIPT is used to gives limit data can be loaded.

NOTE:
Please import 'upgrade_20150131.sql' for `bonus_script` table changes

Signed-off-by: Cydh Ramdh <house.bad@gmail.com>

Cydh Ramdh 10 years ago
parent
commit
95705d41be

+ 2 - 0
db/const.txt

@@ -4643,6 +4643,8 @@ BSF_REM_ON_LUXANIMA	0x040
 BSF_REM_ON_MADOGEAR	0x080
 BSF_REM_ON_DAMAGED	0x100
 BSF_PERMANENT	0x200
+BSF_FORCE_REPLACE	0x400
+BSF_FORCE_DUPLICATE	0x800
 
 false	0
 true	1

+ 4 - 4
doc/packet_interserv.txt

@@ -2491,9 +2491,9 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
 
 0x2b2f
 	Type: AZ
-	Structure: <cmd>.W <len>.W <cid>.L <count>.W
+	Structure: <cmd>.W <len>.W <cid>.L <count>.B { <bonus_script_data>.?B }
 	index: 0,2,4,8
-	len: variable: 10+50*bonus_script_data
+	len: variable: 9+count*bonus_script_data
 	parameter:
 		- cmd : packet identification (0x2b2f)
 	desc:
@@ -2840,9 +2840,9 @@ Currently the max packet size is 0xFFFF (see 'WFIFOSET()' in 'src/common/socket.
 
 0x2b2e
 	Type: ZA
-	Structure: <cmd>.W <len>.W <char_id>.L <count>.W
+	Structure: <cmd>.W <len>.W <char_id>.L <count>.B { <bonus_script_data>.?B }
 	index: 0,2,4,8
-	len: variable: 10+MAX_PC_BONUS_SCRIPT*bonus_script_data
+	len: variable: 9+count*bonus_script_data
 	parameter:
 		- cmd : packet identification (0x2b2e)
 		- len

+ 7 - 7
doc/script_commands.txt

@@ -5350,9 +5350,6 @@ After that time, the script will automatically expire. The same bonus cannot be
 stacked. By default, this bonus will be stored on `bonus_script` table when player
 logs out.
 
-Note that the maximum number of 'bonus_script' commands that can run simultaneously
-for a player is 10 (MAX_PC_BONUS_SCRIPT in 'src/map/pc.h').
-
 Flags (bitmask):
 	1   : Remove when dead.
 	2   : Removable by Dispell.
@@ -5360,10 +5357,13 @@ Flags (bitmask):
 	8   : Remove when player logs out.
 	16  : Removeable by Banishing Buster.
 	32  : Removable by Refresh.
-	128 : Removable by Luxanima.
-	256 : Remove when Madogear is activated or deactivated.
-	512 : Remove when receive damage.
-	1024: Script is permanent, cannot be cleared by bonus_script_clear
+	64  : Removable by Luxanima.
+	128 : Remove when Madogear is activated or deactivated.
+	256 : Remove when receive damage.
+	512 : Script is permanent, cannot be cleared by bonus_script_clear.
+	1024: Force to replace duplicated script by expanding the duration.
+	2048: Force to add duplicated script. This flag cannot be stacked with 1024,
+	      if both are defined, 1024 will be checked first and ignore this flag.
 
 Types:
 	This will be used to decide negative or positive buff for 'debuff_on_logout'.

+ 6 - 6
sql-files/main.sql

@@ -706,12 +706,12 @@ CREATE TABLE IF NOT EXISTS `interreg` (
 --
 
 CREATE TABLE IF NOT EXISTS `bonus_script` (
-  `char_id` varchar(11) NOT NULL,
-  `script` varchar(1024) NOT NULL,
-  `tick` varchar(11) NOT NULL DEFAULT '0',
-  `flag` varchar(3) NOT NULL DEFAULT '0',
-  `type` char(1) NOT NULL DEFAULT '0',
-  `icon` varchar(3) NOT NULL DEFAULT '-1'
+  `char_id` INT(11) UNSIGNED NOT NULL,
+  `script` TEXT NOT NULL,
+  `tick` INT(11) UNSIGNED NOT NULL DEFAULT '0',
+  `flag` SMALLINT(5) UNSIGNED NOT NULL DEFAULT '0',
+  `type` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
+  `icon` SMALLINT(3) NOT NULL DEFAULT '-1'
 ) ENGINE=InnoDB;
 
 CREATE TABLE IF NOT EXISTS `vending_items` (

+ 6 - 0
sql-files/upgrades/upgrade_20150131.sql

@@ -0,0 +1,6 @@
+ALTER TABLE `bonus_script` MODIFY COLUMN `char_id` INT(11) UNSIGNED NOT NULL;
+ALTER TABLE `bonus_script` MODIFY COLUMN `script` TEXT NOT NULL;
+ALTER TABLE `bonus_script` MODIFY COLUMN `tick` INT(11) UNSIGNED NOT NULL DEFAULT '0';
+ALTER TABLE `bonus_script` MODIFY COLUMN `flag` SMALLINT(5) UNSIGNED NOT NULL DEFAULT '0';
+ALTER TABLE `bonus_script` MODIFY COLUMN `type` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0';
+ALTER TABLE `bonus_script` MODIFY COLUMN `icon` SMALLINT(3) NOT NULL DEFAULT '-1';

+ 74 - 52
src/char/char_mapif.c

@@ -1242,90 +1242,112 @@ int chmapif_parse_reqcharunban(int fd){
 	return 1;
 }
 
-/** [Cydh]
-* Get bonus_script data(s) from table to load
-* @param fd
-*/
+/**
+ * ZA 0x2b2d
+ * <cmd>.W <char_id>.L
+ * AZ 0x2b2f
+ * <cmd>.W <len>.W <cid>.L <count>.B { <bonus_script_data>.?B }
+ * Get bonus_script data(s) from table to load then send to player
+ * @param fd
+ * @author [Cydh]
+ **/
 int chmapif_bonus_script_get(int fd) {
 	if (RFIFOREST(fd) < 6)
 		return 0;
 	else {
-		int cid;
-		cid = RFIFOL(fd,2);
+		uint8 num_rows = 0;
+		uint32 cid = RFIFOL(fd,2);
+		struct bonus_script_data tmp_bsdata;
+		SqlStmt* stmt = SqlStmt_Malloc(sql_handle);
+
 		RFIFOSKIP(fd,6);
 
-		if (SQL_ERROR == Sql_Query(sql_handle,"SELECT `script`, `tick`, `flag`, `type`, `icon` FROM `%s` WHERE `char_id`='%d'",
-			schema_config.bonus_script_db,cid))
+		if (SQL_ERROR == SqlStmt_Prepare(stmt,
+			"SELECT `script`, `tick`, `flag`, `type`, `icon` FROM `%s` WHERE `char_id` = '%d' LIMIT %d",
+			schema_config.bonus_script_db, cid, MAX_PC_BONUS_SCRIPT) ||
+			SQL_ERROR == SqlStmt_Execute(stmt) ||
+			SQL_ERROR == SqlStmt_BindColumn(stmt, 0, SQLDT_STRING, &tmp_bsdata.script_str, sizeof(tmp_bsdata.script_str), NULL, NULL) ||
+			SQL_ERROR == SqlStmt_BindColumn(stmt, 1, SQLDT_UINT32, &tmp_bsdata.tick, 0, NULL, NULL) ||
+			SQL_ERROR == SqlStmt_BindColumn(stmt, 2, SQLDT_UINT16, &tmp_bsdata.flag, 0, NULL, NULL) ||
+			SQL_ERROR == SqlStmt_BindColumn(stmt, 3, SQLDT_UINT8,  &tmp_bsdata.type, 0, NULL, NULL) ||
+			SQL_ERROR == SqlStmt_BindColumn(stmt, 4, SQLDT_INT16,  &tmp_bsdata.icon, 0, NULL, NULL)
+			)
 		{
-			Sql_ShowDebug(sql_handle);
+			SqlStmt_ShowDebug(stmt);
+			SqlStmt_Free(stmt);
 			return 1;
 		}
-		if (Sql_NumRows(sql_handle) > 0) {
-			struct bonus_script_data bsdata;
-			int count;
-			char *data;
-
-			WFIFOHEAD(fd,10+50*sizeof(struct bonus_script_data));
-			WFIFOW(fd,0) = 0x2b2f;
-			WFIFOL(fd,4) = cid;
-			for (count = 0; count < MAX_PC_BONUS_SCRIPT && SQL_SUCCESS == Sql_NextRow(sql_handle); ++count) {
-				Sql_GetData(sql_handle,0,&data,NULL); memcpy(bsdata.script,data,strlen(data)+1);
-				Sql_GetData(sql_handle,1,&data,NULL); bsdata.tick = atoi(data);
-				Sql_GetData(sql_handle,2,&data,NULL); bsdata.flag = atoi(data);
-				Sql_GetData(sql_handle,3,&data,NULL); bsdata.type = atoi(data);
-				Sql_GetData(sql_handle,4,&data,NULL); bsdata.icon = atoi(data);
-				memcpy(WFIFOP(fd,10+count*sizeof(struct bonus_script_data)),&bsdata,sizeof(struct bonus_script_data));
-			}
-			if (count >= MAX_PC_BONUS_SCRIPT)
-				ShowWarning("Too many bonus_script for %d, some of them were not loaded.\n",cid);
-			if (count > 0) {
-				WFIFOW(fd,2) = 10 + count*sizeof(struct bonus_script_data);
-				WFIFOW(fd,8) = count;
-				WFIFOSET(fd,WFIFOW(fd,2));
 
-				//Clear the data once loaded.
-				if (SQL_ERROR == Sql_Query(sql_handle,"DELETE FROM `%s` WHERE `char_id`='%d'",schema_config.bonus_script_db,cid))
-					Sql_ShowDebug(sql_handle);
-				ShowInfo("Loaded %d bonus_script for char_id: %d\n",count,cid);
+		if ((num_rows = (uint8)SqlStmt_NumRows(stmt)) > 0) {
+			uint8 i;
+			uint32 size = 9 + num_rows * sizeof(struct bonus_script_data);
+
+			WFIFOHEAD(fd, size);
+			WFIFOW(fd, 0) = 0x2b2f;
+			WFIFOW(fd, 2) = size;
+			WFIFOL(fd, 4) = cid;
+			WFIFOB(fd, 8) = num_rows;
+
+			for (i = 0; i < num_rows && SQL_SUCCESS == SqlStmt_NextRow(stmt); i++) {
+				struct bonus_script_data bsdata;
+				memset(&bsdata, 0, sizeof(bsdata));
+				memset(bsdata.script_str, '\0', sizeof(bsdata.script_str));
+
+				safestrncpy(bsdata.script_str, tmp_bsdata.script_str, strlen(tmp_bsdata.script_str)+1);
+				bsdata.tick = tmp_bsdata.tick;
+				bsdata.flag = tmp_bsdata.flag;
+				bsdata.type = tmp_bsdata.type;
+				bsdata.icon = tmp_bsdata.icon;
+				memcpy(WFIFOP(fd, 9 + i * sizeof(struct bonus_script_data)), &bsdata, sizeof(struct bonus_script_data));
 			}
+
+			WFIFOSET(fd, size);
+
+			ShowInfo("Bonus Script loaded for CID=%d. Total: %d.\n", cid, i);
+
+			if (SQL_ERROR == SqlStmt_Prepare(stmt,"DELETE FROM `%s` WHERE `char_id`='%d'",schema_config.bonus_script_db,cid) ||
+				SQL_ERROR == SqlStmt_Execute(stmt))
+				SqlStmt_ShowDebug(stmt);
 		}
-		Sql_FreeResult(sql_handle);
+		SqlStmt_Free(stmt);
 	}
 	return 1;
 }
 
-/** [Cydh]
-* Save bonus_script data(s) to the table
-* @param fd
-*/
+/**
+ * ZA 0x2b2e
+ * <cmd>.W <len>.W <char_id>.L <count>.B { <bonus_script>.?B }
+ * Save bonus_script data(s) to the table
+ * @param fd
+ * @author [Cydh]
+ **/
 int chmapif_bonus_script_save(int fd) {
 	if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2))
 		return 0;
 	else {
-		int count, cid;
-
-		cid = RFIFOL(fd,4);
-		count = RFIFOW(fd,8);
+		uint32 cid = RFIFOL(fd,4);
+		uint8 count = RFIFOB(fd,8);
 
 		if (count > 0) {
-			struct bonus_script_data bs;
+			char esc_script[MAX_BONUS_SCRIPT_LENGTH*2];
+			struct bonus_script_data bsdata;
 			StringBuf buf;
-			int i;
-			char esc_script[MAX_BONUS_SCRIPT_LENGTH] = "";
+			uint8 i;
 
 			StringBuf_Init(&buf);
-			StringBuf_Printf(&buf,"INSERT INTO `%s` (`char_id`, `script`, `tick`, `flag`, `type`, `icon`) VALUES ",schema_config.bonus_script_db);
+			StringBuf_Printf(&buf, "INSERT INTO `%s` (`char_id`, `script`, `tick`, `flag`, `type`, `icon`) VALUES ", schema_config.bonus_script_db);
 			for (i = 0; i < count; ++i) {
-				memcpy(&bs,RFIFOP(fd,10+i*sizeof(struct bonus_script_data)),sizeof(struct bonus_script_data));
-				Sql_EscapeString(sql_handle,esc_script,bs.script);
+				memcpy(&bsdata, RFIFOP(fd, 9 + i*sizeof(struct bonus_script_data)), sizeof(struct bonus_script_data));
+				Sql_EscapeString(sql_handle, esc_script, bsdata.script_str);
 				if (i > 0)
 					StringBuf_AppendStr(&buf,", ");
-				StringBuf_Printf(&buf,"('%d','%s','%d','%d','%d','%d')",cid,esc_script,bs.tick,bs.flag,bs.type,bs.icon);
+				StringBuf_Printf(&buf, "('%d','%s','%d','%d','%d','%d')", cid, esc_script, bsdata.tick, bsdata.flag, bsdata.type, bsdata.icon);
 			}
 			if (SQL_ERROR == Sql_QueryStr(sql_handle,StringBuf_Value(&buf)))
 				Sql_ShowDebug(sql_handle);
+
+			ShowInfo("Bonus Script saved for CID=%d. Total: %d.\n", cid, count);
 			StringBuf_Destroy(&buf);
-			ShowInfo("Saved %d bonus_script for char_id: %d\n",count,cid);
 		}
 		RFIFOSKIP(fd,RFIFOW(fd,2));
 	}

+ 8 - 6
src/common/mmo.h

@@ -7,6 +7,7 @@
 #include "cbasetypes.h"
 #include "../common/db.h"
 #include <time.h>
+#include "../common/strlib.h"// StringBuf
 
 // server->client protocol version
 //        0 - pre-?
@@ -73,7 +74,7 @@
 #define MAX_GUILDLEVEL 50 ///Max Guild level
 #define MAX_GUARDIANS 8	///Local max per castle. If this value is increased, need to add more fields on MySQL `guild_castle` table [Skotlex]
 #define MAX_QUEST_OBJECTIVES 3 ///Max quest objectives for a quest
-#define MAX_PC_BONUS_SCRIPT 20 ///Max bonus script
+#define MAX_PC_BONUS_SCRIPT 50 ///Max bonus script can be fetched from `bonus_script` table on player load [Cydh]
 
 // for produce
 #define MIN_ATTRIBUTE 0
@@ -259,12 +260,13 @@ struct status_change_data {
 	long val1, val2, val3, val4, tick; //Remaining duration.
 };
 
-#define MAX_BONUS_SCRIPT_LENGTH 1024
+#define MAX_BONUS_SCRIPT_LENGTH 512
 struct bonus_script_data {
-	char script[MAX_BONUS_SCRIPT_LENGTH];
-	long tick;
-	char type;
-	short flag, icon;
+	char script_str[MAX_BONUS_SCRIPT_LENGTH]; //< Script string
+	uint32 tick; ///< Tick
+	uint16 flag; ///< Flags @see enum e_bonus_script_flags
+	int16 icon; ///< Icon SI
+	uint8 type; ///< 0 - None, 1 - Buff, 2 - Debuff
 };
 
 struct skill_cooldown_data {

+ 129 - 119
src/map/chrif.c

@@ -97,8 +97,8 @@ static const int packet_len_table[0x3d] = { // U - used, F - free
 //2b2b: Incoming, chrif_parse_ack_vipActive -> vip info result
 //2b2c: FREE
 //2b2d: Outgoing, chrif_bsdata_request -> request bonus_script for pc_authok'ed char.
-//2b2e: Outgoing, chrif_save_bsdata -> Send bonus_script of player for saving.
-//2b2f: Incoming, chrif_load_bsdata -> received bonus_script of player for loading.
+//2b2e: Outgoing, chrif_bsdata_save -> Send bonus_script of player for saving.
+//2b2f: Incoming, chrif_bsdata_received -> received bonus_script of player for loading.
 
 int chrif_connected = 0;
 int char_fd = -1;
@@ -282,7 +282,8 @@ int chrif_save(struct map_session_data *sd, int flag) {
 		if (chrif_isconnected()) {
 			chrif_save_scdata(sd);
 			chrif_skillcooldown_save(sd);
-			chrif_save_bsdata(sd);
+			if (flag != 3)
+				chrif_bsdata_save(sd);
 			chrif_req_login_operation(sd->status.account_id, sd->status.name, CHRIF_OP_LOGIN_BANK, 0, 2, sd->status.bank_vault); //save Bank data
 		}
 		if ( flag != 3 && !chrif_auth_logout(sd,flag == 1 ? ST_LOGOUT : ST_MAPCHANGE) )
@@ -1598,6 +1599,123 @@ void chrif_parse_ack_vipActive(int fd) {
 #endif
 }
 
+
+/**
+ * ZA 0x2b2d
+ * <cmd>.W <char_id>.L
+ * Requets bonus_script datas
+ * @param char_id
+ * @author [Cydh]
+ **/
+int chrif_bsdata_request(uint32 char_id) {
+	chrif_check(-1);
+	WFIFOHEAD(char_fd,6);
+	WFIFOW(char_fd,0) = 0x2b2d;
+	WFIFOL(char_fd,2) = char_id;
+	WFIFOSET(char_fd,6);
+	return 0;
+}
+
+/**
+ * ZA 0x2b2e
+ * <cmd>.W <len>.W <char_id>.L <count>.B { <bonus_script>.?B }
+ * Stores bonus_script data(s) to the table when player log out
+ * @param sd
+ * @author [Cydh]
+ **/
+int chrif_bsdata_save(struct map_session_data *sd) {
+	uint16 i;
+	uint8 count = 0;
+	unsigned int tick;
+
+	chrif_check(-1);
+
+	if (!sd || !sd->bonus_script_num)
+		return 0;
+
+	tick = gettick();
+
+	WFIFOHEAD(char_fd, 9 + sd->bonus_script_num * sizeof(struct bonus_script_data));
+	WFIFOW(char_fd, 0) = 0x2b2e;
+	WFIFOL(char_fd, 4) = sd->status.char_id;
+
+	i = BSF_REM_ON_LOGOUT; //Remove bonus with this flag
+	if (battle_config.debuff_on_logout&1) //Remove negative buffs
+		i |= BSF_REM_DEBUFF;
+	if (battle_config.debuff_on_logout&2) //Remove positive buffs
+		i |= BSF_REM_BUFF;
+
+	//Clear data that won't be stored
+	pc_bonus_script_clear(sd, i);
+
+	if (!sd->bonus_script_num)
+		return 0;
+
+	for (i = 0; i < sd->bonus_script_num; i++) {
+		const struct TimerData *timer = get_timer(sd->bonus_script[i]->tid);
+		struct bonus_script_data bs;
+
+		if (timer == NULL || DIFF_TICK(timer->tick,tick) < 0)
+			continue;
+
+		memset(&bs, 0, sizeof(bs));
+		safestrncpy(bs.script_str, StringBuf_Value(sd->bonus_script[i]->script_buf), StringBuf_Length(sd->bonus_script[i]->script_buf)+1);
+		bs.tick = DIFF_TICK(timer->tick, tick);
+		bs.flag = sd->bonus_script[i]->flag;
+		bs.type = sd->bonus_script[i]->type;
+		bs.icon = sd->bonus_script[i]->icon;
+		memcpy(WFIFOP(char_fd, 9 + count * sizeof(struct bonus_script_data)), &bs, sizeof(struct bonus_script_data));
+		count++;
+	}
+
+	if (count > 0) {
+		WFIFOB(char_fd, 8) = count;
+		WFIFOW(char_fd, 2) = 9 + sd->bonus_script_num * sizeof(struct bonus_script_data);
+		WFIFOSET(char_fd, WFIFOW(char_fd, 2));
+	}
+
+	// Clear All
+	pc_bonus_script_clear_all(sd,3);
+
+	return 0;
+}
+
+/**
+ * AZ 0x2b2f
+ * <cmd>.W <len>.W <cid>.L <count>.B { <bonus_script_data>.?B }
+ * Bonus script received, set to player
+ * @param fd
+ * @author [Cydh]
+ **/
+int chrif_bsdata_received(int fd) {
+	struct map_session_data *sd;
+	uint32 cid = RFIFOL(fd,4);
+	uint8 i, count = 0;
+	bool calc = false;
+
+	sd = map_charid2sd(cid);
+
+	if (!sd) {
+		ShowError("chrif_bsdata_received: Player with CID %d not found!\n",cid);
+		return -1;
+	}
+
+	count = RFIFOB(fd,8);
+
+	for (i = 0; i < count; i++) {
+		struct bonus_script_data *bs = (struct bonus_script_data*)RFIFOP(fd,9 + i*sizeof(struct bonus_script_data));
+
+		if (bs->script_str[0] == '\0' || !bs->tick)
+			continue;
+
+		if (pc_bonus_script_add(sd, bs->script_str, bs->tick, (enum si_type)bs->icon, bs->flag, bs->type))
+			calc = true;
+	}
+	if (calc)
+		status_calc_pc(sd,SCO_NONE);
+	return 0;
+}
+
 /*==========================================
  *
  *------------------------------------------*/
@@ -1676,7 +1794,7 @@ int chrif_parse(int fd) {
 			case 0x2b27: chrif_authfail(fd); break;
 			case 0x2b29: chrif_load_bankdata(fd); break;
 			case 0x2b2b: chrif_parse_ack_vipActive(fd); break;
-			case 0x2b2f: chrif_load_bsdata(fd); break;
+			case 0x2b2f: chrif_bsdata_received(fd); break;
 			default:
 				ShowError("chrif_parse : unknown packet (session #%d): 0x%x. Disconnecting.\n", fd, cmd);
 				set_eof(fd);
@@ -1798,121 +1916,6 @@ int chrif_send_report(char* buf, int len) {
 	return 0;
 }
 
-/** [Cydh]
-* Requets bonus_script datas
-* @param char_id
-*/
-int chrif_bsdata_request(uint32 char_id) {
-	chrif_check(-1);
-	WFIFOHEAD(char_fd,6);
-	WFIFOW(char_fd,0) = 0x2b2d;
-	WFIFOL(char_fd,2) = char_id;
-	WFIFOSET(char_fd,6);
-	return 0;
-}
-
-/** [Cydh]
-* Stores bonus_script data(s) to the table
-* @param sd
-*/
-int chrif_save_bsdata(struct map_session_data *sd) {
-	int i;
-	uint8 count = 0;
-	unsigned int tick;
-	struct bonus_script_data bs;
-	const struct TimerData *timer;
-
-	chrif_check(-1);
-	tick = gettick();
-
-	WFIFOHEAD(char_fd,10+MAX_PC_BONUS_SCRIPT*sizeof(struct bonus_script_data));
-	WFIFOW(char_fd,0) = 0x2b2e;
-	WFIFOL(char_fd,4) = sd->status.char_id;
-	
-	i = BSF_REM_ON_LOGOUT; //Remove bonus with this flag
-	if (battle_config.debuff_on_logout&1) //Remove negative buffs
-		i |= BSF_REM_DEBUFF;
-	if (battle_config.debuff_on_logout&2) //Remove positive buffs
-		i |= BSF_REM_BUFF;
-	
-	//Clear data that won't be stored
-	pc_bonus_script_clear(sd,i);
-
-	for (i = 0; i < MAX_PC_BONUS_SCRIPT; i++) {
-		if (!(&sd->bonus_script[i]) || !sd->bonus_script[i].script || sd->bonus_script[i].script_str[0] == '\0')
-			continue;
-
-		timer = get_timer(sd->bonus_script[i].tid);
-		if (timer == NULL || DIFF_TICK(timer->tick,tick) < 0)
-			continue;
-
-		memcpy(bs.script,sd->bonus_script[i].script_str,strlen(sd->bonus_script[i].script_str)+1);
-		bs.tick = DIFF_TICK(timer->tick,tick);
-		bs.flag = sd->bonus_script[i].flag;
-		bs.type = sd->bonus_script[i].type;
-		bs.icon = sd->bonus_script[i].icon;
-
-		memcpy(WFIFOP(char_fd,10+count*sizeof(struct bonus_script_data)),&bs,sizeof(struct bonus_script_data));
-		pc_bonus_script_remove(&sd->bonus_script[i]);
-		count++;
-	}
-
-	if (count == 0)
-		return 0;
-
-	WFIFOW(char_fd,8) = count;
-	WFIFOW(char_fd,2) = 10+count*sizeof(struct bonus_script_data);
-	WFIFOSET(char_fd,WFIFOW(char_fd,2));
-	return 0;
-}
-
-/** [Cydh]
-* Loads bonus_script datas
-* @param fd
-*/
-int chrif_load_bsdata(int fd) {
-	struct map_session_data *sd;
-	int cid, count;
-	uint8 i;
-	bool calc = false;
-
-	cid = RFIFOL(fd,4);
-	sd = map_charid2sd(cid);
-
-	if (!sd) {
-		ShowError("chrif_load_bsdata: Player with CID %d not found!\n",cid);
-		return -1;
-	}
-
-	if (sd->status.char_id != cid) {
-		ShowError("chrif_load_bsdata: Receiving data for char id does not matches (%d != %d)!\n",sd->status.char_id,cid);
-		return -1;
-	}
-
-	count = RFIFOW(fd,8);
-
-	for (i = 0; i < count; i++) {
-		struct script_code *script;
-		struct bonus_script_data *bs = (struct bonus_script_data*)RFIFOP(fd,10 + i*sizeof(struct bonus_script_data));
-
-		if (bs->script[0] == '\0' || !(script = parse_script(bs->script,"chrif_load_bsdata",1,1)))
-			continue;
-
-		memcpy(sd->bonus_script[i].script_str,bs->script,strlen(bs->script));
-		sd->bonus_script[i].script = script;
-		sd->bonus_script[i].tick = gettick() + bs->tick;
-		sd->bonus_script[i].flag = (uint8)bs->flag;
-		sd->bonus_script[i].type = bs->type;
-		sd->bonus_script[i].icon = bs->icon;
-		if (bs->icon != SI_BLANK) //Gives status icon if exist
-			clif_status_change(&sd->bl,sd->bonus_script[i].icon,1,bs->tick,1,0,0);
-		calc = true;
-	}
-	if (calc)
-		status_calc_pc(sd,SCO_NONE);
-	return 0;
-}
-
 /**
  * @see DBApply
  */
@@ -1954,6 +1957,13 @@ void do_init_chrif(void) {
 			sizeof(struct mmo_charstatus));
 		exit(EXIT_FAILURE);
 	}
+
+	if((sizeof(struct bonus_script_data) * MAX_PC_BONUS_SCRIPT) > 0xFFFF){
+		ShowError("bonus_script_data size = %d is too, please reduce MAX_PC_BONUS_SCRIPT (%d) size. (must be below 0xFFFF).\n",
+			(sizeof(struct bonus_script_data) * MAX_PC_BONUS_SCRIPT), MAX_PC_BONUS_SCRIPT);
+		exit(EXIT_FAILURE);
+	}
+
 	auth_db = idb_alloc(DB_OPT_BASE);
 	auth_db_ers = ers_new(sizeof(struct auth_node),"chrif.c::auth_db_ers",ERS_OPT_NONE);
 

+ 1 - 2
src/map/chrif.h

@@ -89,8 +89,7 @@ int chrif_req_charunban(int aid, const char* character_name);
 int chrif_load_bankdata(int fd);
 
 int chrif_bsdata_request(uint32 char_id);
-int chrif_save_bsdata(struct map_session_data *sd);
-int chrif_load_bsdata(int fd);
+int chrif_bsdata_save(struct map_session_data *sd);
 
 void do_final_chrif(void);
 void do_init_chrif(void);

+ 204 - 57
src/map/pc.c

@@ -1143,8 +1143,6 @@ bool pc_authok(struct map_session_data *sd, uint32 login_id2, time_t expiration_
 		sd->autobonus2[i].active = INVALID_TIMER;
 	for(i = 0; i < ARRAYLENGTH(sd->autobonus3); i++)
 		sd->autobonus3[i].active = INVALID_TIMER;
-	for(i = 0; i < ARRAYLENGTH(sd->bonus_script); i++)
-		sd->bonus_script[i].tid = INVALID_TIMER;
 
 	if (battle_config.item_auto_get)
 		sd->state.autoloot = 10000;
@@ -1266,6 +1264,9 @@ bool pc_authok(struct map_session_data *sd, uint32 login_id2, time_t expiration_
 
 	sd->last_addeditem_index = -1;
 
+	sd->bonus_script = NULL;
+	sd->bonus_script_num = 0;
+
 	// Request all registries (auth is considered completed whence they arrive)
 	intif_request_registry(sd,7);
 	return true;
@@ -11038,106 +11039,252 @@ void pc_show_version(struct map_session_data *sd) {
 	clif_displaymessage(sd->fd,buf);
 }
 
-/** [Cydh]
+/**
+ * Run bonus_script on player
+ * @param sd
+ * @author [Cydh]
+ **/
+void pc_bonus_script(struct map_session_data *sd) {
+	uint8 i = 0;
+	int now = gettick();
+
+	if (!sd || !sd->bonus_script_num)
+		return;
+
+	for (i = 0; i < sd->bonus_script_num; i++) {
+		if (sd->bonus_script[i]->tid == INVALID_TIMER) { // Start new timer for new bonus_script
+			sd->bonus_script[i]->tick += now;
+			sd->bonus_script[i]->tid = add_timer(sd->bonus_script[i]->tick, pc_bonus_script_timer, sd->bl.id, 0);
+			if (sd->bonus_script[i]->icon != SI_BLANK) //Gives status icon if exist
+				clif_status_change(&sd->bl, sd->bonus_script[i]->icon, 1, sd->bonus_script[i]->tick, 1, 0, 0);
+		}
+		if (!sd->bonus_script[i]->script) {
+			ShowError("pc_bonus_script: The script has been removed somewhere. \"%s\"\n", StringBuf_Value(sd->bonus_script[i]->script_buf));
+			continue;
+		}
+		run_script(sd->bonus_script[i]->script, 0, sd->bl.id, 0);
+	}
+}
+
+/**
+ * Add bonus_script to player
+ * @param sd Player
+ * @param script_str Script string
+ * @param dur Duration in ms
+ * @param icon SI
+ * @param flag Flags @see enum e_bonus_script_flags
+ * @param type 0 - None, 1 - Buff, 2 - Debuff
+ * @return True if added, False if cannot
+ * @author [Cydh]
+ **/
+bool pc_bonus_script_add(struct map_session_data *sd, const char *script_str, uint32 dur, enum si_type icon, uint16 flag, uint8 type) {
+	struct script_code *script = NULL;
+
+	if (!sd)
+		return false;
+	
+	if (!(script = parse_script(script_str, "bonus_script", 0, SCRIPT_IGNORE_EXTERNAL_BRACKETS))) {
+		ShowError("pc_bonus_script_add: Failed to parse script '%s' (CID:%d).\n", script_str, sd->status.char_id);
+		return false;
+	}
+
+	if (!sd->bonus_script_num)
+		CREATE(sd->bonus_script, struct s_bonus_script *, 1);
+	else {
+		uint8 i = 0;
+		for (i = 0; i < sd->bonus_script_num; i++) {
+			if (strcmpi(script_str, StringBuf_Value(sd->bonus_script[i]->script_buf)) == 0)
+				break;
+		}
+
+		// Duplication checks
+		if (i < sd->bonus_script_num) {
+			int newdur = gettick() + dur;
+			if (flag&BSF_FORCE_REPLACE && sd->bonus_script[i]->tick < newdur) {
+				settick_timer(sd->bonus_script[i]->tid, newdur);
+				script_free_code(script);
+				return true;
+			}
+			else if (flag&BSF_FORCE_DUPLICATE) {
+				;
+			}
+			else {
+				// No duplicate bonus
+				script_free_code(script);
+				return false;
+			}
+		}
+
+		if (i >= UINT8_MAX) {
+			ShowError("pc_bonus_script_add: Reached max (%d) possible bonuses for this player %d\n", UINT8_MAX);
+			script_free_code(script);
+			return false;
+		}
+
+		RECREATE(sd->bonus_script, struct s_bonus_script *, sd->bonus_script_num+1);
+	}
+
+	CREATE(sd->bonus_script[sd->bonus_script_num], struct s_bonus_script, 1);
+
+	sd->bonus_script[sd->bonus_script_num]->script_buf = StringBuf_Malloc();
+	StringBuf_AppendStr(sd->bonus_script[sd->bonus_script_num]->script_buf, script_str);
+	sd->bonus_script[sd->bonus_script_num]->tid = INVALID_TIMER;
+	sd->bonus_script[sd->bonus_script_num]->flag = flag;
+	sd->bonus_script[sd->bonus_script_num]->icon = icon;
+	sd->bonus_script[sd->bonus_script_num]->tick = dur; // Use duration first, on run change to expire time
+	sd->bonus_script[sd->bonus_script_num]->type = type;
+	sd->bonus_script[sd->bonus_script_num]->script = script;
+
+	sd->bonus_script_num++;
+	return true;
+}
+
+/**
+ * Move bonus_script allocation to empty space
+ * @param sd
+ * @author [Cydh]
+ **/
+static void pc_bonus_script_move(struct map_session_data *sd) {
+	if (sd && sd->bonus_script_num) {
+		uint8 i, cur;
+
+		for (i = 0, cur = 0; i < sd->bonus_script_num; i++) {
+			if (sd->bonus_script[i] == NULL)
+				continue;
+
+			if (i != cur)
+				sd->bonus_script[cur] = sd->bonus_script[i];
+
+			cur++;
+		}
+
+		if (!(sd->bonus_script_num = cur)) {
+			aFree(sd->bonus_script);
+			sd->bonus_script = NULL;
+			sd->bonus_script_num = 0;
+		}
+	}
+}
+
+/**
+* Remove bonus_script data from player
+* @param sd: Target player
+* @param idx: Bonus script idx in player array
+* @author [Cydh]
+**/
+static void pc_bonus_script_remove(struct map_session_data *sd, uint8 idx) {
+	uint8 i = 0, cursor = 0;
+
+	if (!sd || !sd->bonus_script_num)
+		return;
+
+	if (idx >= sd->bonus_script_num) {
+		ShowError("pc_bonus_script_remove: Invalid index: %d\n", idx);
+		return;
+	}
+
+	if (sd->bonus_script[idx]->tid != INVALID_TIMER)
+		delete_timer(sd->bonus_script[idx]->tid, pc_bonus_script_timer);
+
+	if (sd->bonus_script[idx]->icon != SI_BLANK)
+		clif_status_load(&sd->bl, sd->bonus_script[idx]->icon, 0);
+
+	script_free_code(sd->bonus_script[idx]->script);
+	StringBuf_Free(sd->bonus_script[idx]->script_buf);
+
+	aFree(sd->bonus_script[idx]);
+	sd->bonus_script[idx] = NULL;
+}
+
+/**
 * Timer for bonus_script
 * @param tid
 * @param tick
 * @param id
 * @param data
+* @author [Cydh]
 **/
 int pc_bonus_script_timer(int tid, unsigned int tick, int id, intptr_t data) {
-	uint8 i = (uint8)data;
+	uint8 i;
 	struct map_session_data *sd;
 
 	sd = map_id2sd(id);
 	if (!sd) {
-		ShowDebug("pc_bonus_script_timer: Null pointer id: %d data: %d\n",id,data);
+		ShowError("pc_bonus_script_timer: Null pointer id: %d tid: %d\n", id, tid);
 		return 0;
 	}
 
-	if (i >= MAX_PC_BONUS_SCRIPT || !(&sd->bonus_script[i]) || !sd->bonus_script[i].script) {
-		ShowDebug("pc_bonus_script_timer: Invalid index %d\n",i);
+	if (tid == INVALID_TIMER || !sd->bonus_script_num)
 		return 0;
+
+	for (i = 0; i < sd->bonus_script_num; i++) {
+		if (sd->bonus_script[i]->tid == tid)
+			break;
 	}
 
-	if (sd->bonus_script[i].icon != SI_BLANK)
-		clif_status_load(&sd->bl, sd->bonus_script[i].icon, 0);
-	pc_bonus_script_remove(&sd->bonus_script[i]);
+	if (i == sd->bonus_script_num) {
+		ShowError("pc_bonus_script_timer: Timer %d is not found.\n", tid);
+		return 0;
+	}
+
+	pc_bonus_script_remove(sd, i);
+	pc_bonus_script_move(sd);
 	status_calc_pc(sd,SCO_NONE);
 	return 0;
 }
 
-/** [Cydh]
-* Remove bonus_script data from player
-* @param sd: Target player
-* @param i: Bonus script index
-**/
-void pc_bonus_script_remove(struct s_bonus_script *bscript) {
-	if (!bscript)
-		return;
-
-	if (bscript->script)
-		script_free_code(bscript->script);
-	bscript->script = NULL;
-	memset(bscript->script_str, '\0', sizeof(bscript->script_str));
-	bscript->tick = 0;
-	bscript->flag = 0;
-	bscript->icon = SI_BLANK;
-	if (bscript->tid != INVALID_TIMER)
-		delete_timer(bscript->tid,pc_bonus_script_timer);
-	bscript->tid = INVALID_TIMER;
-}
-
-/** [Cydh]
+/**
 * Check then clear all active timer(s) of bonus_script data from player based on reason
 * @param sd: Target player
 * @param flag: Reason to remove the bonus_script. e_bonus_script_flags or e_bonus_script_types
+* @author [Cydh]
 **/
 void pc_bonus_script_clear(struct map_session_data *sd, uint16 flag) {
 	uint8 i, count = 0;
-	if (!sd)
+	if (!sd || !sd->bonus_script_num)
 		return;
 
-	for (i = 0; i < MAX_PC_BONUS_SCRIPT; i++) {
-		if (&sd->bonus_script[i] && sd->bonus_script[i].script &&
-			(sd->bonus_script[i].flag&flag || //Remove bonus script based on e_bonus_script_flags
-			(sd->bonus_script[i].type && (
-				(flag&BSF_REM_BUFF && sd->bonus_script[i].type == 1) || //Remove bonus script based on buff type
-				(flag&BSF_REM_DEBUFF && sd->bonus_script[i].type == 2)) //Remove bonus script based on debuff type
-			))) 
+	for (i = 0; i < sd->bonus_script_num; i++) {
+		if ((flag&sd->bonus_script[i]->flag) || // Matched flag
+			(sd->bonus_script[i]->type && (
+				(flag&BSF_REM_BUFF   && sd->bonus_script[i]->type == 1) || // Buff type
+				(flag&BSF_REM_DEBUFF && sd->bonus_script[i]->type == 2))   // Debuff type
+			))
 		{
-			if (sd->bonus_script[i].icon != SI_BLANK)
-				clif_status_load(&sd->bl, sd->bonus_script[i].icon, 0);
-			pc_bonus_script_remove(&sd->bonus_script[i]);
+			pc_bonus_script_remove(sd, i);
 			count++;
 		}
 	}
-	if (count && !(flag&BSF_REM_ON_LOGOUT)) //Don't need to do this if log out
-		status_calc_pc(sd,SCO_NONE);
+	if (count) {
+		pc_bonus_script_move(sd);
+		if (!(flag&BSF_REM_ON_LOGOUT)) { //Don't need to do this if log out
+			status_calc_pc(sd,SCO_NONE);
+		}
+	}
 }
 
 /**
 * Clear all bonus script from player
 * @param sd
-* @param permanent If true, will removes permanent bonus script.
+* @param flag &1 - Remove permanent bonus_script, &2 - Logout
 * @author [Cydh]
-*/
-void pc_bonus_script_clear_all(struct map_session_data *sd, bool permanent) {
+**/
+void pc_bonus_script_clear_all(struct map_session_data *sd, uint8 flag) {
 	uint8 i, count = 0;
-	if (!sd)
+	if (!sd || !sd->bonus_script_num)
 		return;
 
-	for (i = 0; i < MAX_PC_BONUS_SCRIPT; i++) {
-		if (!&sd->bonus_script[i] && !sd->bonus_script[i].script)
-			continue;
-		if (!permanent && sd->bonus_script[i].flag&BSF_PERMANENT)
+	for (i = 0; i < sd->bonus_script_num; i++) {
+		if (!(flag&1) && (sd->bonus_script[i]->flag&BSF_PERMANENT))
 			continue;
-		if (sd->bonus_script[i].icon != SI_BLANK)
-			clif_status_load(&sd->bl, sd->bonus_script[i].icon, 0);
-		pc_bonus_script_remove(&sd->bonus_script[i]);
+		pc_bonus_script_remove(sd, i);
 		count++;
 	}
-	status_calc_pc(sd,SCO_NONE);
+	if (count) {
+		pc_bonus_script_move(sd);
+		if (!(flag&2))
+			status_calc_pc(sd,SCO_NONE);
+	}
 }
 
 /** [Cydh]

+ 10 - 7
src/map/pc.h

@@ -7,6 +7,7 @@
 #include "../common/mmo.h" // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus
 #include "../common/ers.h"
 #include "../common/timer.h" // INVALID_TIMER
+#include "../common/strlib.h"// StringBuf
 #include "map.h" // RC_ALL
 #include "atcommand.h" // AtCommandType
 #include "battle.h" // battle_config
@@ -159,11 +160,11 @@ struct s_pc_itemgrouphealrate {
 ///Timed bonus 'bonus_script' struct [Cydh]
 struct s_bonus_script {
 	struct script_code *script;
-	char script_str[MAX_BONUS_SCRIPT_LENGTH]; //Used for comparing and storing on table
+	StringBuf *script_buf; //Used for comparing and storing on table
 	uint32 tick;
-	uint8 flag;
-	char type; //0 - Ignore; 1 - Buff; 2 - Debuff
-	int16 icon;
+	uint16 flag;
+	enum si_type icon;
+	uint8 type; //0 - Ignore; 1 - Buff; 2 - Debuff
 	int tid;
 };
 
@@ -603,7 +604,8 @@ struct map_session_data {
 	struct vip_info vip;
 	bool disableshowrate; //State to disable clif_display_pinfo(). [Cydh]
 #endif
-	struct s_bonus_script bonus_script[MAX_PC_BONUS_SCRIPT]; ///Bonus Script [Cydh]
+	struct s_bonus_script **bonus_script; ///Bonus Script [Cydh]
+	uint8 bonus_script_num;
 	
 	struct s_pc_itemgrouphealrate **itemgrouphealrate; /// List of Item Group Heal rate bonus
 	uint8 itemgrouphealrate_count; /// Number of rate bonuses
@@ -1118,9 +1120,10 @@ void pc_crimson_marker_clear(struct map_session_data *sd);
 void pc_show_version(struct map_session_data *sd);
 
 int pc_bonus_script_timer(int tid, unsigned int tick, int id, intptr_t data);
-void pc_bonus_script_remove(struct s_bonus_script *bscript);
+void pc_bonus_script(struct map_session_data *sd);
+bool pc_bonus_script_add(struct map_session_data *sd, const char *script_str, uint32 dur, enum si_type icon, uint16 flag, uint8 type);
 void pc_bonus_script_clear(struct map_session_data *sd, uint16 flag);
-void pc_bonus_script_clear_all(struct map_session_data *sd, bool permanent);
+void pc_bonus_script_clear_all(struct map_session_data *sd, uint8 flag);
 
 void pc_cell_basilica(struct map_session_data *sd);
 

+ 21 - 44
src/map/script.c

@@ -2352,12 +2352,6 @@ void script_hardcoded_constants(void) {
 	script_set_constant("Option_Dragon",OPTION_DRAGON,false);
 	script_set_constant("Option_Costume",OPTION_COSTUME,false);
 
-	/* bonus_script commands */
-	script_set_constant("BSF_REM_BUFF",BSF_REM_BUFF,false);
-	script_set_constant("BSF_REM_DEBUFF",BSF_REM_DEBUFF,false);
-	script_set_constant("BSF_ALL",BSF_ALL,false);
-	script_set_constant("BSF_CLEARALL",BSF_CLEARALL,false);
-
 	/* sc_start flags */
 	script_set_constant("SCSTART_NONE",SCSTART_NONE,false);
 	script_set_constant("SCSTART_NOAVOID",SCSTART_NOAVOID,false);
@@ -18909,16 +18903,19 @@ BUILDIN_FUNC(montransform) {
 * @author [Cydh]
  **/
 BUILDIN_FUNC(bonus_script) {
-	uint8 i, flag = 0;
+	uint16 flag = 0;
 	int16 icon = SI_BLANK;
 	uint32 dur;
-	char type = 0;
+	uint8 type = 0;
 	TBL_PC* sd;
 	const char *script_str = NULL;
-	struct script_code *script = NULL;
 
-	if (script_hasdata(st,7))
-		sd = map_charid2sd(script_getnum(st,7));
+	if (script_hasdata(st,7)) {
+		if (!(sd = map_charid2sd(script_getnum(st,7)))) {
+			ShowError("buildin_bonus_script: Player CID=%d is not found.\n", script_getnum(st,7));
+			return SCRIPT_CMD_FAILURE;
+		}
+	}
 	else
 		sd = script_rid2sd(st);
 
@@ -18927,46 +18924,26 @@ BUILDIN_FUNC(bonus_script) {
 	
 	script_str = script_getstr(st,2);
 	dur = 1000 * abs(script_getnum(st,3));
-	FETCH(4,flag);
-	FETCH(5,type);
-	FETCH(6,icon);
+	FETCH(4, flag);
+	FETCH(5, type);
+	FETCH(6, icon);
 
+	// No Script string, No Duration!
 	if (script_str[0] == '\0' || !dur) {
-		//ShowWarning("buildin_bonus_script: Invalid script. Skipping...\n");
+		ShowError("buildin_bonus_script: Invalid! Script: \"%s\". Duration: %d\n", script_str, dur);
 		return SCRIPT_CMD_FAILURE;
 	}
 
-	//Skip duplicate entry
-	ARR_FIND(0,MAX_PC_BONUS_SCRIPT,i,&sd->bonus_script[i] && sd->bonus_script[i].script_str && strcmp(sd->bonus_script[i].script_str,script_str) == 0);
-	if (i < MAX_PC_BONUS_SCRIPT) {
-		//ShowWarning("buildin_bonus_script: Duplicate entry with bonus '%d'. Skipping...\n",i);
-		return SCRIPT_CMD_SUCCESS;
-	}
-
-	if (!(script = parse_script(script_str,"bonus_script",0,1))) {
-		ShowWarning("buildin_bonus_script: Failed to parse script '%s' (cid:%d). Skipping...\n",script_str,sd->status.char_id);
+	if (strlen(script_str) >= MAX_BONUS_SCRIPT_LENGTH) {
+		ShowError("buildin_bonus_script: Script string to long: \"%s\".\n", script_str);
 		return SCRIPT_CMD_FAILURE;
 	}
 
-	//Find the empty slot
-	ARR_FIND(0,MAX_PC_BONUS_SCRIPT,i,!sd->bonus_script[i].script);
-	if (i >= MAX_PC_BONUS_SCRIPT) {
-		ShowWarning("buildin_itemscript: Maximum script_bonus is reached (cid:%d max: %d). Skipping...\n",sd->status.char_id,MAX_PC_BONUS_SCRIPT);
-		return SCRIPT_CMD_SUCCESS;
-	}
-
-	//Add the script data
-	memcpy(sd->bonus_script[i].script_str,script_str,strlen(script_str)+1);
-	sd->bonus_script[i].script = script;
-	sd->bonus_script[i].tick = gettick() + dur;
-	sd->bonus_script[i].flag = flag;
-	sd->bonus_script[i].type = type;
-	sd->bonus_script[i].icon = icon;
-
-	if (sd->bonus_script[i].icon != SI_BLANK) //Gives status icon if exist
-		clif_status_change(&sd->bl,sd->bonus_script[i].icon,1,dur,1,0,0);
+	if (icon <= SI_BLANK || icon >= SI_MAX)
+		icon = SI_BLANK;
 
-	status_calc_pc(sd,SCO_NONE);
+	if (pc_bonus_script_add(sd, script_str, dur, (enum si_type)icon, flag, type))
+		status_calc_pc(sd,SCO_NONE);
 	return SCRIPT_CMD_SUCCESS;
 }
 
@@ -18979,7 +18956,7 @@ BUILDIN_FUNC(bonus_script) {
 */
 BUILDIN_FUNC(bonus_script_clear) {
 	TBL_PC* sd;
-	bool flag = 0;
+	bool flag = false;
 
 	if (script_hasdata(st,2))
 		flag = script_getnum(st,2);
@@ -18992,7 +18969,7 @@ BUILDIN_FUNC(bonus_script_clear) {
 	if (sd == NULL)
 		return SCRIPT_CMD_FAILURE;
 
-	pc_bonus_script_clear_all(sd,flag); /// Don't remove permanent script
+	pc_bonus_script_clear_all(sd,(flag ? 1 : 0));
 	return SCRIPT_CMD_SUCCESS;
 }
 

+ 1 - 7
src/map/status.c

@@ -3146,13 +3146,7 @@ int status_calc_pc_(struct map_session_data* sd, enum e_status_calc_opt opt)
 			run_script(data->script,0,sd->bl.id,0);
 	}
 
-	for (i = 0; i < MAX_PC_BONUS_SCRIPT; i++) { //Process script Bonus [Cydh]
-		if (!(&sd->bonus_script[i]) || !sd->bonus_script[i].script)
-			continue;
-		if (sd->bonus_script[i].tid == INVALID_TIMER) //Just add timer only for new attached script
-			sd->bonus_script[i].tid = add_timer(sd->bonus_script[i].tick,pc_bonus_script_timer,sd->bl.id,i);
-		run_script(sd->bonus_script[i].script,0,sd->bl.id,0);
-	}
+	pc_bonus_script(sd);
 
 	if( sd->pd ) { // Pet Bonus
 		struct pet_data *pd = sd->pd;

+ 19 - 17
src/map/status.h

@@ -1746,23 +1746,25 @@ enum e_status_change_start_flags {
 
 ///Enum for bonus_script's flag [Cydh]
 enum e_bonus_script_flags {
-	BSF_REM_ON_DEAD				= 0x001, ///Removed when dead
-	BSF_REM_ON_DISPELL			= 0x002, ///Removed by Dispell
-	BSF_REM_ON_CLEARANCE		= 0x004, ///Removed by Clearance
-	BSF_REM_ON_LOGOUT			= 0x008, ///Removed when player logged out
-	BSF_REM_ON_BANISHING_BUSTER	= 0x010, ///Removed by Banishing Buster
-	BSF_REM_ON_REFRESH			= 0x020, ///Removed by Refresh
-	BSF_REM_ON_LUXANIMA			= 0x040, ///Removed by Luxanima
-	BSF_REM_ON_MADOGEAR			= 0x080, ///Removed when Madogear is activated or deactivated
-	BSF_REM_ON_DAMAGED			= 0x100, ///Removed when receive damage
-	BSF_PERMANENT				= 0x200, ///Cannot be removed by sc_end SC_ALL
-
-	// These flags better in the last of everything
-	BSF_REM_BUFF	= 0x1000,	///Remove positive buff
-	BSF_REM_DEBUFF	= 0x2000,	///Remove negative buff
-
-	BSF_ALL = 0x0FFF|BSF_REM_BUFF|BSF_REM_DEBUFF,
-	BSF_CLEARALL = BSF_ALL&~BSF_PERMANENT,
+	BSF_REM_ON_DEAD				= 0x001, ///< Removed when dead
+	BSF_REM_ON_DISPELL			= 0x002, ///< Removed by Dispell
+	BSF_REM_ON_CLEARANCE		= 0x004, ///< Removed by Clearance
+	BSF_REM_ON_LOGOUT			= 0x008, ///< Removed when player logged out
+	BSF_REM_ON_BANISHING_BUSTER	= 0x010, ///< Removed by Banishing Buster
+	BSF_REM_ON_REFRESH			= 0x020, ///< Removed by Refresh
+	BSF_REM_ON_LUXANIMA			= 0x040, ///< Removed by Luxanima
+	BSF_REM_ON_MADOGEAR			= 0x080, ///< Removed when Madogear is activated or deactivated
+	BSF_REM_ON_DAMAGED			= 0x100, ///< Removed when receive damage
+	BSF_PERMANENT				= 0x200, ///< Cannot be removed by sc_end SC_ALL
+
+	// These flags cannot be stacked, BSF_FORCE_REPLACE has highest priority to check if YOU force to add both
+	BSF_FORCE_REPLACE			= 0x400, ///< Force to replace duplicated script by expanding the duration
+	BSF_FORCE_DUPLICATE			= 0x800, ///< Force to add duplicated script
+
+	// These flags aren't part of 'bonus_script' scripting flags
+
+	BSF_REM_BUFF	= 0x4000,	///< Remove positive buff if battle_config.debuff_on_logout&1
+	BSF_REM_DEBUFF	= 0x8000,	///< Remove negative buff if battle_config.debuff_on_logout&2
 };
 
 ///Enum for status_get_hpbonus and status_get_spbonus