Browse Source

Channel System is Expanded! (#1933)

* Many changes on conf/channel.conf!
- Now capable of setting default values for channels through the config such as the channel name, channel password, member capacity, chat color, chat delay, and more.
* Many new channel script commands!
- Added script commands channel_create, channel_setopt channel_setcolor, channel_setpass, channel_setgroup, channel_chat, channel_ban, channel_unban, channel_kick, and channel_delete.
Cydh Ramdh 8 năm trước cách đây
mục cha
commit
c87dba5a52

+ 103 - 26
conf/channels.conf

@@ -1,16 +1,5 @@
 // Channel System Configuration File
-
-chsys: (
-{
-	/* Default channels (available to all players) */
-	default_channels: {
-		/* channel_name : channel_messages_color */
-	 	main: "Yellow"
-		support: "Blue"
-		trade: "Red"
-		chat: "Default"
-		/* Add as many channels as you'd like. */
-	}
+channel_config: {
 
 	/* Colors available */
 	colors: {
@@ -21,24 +10,112 @@ chsys: (
 		Cyan: "0x00b89d"
 		Yellow: "0xffff90"
 		Green: "0x28bf00"
+		White: "0xFFFFFF"
+		Purple: "0xD67FFF"
+		LightGreen: "0xB6FF00"
 		Normal: "0x00ff00"
 		/* Add as many colors as you'd like. */
 	}
 
-	/* Allow users to create their own (private) channels through @channel command? */
-	/* (must also allow players to use @channel in groups.conf) */
-	allow_user_channel_creation: true
+	/**
+	 * Private channel config
+	 * - Always CHAN_TYPE_PUBLIC
+	 * - Always displayed in chat log as "#channel_name: <name>: <chat>"
+	 * - ID of private channels start at 1000
+	 **/
+	private_channel: {
+		allow: true			  // (bool)	Allow player to create their own channel?
+		color: "Default"	  // (string)	Default color, see colors
+		delay: 1000			  // (int)	Chat delay for each member
+		max_member: 1000	  // (int)	Max members
+		self_notif: true	  // (bool)	Show message when player enters or leaves the channel
+		join_notif: false	  // (bool)	Show message when player joined the channel
+		leave_notif: false	  // (bool)	Show message when player leaves the channel
+		/* Moderation feature for channel owner, allowed to: */
+		ban: true			  // (bool)	Ban players
+		kick: true			  // (bool)	Kick players
+		color_override: false // (bool)	Allow players to change the private channel color to their own
+		change_delay: false	  // (bool)	Allow players to change the private channel delay to their own
+	}
 
-	/* "map_local_channel" is an instanced channel unique to each map. */
-	map_local_channel: false
-	map_local_channel_name: "map"
-	map_local_channel_color: "Yellow"
-	map_local_channel_autojoin: true /* Disable autojoin in specific maps through mapflag 'nomapchannelautojoin'. */
+	/**
+	 * Default server channels
+	 **/
+	channels: (
+	/**
+	 * Structure
+	{
+		name: "#channel"		 // (string)	Channel name
+		password: ""			 // (string)	Channel password
+		alias: "[Channel]"		 // (string)	Message from this that channel will be displayed instead the channel name
+		color: "Default"		 // (string)	Channel color
+		type: "CHAN_TYPE_PUBLIC" // (string)	Channel type: CHAN_TYPE_PUBLIC, CHAN_TYPE_ALLY, CHAN_TYPE_MAP
+		autojoin: false			 // (bool)	Players will auto join channel
+		delay: 1000				 // (int)	Chat delay for each player
+		leave: true				 // (bool)	Player is allowed to leave the channel
+		chat: true				 // (bool)	Player is allowed to chat on this channel
+		color_override: false	 // (bool)	Allow players to change the private channel color to their own
+		self_notif: true		 // (bool)	Show message when player enters or leaves the channel
+		join_notif: false		 // (bool)	Show message when player joined the channel
+		leave_notif: false		 // (bool)	Show message when player leaves the channel
+		groupid: (0,..,99)		 // (list,int)	Only players with valid group IDs are allowed to join. Group with 'channel_admin' can always enter the channel.
+		/// All values above are default settings
+	},							 // Use comma if followed by other channel
+	 **/
+	{
+		name: "#global"
+		alias: "[Global]"
+		color: "White"
+		type: "CHAN_TYPE_PUBLIC"
+		delay: 1000
+		autojoin: false
+		leave: false
+	},
+	{
+		name: "#support"
+		alias: "[Support]"
+		color: "Blue"
+		type: "CHAN_TYPE_PUBLIC"
+		delay: 1000
+		autojoin: false
+	},
+	{
+		name: "#trade"
+		alias: "[Trade]"
+		color: "LightGreen"
+		type: "CHAN_TYPE_PUBLIC"
+		delay: 1000
+		autojoin: false
+	}
+	)
 
-	/* "ally_channel" is a channel shared by all your guild allies. */
-	ally_channel_enabled: true
-	ally_channel_name: "ally"
-	ally_channel_color: "Green"
-	ally_channel_autojoin: true
+	/**
+	 * Channel config for guild alliance
+	 * For the structure, see the 'channels' above
+	 **/
+	ally: {
+		name: "#ally"
+		alias: "[Ally]"
+		color: "Green"
+		type: "CHAN_TYPE_ALLY" // DO NOT CHANGE THIS VALUE
+		delay: 1000
+		autojoin: false
+		leave: true
+		chat: true
+	}
+
+	/**
+	 * Channel config for map channel
+	 * For the structure, see the 'channels' above
+	 **/
+	map: {
+		name: "#map"
+		alias: "[Map]"
+		color: "Yellow"
+		type: "CHAN_TYPE_MAP" // DO NOT CHANGE THIS VALUE
+		delay: 1000
+		autojoin: false
+		leave: true
+		chat: true
+	}
 }
-)

+ 3 - 0
conf/map_athena.conf

@@ -119,6 +119,9 @@ help_txt: conf/help.txt
 help2_txt: conf/help2.txt
 charhelp_txt: conf/charhelp.txt
 
+// Load channel config from
+channel_conf: conf/channels.conf
+
 // Maps:
 import: conf/maps_athena.conf
 

+ 14 - 1
conf/msg_conf/map_msg.conf

@@ -812,7 +812,20 @@
 758: Baby Gunslinger
 759: Baby Rebellion
 
-//760-899 free
+// Channel System
+760: You cannot join channel '%s'. Limit of %d has been met.
+761: %s %s has joined.
+762: You cannot leave channel '%s'.
+763: %s %s left.
+764: You cannot change the color for channel '%s'.
+765: You're not allowed to ban a player.
+766: You cannot kick a player from channel '%s'.
+767: You're not allowed to kick a player.
+768: %s %s has been kicked.
+769: %s %s has been banned.
+770: %s %s has been unbanned.
+
+//771-899 free
 
 //------------------------------------
 // More atcommands message

+ 158 - 1
doc/script_commands.txt

@@ -1,4 +1,4 @@
-//===== rAthena Documentation ================================
+//===== rAthena Documentation ================================
 //= rAthena Script Commands
 //===== By: ==================================================
 //= rAthena Dev Team
@@ -1033,6 +1033,7 @@ From here on, we will have the commands sorted as follow:
 11.- Homunculus commands.
 12.- Mercenary commands.
 13.- Party commands.
+14.- Channel commands.
 
 =====================
 |1.- Basic commands.|
@@ -9444,3 +9445,159 @@ else false if the leave failed.
 If <char id> is specified, the specified player is used rather than the attached one.
 
 ---------------------------------------
+
+========================
+|14.- Channel commands.|
+========================
+---------------------------------------
+
+*channel_create "<chname>","<alias>"{,"<password>"{<option>{,<delay>{,<color>{,<char_id>}}}}};
+
+Creates a public channel with <chname> as the channel name. To protect the
+channel, use <password> or write "null" to create it without a password.
+Channel name must start with '#' and cannot be the same as the map or ally
+channel names.
+
+<alias> will be used to change the channel name when the channel message
+is displayed.
+
+<option> values are:
+	CHAN_OPT_BASE		    - Default option including CHAN_OPT_ANNOUNCE_SELF|CHAN_OPT_MSG_DELAY|CHAN_OPT_CAN_CHAT|CHAN_OPT_CAN_LEAVE
+	CHAN_OPT_ANNOUNCE_SELF  - Show info for player itself if player has joined/leaves the channel
+	CHAN_OPT_ANNOUNCE_JOIN  - Display message when player is joining the channel
+	CHAN_OPT_ANNOUNCE_LEAVE - Display message when player is leaving the channel
+	CHAN_OPT_MSG_DELAY	    - Enable chat delay for the channel
+	CHAN_OPT_COLOR_OVERRIDE - Player's unique font color will override channel's color
+	CHAN_OPT_CAN_CHAT	    - Player can chat in the channel
+	CHAN_OPT_CAN_LEAVE	    - Player can leave the channel
+	CHAN_OPT_AUTOJOIN	    - Players will auto join the channel at login
+
+The <delay> is the minimum chat delay in millisecond for a single player before
+the player can chat again in the same channel.
+
+Use <color> hex code to set the color for this channel, if not defined, default
+channel color will be used.
+
+If <char_id> is defined, the channel will be a private channel and the player
+will be the the channel owner.
+
+Returns 1 on success.
+
+	/**
+	 * This example will shows the message on this channel as
+	 * [rAthena] Admin : Hello world!
+	 * instead of
+	 * #rathena Admin : Hello world!
+	 **/
+	channel_create("#rathena","[rAthena]");
+	channel_create("#vip","[VIP]","vipmemberonly");
+
+---------------------------------------
+
+*channel_setopt "<chname>",<option>,<value>;
+
+Set option for the channel. Use 1 in <value> to set it, or 0 to unset.
+The <option> values are the same as the 'channel_create' options.
+
+For CHAN_OPT_MSG_DELAY, the delay in millisecond must be sent or use 0
+to remove the delay at <value>.
+
+Returns 1 on success.
+
+	// Example to set delay
+	channel_setopt("#global",CHAN_OPT_MSG_DELAY,5000);
+
+Only for public and private channel.
+
+---------------------------------------
+
+*channel_setcolor "<chname>",<color>;
+
+To change channel color.
+<color> uses hex RGB values.
+
+Returns 1 on success.
+
+---------------------------------------
+
+*channel_setpass "<chname>","<password>";
+
+To set, unset, or change password of a channel.
+Use "null" to remove the password.
+
+Returns 1 on success.
+Only for public and private channel.
+
+---------------------------------------
+
+*channel_setgroup "<chname>",<group_id>{,...,<group_id>};
+*channel_setgroup2 "<chname>",<array_of_groups>;
+
+Set group restriction for a channel. Only player with matching <group_id>
+are allowed to to join the channel.
+
+By using 0 in the first group channel, the group restriction will be
+removed from the channel config.
+
+'channel_setgroup2' receives input for group list as an array.
+
+Returns 0 on failure, and 1 (or n groups count) on success.
+
+	// Example 1: Remove groups
+	channel_setgroup("#event",0);
+
+	// Example 2: Multiple values
+	channel_setgroup("#vip",2,5);
+
+	// Example 3: Using array
+	setarray .@staffs[0],2,3,4,10,99;
+	channel_setgroup("#staff",.@staffs);
+
+Only for public and private channel.
+
+---------------------------------------
+
+*channel_chat "<chname>","<message>"{,<color>};
+
+Sends message to the channel.
+Returns 1 on success.
+
+	// Example if channel doesn't have alias
+	channel_chat(#rathena,"Hello World!"); // #rathena Hello World!
+
+	// Example if channel has alias
+	channel_chat(#rathena,"Hello World!"); // [rAthena] Hello World!
+
+---------------------------------------
+
+*channel_ban "<chname>",<char_id>;
+
+Ban player from a public or private channel.
+Channel's owner or group with PC_PERM_CHANNEL_ADMIN cannot be banned.
+Returns 1 on success.
+
+---------------------------------------
+
+*channel_unban "<chname>",<char_id>;
+
+Unban player from a public or private channel.
+Returns 1 on success.
+
+---------------------------------------
+
+*channel_kick "<chname>",<char_id>;
+*channel_kick "<chname>","<char_name>";
+
+Kick player from a public or private channel.
+Channel's owner or group with PC_PERM_CHANNEL_ADMIN cannot be kicked.
+Returns 1 on success.
+
+---------------------------------------
+
+*channel_delete "<chname>";
+
+Delete an existing public or private channel. Cannot delete ally or
+local map channel.
+Returns 0 on success.
+
+---------------------------------------

+ 4 - 0
src/char/char.c

@@ -1324,6 +1324,10 @@ int char_check_char_name(char * name, char * esc_name)
 	if( strcmpi(name, charserv_config.wisp_server_name) == 0 )
 		return -1; // nick reserved for internal server messages
 
+	// check for the channel symbol
+	if( name[0] == '#' )
+		return -2;
+
 	// Check Authorised letters/symbols in the name of the character
 	if( charserv_config.char_config.char_name_option == 1 )
 	{ // only letters/symbols in char_name_letters are authorised

+ 4 - 2
src/map/atcommand.c

@@ -9378,7 +9378,7 @@ static inline void atcmd_channel_help(struct map_session_data *sd, const char *c
 {
 	int fd = sd->fd;
 	bool can_delete = pc_has_permission(sd, PC_PERM_CHANNEL_ADMIN);
-	bool can_create = (can_delete || channel_config.user_chenable);
+	bool can_create = (can_delete || channel_config.private_channel.allow);
 	clif_displaymessage(fd, msg_txt(sd,1414));// ---- Available options:
 
 	//option create
@@ -9487,6 +9487,8 @@ ACMD_FUNC(channel) {
 		return channel_pcunbind(sd);
 	} else if ( strcmpi(key,"ban") == 0 ) {
 		return channel_pcban(sd,sub1,sub2,0);
+	} else if ( strcmpi(key,"kick") == 0 ) {
+		return channel_pckick(sd,sub1,sub2);
 	} else if ( strcmpi(key,"banlist") == 0 ) {
 		return channel_pcban(sd,sub1,NULL,3);
 	} else if ( strcmpi(key,"unban") == 0 ) {
@@ -9497,7 +9499,7 @@ ACMD_FUNC(channel) {
 		char sub3[CHAN_NAME_LENGTH], sub4[CHAN_NAME_LENGTH];
 		sub3[0] = sub4[0] = '\0';
 		sscanf(sub2, "%19s %19s", sub3, sub4);
-		if( strcmpi(key,"create") == 0 && ( channel_config.user_chenable || pc_has_permission(sd, PC_PERM_CHANNEL_ADMIN) ) ) {
+		if( strcmpi(key,"create") == 0 && ( channel_config.private_channel.allow || pc_has_permission(sd, PC_PERM_CHANNEL_ADMIN) ) ) {
 			if (sub4[0] != '\0') {
 				clif_displaymessage(fd, msg_txt(sd, 1408)); // Channel password may not contain spaces.
 				return -1;

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 371 - 250
src/map/channel.c


+ 68 - 32
src/map/channel.h

@@ -14,49 +14,79 @@ extern "C" {
 #define CHAN_MSG_LENGTH 150
 
 enum Channel_Opt {
-	CHAN_OPT_BASE		= 0,
-	CHAN_OPT_ANNOUNCE_JOIN	= 1,	//display message when join or leave
-	CHAN_OPT_MSG_DELAY	= 2,
-	CHAN_OPT_COLOR_OVERRIDE = 3,
+	CHAN_OPT_NONE		    = 0,	///< None
+	CHAN_OPT_ANNOUNCE_SELF  = 0x01,	///< Shows info when player joined/left channel to self
+	CHAN_OPT_ANNOUNCE_JOIN  = 0x02,	///< Shows info if player joined the channel
+	CHAN_OPT_ANNOUNCE_LEAVE = 0x04,	///< Shows info if player left the channel
+	CHAN_OPT_MSG_DELAY	    = 0x08,	///< Enables chat delay
+	CHAN_OPT_COLOR_OVERRIDE = 0x10,	///< Enables color channel be override by player's font color
+	CHAN_OPT_CAN_CHAT		= 0x20,	///< Allows player to chat in the channel
+	CHAN_OPT_CAN_LEAVE		= 0x40,	///< Allows player to leave the channel
+	CHAN_OPT_AUTOJOIN		= 0x80,	///< Player will be autojoined to the channel
+
+	CHAN_OPT_BASE = CHAN_OPT_ANNOUNCE_SELF|CHAN_OPT_MSG_DELAY|CHAN_OPT_CAN_CHAT|CHAN_OPT_CAN_LEAVE,
 };
 
 enum Channel_Type {
-	CHAN_TYPE_PUBLIC	= 0,	//config file made
-	CHAN_TYPE_PRIVATE	= 1,	//user made
-	CHAN_TYPE_MAP		= 2,	//made by map
-	CHAN_TYPE_ALLY		= 3,	//guild
+	CHAN_TYPE_PUBLIC  = 0, ///< Config file made
+	CHAN_TYPE_PRIVATE = 1, ///< User's channel
+	CHAN_TYPE_MAP	  = 2, ///< Local map
+	CHAN_TYPE_ALLY	  = 3, ///< Guild + its alliance
 };
 
-struct Channel_Config {
-	unsigned long *colors;		//color avail int list
-	char **colors_name;		//colors avail name list
-	unsigned char colors_count;	//color avail count
-	unsigned char map_chcolor, ally_chcolor; //msg color for map, ally
-	bool map_enable, ally_enable, user_chenable; //map, ally, users channels enable ?
-	bool map_autojoin, ally_autojoin;	//do user auto join in mapchange, guildjoin ?
-	char map_chname[CHAN_NAME_LENGTH], ally_chname[CHAN_NAME_LENGTH]; //channel name for map and ally
-	bool closing;			//server is closing
+struct Channel {
+	//unsigned short id;			  ///< Channel ID (unused yet)
+	char name[CHAN_NAME_LENGTH];  ///< Channel Name
+	char pass[CHAN_NAME_LENGTH];  ///< Channe display name
+	char alias[CHAN_NAME_LENGTH]; ///< Password
+	enum Channel_Type type;		  ///< Channel type @see enum Channel_Type
+	unsigned long color;		  ///< Channel color in BGR
+	unsigned char opt;			  ///< Channel options @see enum Channel_Opt
+	unsigned short msg_delay;	  ///< Chat delay in miliseconds
+	unsigned int char_id;		  ///< If CHAN_TYPE_PRIVATE, owner is char_id of channel creator
+	uint16 m;					  ///< If CHAN_TYPE_MAP, owner is map id
+	int gid;					  ///< If CHAN_TYPE_ALLY, owner is first logged guild_id
+	DBMap *users;				  ///< List of users
+	DBMap *banned;				  ///< List of banned chars -> char_id
+	unsigned short group_count;	  ///< Number of group id
+	unsigned short *groups;		  ///< List of group id, only these groups can join the channel
 };
-extern struct Channel_Config channel_config;
 
-struct Channel {
-	char name[CHAN_NAME_LENGTH];	//channel name
-	char pass[CHAN_NAME_LENGTH];	//channel password
-	unsigned char color;		//msg color
-	DBMap *users;			//users in channel charid list
-	DBMap *banned;			//users banned from channel charid list
-	enum Channel_Opt opt;		//flag for some treatement
-	enum Channel_Type type;		//type of channel
-	unsigned int owner;		//if chan_type private charid of creator
-	uint16 m;			//if chan_type map guild_id
-	int gid;			//if chan_type guild type guild_id
-	unsigned char msg_delay;	//delay in second if opt_msg_delay
+struct chan_banentry {
+	uint32 char_id;
+	char char_name[NAME_LENGTH];
+} chan_banentry;
+
+struct Channel_Config {
+	unsigned long *colors;		///< List of available colors
+	char **colors_name;			///< Name list of available colors
+	unsigned char colors_count;	///< Number of available colors
+
+	/// Private channel default configs
+	struct {
+		unsigned char opt;			 ///< Options @see enum Channel_Opt
+		unsigned long color;		 ///< Default color
+		unsigned int delay;			 ///< Message delay
+		unsigned short max_member;	 ///< Max member for each channel
+		unsigned allow : 1;			 ///< Allow private channel creation?
+		unsigned ban : 1;			 ///< Allow player to ban
+		unsigned kick : 1;			 ///< Allow player to kick
+		unsigned color_override : 1; ///< Owner cannot change the color_override
+		unsigned change_delay : 1;	 ///< Owner cannot change the delay
+	} private_channel;
+
+	struct Channel map_tmpl;  ///< Map channel default config
+	struct Channel ally_tmpl; ///< Alliance channel default config
+
+	bool closing; ///< Server is closing
 };
+extern struct Channel_Config channel_config;
 
 DBMap* channel_get_db(void);
 
-struct Channel* channel_create(char *name, char *pass, unsigned char color, enum Channel_Type chantype, int val);
-int channel_delete(struct Channel *channel);
+struct Channel* channel_create(struct Channel *tmp_chan);
+struct Channel* channel_create_simple(char *name, char *pass, enum Channel_Type chantype, unsigned int owner);
+int channel_delete(struct Channel *channel, bool force);
 
 int channel_join(struct Channel *channel, struct map_session_data *sd);
 int channel_mjoin(struct map_session_data *sd);
@@ -65,6 +95,8 @@ int channel_ajoin(struct guild *g);
 int channel_clean(struct Channel *channel, struct map_session_data *sd, int flag);
 int channel_pcquit(struct map_session_data *sd, int type);
 
+unsigned long channel_getColor(const char *color_str);
+
 int channel_send(struct Channel *channel, struct map_session_data *sd, const char *msg);
 void channel_read_config(void);
 
@@ -75,6 +107,9 @@ int channel_haspcbanned(struct Channel *channel,struct map_session_data *sd);
 int channel_pc_haschan(struct map_session_data *sd, struct Channel *channel);
 int channel_display_list(struct map_session_data *sd, char *option);
 
+void channel_autojoin(struct map_session_data *sd);
+bool channel_pccheckgroup(struct Channel *channel, int group_id);
+
 int channel_pccreate(struct map_session_data *sd, char *chname, char *pass);
 int channel_pcdelete(struct map_session_data *sd, char *chname);
 int channel_pcjoin(struct map_session_data *sd, char *chname, char *pass);
@@ -83,6 +118,7 @@ int channel_pccolor(struct map_session_data *sd, char *chname, char *color);
 int channel_pcbind(struct map_session_data *sd, char *chname);
 int channel_pcunbind(struct map_session_data *sd);
 int channel_pcban(struct map_session_data *sd, char *chname, char *pname, int flag);
+int channel_pckick(struct map_session_data *sd, char *chname, char *pname);
 int channel_pcsetopt(struct map_session_data *sd, char *chname, const char *option, const char *val);
 
 void do_init_channel(void);

+ 25 - 20
src/map/clif.c

@@ -6197,31 +6197,35 @@ void clif_broadcast2(struct block_list* bl, const char* mes, int len, unsigned l
 }
 
 /*
- * Display *msg from *sd to all *users in channel
+ * Display *msg to all *users in channel
  */
-void clif_channel_msg(struct Channel *channel, struct map_session_data *sd, char *msg, short color) {
+void clif_channel_msg(struct Channel *channel, const char *msg, unsigned long color) {
 	DBIterator *iter;
 	struct map_session_data *user;
-	unsigned short msg_len = (unsigned short)(strlen(msg) + 1);
+	unsigned short msg_len = 0, len = 0;
+	unsigned char buf[CHAT_SIZE_MAX];
+
+	if (!channel || !msg)
+		return;
+
+	msg_len = (unsigned short)(strlen(msg) + 1);
+
+	if( msg_len > sizeof(buf)-12 ) {
+		ShowWarning("clif_channel_msg: Truncating too long message '%s' (len=%u).\n", msg, msg_len);
+		msg_len = sizeof(buf)-12;
+	}
 
-	WFIFOHEAD(sd->fd,msg_len + 12);
-	WFIFOW(sd->fd,0) = 0x2C1;
-	WFIFOW(sd->fd,2) = msg_len + 12;
-	WFIFOL(sd->fd,4) = 0;
-	WFIFOL(sd->fd,8) = channel_config.colors[color];
-	safestrncpy(WFIFOCP(sd->fd,12), msg, msg_len);
+	WBUFW(buf,0) = 0x2C1;
+	WBUFW(buf,2) = (len = msg_len + 12);
+	WBUFL(buf,4) = 0;
+	WBUFL(buf,8) = color;
+	safestrncpy(WBUFCP(buf,12), msg, msg_len);
 
 	iter = db_iterator(channel->users);
 	for( user = (struct map_session_data *)dbi_first(iter); dbi_exists(iter); user = (struct map_session_data *)dbi_next(iter) ) {
-		if( user->fd == sd->fd )
-			continue;
-		WFIFOHEAD(user->fd,msg_len + 12);
-		memcpy(WFIFOP(user->fd,0), WFIFOP(sd->fd,0), msg_len + 12);
-		WFIFOSET(user->fd, msg_len + 12);
+		clif_send(buf, len, &user->bl, SELF);
 	}
 	dbi_destroy(iter);
-
-	WFIFOSET(sd->fd, msg_len + 12);
 }
 
 /// Displays heal effect.
@@ -10351,7 +10355,7 @@ void clif_parse_LoadEndAck(int fd,struct map_session_data *sd)
 #endif
 
 		// Instances do not need their own channels
-		if( channel_config.map_enable && channel_config.map_autojoin && !map[sd->bl.m].flag.chmautojoin && !map[sd->bl.m].instance_id )
+		if( channel_config.map_tmpl.name != NULL && (channel_config.map_tmpl.opt&CHAN_OPT_AUTOJOIN) && !map[sd->bl.m].flag.chmautojoin && !map[sd->bl.m].instance_id )
 			channel_mjoin(sd); //join new map
 	} else if (sd->guild && (battle_config.guild_notice_changemap == 2 || guild_notice))
 		clif_guild_notice(sd); // Displays at end
@@ -10679,7 +10683,7 @@ void clif_parse_GlobalMessage(int fd, struct map_session_data* sd)
 	if( !clif_process_message(sd, false, name, message, output ) )
 		return;
 
-	if( sd->gcbind ) {
+	if( sd->gcbind && ((sd->gcbind->opt&CHAN_OPT_CAN_CHAT) || pc_has_permission(sd, PC_PERM_CHANNEL_ADMIN)) ) {
 		channel_send(sd->gcbind,sd,message);
 		return;
 	}
@@ -11035,12 +11039,13 @@ void clif_parse_WisMessage(int fd, struct map_session_data* sd)
 		char* chname = target;
 
 		channel = channel_name2channel(chname,sd,3);
-		if(channel){
+		if(channel && (pc_has_permission(sd, PC_PERM_CHANNEL_ADMIN) || ((channel->opt&CHAN_OPT_CAN_CHAT) && channel_pccheckgroup(channel,sd->group_id)))){
 			if(channel_pc_haschan(sd,channel)>=0){ //we are in the chan
 				channel_send(channel,sd,message);
 			}
 			else if( channel->pass[0] == '\0') { //no pass needed
-				if (channel_join(channel,sd)==0) channel_send(channel,sd,message); //join success
+				if (channel_join(channel,sd)==0)
+					channel_send(channel,sd,message); //join success
 			}
 			else {
 				clif_displaymessage(fd, msg_txt(sd,1402)); //You're not in that channel, type '@join <#channel_name>'

+ 1 - 1
src/map/clif.h

@@ -1003,7 +1003,7 @@ enum clif_colors {
 };
 unsigned long color_table[COLOR_MAX];
 
-void clif_channel_msg(struct Channel *channel, struct map_session_data *sd, char *msg, short color);
+void clif_channel_msg(struct Channel *channel, const char *msg, unsigned long color);
 
 #define clif_menuskill_clear(sd) (sd)->menuskill_id = (sd)->menuskill_val = (sd)->menuskill_val2 = 0;
 

+ 5 - 5
src/map/guild.c

@@ -534,7 +534,7 @@ int guild_recv_info(struct guild *sg) {
 		if( sd==NULL )
 			continue;
 		sd->guild = g;
-		if(channel_config.ally_autojoin ) {
+		if(channel_config.ally_tmpl.name && (channel_config.ally_tmpl.opt&CHAN_OPT_AUTOJOIN)) {
 			channel_gjoin(sd,3); //make all member join guildchan+allieschan
 		}
 
@@ -703,7 +703,7 @@ void guild_member_joined(struct map_session_data *sd) {
 
 		if (g->instance_id != 0)
 			instance_reqinfo(sd, g->instance_id);
-		if( channel_config.ally_enable && channel_config.ally_autojoin ) {
+		if( channel_config.ally_tmpl.name != NULL && (channel_config.ally_tmpl.opt&CHAN_OPT_AUTOJOIN) ) {
 			channel_gjoin(sd,3);
 		}
 	}
@@ -1745,8 +1745,8 @@ int guild_broken(int guild_id,int flag) {
 	guild_db->foreach(guild_db,guild_broken_sub,guild_id);
 	castle_db->foreach(castle_db,castle_guild_broken_sub,guild_id);
 	storage_guild_delete(guild_id);
-	if( channel_config.ally_enable ) {
-		channel_delete(g->channel);
+	if( channel_config.ally_tmpl.name != NULL ) {
+		channel_delete(g->channel,false);
 	}
 	idb_remove(guild_db,guild_id);
 	return 0;
@@ -2303,7 +2303,7 @@ void do_final_guild(void) {
 	struct guild *g;
 
 	for( g = (struct guild *)dbi_first(iter); dbi_exists(iter); g = (struct guild *)dbi_next(iter) ) {
-		channel_delete(g->channel);
+		channel_delete(g->channel,false);
 	}
 	dbi_destroy(iter);
 

+ 5 - 2
src/map/map.c

@@ -155,6 +155,7 @@ char motd_txt[256] = "conf/motd.txt";
 char help_txt[256] = "conf/help.txt";
 char help2_txt[256] = "conf/help2.txt";
 char charhelp_txt[256] = "conf/charhelp.txt";
+char channel_conf[256] = "conf/channels.conf";
 
 char wisp_server_name[NAME_LENGTH] = "Server"; // can be modified in char-server configuration file
 
@@ -3889,6 +3890,8 @@ int map_config_read(char *cfgName)
 			strcpy(help2_txt, w2);
 		else if (strcmpi(w1, "charhelp_txt") == 0)
 			strcpy(charhelp_txt, w2);
+		else if (strcmpi(w1, "channel_conf") == 0)
+			safestrncpy(channel_conf, w2, sizeof(channel_conf));
 		else if(strcmpi(w1,"db_path") == 0)
 			safestrncpy(db_path,w2,ARRAYLENGTH(db_path));
 		else if (strcmpi(w1, "console") == 0) {
@@ -4404,7 +4407,7 @@ void do_final(void)
 		ShowStatus("Cleaning up maps [%d/%d]: %s..."CL_CLL"\r", i+1, map_num, map[i].name);
 		if (map[i].m >= 0) {
 			map_foreachinmap(cleanup_sub, i, BL_ALL);
-			channel_delete(map[i].channel);
+			channel_delete(map[i].channel,false);
 		}
 	}
 	ShowStatus("Cleaned up %d maps."CL_CLL"\n", map_num);
@@ -4755,12 +4758,12 @@ int do_init(int argc, char *argv[])
 	do_init_atcommand();
 	do_init_battle();
 	do_init_instance();
-	do_init_channel();
 	do_init_chrif();
 	do_init_clan();
 	do_init_clif();
 	do_init_script();
 	do_init_itemdb();
+	do_init_channel();
 	do_init_cashshop();
 	do_init_skill();
 	do_init_mob();

+ 1 - 0
src/map/map.h

@@ -758,6 +758,7 @@ extern char motd_txt[];
 extern char help_txt[];
 extern char help2_txt[];
 extern char charhelp_txt[];
+extern char channel_conf[];
 
 extern char wisp_server_name[];
 

+ 2 - 0
src/map/pc.c

@@ -1462,6 +1462,8 @@ void pc_reg_received(struct map_session_data *sd)
 
 		clif_changeoption( &sd->bl );
 	}
+
+	channel_autojoin(sd);
 }
 
 static int pc_calc_skillpoint(struct map_session_data* sd)

+ 1 - 1
src/map/pc.h

@@ -643,7 +643,7 @@ struct map_session_data {
 	struct Channel *gcbind;
 	bool stealth;
 	unsigned char fontcolor;
-	unsigned int channel_tick;
+	unsigned int *channel_tick;
 
 	/* [Ind] */
 	struct sc_display_entry **sc_display;

+ 463 - 0
src/map/script.c

@@ -49,6 +49,7 @@
 #include "mail.h"
 #include "quest.h"
 #include "elemental.h"
+#include "channel.h"
 
 #include <math.h>
 #include <stdlib.h> // atoi, strtol, strtoll, exit
@@ -22366,6 +22367,455 @@ BUILDIN_FUNC(openstorage2) {
 	return SCRIPT_CMD_SUCCESS;
 }
 
+/**
+ * Create a new channel
+ * channel_create "<chname>","<alias>"{,"<password>"{<option>{,<delay>{,<color>{,<char_id>}}}}};
+ * @author [Cydh]
+ **/
+BUILDIN_FUNC(channel_create) {
+	struct Channel tmp_chan, *ch = NULL;
+	const char *chname = script_getstr(st,2), *pass = NULL;
+	int i = channel_chk((char*)chname, NULL, 3);
+	TBL_PC *sd = NULL;
+
+	if (i != 0) {
+		ShowError("buildin_channel_create: Channel name '%s' is invalid. Errno %d\n", chname, i);
+		script_pushint(st,i);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	memset(&tmp_chan, 0, sizeof(struct Channel));
+
+	if (script_hasdata(st,8)) {
+		tmp_chan.char_id = script_getnum(st,8);
+		if (!(sd = map_charid2sd(tmp_chan.char_id))) {
+			ShowError("buildin_channel_create: Player with char id '%d' is not found.\n", tmp_chan.char_id);
+			script_pushint(st,-5);
+			return SCRIPT_CMD_FAILURE;
+		}
+		tmp_chan.type = CHAN_TYPE_PRIVATE;
+		i = 1;
+	}
+	else {
+		tmp_chan.type = CHAN_TYPE_PUBLIC;
+		i = 0;
+	}
+
+	safestrncpy(tmp_chan.name, chname+1, sizeof(tmp_chan.name));
+	safestrncpy(tmp_chan.alias, script_getstr(st,3), sizeof(tmp_chan.alias));
+	if (script_hasdata(st,4) && (pass = script_getstr(st,4)) && strcmpi(pass,"null") != 0)
+		safestrncpy(tmp_chan.pass, pass, sizeof(tmp_chan.pass));
+	if (script_hasdata(st,5))
+		tmp_chan.opt = script_getnum(st,5);
+	else
+		tmp_chan.opt = i ? channel_config.private_channel.opt : CHAN_OPT_BASE;
+	if (script_hasdata(st,6))
+		tmp_chan.msg_delay = script_getnum(st,6);
+	else
+		tmp_chan.msg_delay = i ? channel_config.private_channel.delay : 1000;
+	if (script_hasdata(st,7))
+		tmp_chan.color = script_getnum(st,7);
+	else
+		tmp_chan.color = i ? channel_config.private_channel.color : channel_getColor("Default");
+
+	if (!(ch = channel_create(&tmp_chan))) {
+		ShowError("buildin_channel_create: Cannot create channel '%s'.\n", chname);
+		script_pushint(st,0);
+		return SCRIPT_CMD_FAILURE;
+	}
+	if (tmp_chan.char_id)
+		channel_join(ch, sd);
+	script_pushint(st,1);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Set channel option
+ * channel_setopt "<chname>",<option>,<value>;
+ * @author [Cydh]
+ **/
+BUILDIN_FUNC(channel_setopt) {
+	struct Channel *ch = NULL;
+	const char *chname = script_getstr(st,2);
+	int opt = script_getnum(st,3), value = script_getnum(st,4);
+
+	if (!(ch = channel_name2channel((char *)chname, NULL, 0))) {
+		ShowError("buildin_channel_setopt: Channel name '%s' is invalid.\n", chname);
+		script_pushint(st,0);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	switch (opt) {
+		case CHAN_OPT_ANNOUNCE_SELF:
+		case CHAN_OPT_ANNOUNCE_JOIN:
+		case CHAN_OPT_ANNOUNCE_LEAVE:
+		case CHAN_OPT_COLOR_OVERRIDE:
+		case CHAN_OPT_CAN_CHAT:
+		case CHAN_OPT_CAN_LEAVE:
+		case CHAN_OPT_AUTOJOIN:
+			if (value)
+				ch->opt |= opt;
+			else
+				ch->opt &= ~opt;
+			break;
+		case CHAN_OPT_MSG_DELAY:
+			ch->msg_delay = value;
+			break;
+		default:
+			ShowError("buildin_channel_setopt: Invalid option %d!\n", opt);
+			script_pushint(st,0);
+			return SCRIPT_CMD_FAILURE;
+	}
+
+	script_pushint(st,1);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Set channel color
+ * channel_setcolor "<chname>",<color>;
+ * @author [Cydh]
+ **/
+BUILDIN_FUNC(channel_setcolor) {
+	struct Channel *ch = NULL;
+	const char *chname = script_getstr(st,2);
+	int color = script_getnum(st,3);
+
+	if (!(ch = channel_name2channel((char *)chname, NULL, 0))) {
+		ShowError("buildin_channel_setcolor: Channel name '%s' is invalid.\n", chname);
+		script_pushint(st,0);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	ch->color = (color & 0x0000FF) << 16 | (color & 0x00FF00) | (color & 0xFF0000) >> 16;//RGB to BGR
+
+	script_pushint(st,1);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Set channel password
+ * channel_setpass "<chname>","<password>";
+ * @author [Cydh]
+ **/
+BUILDIN_FUNC(channel_setpass) {
+	struct Channel *ch = NULL;
+	const char *chname = script_getstr(st,2), *passwd = script_getstr(st,3);
+
+	if (!(ch = channel_name2channel((char *)chname, NULL, 0))) {
+		ShowError("buildin_channel_setpass: Channel name '%s' is invalid.\n", chname);
+		script_pushint(st,0);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (!passwd || !strcmpi(passwd,"null"))
+		memset(ch->pass, '\0', sizeof(ch->pass));
+	else
+		safestrncpy(ch->pass, passwd, sizeof(ch->pass));
+	script_pushint(st,1);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Set authorized groups
+ * channel_setgroup "<chname>",<group_id>{,...,<group_id>};
+ * @author [Cydh]
+ **/
+BUILDIN_FUNC(channel_setgroup) {
+	struct Channel *ch = NULL;
+	const char *funcname = script_getfuncname(st), *chname = script_getstr(st,2);
+	int i, n = 0, group = 0;
+
+	if (!(ch = channel_name2channel((char *)chname, NULL, 0))) {
+		ShowError("buildin_channel_setgroup: Channel name '%s' is invalid.\n", chname);
+		script_pushint(st,0);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (funcname[strlen(funcname)-1] == '2') {
+		struct script_data *data = script_getdata(st,3);
+		const char *varname = reference_getname(data);
+		int32 id, idx;
+
+		if (varname[strlen(varname)-1] == '$') {
+			ShowError("buildin_channel_setgroup: The array %s is not numeric type.\n", varname);
+			script_pushint(st,0);
+			return SCRIPT_CMD_FAILURE;
+		}
+
+		n = script_array_highest_key(st, NULL, reference_getname(data), reference_getref(data));
+		if (n < 1) {
+			ShowError("buildin_channel_setgroup: No group id listed.\n");
+			script_pushint(st,0);
+			return SCRIPT_CMD_FAILURE;
+		}
+
+		if (ch->groups)
+			aFree(ch->groups);
+		ch->groups = NULL;
+		ch->group_count = 0;
+
+		id = reference_getid(data);
+		idx = reference_getindex(data);
+		for (i = 0; i < n; i++) {
+			group = (int32)__64BPRTSIZE(get_val2(st,reference_uid(id,idx+i),reference_getref(data)));
+			if (group == 0) {
+				script_pushint(st,1);
+				return SCRIPT_CMD_SUCCESS;
+			}
+			RECREATE(ch->groups, unsigned short, ++ch->group_count);
+			ch->groups[ch->group_count-1] = group;
+			ShowInfo("buildin_channel_setgroup: (2) Added group %d. Num: %d\n", ch->groups[ch->group_count-1], ch->group_count);
+		}
+	}
+	else {
+		group = script_getnum(st,3);
+		n = script_lastdata(st)-1;
+
+		if (n < 1) {
+			ShowError("buildin_channel_setgroup: Please input at least 1 group_id.\n");
+			script_pushint(st,0);
+			return SCRIPT_CMD_FAILURE;
+		}
+
+		if (ch->groups)
+			aFree(ch->groups);
+		ch->groups = NULL;
+		ch->group_count = 0;
+
+		if (group == 0) { // Removed group list
+			script_pushint(st,1);
+			return SCRIPT_CMD_SUCCESS;
+		}
+
+		CREATE(ch->groups, unsigned short, n);
+		for (i = 3; i < n+2; i++) {
+			ch->groups[ch->group_count++] = script_getnum(st,i);
+			ShowInfo("buildin_channel_setgroup: (1) Added group %d. Num: %d\n", ch->groups[ch->group_count-1], ch->group_count);
+		}
+	}
+	script_pushint(st,n);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Send message on channel
+ * channel_chat "<chname>","<message>"{,<color>};
+ * @author [Cydh]
+ **/
+BUILDIN_FUNC(channel_chat) {
+	struct Channel *ch = NULL;
+	const char *chname = script_getstr(st,2), *msg = script_getstr(st,3);
+	char output[CHAT_SIZE_MAX+1];
+	unsigned long color = 0;
+
+	// Check also local channels
+	if (chname[0] != '#') { // By Map
+		int m = mapindex_name2id(chname);
+		if (!m || (m = map_mapindex2mapid(m)) < 0) {
+			ShowError("buildin_channel_chat: Invalid map '%s'.\n", chname);
+			script_pushint(st,0);
+			return SCRIPT_CMD_FAILURE;
+		}
+		if (!(ch = map[m].channel)) {
+			ShowDebug("buildin_channel_chat: Map '%s' doesn't have local channel yet.\n", chname);
+			script_pushint(st,0);
+			return SCRIPT_CMD_SUCCESS;
+		}
+	}
+	else if (strcmpi(chname+1,channel_config.map_tmpl.name) == 0) {
+		TBL_NPC *nd = map_id2nd(st->oid);
+		if (!nd || nd->bl.m == -1) {
+			ShowError("buildin_channel_chat: Floating NPC needs map name instead '%s'.\n", chname);
+			script_pushint(st,0);
+			return SCRIPT_CMD_FAILURE;
+		}
+		if (!(ch = map[nd->bl.m].channel)) {
+			ShowDebug("buildin_channel_chat: Map '%s' doesn't have local channel yet.\n", chname);
+			script_pushint(st,0);
+			return SCRIPT_CMD_SUCCESS;
+		}
+	}
+	else if (!(ch = channel_name2channel((char *)chname, NULL, 0))) {
+		ShowError("buildin_channel_chat: Channel name '%s' is invalid.\n", chname);
+		script_pushint(st,0);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (!ch) {
+		script_pushint(st,0);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	color = ch->color;
+	FETCH(4, color);
+
+	safesnprintf(output, CHAT_SIZE_MAX, "%s %s", ch->alias, msg);
+	clif_channel_msg(ch,output,color);
+	script_pushint(st,1);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Ban player from a channel
+ * channel_ban "<chname>",<char_id>;
+ * @author [Cydh]
+ **/
+BUILDIN_FUNC(channel_ban) {
+	struct Channel *ch = NULL;
+	const char *chname = script_getstr(st,2);
+	unsigned int char_id = script_getnum(st,3);
+	TBL_PC *tsd;
+
+	if (!(ch = channel_name2channel((char *)chname, NULL, 0))) {
+		ShowError("buildin_channel_ban: Channel name '%s' is invalid.\n", chname);
+		script_pushint(st,0);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (ch->char_id == char_id) {
+		script_pushint(st,0);
+		return SCRIPT_CMD_SUCCESS;
+	}
+
+	tsd = map_charid2sd(char_id);
+	if (tsd && pc_has_permission(tsd,PC_PERM_CHANNEL_ADMIN)) {
+		script_pushint(st,0);
+		return SCRIPT_CMD_SUCCESS;
+	}
+
+	if (!idb_exists(ch->banned, char_id)) {
+		struct chan_banentry *cbe;
+		char output[CHAT_SIZE_MAX+1];
+
+		CREATE(cbe, struct chan_banentry, 1);
+		cbe->char_id = char_id;
+		if (tsd) {
+			strcpy(cbe->char_name,tsd->status.name);
+			channel_clean(ch,tsd,0);
+			safesnprintf(output, CHAT_SIZE_MAX, msg_txt(tsd,769), ch->alias, tsd->status.name); // %s %s has been banned.
+		}
+		else
+			safesnprintf(output, CHAT_SIZE_MAX, msg_txt(tsd,769), ch->alias, "****"); // %s %s has been banned.
+		idb_put(ch->banned, char_id, cbe);
+		clif_channel_msg(ch,output,ch->color);
+	}
+
+	script_pushint(st,1);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Ban player from a channel
+ * channel_unban "<chname>",<char_id>;
+ * @author [Cydh]
+ **/
+BUILDIN_FUNC(channel_unban) {
+	struct Channel *ch = NULL;
+	const char *chname = script_getstr(st,2);
+	unsigned int char_id = script_getnum(st,3);
+
+	if (!(ch = channel_name2channel((char *)chname, NULL, 0))) {
+		ShowError("buildin_channel_unban: Channel name '%s' is invalid.\n", chname);
+		script_pushint(st,0);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (ch->char_id == char_id) {
+		script_pushint(st,0);
+		return SCRIPT_CMD_SUCCESS;
+	}
+
+	if (idb_exists(ch->banned, char_id)) {
+		char output[CHAT_SIZE_MAX+1];
+		TBL_PC *tsd = map_charid2sd(char_id);
+		if (tsd)
+			safesnprintf(output, CHAT_SIZE_MAX, msg_txt(tsd,770), ch->alias, tsd->status.name); // %s %s has been unbanned
+		else
+			safesnprintf(output, CHAT_SIZE_MAX, msg_txt(tsd,770), ch->alias, "****"); // %s %s has been unbanned.
+		idb_remove(ch->banned, char_id);
+		clif_channel_msg(ch,output,ch->color);
+	}
+
+	script_pushint(st,1);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Kick player from a channel
+ * channel_kick "<chname>",<char_id>;
+ * channel_kick "<chname>","<char_name>";
+ * @author [Cydh]
+ **/
+BUILDIN_FUNC(channel_kick) {
+	struct Channel *ch = NULL;
+	const char *chname = script_getstr(st,2);
+	struct script_data *data = script_getdata(st,3);
+	TBL_PC *tsd = NULL;
+	int res = 1;
+
+	get_val(st, data);
+	if (data_isstring(data)) {
+		if (!(tsd = map_nick2sd(conv_str(st,data),false))) {
+			ShowError("buildin_channel_kick: Player with nick '%s' is not online\n", conv_str(st,data));
+			script_pushint(st,0);
+			return SCRIPT_CMD_FAILURE;
+		}
+	}
+	else {
+		if (!(tsd = map_charid2sd(conv_num(st,data)))) {
+			ShowError("buildin_channel_kick: Player with char_id '%d' is not online\n", conv_num(st,data));
+			script_pushint(st,0);
+			return SCRIPT_CMD_FAILURE;
+		}
+	}
+
+	if (!(ch = channel_name2channel((char *)chname, tsd, 0))) {
+		ShowError("buildin_channel_kick: Channel name '%s' is invalid.\n", chname);
+		script_pushint(st,0);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	if (channel_pc_haschan(tsd, ch) < 0 || ch->char_id == tsd->status.char_id || pc_has_permission(tsd,PC_PERM_CHANNEL_ADMIN)) {
+		script_pushint(st,0);
+		return SCRIPT_CMD_SUCCESS;
+	}
+
+	switch(ch->type){
+		case CHAN_TYPE_ALLY: res = channel_pcquit(tsd,3); break;
+		case CHAN_TYPE_MAP: res = channel_pcquit(tsd,4); break;
+		default: res = channel_clean(ch,tsd,0); break;
+	}
+	
+	if (res == 0) {
+		char output[CHAT_SIZE_MAX+1];
+		safesnprintf(output, CHAT_SIZE_MAX, msg_txt(tsd,889), ch->alias, tsd->status.name); // "%s %s is kicked"
+		clif_channel_msg(ch,output,ch->color);
+	}
+
+	script_pushint(st,1);
+	return SCRIPT_CMD_SUCCESS;
+}
+
+/**
+ * Delete a channel
+ * channel_delete "<chname>";
+ * @author [Cydh]
+ **/
+BUILDIN_FUNC(channel_delete) {
+	struct Channel *ch = NULL;
+	const char *chname = script_getstr(st,2);
+
+	if (!(ch = channel_name2channel((char *)chname, NULL, 0))) {
+		ShowError("channel_delete: Channel name '%s' is invalid.\n", chname);
+		script_pushint(st,-1);
+		return SCRIPT_CMD_FAILURE;
+	}
+
+	script_pushint(st,channel_delete(ch,true));
+	return SCRIPT_CMD_SUCCESS;
+}
+
 #include "../custom/script.inc"
 
 // declarations that were supposed to be exported from npc_chat.c
@@ -22974,6 +23424,19 @@ struct script_function buildin_func[] = {
 	BUILDIN_DEF(gvgon3,"s"),
 	BUILDIN_DEF(gvgoff3,"s"),
 
+	// Channel System
+	BUILDIN_DEF(channel_create,"ss?????"),
+	BUILDIN_DEF(channel_setopt,"sii"),
+	BUILDIN_DEF(channel_setcolor,"si"),
+	BUILDIN_DEF(channel_setpass,"ss"),
+	BUILDIN_DEF(channel_setgroup,"si*"),
+	BUILDIN_DEF2(channel_setgroup,"channel_setgroup2","sr"),
+	BUILDIN_DEF(channel_chat,"ss?"),
+	BUILDIN_DEF(channel_ban,"si"),
+	BUILDIN_DEF(channel_unban,"si"),
+	BUILDIN_DEF(channel_kick,"sv"),
+	BUILDIN_DEF(channel_delete,"s"),
+
 #include "../custom/script_def.inc"
 
 	{NULL,NULL,NULL},

+ 15 - 0
src/map/script_constants.h

@@ -3229,6 +3229,21 @@
 	export_constant(CARD0_CREATE);
 	export_constant(CARD0_PET);
 
+	/* Channel System */
+	export_constant(CHAN_TYPE_PUBLIC);
+	export_constant(CHAN_TYPE_PRIVATE);
+	export_constant(CHAN_TYPE_MAP);
+	export_constant(CHAN_TYPE_ALLY);
+	export_constant(CHAN_OPT_BASE);
+	export_constant(CHAN_OPT_ANNOUNCE_SELF);
+	export_constant(CHAN_OPT_ANNOUNCE_JOIN);
+	export_constant(CHAN_OPT_ANNOUNCE_LEAVE);
+	export_constant(CHAN_OPT_MSG_DELAY);
+	export_constant(CHAN_OPT_COLOR_OVERRIDE);
+	export_constant(CHAN_OPT_CAN_CHAT);
+	export_constant(CHAN_OPT_CAN_LEAVE);
+	export_constant(CHAN_OPT_AUTOJOIN);
+
 	export_constant(STOR_MODE_NONE);
 	export_constant(STOR_MODE_GET);
 	export_constant(STOR_MODE_PUT);

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác