// Copyright (c) Hercules Dev Team, licensed under GNU GPL.
// See the LICENSE file
#define HERCULES_CORE
#include "channel.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "atcommand.h"
#include "guild.h"
#include "instance.h"
#include "irc-bot.h"
#include "map.h"
#include "pc.h"
#include "../common/cbasetypes.h"
#include "../common/conf.h"
#include "../common/db.h"
#include "../common/malloc.h"
#include "../common/nullpo.h"
#include "../common/random.h"
#include "../common/showmsg.h"
#include "../common/socket.h"
#include "../common/strlib.h"
#include "../common/timer.h"
#include "../common/utils.h"
struct channel_interface channel_s;
static struct Channel_Config channel_config;
/**
* Creates a chat channel.
*
* If the channel type isn't HCS_TYPE_MAP or HCS_TYPE_ALLY, the channel is added to the channel->db.
*
* @param type The channel type.
* @param name The channel name.
* @param color The channel chat color.
* @return A pointer to the created channel.
*/
struct channel_data *channel_create(enum channel_types type, const char *name, unsigned char color)
{
struct channel_data *chan;
CREATE(chan, struct channel_data, 1);
chan->users = idb_alloc(DB_OPT_BASE);
if (name)
safestrncpy(chan->name, name, HCS_NAME_LENGTH);
chan->color = color;
chan->options = HCS_OPT_BASE;
chan->banned = NULL;
chan->msg_delay = 0;
chan->type = type;
if (chan->type != HCS_TYPE_MAP && chan->type != HCS_TYPE_ALLY)
strdb_put(channel->db, chan->name, chan);
return chan;
}
/**
* Sets a chat channel password.
*
* @param chan The channel to edit.
* @param pass The password to set. Pass NULL to remove existing passwords.
*/
void channel_set_password(struct channel_data *chan, const char *password)
{
nullpo_retv(chan);
if (password)
safestrncpy(chan->password, password, HCS_NAME_LENGTH);
else
chan->password[0] = '\0';
}
/**
* Bans a character from the given channel.
*
* @param chan The channel.
* @param ssd The source character, if any.
* @param tsd The target character.
* @retval HCS_STATUS_OK if the operation succeeded.
* @retval HCS_STATUS_ALREADY if the target character is already banned.
* @retval HCS_STATUS_NOPERM if the source character doesn't have enough permissions.
* @retval HCS_STATUS_FAIL in case of generic failure.
*/
enum channel_operation_status channel_ban(struct channel_data *chan, const struct map_session_data *ssd, struct map_session_data *tsd)
{
struct channel_ban_entry *entry = NULL;
nullpo_retr(HCS_STATUS_FAIL, chan);
nullpo_retr(HCS_STATUS_FAIL, tsd);
if (ssd && chan->owner != ssd->status.char_id && !pc_has_permission(ssd, PC_PERM_HCHSYS_ADMIN))
return HCS_STATUS_NOPERM;
if (pc_has_permission(tsd, PC_PERM_HCHSYS_ADMIN))
return HCS_STATUS_FAIL;
if (chan->banned && idb_exists(chan->banned, tsd->status.account_id))
return HCS_STATUS_ALREADY;
if (!chan->banned)
chan->banned = idb_alloc(DB_OPT_BASE|DB_OPT_ALLOW_NULL_DATA|DB_OPT_RELEASE_DATA);
CREATE(entry, struct channel_ban_entry, 1);
safestrncpy(entry->name, tsd->status.name, NAME_LENGTH);
idb_put(chan->banned, tsd->status.account_id, entry);
channel->leave(chan, tsd);
return HCS_STATUS_OK;
}
/**
* Unbans a character from the given channel.
*
* @param chan The channel.
* @param ssd The source character, if any.
* @param tsd The target character. If no target character is specified, all characters are unbanned.
* @retval HCS_STATUS_OK if the operation succeeded.
* @retval HCS_STATUS_ALREADY if the target character is not banned.
* @retval HCS_STATUS_NOPERM if the source character doesn't have enough permissions.
* @retval HCS_STATUS_FAIL in case of generic failure.
*/
enum channel_operation_status channel_unban(struct channel_data *chan, const struct map_session_data *ssd, struct map_session_data *tsd)
{
nullpo_retr(HCS_STATUS_FAIL, chan);
if (ssd && chan->owner != ssd->status.char_id && !pc_has_permission(ssd, PC_PERM_HCHSYS_ADMIN))
return HCS_STATUS_NOPERM;
if (!chan->banned)
return HCS_STATUS_ALREADY;
if (!tsd) {
// Unban all
db_destroy(chan->banned);
chan->banned = NULL;
return HCS_STATUS_OK;
}
// Unban one
if (!idb_exists(chan->banned, tsd->status.account_id))
return HCS_STATUS_ALREADY;
idb_remove(chan->banned, tsd->status.account_id);
if (!db_size(chan->banned)) {
db_destroy(chan->banned);
chan->banned = NULL;
}
return HCS_STATUS_OK;
}
/**
* Sets or edits a channel's options.
*
* @param chan The channel.
* @param options The new options set to apply.
*/
void channel_set_options(struct channel_data *chan, unsigned int options)
{
nullpo_retv(chan);
chan->options = options;
}
/**
* Sends a message to a channel.
*
* @param chan The destination channel.
* @param sd The source character.
* @param msg The message to send.
*/
void channel_send(struct channel_data *chan, struct map_session_data *sd, const char *msg)
{
nullpo_retv(chan);
nullpo_retv(sd);
if (chan->msg_delay != 0 && DIFF_TICK(sd->hchsysch_tick + chan->msg_delay*1000, timer->gettick()) > 0
&& !pc_has_permission(sd, PC_PERM_HCHSYS_ADMIN)) {
clif->colormes(sd->fd,COLOR_RED,msg_txt(1455));
return;
} else {
char message[150];
snprintf(message, 150, "[ #%s ] %s : %s",chan->name,sd->status.name, msg);
clif->channel_msg(chan,sd,message);
if (chan->type == HCS_TYPE_IRC)
ircbot->relay(sd->status.name,msg);
if (chan->msg_delay != 0)
sd->hchsysch_tick = timer->gettick();
}
}
void channel_join(struct channel_data *chan, struct map_session_data *sd)
{
if (idb_put(chan->users, sd->status.char_id, sd))
return;
RECREATE(sd->channels, struct channel_data *, ++sd->channel_count);
sd->channels[ sd->channel_count - 1 ] = chan;
if (sd->stealth) {
sd->stealth = false;
} else if (chan->options & HCS_OPT_ANNOUNCE_JOIN) {
char message[60];
sprintf(message, "#%s '%s' joined",chan->name,sd->status.name);
clif->channel_msg(chan,sd,message);
}
/* someone is cheating, we kindly disconnect the bastard */
if (sd->channel_count > 200) {
set_eof(sd->fd);
}
}
void channel_map_join(struct map_session_data *sd)
{
if (sd->state.autotrade || sd->state.standalone)
return;
if (!map->list[sd->bl.m].channel) {
if (map->list[sd->bl.m].flag.chsysnolocalaj || (map->list[sd->bl.m].instance_id >= 0 && instance->list[map->list[sd->bl.m].instance_id].owner_type != IOT_NONE))
return;
map->list[sd->bl.m].channel = channel->create(HCS_TYPE_MAP, channel->config->local_name, channel->config->local_color);
map->list[sd->bl.m].channel->m = sd->bl.m;
}
if (map->list[sd->bl.m].channel->banned && idb_exists(map->list[sd->bl.m].channel->banned, sd->status.account_id)) {
return;
}
channel->join(map->list[sd->bl.m].channel,sd);
if (!( map->list[sd->bl.m].channel->options & HCS_OPT_ANNOUNCE_JOIN )) {
char mout[60];
sprintf(mout, msg_txt(1435), channel->config->local_name, map->list[sd->bl.m].name); // You're now in the '#%s' channel for '%s'
clif->colormes(sd->fd, COLOR_DEFAULT, mout);
}
}
void channel_leave(struct channel_data *chan, struct map_session_data *sd)
{
unsigned char i;
if ( !idb_remove(chan->users,sd->status.char_id) )
return;
if( chan == sd->gcbind )
sd->gcbind = NULL;
if( !db_size(chan->users) && chan->type == HCS_TYPE_PRIVATE ) {
channel->delete(chan);
} else if( !channel->config->closing && (chan->options & HCS_OPT_ANNOUNCE_JOIN) ) {
char message[60];
sprintf(message, "#%s '%s' left",chan->name,sd->status.name);
clif->channel_msg(chan,sd,message);
}
for( i = 0; i < sd->channel_count; i++ ) {
if( sd->channels[i] == chan ) {
sd->channels[i] = NULL;
break;
}
}
if( i < sd->channel_count ) {
unsigned char cursor = 0;
for( i = 0; i < sd->channel_count; i++ ) {
if( sd->channels[i] == NULL )
continue;
if( cursor != i ) {
sd->channels[cursor] = sd->channels[i];
}
cursor++;
}
if ( !(sd->channel_count = cursor) ) {
aFree(sd->channels);
sd->channels = NULL;
}
}
}
void channel_quit_guild(struct map_session_data *sd)
{
unsigned char i;
for( i = 0; i < sd->channel_count; i++ ) {
struct channel_data *chan = sd->channels[i];
if (chan != NULL && chan->type == HCS_TYPE_ALLY) {
if (!idb_remove(chan->users,sd->status.char_id))
continue;
if( chan == sd->gcbind )
sd->gcbind = NULL;
if (!db_size(chan->users) && chan->type == HCS_TYPE_PRIVATE) {
channel->delete(chan);
} else if (!channel->config->closing && (chan->options & HCS_OPT_ANNOUNCE_JOIN)) {
char message[60];
sprintf(message, "#%s '%s' left",chan->name,sd->status.name);
clif->channel_msg(chan,sd,message);
}
sd->channels[i] = NULL;
}
}
if( i < sd->channel_count ) {
unsigned char cursor = 0;
for( i = 0; i < sd->channel_count; i++ ) {
if( sd->channels[i] == NULL )
continue;
if( cursor != i ) {
sd->channels[cursor] = sd->channels[i];
}
cursor++;
}
if ( !(sd->channel_count = cursor) ) {
aFree(sd->channels);
sd->channels = NULL;
}
}
}
void channel_quit(struct map_session_data *sd)
{
unsigned char i;
for (i = 0; i < sd->channel_count; i++) {
struct channel_data *chan = sd->channels[i];
if (chan != NULL) {
idb_remove(chan->users,sd->status.char_id);
if( chan == sd->gcbind )
sd->gcbind = NULL;
if (!db_size(chan->users) && chan->type == HCS_TYPE_PRIVATE) {
channel->delete(chan);
} else if (!channel->config->closing && (chan->options & HCS_OPT_ANNOUNCE_JOIN)) {
char message[60];
sprintf(message, "#%s '%s' left",chan->name,sd->status.name);
clif->channel_msg(chan,sd,message);
}
}
}
sd->channel_count = 0;
aFree(sd->channels);
sd->channels = NULL;
}
void channel_delete(struct channel_data *chan)
{
if (db_size(chan->users) && !channel->config->closing) {
DBIterator *iter;
struct map_session_data *sd;
unsigned char i;
iter = db_iterator(chan->users);
for( sd = dbi_first(iter); dbi_exists(iter); sd = dbi_next(iter) ) {
for( i = 0; i < sd->channel_count; i++ ) {
if( sd->channels[i] == chan ) {
sd->channels[i] = NULL;
break;
}
}
if( i < sd->channel_count ) {
unsigned char cursor = 0;
for( i = 0; i < sd->channel_count; i++ ) {
if( sd->channels[i] == NULL )
continue;
if( cursor != i ) {
sd->channels[cursor] = sd->channels[i];
}
cursor++;
}
if ( !(sd->channel_count = cursor) ) {
aFree(sd->channels);
sd->channels = NULL;
}
}
}
dbi_destroy(iter);
}
if( chan->banned ) {
db_destroy(chan->banned);
chan->banned = NULL;
}
db_destroy(chan->users);
if( chan->m ) {
map->list[chan->m].channel = NULL;
aFree(chan);
} else if ( chan->type == HCS_TYPE_ALLY )
aFree(chan);
else if (!channel->config->closing)
strdb_remove(channel->db, chan->name);
}
void channel_guild_join(struct guild *g1,struct guild *g2)
{
struct map_session_data *sd;
struct channel_data *chan;
int j;
if( (chan = g1->channel) ) {
for(j = 0; j < g2->max_member; j++) {
if( (sd = g2->member[j].sd) != NULL ) {
if( !(g1->channel->banned && idb_exists(g1->channel->banned, sd->status.account_id)))
channel->join(chan,sd);
}
}
}
if( (chan = g2->channel) ) {
for(j = 0; j < g1->max_member; j++) {
if( (sd = g1->member[j].sd) != NULL ) {
if( !(g2->channel->banned && idb_exists(g2->channel->banned, sd->status.account_id)))
channel->join(chan,sd);
}
}
}
}
void channel_guild_leave(struct guild *g1,struct guild *g2)
{
struct map_session_data *sd;
struct channel_data *chan;
int j;
if( (chan = g1->channel) ) {
for(j = 0; j < g2->max_member; j++) {
if( (sd = g2->member[j].sd) != NULL ) {
channel->leave(chan,sd);
}
}
}
if( (chan = g2->channel) ) {
for(j = 0; j < g1->max_member; j++) {
if( (sd = g1->member[j].sd) != NULL ) {
channel->leave(chan,sd);
}
}
}
}
void read_channels_config(void)
{
config_t channels_conf;
config_setting_t *chsys = NULL;
const char *config_filename = "conf/channels.conf"; // FIXME hardcoded name
if (libconfig->read_file(&channels_conf, config_filename))
return;
chsys = libconfig->lookup(&channels_conf, "chsys");
if (chsys != NULL) {
config_setting_t *settings = libconfig->setting_get_elem(chsys, 0);
config_setting_t *channels;
config_setting_t *colors;
int i,k;
const char *local_name, *ally_name,
*local_color, *ally_color,
*irc_name, *irc_color;
int ally_enabled = 0, local_enabled = 0,
local_autojoin = 0, ally_autojoin = 0,
allow_user_channel_creation = 0,
irc_enabled = 0;
if( !libconfig->setting_lookup_string(settings, "map_local_channel_name", &local_name) )
local_name = "map";
safestrncpy(channel->config->local_name, local_name, HCS_NAME_LENGTH);
if( !libconfig->setting_lookup_string(settings, "ally_channel_name", &ally_name) )
ally_name = "ally";
safestrncpy(channel->config->ally_name, ally_name, HCS_NAME_LENGTH);
if( !libconfig->setting_lookup_string(settings, "irc_channel_name", &irc_name) )
irc_name = "irc";
safestrncpy(channel->config->irc_name, irc_name, HCS_NAME_LENGTH);
libconfig->setting_lookup_bool(settings, "map_local_channel", &local_enabled);
libconfig->setting_lookup_bool(settings, "ally_channel_enabled", &ally_enabled);
libconfig->setting_lookup_bool(settings, "irc_channel_enabled", &irc_enabled);
if (local_enabled)
channel->config->local = true;
if (ally_enabled)
channel->config->ally = true;
if (irc_enabled)
channel->config->irc = true;
channel->config->irc_server[0] = channel->config->irc_channel[0] = channel->config->irc_nick[0] = channel->config->irc_nick_pw[0] = '\0';
if (channel->config->irc) {
const char *irc_server, *irc_channel,
*irc_nick, *irc_nick_pw;
int irc_use_ghost = 0;
if( libconfig->setting_lookup_string(settings, "irc_channel_network", &irc_server) ) {
if( !strstr(irc_server,":") ) {
channel->config->irc = false;
ShowWarning("channels.conf : network port wasn't found in 'irc_channel_network', disabling irc channel...\n");
} else {
unsigned char d = 0, dlen = strlen(irc_server);
char server[40];
if (dlen > 39)
dlen = 39;
memset(server, '\0', sizeof(server));
for(d = 0; d < dlen; d++) {
if(irc_server[d] == ':') {
memcpy(server, irc_server, d);
safestrncpy(channel->config->irc_server, server, 40);
memcpy(server, &irc_server[d+1], dlen - d - 1);
channel->config->irc_server_port = atoi(server);
break;
}
}
}
} else {
channel->config->irc = false;
ShowWarning("channels.conf : irc channel enabled but irc_channel_network wasn't found, disabling irc channel...\n");
}
if( libconfig->setting_lookup_string(settings, "irc_channel_channel", &irc_channel) )
safestrncpy(channel->config->irc_channel, irc_channel, 50);
else {
channel->config->irc = false;
ShowWarning("channels.conf : irc channel enabled but irc_channel_channel wasn't found, disabling irc channel...\n");
}
if( libconfig->setting_lookup_string(settings, "irc_channel_nick", &irc_nick) ) {
if( strcmpi(irc_nick,"Hercules_chSysBot") == 0 ) {
sprintf(channel->config->irc_nick, "Hercules_chSysBot%d",rnd()%777);
} else
safestrncpy(channel->config->irc_nick, irc_nick, 40);
} else {
channel->config->irc = false;
ShowWarning("channels.conf : irc channel enabled but irc_channel_nick wasn't found, disabling irc channel...\n");
}
if( libconfig->setting_lookup_string(settings, "irc_channel_nick_pw", &irc_nick_pw) ) {
safestrncpy(channel->config->irc_nick_pw, irc_nick_pw, 30);
config_setting_lookup_bool(settings, "irc_channel_use_ghost", &irc_use_ghost);
channel->config->irc_use_ghost = irc_use_ghost;
}
}
libconfig->setting_lookup_bool(settings, "map_local_channel_autojoin", &local_autojoin);
libconfig->setting_lookup_bool(settings, "ally_channel_autojoin", &ally_autojoin);
if (local_autojoin)
channel->config->local_autojoin = true;
if (ally_autojoin)
channel->config->ally_autojoin = true;
libconfig->setting_lookup_bool(settings, "allow_user_channel_creation", &allow_user_channel_creation);
if( allow_user_channel_creation )
channel->config->allow_user_channel_creation = true;
if( (colors = libconfig->setting_get_member(settings, "colors")) != NULL ) {
int color_count = libconfig->setting_length(colors);
CREATE(channel->config->colors, unsigned int, color_count);
CREATE(channel->config->colors_name, char *, color_count);
for(i = 0; i < color_count; i++) {
config_setting_t *color = libconfig->setting_get_elem(colors, i);
CREATE(channel->config->colors_name[i], char, HCS_NAME_LENGTH);
safestrncpy(channel->config->colors_name[i], config_setting_name(color), HCS_NAME_LENGTH);
channel->config->colors[i] = (unsigned int)strtoul(libconfig->setting_get_string_elem(colors,i),NULL,0);
channel->config->colors[i] = (channel->config->colors[i] & 0x0000FF) << 16 | (channel->config->colors[i] & 0x00FF00) | (channel->config->colors[i] & 0xFF0000) >> 16;//RGB to BGR
}
channel->config->colors_count = color_count;
}
libconfig->setting_lookup_string(settings, "map_local_channel_color", &local_color);
for (k = 0; k < channel->config->colors_count; k++) {
if (strcmpi(channel->config->colors_name[k], local_color) == 0)
break;
}
if (k < channel->config->colors_count) {
channel->config->local_color = k;
} else {
ShowError("channels.conf: unknown color '%s' for 'map_local_channel_color', disabling '#%s'...\n",local_color,local_name);
channel->config->local = false;
}
libconfig->setting_lookup_string(settings, "ally_channel_color", &ally_color);
for (k = 0; k < channel->config->colors_count; k++) {
if (strcmpi(channel->config->colors_name[k], ally_color) == 0)
break;
}
if( k < channel->config->colors_count ) {
channel->config->ally_color = k;
} else {
ShowError("channels.conf: unknown color '%s' for 'ally_channel_color', disabling '#%s'...\n",ally_color,ally_name);
channel->config->ally = false;
}
libconfig->setting_lookup_string(settings, "irc_channel_color", &irc_color);
for (k = 0; k < channel->config->colors_count; k++) {
if (strcmpi(channel->config->colors_name[k], irc_color) == 0)
break;
}
if (k < channel->config->colors_count) {
channel->config->irc_color = k;
} else {
ShowError("channels.conf: unknown color '%s' for 'irc_channel_color', disabling '#%s'...\n",irc_color,irc_name);
channel->config->irc = false;
}
if (channel->config->irc) {
ircbot->channel = channel->create(HCS_TYPE_IRC, channel->config->irc_name, channel->config->irc_color);
}
if( (channels = libconfig->setting_get_member(settings, "default_channels")) != NULL ) {
int channel_count = libconfig->setting_length(channels);
for(i = 0; i < channel_count; i++) {
config_setting_t *chan = libconfig->setting_get_elem(channels, i);
const char *name = config_setting_name(chan);
const char *color = libconfig->setting_get_string_elem(channels,i);
ARR_FIND(0, channel->config->colors_count, k, strcmpi(channel->config->colors_name[k],color) == 0);
if (k == channel->config->colors_count) {
ShowError("channels.conf: unknown color '%s' for channel '%s', skipping channel...\n",color,name);
continue;
}
if (strcmpi(name, channel->config->local_name) == 0
|| strcmpi(name, channel->config->ally_name) == 0
|| strcmpi(name, channel->config->irc_name) == 0
|| strdb_exists(channel->db, name)) {
ShowError("channels.conf: duplicate channel '%s', skipping channel...\n",name);
continue;
}
channel->create(HCS_TYPE_PUBLIC, name, k);
}
}
ShowStatus("Done reading '"CL_WHITE"%d"CL_RESET"' channels in '"CL_WHITE"%s"CL_RESET"'.\n", db_size(channel->db), config_filename);
libconfig->destroy(&channels_conf);
}
}
/*==========================================
*
*------------------------------------------*/
int do_init_channel(bool minimal)
{
if (minimal)
return 0;
channel->db = stridb_alloc(DB_OPT_DUP_KEY|DB_OPT_RELEASE_DATA, HCS_NAME_LENGTH);
channel->config->ally = channel->config->local = channel->config->irc = channel->config->ally_autojoin = channel->config->local_autojoin = false;
channel->config_read();
return 0;
}
void do_final_channel(void)
{
DBIterator *iter = db_iterator(channel->db);
struct channel_data *chan;
unsigned char i;
for( chan = dbi_first(iter); dbi_exists(iter); chan = dbi_next(iter) ) {
channel->delete(chan);
}
dbi_destroy(iter);
for(i = 0; i < channel->config->colors_count; i++) {
aFree(channel->config->colors_name[i]);
}
if (channel->config->colors_count) {
aFree(channel->config->colors_name);
aFree(channel->config->colors);
}
db_destroy(channel->db);
}
void channel_defaults(void)
{
channel = &channel_s;
/* core */
channel->init = do_init_channel;
channel->final = do_final_channel;
channel->config = &channel_config;
channel->create = channel_create;
channel->set_password = channel_set_password;
channel->ban = channel_ban;
channel->unban = channel_unban;
channel->set_options = channel_set_options;
channel->send = channel_send;
channel->join = channel_join;
channel->leave = channel_leave;
channel->delete = channel_delete;
channel->map_join = channel_map_join;
channel->quit = channel_quit;
channel->quit_guild = channel_quit_guild;
channel->guild_join = channel_guild_join;
channel->guild_leave = channel_guild_leave;
channel->config_read = read_channels_config;
}