/**
* This file is part of Hercules.
* http://herc.ws - http://github.com/HerculesWS/Hercules
*
* Copyright (C) 2017-2020 Hercules Dev Team
*
* Hercules is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define HERCULES_CORE
#include "config/core.h"
#include "clan.h"
#include "map/clif.h"
#include "map/chrif.h"
#include "map/homunculus.h"
#include "map/intif.h"
#include "map/log.h"
#include "map/mercenary.h"
#include "map/mob.h"
#include "map/npc.h"
#include "map/pc.h"
#include "map/pet.h"
#include "map/script.h"
#include "map/skill.h"
#include "map/status.h"
#include "common/HPM.h"
#include "common/conf.h"
#include "common/cbasetypes.h"
#include "common/db.h"
#include "common/memmgr.h"
#include "common/mapindex.h"
#include "common/nullpo.h"
#include "common/showmsg.h"
#include "common/socket.h"
#include "common/strlib.h"
#include "common/timer.h"
#include "common/utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static struct clan_interface clan_s;
struct clan_interface *clan;
/**
* Searches a Clan by clan_id
*
* @param clan_id Clan ID
* @return struct clan*
*/
static struct clan *clan_search(int clan_id)
{
if (clan_id <= 0) {
return NULL;
}
return (struct clan *)idb_get(clan->db, clan_id);
}
/**
* Searches a Clan by clan_name or constant
*
* @param name Clan Name
* @return struct clan*
*/
static struct clan *clan_searchname(const char *name)
{
struct clan *c;
struct DBIterator *iter;
nullpo_retr(NULL, name);
iter = db_iterator(clan->db);
for (c = dbi_first(iter); dbi_exists(iter); c = dbi_next(iter)) {
if (strncmpi(c->name, name, NAME_LENGTH) == 0 || strncmpi(c->constant, name, NAME_LENGTH) == 0) {
break;
}
}
dbi_destroy(iter);
return c;
}
/**
* Returns the first online character of clan
*
* @param (struct clan *) c clan structure
* @return (struct map_session_data *)
*/
static struct map_session_data *clan_getonlinesd(struct clan *c)
{
int i;
nullpo_retr(NULL, c);
ARR_FIND(0, VECTOR_LENGTH(c->members), i, (VECTOR_INDEX(c->members, i).sd != NULL && VECTOR_INDEX(c->members, i).online == 1));
return (i < VECTOR_LENGTH(c->members)) ? VECTOR_INDEX(c->members, i).sd : NULL;
}
/**
* Returns the member index of given Player
*
* @param c Clan Data
* @param char_id Player's Char ID
* @return int
*/
static int clan_getindex(const struct clan *c, int char_id)
{
int i;
nullpo_retr(INDEX_NOT_FOUND, c);
ARR_FIND(0, VECTOR_LENGTH(c->members), i, VECTOR_INDEX(c->members, i).char_id == char_id);
if (i == VECTOR_LENGTH(c->members)) {
return INDEX_NOT_FOUND;
}
return i;
}
/**
* Starts clan buff
*/
static void clan_buff_start(struct map_session_data *sd, struct clan *c)
{
nullpo_retv(sd);
nullpo_retv(c);
if (c->buff.icon <= 0) {
return;
}
clif->sc_load(&sd->bl, sd->bl.id, SELF, c->buff.icon, 0, c->clan_id, 0);
script->run(c->buff.script, 0, sd->bl.id, npc->fake_nd->bl.id);
}
/**
* Ends clan buff
*/
static void clan_buff_end(struct map_session_data *sd, struct clan *c)
{
nullpo_retv(sd);
nullpo_retv(c);
if (c->buff.icon <= 0) {
return;
}
clif->sc_end(&sd->bl, sd->bl.id, SELF, c->buff.icon);
}
/**
* Joins a Player into a Clan
*
* @param sd Player's Map Session Data
* @param clan_id Clan which will add this player
* @return bool
*/
static bool clan_join(struct map_session_data *sd, int clan_id)
{
struct clan *c;
struct clan_member m;
nullpo_ret(sd);
// Already joined a guild or clan
if (sd->status.guild_id > 0 || sd->guild != NULL) {
ShowError("clan_join: Player already joined in a guild. char_id: %d\n", sd->status.char_id);
return false;
} else if ( sd->status.clan_id > 0 || sd->clan != NULL) {
ShowError("clan_join: Player already joined in a clan. char_id: %d\n", sd->status.char_id);
return false;
}
c = clan->search(clan_id);
if (c == NULL) {
ShowError("clan_join: Invalid Clan ID: %d\n", clan_id);
return false;
}
if (!c->received) {
return false;
}
if (clan->getindex(c, sd->status.char_id) != INDEX_NOT_FOUND) {
ShowError("clan_join: Player already joined this clan. char_id: %d clan_id: %d\n", sd->status.char_id, clan_id);
return false;
}
if (VECTOR_LENGTH(c->members) >= c->max_member || c->member_count >= c->max_member) {
ShowError("clan_join: Clan '%s' already reached its max capacity!\n", c->name);
return false;
}
VECTOR_ENSURE(c->members, 1, 1);
m.sd = sd;
m.char_id = sd->status.char_id;
m.online = 1;
m.last_login = sd->status.last_login;
VECTOR_PUSH(c->members, m);
c->connect_member++;
c->member_count++;
sd->status.clan_id = c->clan_id;
sd->clan = c;
sc_start2(NULL, &sd->bl, SC_CLAN_INFO, 10000, 0, c->clan_id, INFINITE_DURATION);
status_calc_pc(sd, SCO_FORCE);
chrif->save(sd, 0);
clif->clan_basicinfo(sd);
clif->clan_onlinecount(c);
return true;
}
/**
* Invoked when a player joins
* It assumes that clan_id is not 0
*
* @param sd Player Data
*/
static void clan_member_online(struct map_session_data *sd, bool first)
{
struct clan *c;
int i, inactivity;
nullpo_retv(sd);
// For invalid values we must reset it to 0 (no clan)
if (sd->status.clan_id < 0) {
ShowError("clan_member_online: Invalid clan id, changing to '0'. clan_id='%d' char_id='%d'\n", sd->status.clan_id, sd->status.char_id);
sd->status.clan_id = 0;
return;
}
c = clan->search(sd->status.clan_id);
if (c == NULL) {
// This is a silent return because it will reset clan_id in case
// a custom clan that was removed and this is a remaining member
sd->status.clan_id = 0;
sd->clan = NULL;
if (!first) {
status_change_end(&sd->bl, SC_CLAN_INFO, INVALID_TIMER); // Remove the status
status_calc_pc(sd, SCO_FORCE);
}
clif->clan_leave(sd);
return;
}
if (!c->received) {
return;
}
if (c->max_member <= c->member_count || VECTOR_LENGTH(c->members) >= c->max_member) {
ShowError("Clan System: More members than the maximum allowed in clan #%d\n", c->clan_id);
return;
}
i = clan->getindex(c, sd->status.char_id);
inactivity = (int)(time(NULL) - sd->status.last_login);
if (i == INDEX_NOT_FOUND) {
struct clan_member m;
if (c->kick_time > 0 && inactivity > c->kick_time) {
sd->status.clan_id = 0;
sd->clan = NULL;
clan->buff_end(sd, c);
status_change_end(&sd->bl, SC_CLAN_INFO, INVALID_TIMER);
clif->clan_leave(sd);
return;
}
VECTOR_ENSURE(c->members, 1, 1);
m.sd = sd;
m.char_id = sd->status.char_id;
m.online = 1;
m.last_login = sd->status.last_login;
VECTOR_PUSH(c->members, m);
} else {
struct clan_member *m = &VECTOR_INDEX(c->members, i);
if (c->kick_time > 0 && inactivity > c->kick_time) {
if (m->online == 1) {
m->online = 0;
m->sd = NULL;
c->connect_member--;
c->member_count--;
}
clan->buff_end(sd, c);
sd->status.clan_id = 0;
sd->clan = NULL;
status_change_end(&sd->bl, SC_CLAN_INFO, INVALID_TIMER);
VECTOR_ERASE(c->members, i);
clif->clan_leave(sd);
return;
}
m->sd = sd;
m->online = 1;
m->last_login = sd->status.last_login;
}
sd->clan = c;
c->connect_member++;
sc_start2(NULL, &sd->bl, SC_CLAN_INFO, 10000, 0, c->clan_id, INFINITE_DURATION);
if (!first) {
// When first called from pc.c we don't need to do status_calc
status_calc_pc(sd, SCO_FORCE);
}
clif->clan_basicinfo(sd);
clif->clan_onlinecount(c);
}
/**
* Re-join a player on its clan
*/
static int clan_rejoin(struct map_session_data *sd, va_list ap)
{
nullpo_ret(sd);
if (sd->status.clan_id != 0) {
// Note: Even if the value is invalid (lower than zero)
// the function will fix the invalid value
clan->member_online(sd, false);
}
return 0;
}
/**
* Removes Player from clan
*/
static bool clan_leave(struct map_session_data *sd, bool first)
{
int i;
struct clan *c;
nullpo_ret(sd);
c = sd->clan;
if (c == NULL) {
return false;
}
if (!c->received) {
return false;
}
i = clan->getindex(c, sd->status.char_id);
if (i != INDEX_NOT_FOUND) {
VECTOR_ERASE(c->members, i);
c->connect_member--;
c->member_count--;
}
sd->status.clan_id = 0;
sd->clan = NULL;
clan->buff_end(sd, c);
status_change_end(&sd->bl, SC_CLAN_INFO, INVALID_TIMER);
if (!first) {
status_calc_pc(sd, SCO_FORCE);
}
chrif->save(sd, 0);
clif->clan_onlinecount(c);
clif->clan_leave(sd);
return true;
}
/**
* Sets a player offline
*
* @param (struct map_session_data *) sd Player Data
*/
static void clan_member_offline(struct map_session_data *sd)
{
struct clan *c;
int i;
nullpo_retv(sd);
c = sd->clan;
if (c == NULL) {
return;
}
i = clan->getindex(c, sd->status.char_id);
if (i != INDEX_NOT_FOUND && VECTOR_INDEX(c->members, i).online == 1) {
// Only if it is online, because unit->free is called twice
VECTOR_INDEX(c->members, i).online = 0;
VECTOR_INDEX(c->members, i).sd = NULL;
c->connect_member--;
}
clif->clan_onlinecount(c);
}
/**
* Sends a message to the whole clan
*/
static bool clan_send_message(struct map_session_data *sd, const char *mes)
{
int len;
nullpo_retr(false, sd);
nullpo_retr(false, mes);
len = (int)strlen(mes);
if (sd->status.clan_id == 0) {
return false;
}
clan->recv_message(sd->clan, mes, len);
// Chat logging type 'C' / Clan Chat
logs->chat(LOG_CHAT_CLAN, sd->status.clan_id, sd->status.char_id, sd->status.account_id, mapindex_id2name(sd->mapindex), sd->bl.x, sd->bl.y, NULL, mes);
return true;
}
/**
* Clan receive a message, will be displayed to whole clan
*/
static void clan_recv_message(struct clan *c, const char *mes, int len)
{
clif->clan_message(c, mes, len);
}
/**
* Set constants for each clan
*/
static void clan_set_constants(void)
{
struct DBIterator *iter = db_iterator(clan->db);
struct clan *c;
for (c = dbi_first(iter); dbi_exists(iter); c = dbi_next(iter)) {
script->set_constant2(c->constant, c->clan_id, false, false);
}
dbi_destroy(iter);
}
/**
* Returns the clan_id of bl
*/
static int clan_get_id(const struct block_list *bl)
{
nullpo_ret(bl);
switch (bl->type) {
case BL_PC: {
const struct map_session_data *sd = BL_UCCAST(BL_PC, bl);
return sd->status.clan_id;
}
case BL_NPC: {
const struct npc_data *nd = BL_UCCAST(BL_NPC, bl);
return nd->clan_id;
}
case BL_PET: {
const struct pet_data *pd = BL_UCCAST(BL_PET, bl);
if (pd->msd != NULL)
return pd->msd->status.clan_id;
}
break;
case BL_MOB: {
const struct mob_data *md = BL_UCCAST(BL_MOB, bl);
const struct map_session_data *msd;
if (md->special_state.ai != AI_NONE && (msd = map->id2sd(md->master_id)) != NULL) {
return msd->status.clan_id;
}
return md->clan_id;
}
case BL_HOM: {
const struct homun_data *hd = BL_UCCAST(BL_HOM, bl);
if (hd->master != NULL) {
return hd->master->status.clan_id;
}
}
break;
case BL_MER: {
const struct mercenary_data *md = BL_UCCAST(BL_MER, bl);
if (md->master != NULL) {
return md->master->status.clan_id;
}
}
break;
case BL_SKILL: {
const struct skill_unit *su = BL_UCCAST(BL_SKILL, bl);
if (su->group != NULL)
return su->group->clan_id;
}
break;
}
return 0;
}
/**
* Checks every clan player and remove those who are inactive
*/
static int clan_inactivity_kick(int tid, int64 tick, int id, intptr_t data)
{
struct clan *c = NULL;
int i;
if ((c = clan->search(id)) != NULL) {
if (!c->kick_time || c->tid != tid || tid == INVALID_TIMER || c->tid == INVALID_TIMER) {
ShowError("Timer Mismatch (Time: %d seconds) %d != %d", c->kick_time, c->tid, tid);
Assert_report(0);
return 0;
}
for (i = 0; i < VECTOR_LENGTH(c->members); i++) {
struct clan_member *m = &VECTOR_INDEX(c->members, i);
if (m->char_id <= 0 || m->online <= 0)
continue;
if (m->online) {
struct map_session_data *sd = m->sd;
if (DIFF_TICK32(sockt->last_tick, sd->idletime) > c->kick_time) {
clan->leave(sd, false);
}
} else if ((time(NULL) - m->last_login) > c->kick_time) {
VECTOR_ERASE(c->members, i);
c->member_count--;
clif->clan_onlinecount(c);
}
}
//Perform the kick for offline members that didn't connect after a server restart
c->received = false;
intif->clan_kickoffline(c->clan_id, c->kick_time);
c->tid = timer->add(timer->gettick() + c->check_time, clan->inactivity_kick, c->clan_id, 0);
}
return 1;
}
/**
* Timeout for the request of offline kick
*/
static int clan_request_kickoffline(int tid, int64 tick, int id, intptr_t data)
{
struct clan *c = NULL;
if ((c = clan->search(id)) != NULL) {
if (c->req_kick_tid != tid || c->req_kick_tid == INVALID_TIMER) {
ShowError("Timer Mismatch %d != %d", c->tid, tid);
return 0;
}
if (c->received) {
c->req_kick_tid = INVALID_TIMER;
return 1;
}
intif->clan_kickoffline(c->clan_id, c->kick_time);
c->req_kick_tid = timer->add(timer->gettick() + clan->req_timeout, clan->request_kickoffline, c->clan_id, 0);
}
return 1;
}
/**
* Timeout of the request for counting members
*/
static int clan_request_membercount(int tid, int64 tick, int id, intptr_t data)
{
struct clan *c = NULL;
if ((c = clan->search(id)) != NULL) {
if (c->req_count_tid != tid || c->req_count_tid == INVALID_TIMER) {
ShowError("Timer Mismatch %d != %d", c->tid, tid);
return 0;
}
if (c->received) {
c->req_count_tid = INVALID_TIMER;
return 1;
}
intif->clan_membercount(c->clan_id, c->kick_time);
c->req_count_tid = timer->add(timer->gettick() + clan->req_timeout, clan->request_membercount, c->clan_id, 0);
}
return 1;
}
/**
* Processes any (plugin-defined) additional fields for a clan entry.
*
* @param[in, out] entry The destination clan entry, already initialized (clan_id is expected to be already set).
* @param[in] t The libconfig entry.
* @param[in] n Ordinal number of the entry, to be displayed in case of validation errors.
* @param[in] source Source of the entry (file name), to be displayed in case of validation errors.
*/
static void clan_read_db_additional_fields(struct clan *entry, struct config_setting_t *t, int n, const char *source)
{
// do nothing. plugins can do own work
}
static void clan_read_buffs(struct clan *c, struct config_setting_t *buff, const char *source)
{
struct clan_buff *b;
const char *str = NULL;
nullpo_retv(c);
nullpo_retv(buff);
b = &c->buff;
if (!libconfig->setting_lookup_int(buff, "Icon", &b->icon)) {
const char *temp_str = NULL;
if (libconfig->setting_lookup_string(buff, "Icon", &temp_str)) {
if (*temp_str && !script->get_constant(temp_str, &b->icon)) {
ShowWarning("Clan %d: Invalid Buff icon value. Defaulting to SI_BLANK.\n", c->clan_id);
b->icon = -1; // SI_BLANK
}
}
}
if (libconfig->setting_lookup_string(buff, "Script", &str)) {
b->script = *str ? script->parse(str, source, -b->icon, SCRIPT_IGNORE_EXTERNAL_BRACKETS, NULL) : NULL;
}
}
/**
* Processes one clandb entry from the libconfig file, loading and inserting it
* into the clan database.
*
* @param settings Libconfig setting entry. It is expected to be valid and it
* won't be freed (it is care of the caller to do so if
* necessary).
* @param source Source of the entry (file name), to be displayed in case of
* validation errors.
* @return int.
*/
static int clan_read_db_sub(struct config_setting_t *settings, const char *source, bool reload)
{
int total, s, valid = 0;
nullpo_retr(false, settings);
total = libconfig->setting_length(settings);
for (s = 0; s < total; s++) {
struct clan *c;
struct config_setting_t *cl, *buff, *allies, *antagonists;
const char *aName, *aLeader, *aMap, *aConst;
int max_members, kicktime = 0, kickchecktime = 0, id, i, j;
cl = libconfig->setting_get_elem(settings, s);
if (libconfig->setting_lookup_int(cl, "Id", &id)) {
if (id <= 0) {
ShowError("clan_read_db: Invalid Clan Id %d, skipping...\n", id);
return false;
}
if (clan->search(id) != NULL) {
ShowError("clan_read_db: Duplicate entry for Clan Id %d, skipping...\n", id);
return false;
}
} else {
ShowError("clan_read_db: failed to find 'Id' for clan #%d\n", s);
return false;
}
if (libconfig->setting_lookup_string(cl, "Const", &aConst)) {
if (!*aConst) {
ShowError("clan_read_db: Invalid Clan Const '%s', skipping...\n", aConst);
return false;
}
if (strlen(aConst) > NAME_LENGTH) {
ShowError("clan_read_db: Clan Name '%s' is longer than %d characters, skipping...\n", aConst, NAME_LENGTH);
return false;
}
if (clan->searchname(aConst) != NULL) {
ShowError("clan_read_db: Duplicate entry for Clan Const '%s', skipping...\n", aConst);
return false;
}
} else {
ShowError("clan_read_db: failed to find 'Const' for clan #%d\n", s);
return false;
}
if (libconfig->setting_lookup_string(cl, "Name", &aName)) {
if (!*aName) {
ShowError("clan_read_db: Invalid Clan Name '%s', skipping...\n", aName);
return false;
}
if (strlen(aName) > NAME_LENGTH) {
ShowError("clan_read_db: Clan Name '%s' is longer than %d characters, skipping...\n", aName, NAME_LENGTH);
return false;
}
if (clan->searchname(aName) != NULL) {
ShowError("clan_read_db: Duplicate entry for Clan Name '%s', skipping...\n", aName);
return false;
}
} else {
ShowError("clan_read_db: failed to find 'Name' for clan #%d\n", s);
return false;
}
if (!libconfig->setting_lookup_string(cl, "Leader", &aLeader)) {
ShowError("clan_read_db: failed to find 'Leader' for clan #%d\n", s);
return false;
}
if (!libconfig->setting_lookup_string(cl, "Map", &aMap)) {
ShowError("clan_read_db: failed to find 'Map' for clan #%d\n", s);
return false;
}
CREATE(c, struct clan, 1);
c->clan_id = id;
safestrncpy(c->constant, aConst, NAME_LENGTH);
safestrncpy(c->name, aName, NAME_LENGTH);
safestrncpy(c->master, aLeader, NAME_LENGTH);
safestrncpy(c->map, aMap, MAP_NAME_LENGTH_EXT);
c->connect_member = 0;
c->member_count = 0; // Char server will count members for us
c->received = false;
c->req_count_tid = INVALID_TIMER;
c->req_kick_tid = INVALID_TIMER;
c->tid = INVALID_TIMER;
if (libconfig->setting_lookup_int(cl, "MaxMembers", &max_members)) {
if (max_members > 0) {
c->max_member = max_members;
} else {
ShowError("clan_read_db: Clan #%d has invalid value for 'MaxMembers' setting, defaulting to 'clan->max'...\n", id);
c->max_member = clan->max;
}
} else {
c->max_member = clan->max;
}
if (libconfig->setting_lookup_int(cl, "KickTime", &kicktime)) {
c->kick_time = 60 * 60 * kicktime;
} else {
c->kick_time = clan->kicktime;
}
if (libconfig->setting_lookup_int(cl, "CheckTime", &kickchecktime)) {
c->check_time = 60 * 60 * max(1, kickchecktime) * 1000;
} else {
c->check_time = clan->checktime;
}
if ((buff = libconfig->setting_get_member(cl, "Buff")) != NULL) {
clan->read_buffs(c, buff, source);
}
allies = libconfig->setting_get_member(cl, "Allies");
if (allies != NULL) {
int a = libconfig->setting_length(allies);
VECTOR_INIT(c->allies);
if (a > 0 && clan->max_relations > 0) {
const char *allyConst;
if (a > clan->max_relations) {
ShowWarning("clan_read_db: Clan %d has more allies(%d) than allowed(%d), reading only the first %d...\n", c->clan_id, a, clan->max_relations, clan->max_relations);
a = clan->max_relations;
}
VECTOR_ENSURE(c->allies, a, 1);
for (i = 0; i < a; i++) {
struct clan_relationship r;
if ((allyConst = libconfig->setting_get_string_elem(allies, i)) != NULL) {
ARR_FIND(0, VECTOR_LENGTH(c->allies), j, strcmp(VECTOR_INDEX(c->allies, j).constant, allyConst) == 0);
if (j != VECTOR_LENGTH(c->allies)) {
ShowError("clan_read_db: Duplicate entry '%s' in allies for Clan %d in '%s', skipping...\n", allyConst, c->clan_id, source);
continue;
} else if (strcmp(allyConst, c->constant) == 0) {
ShowError("clan_read_db: Clans can't be allies of themselves! Clan Id: %d, in '%s'\n", c->clan_id, source);
continue;
}
safestrncpy(r.constant, allyConst, NAME_LENGTH);
VECTOR_PUSH(c->allies, r);
}
}
}
}
antagonists = libconfig->setting_get_member(cl, "Antagonists");
if (antagonists != NULL) {
int a = libconfig->setting_length(antagonists);
VECTOR_INIT(c->antagonists);
if (a > 0 && clan->max_relations > 0) {
const char *antagonistConst;
if (a > clan->max_relations) {
ShowWarning("clan_read_db: Clan %d has more antagonists(%d) than allowed(%d), reading only the first %d...\n", c->clan_id, a, clan->max_relations, clan->max_relations);
a = clan->max_relations;
}
VECTOR_ENSURE(c->antagonists, a, 1);
for (i = 0; i < a; i++) {
struct clan_relationship r;
if ((antagonistConst = libconfig->setting_get_string_elem(antagonists, i)) != NULL) {
ARR_FIND(0, VECTOR_LENGTH(c->antagonists), j, strcmp(VECTOR_INDEX(c->antagonists, j).constant, antagonistConst) == 0);
if (j != VECTOR_LENGTH(c->antagonists)) {
ShowError("clan_read_db: Duplicate entry '%s' in antagonists for Clan %d in '%s', skipping...\n", antagonistConst, c->clan_id, source);
continue;
} else if (strcmp(antagonistConst, c->constant) == 0) {
ShowError("clan_read_db: Clans can't be antagonists of themselves! Clan Id: %d, in '%s'\n", c->clan_id, source);
continue;
}
safestrncpy(r.constant, antagonistConst, NAME_LENGTH);
VECTOR_PUSH(c->antagonists, r);
}
}
}
}
clan->read_db_additional_fields(c, cl, s, source);
if (c->kick_time > 0) {
c->tid = timer->add(timer->gettick() + c->check_time, clan->inactivity_kick, c->clan_id, 0);
}
c->received = false;
c->req_state = reload ? CLAN_REQ_RELOAD : CLAN_REQ_FIRST;
c->req_count_tid = timer->add(timer->gettick() + clan->req_timeout, clan->request_membercount, c->clan_id, 0);
idb_put(clan->db, c->clan_id, c);
VECTOR_INIT(c->members);
valid++;
}
// Validating Relations
if (valid > 0) {
struct DBIterator *iter = db_iterator(clan->db);
struct clan *c_ok, *c;
int i;
for (c_ok = dbi_first(iter); dbi_exists(iter); c_ok = dbi_next(iter)) {
i = VECTOR_LENGTH(c_ok->allies);
while ( i > 0) {
struct clan_relationship *r;
i--;
r = &VECTOR_INDEX(c_ok->allies, i);
if ((c = clan->searchname(r->constant)) == NULL) {
ShowError("clan_read_db: Invalid (nonexistent) Ally '%s' for clan %d in '%s', skipping ally...\n", r->constant, c_ok->clan_id, source);
VECTOR_ERASE(c_ok->allies, i);
} else {
r->clan_id = c->clan_id;
}
}
i = VECTOR_LENGTH(c_ok->antagonists);
while ( i > 0) {
struct clan_relationship *r;
i--;
r = &VECTOR_INDEX(c_ok->antagonists, i);
if ((c = clan->searchname(r->constant)) == NULL) {
ShowError("clan_read_db: Invalid (nonexistent) Antagonist '%s' for clan %d in '%s', skipping antagonist...", r->constant, c_ok->clan_id, source);
VECTOR_ERASE(c_ok->antagonists, i);
} else {
r->clan_id = c->clan_id;
}
}
}
dbi_destroy(iter);
}
ShowStatus("Done reading '" CL_WHITE "%d" CL_RESET "' valid clans of '" CL_WHITE "%d" CL_RESET "' entries in '" CL_WHITE "%s" CL_RESET "'.\n", valid, total, source);
return valid;
}
/**
* Reads Clan DB included by clan configuration file.
*
* @param settings The Settings Group from config file.
* @param source File name.
*/
static void clan_read_db(struct config_setting_t *settings, const char *source, bool reload)
{
struct config_setting_t *clans;
nullpo_retv(settings);
if ((clans = libconfig->setting_get_member(settings, "clans")) != NULL) {
int read;
read = clan->read_db_sub(clans, source, reload);
if (read > 0) {
clan->set_constants();
}
} else {
ShowError("clan_read_db: Can't find setting 'clans' in '%s'. No Clans found.\n", source);
}
}
/**
* Reads clan config file
*
* @param bool clear Whether to clear clan->db before reading clans
*/
static bool clan_config_read(bool reload)
{
struct config_t clan_conf;
struct config_setting_t *settings = NULL;
const char *config_filename = "conf/clans.conf"; // FIXME: hardcoded name
int kicktime = 0, kickchecktime = 0;
if (reload) {
struct DBIterator *iter = db_iterator(clan->db);
struct clan *c_clear;
for (c_clear = dbi_first(iter); dbi_exists(iter); c_clear = dbi_next(iter)) {
if (c_clear->buff.script != NULL) {
script->free_code(c_clear->buff.script);
}
if (c_clear->tid != INVALID_TIMER) {
timer->delete(c_clear->tid, clan->inactivity_kick);
}
VECTOR_CLEAR(c_clear->allies);
VECTOR_CLEAR(c_clear->antagonists);
VECTOR_CLEAR(c_clear->members);
HPM->data_store_destroy(&c_clear->hdata);
}
dbi_destroy(iter);
clan->db->clear(clan->db, NULL);
}
if (!libconfig->load_file(&clan_conf, config_filename)) {
return false;
}
if ((settings = libconfig->lookup(&clan_conf, "clan_configuration")) == NULL) {
ShowError("clan_config_read: failed to find 'clan_configuration'.\n");
return false;
}
libconfig->setting_lookup_int(settings, "MaxMembers", &clan->max);
libconfig->setting_lookup_int(settings, "MaxRelations", &clan->max_relations);
libconfig->setting_lookup_int(settings, "InactivityKickTime", &kicktime);
if (!libconfig->setting_lookup_int(settings, "InactivityCheckTime", &kickchecktime)) {
ShowError("clan_config_read: failed to find InactivityCheckTime using official value.\n");
kickchecktime = 24;
}
// On config file we set the time in hours but here we use in seconds
clan->kicktime = 60 * 60 * kicktime;
clan->checktime = 60 * 60 * max(kickchecktime, 1) * 1000;
clan->config_read_additional_settings(settings, config_filename);
clan->read_db(settings, config_filename, reload);
libconfig->destroy(&clan_conf);
return true;
}
/**
* Processes any (plugin-defined) additional settings for clan config.
*
* @param settings The Settings Group from config file.
* @param source Source of the entry (file name), to be displayed in
* case of validation errors.
*/
static void clan_config_read_additional_settings(struct config_setting_t *settings, const char *source)
{
// do nothing. plugins can do own work
}
/**
* Reloads Clan DB
*/
static void clan_reload(void)
{
clan->config_read(true);
}
/**
*
*/
static void do_init_clan(bool minimal)
{
clan->db = idb_alloc(DB_OPT_RELEASE_DATA);
if (minimal) {
return;
}
clan->config_read(false);
timer->add_func_list(clan->inactivity_kick, "clan_inactivity_kick");
}
/**
*
*/
static void do_final_clan(void)
{
struct DBIterator *iter = db_iterator(clan->db);
struct clan *c;
for (c = dbi_first(iter); dbi_exists(iter); c = dbi_next(iter)) {
if (c->buff.script) {
script->free_code(c->buff.script);
}
if (c->tid != INVALID_TIMER) {
timer->delete(c->tid, clan->inactivity_kick);
}
VECTOR_CLEAR(c->allies);
VECTOR_CLEAR(c->antagonists);
VECTOR_CLEAR(c->members);
HPM->data_store_destroy(&c->hdata);
}
dbi_destroy(iter);
db_destroy(clan->db);
}
/**
* Inits Clan Defaults
*/
void clan_defaults(void)
{
clan = &clan_s;
clan->init = do_init_clan;
clan->final = do_final_clan;
/* */
clan->db = NULL;
clan->max = 0;
clan->max_relations = 0;
clan->kicktime = 0;
clan->checktime = 0;
clan->req_timeout = 60;
/* */
clan->config_read = clan_config_read;
clan->config_read_additional_settings = clan_config_read_additional_settings;
clan->read_db = clan_read_db;
clan->read_db_sub = clan_read_db_sub;
clan->read_db_additional_fields = clan_read_db_additional_fields;
clan->read_buffs = clan_read_buffs;
clan->search = clan_search;
clan->searchname = clan_searchname;
clan->getonlinesd = clan_getonlinesd;
clan->getindex = clan_getindex;
clan->join = clan_join;
clan->member_online = clan_member_online;
clan->leave = clan_leave;
clan->send_message = clan_send_message;
clan->recv_message = clan_recv_message;
clan->member_offline = clan_member_offline;
clan->set_constants = clan_set_constants;
clan->get_id = clan_get_id;
clan->buff_start = clan_buff_start;
clan->buff_end = clan_buff_end;
clan->reload = clan_reload;
clan->rejoin = clan_rejoin;
clan->inactivity_kick = clan_inactivity_kick;
clan->request_kickoffline = clan_request_kickoffline;
clan->request_membercount = clan_request_membercount;
}