From fc1684c82d92de81e5688e33a8386cde3c2407db Mon Sep 17 00:00:00 2001 From: Murilo Pereti Tavares Date: Thu, 25 Jan 2018 01:15:08 -0200 Subject: Implementation of Official Clan System All official features work including the autokick for inactive members And the system is completely customizable. --- src/map/clan.c | 1071 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1071 insertions(+) create mode 100644 src/map/clan.c (limited to 'src/map/clan.c') diff --git a/src/map/clan.c b/src/map/clan.c new file mode 100644 index 000000000..b67726b37 --- /dev/null +++ b/src/map/clan.c @@ -0,0 +1,1071 @@ +/** + * This file is part of Hercules. + * http://herc.ws - http://github.com/HerculesWS/Hercules + * + * Copyright (C) 2017 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 . + */ +#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 +#include +#include + +struct clan_interface clan_s; +struct clan_interface *clan; + +/** + * Searches a Clan by clan_id + * + * @param clan_id Clan ID + * @return struct clan* + */ +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* + */ +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 *) + */ +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); + 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 + */ +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 + */ +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 + */ +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 + */ +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 + */ +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; + 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->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 + */ +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 + */ +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 + */ +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; + c->connect_member--; + } + clif->clan_onlinecount(c); +} + + +/** + * Sends a message to the whole clan + */ +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 + */ +void clan_recv_message(struct clan *c, const char *mes, int len) +{ + clif->clan_message(c, mes, len); +} + +/** + * Set constants for each clan + */ +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 + */ +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 + */ +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_retr(0, 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 + */ +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 + */ +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. + */ +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 +} + +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. + */ +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. + */ +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 + */ +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. + */ +void clan_config_read_additional_settings(struct config_setting_t *settings, const char *source) +{ + // do nothing. plugins can do own work +} + +/** + * Reloads Clan DB + */ +void clan_reload(void) +{ + clan->config_read(true); +} + +/** + * + */ +void do_init_clan(bool minimal) +{ + if (minimal) { + return; + } + clan->db = idb_alloc(DB_OPT_RELEASE_DATA); + clan->config_read(false); + timer->add_func_list(clan->inactivity_kick, "clan_inactivity_kick"); +} + +/** + * + */ +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; +} -- cgit v1.2.3-60-g2f50