diff options
author | Murilo Pereti Tavares <murilopereti@gmail.com> | 2018-01-25 01:15:08 -0200 |
---|---|---|
committer | Murilo Pereti Tavares <murilopereti@gmail.com> | 2018-01-25 01:15:08 -0200 |
commit | fc1684c82d92de81e5688e33a8386cde3c2407db (patch) | |
tree | cb6af3fd8e9f940cc4cb9f2181e78c2dc751d203 /src | |
parent | 33982166de006d777aa2d95a9d95b2778db1c65a (diff) | |
download | hercules-fc1684c82d92de81e5688e33a8386cde3c2407db.tar.gz hercules-fc1684c82d92de81e5688e33a8386cde3c2407db.tar.bz2 hercules-fc1684c82d92de81e5688e33a8386cde3c2407db.tar.xz hercules-fc1684c82d92de81e5688e33a8386cde3c2407db.zip |
Implementation of Official Clan System
All official features work including the autokick for inactive members
And the system is completely customizable.
Diffstat (limited to 'src')
39 files changed, 2327 insertions, 103 deletions
diff --git a/src/char/HPMchar.c b/src/char/HPMchar.c index 3a74f443d..43a94604d 100644 --- a/src/char/HPMchar.c +++ b/src/char/HPMchar.c @@ -28,6 +28,7 @@ #include "char/geoip.h" #include "char/inter.h" #include "char/int_auction.h" +#include "char/int_clan.h" #include "char/int_elemental.h" #include "char/int_guild.h" #include "char/int_homun.h" diff --git a/src/char/Makefile.in b/src/char/Makefile.in index 1a7d067a4..fed7ec030 100644 --- a/src/char/Makefile.in +++ b/src/char/Makefile.in @@ -40,11 +40,11 @@ MT19937AR_D = $(THIRDPARTY_D)/mt19937ar MT19937AR_OBJ = $(MT19937AR_D)/mt19937ar.o MT19937AR_H = $(MT19937AR_D)/mt19937ar.h -CHAR_C = char.c HPMchar.c loginif.c mapif.c geoip.c inter.c int_auction.c int_elemental.c int_guild.c \ +CHAR_C = char.c HPMchar.c loginif.c mapif.c geoip.c inter.c int_auction.c int_clan.c int_elemental.c int_guild.c \ int_homun.c int_mail.c int_mercenary.c int_party.c int_pet.c \ int_quest.c int_rodex.c int_storage.c pincode.c CHAR_OBJ = $(addprefix obj_sql/, $(patsubst %.c,%.o,$(CHAR_C))) -CHAR_H = char.h HPMchar.h loginif.h mapif.h geoip.h inter.h int_auction.h int_elemental.h int_guild.h \ +CHAR_H = char.h HPMchar.h loginif.h mapif.h geoip.h inter.h int_auction.h int_clan.h int_elemental.h int_guild.h \ int_homun.h int_mail.h int_mercenary.h int_party.h int_pet.h \ int_quest.h int_rodex.h int_storage.h pincode.h CHAR_PH = diff --git a/src/char/char.c b/src/char/char.c index c3fcd9e0a..2532ca154 100644 --- a/src/char/char.c +++ b/src/char/char.c @@ -26,6 +26,7 @@ #include "char/HPMchar.h" #include "char/geoip.h" #include "char/int_auction.h" +#include "char/int_clan.h" #include "char/int_elemental.h" #include "char/int_guild.h" #include "char/int_homun.h" @@ -465,7 +466,8 @@ int char_mmo_char_tosql(int char_id, struct mmo_charstatus* p) (p->look.head_mid != cp->look.head_mid) || (p->look.head_bottom != cp->look.head_bottom) || (p->delete_date != cp->delete_date) || (p->rename != cp->rename) || (p->slotchange != cp->slotchange) || (p->look.robe != cp->look.robe) || (p->show_equip != cp->show_equip) || (p->allow_party != cp->allow_party) || (p->font != cp->font) || - (p->uniqueitem_counter != cp->uniqueitem_counter) || (p->hotkey_rowshift != cp->hotkey_rowshift) + (p->uniqueitem_counter != cp->uniqueitem_counter) || (p->hotkey_rowshift != cp->hotkey_rowshift) || + (p->clan_id != cp->clan_id) || (p->last_login != cp->last_login) ) { //Save status unsigned int opt = 0; @@ -483,7 +485,7 @@ int char_mmo_char_tosql(int char_id, struct mmo_charstatus* p) "`weapon`='%d',`shield`='%d',`head_top`='%d',`head_mid`='%d',`head_bottom`='%d'," "`last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d', `rename`='%d'," "`delete_date`='%lu',`robe`='%d',`slotchange`='%d', `char_opt`='%u', `font`='%u', `uniqueitem_counter` ='%u'," - "`hotkey_rowshift`='%d'" + "`hotkey_rowshift`='%d',`clan_id`='%d',`last_login`='%"PRId64"'" " WHERE `account_id`='%d' AND `char_id` = '%d'", char_db, p->base_level, p->job_level, p->base_exp, p->job_exp, p->zeny, @@ -495,7 +497,7 @@ int char_mmo_char_tosql(int char_id, struct mmo_charstatus* p) mapindex_id2name(p->save_point.map), p->save_point.x, p->save_point.y, p->rename, (unsigned long)p->delete_date, // FIXME: platform-dependent size p->look.robe,p->slotchange,opt,p->font,p->uniqueitem_counter, - p->hotkey_rowshift, + p->hotkey_rowshift,p->clan_id,p->last_login, p->account_id, p->char_id) ) { Sql_ShowDebug(inter->sql_handle); @@ -1173,7 +1175,7 @@ int char_mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_every "`status_point`,`skill_point`,`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`homun_id`,`elemental_id`,`hair`," "`hair_color`,`clothes_color`,`body`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`last_map`,`last_x`,`last_y`," "`save_map`,`save_x`,`save_y`,`partner_id`,`father`,`mother`,`child`,`fame`,`rename`,`delete_date`,`robe`,`slotchange`," - "`char_opt`,`font`,`uniqueitem_counter`,`sex`,`hotkey_rowshift`" + "`char_opt`,`font`,`uniqueitem_counter`,`sex`,`hotkey_rowshift`,`clan_id`,`last_login`" " FROM `%s` WHERE `char_id`=? LIMIT 1", char_db) || SQL_ERROR == SQL->StmtBindParam(stmt, 0, SQLDT_INT, &char_id, sizeof char_id) || SQL_ERROR == SQL->StmtExecute(stmt) @@ -1236,6 +1238,8 @@ int char_mmo_char_fromsql(int char_id, struct mmo_charstatus* p, bool load_every || SQL_ERROR == SQL->StmtBindColumn(stmt, 56, SQLDT_UINT32, &p->uniqueitem_counter, sizeof p->uniqueitem_counter, NULL, NULL) || SQL_ERROR == SQL->StmtBindColumn(stmt, 57, SQLDT_ENUM, &sex, sizeof sex, NULL, NULL) || SQL_ERROR == SQL->StmtBindColumn(stmt, 58, SQLDT_UCHAR, &p->hotkey_rowshift, sizeof p->hotkey_rowshift, NULL, NULL) + || SQL_ERROR == SQL->StmtBindColumn(stmt, 59, SQLDT_INT, &p->clan_id, sizeof p->clan_id, NULL, NULL) + || SQL_ERROR == SQL->StmtBindColumn(stmt, 60, SQLDT_INT64, &p->last_login, sizeof p->last_login, NULL, NULL) ) { SqlStmt_ShowDebug(stmt); SQL->StmtFree(stmt); @@ -6424,6 +6428,7 @@ void char_load_defaults(void) loginif_defaults(); mapif_defaults(); inter_auction_defaults(); + inter_clan_defaults(); inter_elemental_defaults(); inter_guild_defaults(); inter_homunculus_defaults(); diff --git a/src/char/int_clan.c b/src/char/int_clan.c new file mode 100644 index 000000000..76a9639c5 --- /dev/null +++ b/src/char/int_clan.c @@ -0,0 +1,181 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + */ +#define HERCULES_CORE + +#include "config/core.h" // DBPATH +#include "int_clan.h" + +#include "char/char.h" +#include "char/inter.h" +#include "char/mapif.h" +#include "common/cbasetypes.h" +#include "common/memmgr.h" +#include "common/mmo.h" +#include "common/nullpo.h" +#include "common/showmsg.h" +#include "common/socket.h" +#include "common/sql.h" +#include "common/strlib.h" +#include "common/timer.h" + +#include <stdio.h> +#include <stdlib.h> + +struct inter_clan_interface inter_clan_s; +struct inter_clan_interface *inter_clan; + +/** + * Kick offline members of a clan + * + * Perform the update on the DB to reset clan id to 0 + * of the members that are inactive for too long + * + * @param clan_id Id of the clan + * @param kick_interval Time needed to consider a player inactive and kick it + * @return 0 on failure, 1 on success + */ +int inter_clan_kick_inactive_members(int clan_id, int kick_interval) +{ + if (clan_id <= 0) { + ShowError("inter_clan_kick_inactive_members: Invalid clan id received '%d'\n", clan_id); + Assert_retr(0, 0); + return 0; + } else if (kick_interval <= 0) { + ShowError("inter_clan_kick_inactive_members: Invalid kick_interval received '%d'", kick_interval); + Assert_retr(0, 0); + return 0; + } + + // Kick Inactive members + if (SQL_ERROR == SQL->Query(inter->sql_handle, "UPDATE `%s` SET " + "`clan_id` = 0 WHERE `clan_id` = '%d' AND `online` = 0 AND `last_login` < %"PRId64, + char_db, clan_id, (int64)(time(NULL) - kick_interval))) + { + Sql_ShowDebug(inter->sql_handle); + return 0; + } + + return 1; +} + +/** + * Count members of a clan + * + * @param clan_id Id of the clan + * @param kick_interval Time needed to consider a player inactive and ignore it on the count + */ +int inter_clan_count_members(int clan_id, int kick_interval) +{ + struct SqlStmt *stmt; + int count = 0; + + if (clan_id <= 0) { + ShowError("inter_clan_count_members: Invalid clan id received '%d'\n", clan_id); + Assert_retr(0, 0); + return 0; + } else if (kick_interval <= 0) { + ShowError("inter_clan_count_member: Invalid kick_interval received '%d'", kick_interval); + Assert_retr(0, 0); + return 0; + } + + stmt = SQL->StmtMalloc(inter->sql_handle); + if (stmt == NULL) { + SqlStmt_ShowDebug(stmt); + return 0; + } + + // Count members + if (SQL_ERROR == SQL->StmtPrepare(stmt, "SELECT COUNT(*) FROM `%s` WHERE `clan_id` = ? AND `last_login` >= %"PRId64, char_db, (int64)(time(NULL) - kick_interval)) + || SQL_ERROR == SQL->StmtBindParam(stmt, 0, SQLDT_INT, &clan_id, sizeof(clan_id)) + || SQL_ERROR == SQL->StmtExecute(stmt) + || SQL_ERROR == SQL->StmtBindColumn(stmt, 0, SQLDT_INT, &count, sizeof(count), NULL, NULL) + ) { + SqlStmt_ShowDebug(stmt); + SQL->StmtFree(stmt); + return 0; + } + + if (SQL->StmtNumRows(stmt) > 0 && SQL_SUCCESS != SQL->StmtNextRow(stmt)) { + SqlStmt_ShowDebug(stmt); + SQL->StmtFree(stmt); + return 0; + } + + SQL->StmtFree(stmt); + return count; +} + +int mapif_parse_ClanMemberCount(int fd, int clan_id, int kick_interval) +{ + + WFIFOHEAD(fd, 10); + WFIFOW(fd, 0) = 0x3858; + WFIFOL(fd, 2) = clan_id; + WFIFOL(fd, 6) = inter_clan->count_members(clan_id, kick_interval); + WFIFOSET(fd, 10); + return 0; +} + +int mapif_parse_ClanMemberKick(int fd, int clan_id, int kick_interval) +{ + int count = 0; + + if (inter_clan->kick_inactive_members(clan_id, kick_interval) == 1) + count = inter_clan->count_members(clan_id, kick_interval); + + WFIFOHEAD(fd, 10); + WFIFOW(fd, 0) = 0x3858; + WFIFOL(fd, 2) = clan_id; + WFIFOL(fd, 6) = count; + WFIFOSET(fd, 10); + return 0; +} + +// Communication from the map server +// - Can analyzed only one by one packet +// Data packet length that you set to inter.c +//- Shouldn't do checking and packet length, RFIFOSKIP is done by the caller +// Must Return +// 1 : ok +// 0 : error +int inter_clan_parse_frommap(int fd) +{ + RFIFOHEAD(fd); + + switch(RFIFOW(fd, 0)) { + case 0x3044: mapif->parse_ClanMemberCount(fd, RFIFOL(fd, 2), RFIFOL(fd, 6)); break; + case 0x3045: mapif->parse_ClanMemberKick(fd, RFIFOL(fd, 2), RFIFOL(fd, 6)); break; + + default: + return 0; + } + + return 1; +} + +void inter_clan_defaults(void) +{ + inter_clan = &inter_clan_s; + + inter_clan->kick_inactive_members = inter_clan_kick_inactive_members; + inter_clan->count_members = inter_clan_count_members; + inter_clan->parse_frommap = inter_clan_parse_frommap; +} diff --git a/src/char/int_clan.h b/src/char/int_clan.h new file mode 100644 index 000000000..e7b44ecd3 --- /dev/null +++ b/src/char/int_clan.h @@ -0,0 +1,39 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + */ +#ifndef CHAR_INT_CLAN_H +#define CHAR_INT_CLAN_H + +#include "common/mmo.h" + +/** + * inter clan Interface + **/ +struct inter_clan_interface { + int (*kick_inactive_members) (int clan_id, int kick_interval); + int (*count_members) (int clan_id, int kick_interval); + int (*parse_frommap) (int fd); +}; + +#ifdef HERCULES_CORE +void inter_clan_defaults(void); +#endif // HERCULES_CORE + +HPShared struct inter_clan_interface *inter_clan; +#endif /* CHAR_INT_CLAN_H */ diff --git a/src/char/inter.c b/src/char/inter.c index 557ee5313..baa000d82 100644 --- a/src/char/inter.c +++ b/src/char/inter.c @@ -25,6 +25,7 @@ #include "char/char.h" #include "char/geoip.h" #include "char/int_auction.h" +#include "char/int_clan.h" #include "char/int_elemental.h" #include "char/int_guild.h" #include "char/int_homun.h" @@ -72,7 +73,7 @@ int inter_recv_packet_length[] = { 6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, // 3010- Account Storage [Smokexyz] -1,10,-1,14, 14,19, 6,-1, 14,14, 0, 0, 0, 0, 0, 0, // 3020- Party -1, 6,-1,-1, 55,19, 6,-1, 14,-1,-1,-1, 18,19,186,-1, // 3030- - -1, 9, 0, 0, 0, 0, 0, 0, 7, 6,10,10, 10,-1, 0, 0, // 3040- + -1, 9, 0, 0, 10,10, 0, 0, 7, 6,10,10, 10,-1, 0, 0, // 3040- Clan System(3044-3045) -1,-1,10,10, 0,-1,12, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3050- Auction System [Zephyrus], Item Bound [Mhalicot] 6,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3060- Quest system [Kevin] [Inkfish] -1,10, 6,-1, 0, 0, 0, 0, 0, 0, 0, 0, -1,10, 6,-1, // 3070- Mercenary packets [Zephyrus], Elemental packets [pakpil] @@ -1420,6 +1421,7 @@ int inter_parse_frommap(int fd) || inter_auction->parse_frommap(fd) || inter_quest->parse_frommap(fd) || inter_rodex->parse_frommap(fd) + || inter_clan->parse_frommap(fd) ) break; else diff --git a/src/char/mapif.c b/src/char/mapif.c index 9fccd691b..241edc925 100644 --- a/src/char/mapif.c +++ b/src/char/mapif.c @@ -221,6 +221,9 @@ int mapif_parse_Registry(int fd); int mapif_parse_RegistryRequest(int fd); void mapif_namechange_ack(int fd, int account_id, int char_id, int type, int flag, const char *const name); int mapif_parse_NameChangeRequest(int fd); +// Clan System +int mapif_parse_ClanMemberKick(int fd, int clan_id, int kick_interval); +int mapif_parse_ClanMemberCount(int fd, int clan_id, int kick_interval); struct mapif_interface mapif_s; struct mapif_interface *mapif; @@ -411,4 +414,7 @@ void mapif_defaults(void) { mapif->parse_RegistryRequest = mapif_parse_RegistryRequest; mapif->namechange_ack = mapif_namechange_ack; mapif->parse_NameChangeRequest = mapif_parse_NameChangeRequest; + /* Clan System */ + mapif->parse_ClanMemberKick = mapif_parse_ClanMemberKick; + mapif->parse_ClanMemberCount = mapif_parse_ClanMemberCount; } diff --git a/src/char/mapif.h b/src/char/mapif.h index a78940ba2..f03f1ad9a 100644 --- a/src/char/mapif.h +++ b/src/char/mapif.h @@ -213,6 +213,9 @@ struct mapif_interface { int (*parse_RegistryRequest) (int fd); void (*namechange_ack) (int fd, int account_id, int char_id, int type, int flag, const char *name); int (*parse_NameChangeRequest) (int fd); + // Clan System + int (*parse_ClanMemberKick) (int fd, int clan_id, int kick_interval); + int (*parse_ClanMemberCount) (int fd, int clan_id, int kick_interval); }; #ifdef HERCULES_CORE diff --git a/src/common/HPMi.h b/src/common/HPMi.h index 143c325c1..7698d3ca7 100644 --- a/src/common/HPMi.h +++ b/src/common/HPMi.h @@ -89,6 +89,7 @@ enum HPluginDataTypes { HPDT_ITEMDATA, ///< For struct item_data. HPDT_BGDATA, ///< For struct battleground_data. HPDT_AUTOTRADE_VEND, ///< For struct autotrade_vending. + HPDT_CLAN, ///< For struct clan. }; /* used in macros and conf storage */ @@ -153,6 +154,10 @@ enum HPluginConfType { #define addToATVEND(ptr,data,classid,autofree) (HPMi->addToHPData(HPDT_AUTOTRADE_VEND,HPMi->pid,&(ptr)->hdata,(data),(classid),(autofree))) #define getFromATVEND(ptr,classid) (HPMi->getFromHPData(HPDT_AUTOTRADE_VEND,HPMi->pid,(ptr)->hdata,(classid))) #define removeFromATVEND(ptr,classid) (HPMi->removeFromHPData(HPDT_AUTOTRADE_VEND,HPMi->pid,(ptr)->hdata,(classid))) +/* clan */ +#define addtoCLAN(ptr,data,classid,autofree) (HPMi->addToHPData(HPDT_CLAN,HPMi->pid,&(ptr)->hdata,(data),(classid),(autofree))) +#define getfromCLAN(ptr,classid) (HPMi->getFromHPData(HPDT_CLAN,HPMi->pid,(ptr)->hdata,(classid))) +#define removefromCLAN(ptr,classid) (HPMi->removeFromHPData(HPDT_CLAN,HPMi->pid,(ptr)->hdata,(classid))) /// HPMi->addCommand #define addAtcommand(cname,funcname) do { \ diff --git a/src/common/mmo.h b/src/common/mmo.h index 7fd4ca6ea..d7d0820b3 100644 --- a/src/common/mmo.h +++ b/src/common/mmo.h @@ -304,6 +304,7 @@ STATIC_ASSERT(MAX_ITEM_OPTIONS <= 5, "This value is limited by the client and da #define INFINITE_DURATION (-1) // Infinite duration for status changes struct hplugin_data_store; +struct script_code; enum item_types { IT_HEALING = 0, @@ -616,7 +617,7 @@ struct mmo_charstatus { short manner; // Defines how many minutes a char will be muted, each negative point is equivalent to a minute. unsigned char karma; short hair,hair_color,clothes_color,body; - int party_id,guild_id,pet_id,hom_id,mer_id,ele_id; + int party_id,guild_id,clan_id,pet_id,hom_id,mer_id,ele_id; int fame; // Mercenary Guilds Rank @@ -641,6 +642,7 @@ struct mmo_charstatus { uint32 mapip; uint16 mapport; + int64 last_login; struct point last_point,save_point,memo_point[MAX_MEMOPOINTS]; struct item inventory[MAX_INVENTORY],cart[MAX_CART]; struct s_skill skill[MAX_SKILL_DB]; @@ -818,6 +820,59 @@ struct guild_castle { int temp_guardians_max; }; +/** + * Clan Member Struct + */ +struct clan_member { + int char_id; ///< Char Id of member + short online; ///< Flag to know if the player is online or not + int64 last_login; ///< Last login of this member, used to kick if inactive for long time + struct map_session_data *sd; ///< Player data of member +}; + +/** + * Clan Buff Struct + */ +struct clan_buff { + int icon; ///< Status Icon to be shown in client (Use one of the 'SI_' constants) + struct script_code *script; ///< The script to be executed as CLan Buff +}; + +/** + * Clan Relationship Struct + */ +struct clan_relationship { + char constant[NAME_LENGTH]; ///< Unique name of the related clan + int clan_id; ///< Id of the related clan +}; + +/** + * Clan Struct + */ +struct clan { + int clan_id; ///< CLan's Id + char constant[NAME_LENGTH]; ///< Clan's Unique Name + char name[NAME_LENGTH]; ///< Clan's Name + char master[NAME_LENGTH]; ///< Name of the clan's master (used for clan information on client) + char map[MAP_NAME_LENGTH_EXT]; ///< The map of that clan (used for clan information on client) + struct clan_buff buff; ///< The buff for a clan when a member joins it + short max_member; ///< Limit of Members + short member_count; ///< Holds the amount of members in this clan, online and offline + short connect_member; ///< Members that are Online + VECTOR_DECL(struct clan_member) members; ///< Vector of Members + VECTOR_DECL(struct clan_relationship) allies; ///< Vector of Allies + VECTOR_DECL(struct clan_relationship) antagonists; ///< Vector of Antagonists + int kick_time; /// Needed inactive time to be kicked + int check_time; ///< Interval to check for inactive players + int tid; ///< Timer ID for inactivity kick + bool received; ///< Whether or not the requested data was received + int req_state; ///< Flag for knowing what to do after receiving the data from inter server + int req_count_tid; ///< Timer ID for the timer that handles the timeout of requests for interserver to count members + int req_kick_tid; ///< Timer ID for the timer that handles the timeout of requests for interserver to kick inactive members + + struct hplugin_data_store *hdata; ///< HPM Plugin Data Store +}; + struct fame_list { int id; int fame; diff --git a/src/map/HPMmap.c b/src/map/HPMmap.c index 381dbf599..17b4fd813 100644 --- a/src/map/HPMmap.c +++ b/src/map/HPMmap.c @@ -54,6 +54,7 @@ #include "map/channel.h" #include "map/chat.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/clif.h" #include "map/date.h" #include "map/duel.h" @@ -114,21 +115,22 @@ unsigned int atcommand_list_items = 0; bool HPM_map_data_store_validate(enum HPluginDataTypes type, struct hplugin_data_store **storeptr, bool initialize) { switch (type) { - case HPDT_MSD: - case HPDT_NPCD: - case HPDT_MAP: - case HPDT_PARTY: - case HPDT_GUILD: - case HPDT_INSTANCE: - case HPDT_MOBDB: - case HPDT_MOBDATA: - case HPDT_ITEMDATA: - case HPDT_BGDATA: - case HPDT_AUTOTRADE_VEND: - // Initialized by the caller. - return true; - default: - break; + case HPDT_MSD: + case HPDT_NPCD: + case HPDT_MAP: + case HPDT_PARTY: + case HPDT_GUILD: + case HPDT_INSTANCE: + case HPDT_MOBDB: + case HPDT_MOBDATA: + case HPDT_ITEMDATA: + case HPDT_BGDATA: + case HPDT_AUTOTRADE_VEND: + case HPDT_CLAN: + // Initialized by the caller. + return true; + default: + break; } return false; } diff --git a/src/map/Makefile.in b/src/map/Makefile.in index 91efba0ae..e6966d9a2 100644 --- a/src/map/Makefile.in +++ b/src/map/Makefile.in @@ -41,14 +41,14 @@ MT19937AR_OBJ = $(MT19937AR_D)/mt19937ar.o MT19937AR_H = $(MT19937AR_D)/mt19937ar.h MAP_C = atcommand.c battle.c battleground.c buyingstore.c channel.c chat.c \ - chrif.c clif.c date.c duel.c elemental.c guild.c homunculus.c HPMmap.c \ + chrif.c clan.c clif.c date.c duel.c elemental.c guild.c homunculus.c HPMmap.c \ instance.c intif.c irc-bot.c itemdb.c log.c mail.c map.c mapreg_sql.c \ mercenary.c mob.c npc.c npc_chat.c party.c path.c pc.c pc_groups.c \ pet.c quest.c rodex.c script.c searchstore.c skill.c status.c storage.c \ trade.c unit.c vending.c MAP_OBJ = $(addprefix obj_sql/, $(patsubst %c,%o,$(MAP_C))) MAP_H = atcommand.h battle.h battleground.h buyingstore.h channel.h chat.h \ - chrif.h clif.h date.h duel.h elemental.h guild.h homunculus.h HPMmap.h \ + chrif.h clan.h clif.h date.h duel.h elemental.h guild.h homunculus.h HPMmap.h \ instance.h intif.h irc-bot.h itemdb.h log.h mail.h map.h mapreg.h \ mercenary.h mob.h npc.h packets.h packets_keys_main.h packets_keys_zero.h \ packets_shuffle_main.h packets_shuffle_zero.h packets_struct.h party.h \ diff --git a/src/map/atcommand.c b/src/map/atcommand.c index aa25f11d6..d0a9bb10e 100644 --- a/src/map/atcommand.c +++ b/src/map/atcommand.c @@ -28,6 +28,7 @@ #include "map/channel.h" #include "map/chat.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/clif.h" #include "map/duel.h" #include "map/elemental.h" @@ -1677,6 +1678,45 @@ ACMD(gvgon) /*========================================== * *------------------------------------------*/ +ACMD(cvcoff) +{ + if (!map->list[sd->bl.m].flag.cvc) { + clif->message(fd, msg_fd(fd, 141)); // CvC is already Off. + return false; + } + + map->zone_change2(sd->bl.m, map->list[sd->bl.m].prev_zone); + map->list[sd->bl.m].flag.cvc = 0; + clif->map_property_mapall(sd->bl.m, MAPPROPERTY_NOTHING); + clif->maptypeproperty2(&sd->bl, ALL_SAMEMAP); + map->foreachinmap(atcommand->stopattack, sd->bl.m, BL_CHAR, 0); + clif->message(fd, msg_fd(fd, 26)); // CvC: Off. + + return true; +} + +/*========================================== + * + *------------------------------------------*/ +ACMD(cvcon) +{ + if (map->list[sd->bl.m].flag.cvc) { + clif->message(fd, msg_fd(fd, 142)); // CvC is already On. + return false; + } + + map->zone_change2(sd->bl.m, strdb_get(map->zone_db, MAP_ZONE_CVC_NAME)); + map->list[sd->bl.m].flag.cvc = 1; + clif->map_property_mapall(sd->bl.m, MAPPROPERTY_AGITZONE); + clif->maptypeproperty2(&sd->bl, ALL_SAMEMAP); + clif->message(fd, msg_fd(fd, 27)); // CvC: On. + + return true; +} + +/*========================================== + * + *------------------------------------------*/ ACMD(model) { int hair_style = 0, hair_color = 0, cloth_color = 0; @@ -3798,6 +3838,9 @@ ACMD(mapinfo) if (map->list[m_id].flag.battleground) clif->message(fd, msg_fd(fd,1045)); // Battlegrounds ON + if (map->list[m_id].flag.cvc) + clif->message(fd, msg_fd(fd, 139)); // CvC ON + strcpy(atcmd_output,msg_fd(fd,1046)); // PvP Flags: if (map->list[m_id].flag.pvp) strcat(atcmd_output, msg_fd(fd,1047)); // Pvp ON | @@ -7551,11 +7594,11 @@ ACMD(mapflag) { CHECKFLAG(noreturn); CHECKFLAG(monster_noteleport); CHECKFLAG(nosave); CHECKFLAG(nobranch); CHECKFLAG(noexppenalty); CHECKFLAG(pvp); CHECKFLAG(pvp_noparty); CHECKFLAG(pvp_noguild); CHECKFLAG(pvp_nightmaredrop); CHECKFLAG(pvp_nocalcrank); CHECKFLAG(gvg_castle); CHECKFLAG(gvg); - CHECKFLAG(gvg_dungeon); CHECKFLAG(gvg_noparty); CHECKFLAG(battleground); CHECKFLAG(nozenypenalty); - CHECKFLAG(notrade); CHECKFLAG(noskill); CHECKFLAG(nowarp); CHECKFLAG(nowarpto); - CHECKFLAG(noicewall); CHECKFLAG(snow); CHECKFLAG(clouds); CHECKFLAG(clouds2); - CHECKFLAG(fog); CHECKFLAG(fireworks); CHECKFLAG(sakura); CHECKFLAG(leaves); - CHECKFLAG(nobaseexp); + CHECKFLAG(gvg_dungeon); CHECKFLAG(gvg_noparty); CHECKFLAG(battleground); CHECKFLAG(cvc); + CHECKFLAG(nozenypenalty); CHECKFLAG(notrade); CHECKFLAG(noskill); CHECKFLAG(nowarp); + CHECKFLAG(nowarpto); CHECKFLAG(noicewall); CHECKFLAG(snow); CHECKFLAG(clouds); + CHECKFLAG(clouds2); CHECKFLAG(fog); CHECKFLAG(fireworks); CHECKFLAG(sakura); + CHECKFLAG(leaves); CHECKFLAG(nobaseexp); CHECKFLAG(nojobexp); CHECKFLAG(nomobloot); CHECKFLAG(nomvploot); CHECKFLAG(nightenabled); CHECKFLAG(nodrop); CHECKFLAG(novending); CHECKFLAG(loadevent); CHECKFLAG(nochat); CHECKFLAG(partylock); CHECKFLAG(guildlock); CHECKFLAG(src4instance); @@ -7567,48 +7610,53 @@ ACMD(mapflag) { } for (i = 0; flag_name[i]; i++) flag_name[i] = TOLOWER(flag_name[i]); //lowercase - if (strcmp( flag_name , "gvg" ) == 0) { - if( flag && !map->list[sd->bl.m].flag.gvg ) - map->zone_change2(sd->bl.m,strdb_get(map->zone_db, MAP_ZONE_GVG_NAME)); - else if ( !flag && map->list[sd->bl.m].flag.gvg ) - map->zone_change2(sd->bl.m,map->list[sd->bl.m].prev_zone); - } else if ( strcmp( flag_name , "pvp" ) == 0 ) { - if ( flag && !map->list[sd->bl.m].flag.pvp ) - map->zone_change2(sd->bl.m,strdb_get(map->zone_db, MAP_ZONE_PVP_NAME)); - else if ( !flag && map->list[sd->bl.m].flag.pvp ) - map->zone_change2(sd->bl.m,map->list[sd->bl.m].prev_zone); - } else if ( strcmp( flag_name , "battleground" ) == 0 ) { - if ( flag && !map->list[sd->bl.m].flag.battleground ) - map->zone_change2(sd->bl.m,strdb_get(map->zone_db, MAP_ZONE_BG_NAME)); - else if ( !flag && map->list[sd->bl.m].flag.battleground ) - map->zone_change2(sd->bl.m,map->list[sd->bl.m].prev_zone); + if (strcmp(flag_name, "gvg") == 0) { + if (flag && !map->list[sd->bl.m].flag.gvg) + map->zone_change2(sd->bl.m, strdb_get(map->zone_db, MAP_ZONE_GVG_NAME)); + else if (!flag && map->list[sd->bl.m].flag.gvg) + map->zone_change2(sd->bl.m, map->list[sd->bl.m].prev_zone); + } else if (strcmp(flag_name, "pvp") == 0) { + if (flag && !map->list[sd->bl.m].flag.pvp) + map->zone_change2(sd->bl.m, strdb_get(map->zone_db, MAP_ZONE_PVP_NAME)); + else if (!flag && map->list[sd->bl.m].flag.pvp) + map->zone_change2(sd->bl.m, map->list[sd->bl.m].prev_zone); + } else if (strcmp(flag_name, "battleground") == 0) { + if (flag && !map->list[sd->bl.m].flag.battleground) + map->zone_change2(sd->bl.m, strdb_get(map->zone_db, MAP_ZONE_BG_NAME)); + else if (!flag && map->list[sd->bl.m].flag.battleground) + map->zone_change2(sd->bl.m, map->list[sd->bl.m].prev_zone); + } else if (strcmp(flag_name, "cvc") == 0) { + if (flag && !map->list[sd->bl.m].flag.cvc) + map->zone_change2(sd->bl.m, strdb_get(map->zone_db, MAP_ZONE_CVC_NAME)); + else if (!flag && map->list[sd->bl.m].flag.cvc) + map->zone_change2(sd->bl.m, map->list[sd->bl.m].prev_zone); } SETFLAG(autotrade); SETFLAG(allowks); SETFLAG(nomemo); SETFLAG(noteleport); SETFLAG(noreturn); SETFLAG(monster_noteleport); SETFLAG(nosave); SETFLAG(nobranch); SETFLAG(noexppenalty); SETFLAG(pvp); SETFLAG(pvp_noparty); SETFLAG(pvp_noguild); SETFLAG(pvp_nightmaredrop); SETFLAG(pvp_nocalcrank); SETFLAG(gvg_castle); SETFLAG(gvg); - SETFLAG(gvg_dungeon); SETFLAG(gvg_noparty); SETFLAG(battleground); SETFLAG(nozenypenalty); - SETFLAG(notrade); SETFLAG(noskill); SETFLAG(nowarp); SETFLAG(nowarpto); - SETFLAG(noicewall); SETFLAG(snow); SETFLAG(clouds); SETFLAG(clouds2); - SETFLAG(fog); SETFLAG(fireworks); SETFLAG(sakura); SETFLAG(leaves); - SETFLAG(nobaseexp); - SETFLAG(nojobexp); SETFLAG(nomobloot); SETFLAG(nomvploot); SETFLAG(nightenabled); - SETFLAG(nodrop); SETFLAG(novending); SETFLAG(loadevent); - SETFLAG(nochat); SETFLAG(partylock); SETFLAG(guildlock); SETFLAG(src4instance); - SETFLAG(notomb); SETFLAG(nocashshop); SETFLAG(noviewid); - - clif->message(sd->fd,msg_fd(fd,1314)); // Invalid flag name or flag. - clif->message(sd->fd,msg_fd(fd,1312)); // Usage: "@mapflag monster_noteleport 1" (0=Off | 1=On) - clif->message(sd->fd,msg_fd(fd,1315)); // Available Flags: - clif->message(sd->fd,"----------------------------------"); - clif->message(sd->fd,"town, autotrade, allowks, nomemo, noteleport, noreturn, monster_noteleport, nosave,"); - clif->message(sd->fd,"nobranch, noexppenalty, pvp, pvp_noparty, pvp_noguild, pvp_nightmaredrop,"); - clif->message(sd->fd,"pvp_nocalcrank, gvg_castle, gvg, gvg_dungeon, gvg_noparty, battleground,"); - clif->message(sd->fd,"nozenypenalty, notrade, noskill, nowarp, nowarpto, noicewall, snow, clouds, clouds2,"); - clif->message(sd->fd,"fog, fireworks, sakura, leaves, nobaseexp, nojobexp, nomobloot,"); - clif->message(sd->fd,"nomvploot, nightenabled, nodrop, novending, loadevent, nochat, partylock,"); - clif->message(sd->fd,"guildlock, src4instance, notomb, nocashshop, noviewid"); + SETFLAG(gvg_dungeon); SETFLAG(gvg_noparty); SETFLAG(battleground); SETFLAG(cvc); + SETFLAG(nozenypenalty); SETFLAG(notrade); SETFLAG(noskill); SETFLAG(nowarp); + SETFLAG(nowarpto); SETFLAG(noicewall); SETFLAG(snow); SETFLAG(clouds); + SETFLAG(clouds2); SETFLAG(fog); SETFLAG(fireworks); SETFLAG(sakura); + SETFLAG(leaves); SETFLAG(nobaseexp); SETFLAG(nojobexp); SETFLAG(nomobloot); + SETFLAG(nomvploot); SETFLAG(nightenabled); SETFLAG(nodrop); SETFLAG(novending); + SETFLAG(loadevent); SETFLAG(nochat); SETFLAG(partylock); SETFLAG(guildlock); + SETFLAG(src4instance); SETFLAG(notomb); SETFLAG(nocashshop); SETFLAG(noviewid); + + + clif->message(sd->fd, msg_fd(fd, 1314)); // Invalid flag name or flag. + clif->message(sd->fd, msg_fd(fd, 1312)); // Usage: "@mapflag monster_noteleport 1" (0=Off | 1=On) + clif->message(sd->fd, msg_fd(fd, 1315)); // Available Flags: + clif->message(sd->fd, "----------------------------------"); + clif->message(sd->fd, "town, autotrade, allowks, nomemo, noteleport, noreturn, monster_noteleport, nosave,"); + clif->message(sd->fd, "nobranch, noexppenalty, pvp, pvp_noparty, pvp_noguild, pvp_nightmaredrop,"); + clif->message(sd->fd, "pvp_nocalcrank, gvg_castle, gvg, gvg_dungeon, gvg_noparty, battleground, cvc,"); + clif->message(sd->fd, "nozenypenalty, notrade, noskill, nowarp, nowarpto, noicewall, snow, clouds, clouds2,"); + clif->message(sd->fd, "fog, fireworks, sakura, leaves, nobaseexp, nojobexp, nomobloot,"); + clif->message(sd->fd, "nomvploot, nightenabled, nodrop, novending, loadevent, nochat, partylock,"); + clif->message(sd->fd, "guildlock, src4instance, notomb, nocashshop, noviewid"); #undef CHECKFLAG #undef SETFLAG @@ -9451,6 +9499,145 @@ ACMD(lang) { return true; } + +ACMD(claninfo) +{ + struct DBIterator *iter = db_iterator(clan->db); + struct clan *c; + int i, count; + + for (c = dbi_first(iter); dbi_exists(iter); c = dbi_next(iter)) { + safesnprintf(atcmd_output, sizeof(atcmd_output), "Clan #%d:", c->clan_id); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- Name: %s", c->name); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- Constant: %s", c->constant); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- Master: %s", c->master); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- Map: %s", c->map); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- Max Member: %d", c->max_member); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- Kick Time: %dh", c->kick_time / 3600); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- Check Time: %dh", c->check_time / 3600000); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- Connected Members: %d", c->connect_member); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- Total Members: %d", c->member_count); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- Allies: %d", VECTOR_LENGTH(c->allies)); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + count = 0; + for (i = 0; i < VECTOR_LENGTH(c->allies); i++) { + struct clan_relationship *ally = &VECTOR_INDEX(c->allies, i); + + if (ally == NULL) + continue; + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- - Ally #%d (Id: %d): %s", i + 1, ally->clan_id, ally->constant); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + count++; + } + + if (count == 0) { + clif->messagecolor_self(fd, COLOR_DEFAULT, "- - No Allies Found!"); + } + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- Antagonists: %d", VECTOR_LENGTH(c->antagonists)); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + + count = 0; + for (i = 0; i < VECTOR_LENGTH(c->antagonists); i++) { + struct clan_relationship *antagonist = &VECTOR_INDEX(c->antagonists, i); + + if (antagonist == NULL) + continue; + + safesnprintf(atcmd_output, sizeof(atcmd_output), "- - Antagonist #%d (Id: %d): %s", i + 1, antagonist->clan_id, antagonist->constant); + clif->messagecolor_self(fd, COLOR_DEFAULT, atcmd_output); + count++; + } + + if (count == 0) { + clif->messagecolor_self(fd, COLOR_DEFAULT, "- - No Antagonists Found!"); + } + + clif->messagecolor_self(fd, COLOR_DEFAULT, "============================"); + } + dbi_destroy(iter); + return true; +} + +/** + * Clan System: Joins in the given clan + */ +ACMD(joinclan) +{ + int clan_id; + + if (*message == '\0') { + clif->message(fd, "Please enter a Clan ID (usage: @joinclan <clan ID>)."); + return false; + } + if (sd->status.clan_id != 0) { + clif->messagecolor_self(fd, COLOR_RED, "You are already in a clan."); + return false; + } else if (sd->status.guild_id != 0) { + clif->messagecolor_self(fd, COLOR_RED, "You must leave your guild before enter in a clan."); + return false; + } + + clan_id = atoi(message); + if (clan_id <= 0) { + clif->messagecolor_self(fd, COLOR_RED, "Invalid Clan ID."); + return false; + } + if (!clan->join(sd, clan_id)) { + clif->messagecolor_self(fd, COLOR_RED, "The clan couldn't be joined."); + return false; + } + return true; +} + +/** + * Clan System: Leaves current clan + */ +ACMD(leaveclan) +{ + if (sd->status.clan_id == 0) { + clif->messagecolor_self(fd, COLOR_RED, "You aren't in a clan."); + return false; + } + if (!clan->leave(sd, false)) { + clif->messagecolor_self(fd, COLOR_RED, "Failed on leaving clan."); + return false; + } + return true; +} + +/** + * Clan System: Reloads clan db + */ +ACMD(reloadclans) +{ + clan->reload(); + clif->messagecolor_self(fd, COLOR_DEFAULT, "Clan configuration and database have been reloaded."); + return true; +} + /** * Fills the reference of available commands in atcommand DBMap **/ @@ -9723,6 +9910,12 @@ void atcommand_basecommands(void) { ACMD_DEF(cddebug), ACMD_DEF(lang), ACMD_DEF(bodystyle), + ACMD_DEF(cvcoff), + ACMD_DEF(cvcon), + ACMD_DEF(claninfo), + ACMD_DEF(joinclan), + ACMD_DEF(leaveclan), + ACMD_DEF(reloadclans), }; int i; diff --git a/src/map/battle.c b/src/map/battle.c index 70ebc4a08..d52d20f24 100644 --- a/src/map/battle.c +++ b/src/map/battle.c @@ -25,6 +25,7 @@ #include "map/battleground.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/clif.h" #include "map/elemental.h" #include "map/guild.h" @@ -6774,14 +6775,17 @@ int battle_check_target( struct block_list *src, struct block_list *target,int f return (flag&state)?1:-1; } - if( map_flag_vs(m) ) { + if (map_flag_vs(m)) { //Check rivalry settings. - int sbg_id = 0, tbg_id = 0; - if( map->list[m].flag.battleground ) { + int sbg_id = 0, tbg_id = 0, s_clan = 0, t_clan = 0; + if (map->list[m].flag.battleground) { sbg_id = bg->team_get_id(s_bl); tbg_id = bg->team_get_id(t_bl); + } else if (map->list[m].flag.cvc) { + s_clan = clan->get_id(s_bl); + t_clan = clan->get_id(t_bl); } - if( flag&(BCT_PARTY|BCT_ENEMY) ) { + if (flag & (BCT_PARTY | BCT_ENEMY)) { int s_party = status->get_party_id(s_bl); int s_guild = status->get_guild_id(s_bl); int t_guild = status->get_guild_id(t_bl); @@ -6791,10 +6795,12 @@ int battle_check_target( struct block_list *src, struct block_list *target,int f if (s_guild != 0 && t_guild != 0 && (s_guild == t_guild || guild->isallied(s_guild, t_guild))) state |= BCT_PARTY; else - state |= flag&BCT_ENEMY ? BCT_ENEMY : BCT_PARTY; + state |= flag & BCT_ENEMY ? BCT_ENEMY : BCT_PARTY; } else if (!(map->list[m].flag.pvp && map->list[m].flag.pvp_noparty) && (!map->list[m].flag.battleground || sbg_id == tbg_id)) { state |= BCT_PARTY; + } else if (!map->list[m].flag.cvc || s_clan == t_clan) { + state |= BCT_PARTY; } else { state |= BCT_ENEMY; } @@ -6802,18 +6808,22 @@ int battle_check_target( struct block_list *src, struct block_list *target,int f state |= BCT_ENEMY; } } - if( flag&(BCT_GUILD|BCT_ENEMY) ) { + if (flag & (BCT_GUILD | BCT_ENEMY)) { int s_guild = status->get_guild_id(s_bl); int t_guild = status->get_guild_id(t_bl); - if( !(map->list[m].flag.pvp && map->list[m].flag.pvp_noguild) + if (!(map->list[m].flag.pvp && map->list[m].flag.pvp_noguild) && s_guild && t_guild - && (s_guild == t_guild || (!(flag&BCT_SAMEGUILD) && guild->isallied(s_guild, t_guild))) - && (!map->list[m].flag.battleground || sbg_id == tbg_id) ) + && (s_guild == t_guild || (!(flag & BCT_SAMEGUILD) && guild->isallied(s_guild, t_guild))) + && (!map->list[m].flag.battleground || sbg_id == tbg_id) + && (!map->list[m].flag.cvc || s_clan == t_clan) + ) { state |= BCT_GUILD; - else + } else { state |= BCT_ENEMY; + } } - if( state&BCT_ENEMY && map->list[m].flag.battleground && sbg_id && sbg_id == tbg_id ) + + if (state & BCT_ENEMY && ((map->list[m].flag.battleground && sbg_id && sbg_id == tbg_id) || (map->list[m].flag.cvc && s_clan && s_clan == t_clan))) state &= ~BCT_ENEMY; if (state&BCT_ENEMY && battle_config.pk_mode && !map_flag_gvg(m) && s_bl->type == BL_PC && t_bl->type == BL_PC) { 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 <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> + +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; +} diff --git a/src/map/clan.h b/src/map/clan.h new file mode 100644 index 000000000..15ed52680 --- /dev/null +++ b/src/map/clan.h @@ -0,0 +1,85 @@ +/** + * 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 <http://www.gnu.org/licenses/>. + */ +#ifndef MAP_CLAN_H +#define MAP_CLAN_H + +#include "map/map.h" +#include "common/hercules.h" +#include "common/db.h" +#include "common/mmo.h" + +struct map_session_data; + +enum clan_req_state { + CLAN_REQ_NONE = 0, + CLAN_REQ_FIRST = 1, + CLAN_REQ_RELOAD = 2, + CLAN_REQ_AFTER_KICK = 3, +}; + +/** + * clan Interface + **/ +struct clan_interface { + struct DBMap *db; // int clan_id -> struct clan* + + int max; + int max_relations; + int kicktime; + int checktime; + int req_timeout; + + void (*init) (bool minimal); + void (*final) (void); + + bool (*config_read) (bool reload); + void (*config_read_additional_settings) (struct config_setting_t *settings, const char *source); + void (*read_db) (struct config_setting_t *settings, const char *source, bool reload); + int (*read_db_sub) (struct config_setting_t *settings, const char *source, bool reload); + void (*read_db_additional_fields) (struct clan *entry, struct config_setting_t *t, int n, const char *source); + void (*read_buffs) (struct clan *c, struct config_setting_t *buff, const char *source); + struct clan *(*search) (int clan_id); + struct clan *(*searchname) (const char *name); + struct map_session_data *(*getonlinesd) (struct clan *c); + int (*getindex) (const struct clan *c, int char_id); + bool (*join) (struct map_session_data *sd, int clan_id); + void (*member_online) (struct map_session_data *sd, bool first); + bool (*leave) (struct map_session_data *sd, bool first); + bool (*send_message) (struct map_session_data *sd, const char *mes); + void (*recv_message) (struct clan *c, const char *mes, int len); + void (*member_offline) (struct map_session_data *sd); + void (*set_constants) (void); + int (*get_id) (const struct block_list *bl); + void (*buff_start) (struct map_session_data *sd, struct clan *c); + void (*buff_end) (struct map_session_data *sd, struct clan *c); + void (*reload) (void); + int (*rejoin) (struct map_session_data *sd, va_list ap); + int (*inactivity_kick) (int tid, int64 tick, int id, intptr_t data); + int (*request_kickoffline) (int tid, int64 tick, int id, intptr_t data); + int (*request_membercount) (int tid, int64 tick, int id, intptr_t data); +}; + +#ifdef HERCULES_CORE +void clan_defaults (void); +#endif // HERCULES_CORE + +HPShared struct clan_interface *clan; + +#endif /* MAP_CLAN_H */ diff --git a/src/map/clif.c b/src/map/clif.c index 988e821e7..dc279f7ed 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -29,6 +29,7 @@ #include "map/channel.h" #include "map/chat.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/elemental.h" #include "map/guild.h" #include "map/homunculus.h" @@ -659,6 +660,22 @@ bool clif_send(const void* buf, int len, struct block_list* bl, enum send_target } break; + case CLAN: + if (sd && sd->status.clan_id) { + struct clan *c = clan->search(sd->status.clan_id); + + nullpo_retr(false, c); + + for (i = 0; i < VECTOR_LENGTH(c->members); i++) { + if ((sd = VECTOR_INDEX(c->members, i).sd) == NULL || (fd = sd->fd) <= 0) + continue; + WFIFOHEAD(fd, len); + memcpy(WFIFOP(fd, 0), buf, len); + WFIFOSET(fd, len); + } + } + break; + default: ShowError("clif_send: Unrecognized type %u\n", type); return false; @@ -1534,6 +1551,8 @@ bool clif_spawn(struct block_list *bl) clif->specialeffect(&nd->bl,423,AREA); else if (nd->size == SZ_MEDIUM) clif->specialeffect(&nd->bl,421,AREA); + if (nd->clan_id > 0) + clif->sc_load(&nd->bl, nd->bl.id, AREA, status->dbs->IconChangeTable[SC_CLAN_INFO], 0, nd->clan_id, 0); } break; case BL_PET: @@ -4388,6 +4407,8 @@ void clif_getareachar_unit(struct map_session_data* sd,struct block_list *bl) { clif->specialeffect_single(bl,423,sd->fd); else if (nd->size == SZ_MEDIUM) clif->specialeffect_single(bl,421,sd->fd); + if (nd->clan_id > 0) + clif->sc_load(&nd->bl, nd->bl.id, AREA, status->dbs->IconChangeTable[SC_CLAN_INFO], 0, nd->clan_id, 0); } break; case BL_MOB: @@ -13340,6 +13361,12 @@ bool clif_sub_guild_invite(int fd, struct map_session_data *sd, struct map_sessi return false; } + // Players in a clan can't join a guild + if (t_sd->clan != NULL) { + clif->message(fd, msg_fd(fd, 140)); // You can't join in a clan if you're in a guild. + return false; + } + guild->invite(sd,t_sd); return true; } @@ -19282,6 +19309,156 @@ const char *clif_get_bl_name(const struct block_list *bl) return name; } +/** + * Clan System: Sends the basic clan informations to client. + * 098a <length>.W <clan id>.L <clan name>.24B <clan master>.24B <clan map>.16B <alliance count>.B + * <antagonist count>.B { <alliance>.24B } * alliance count { <antagonist>.24B } * antagonist count (ZC_CLANINFO) + */ +void clif_clan_basicinfo(struct map_session_data *sd) +{ +#if PACKETVER >= 20120716 + int len, i, fd; + struct clan *c, *ally, *antagonist; + struct PACKET_ZC_CLANINFO *packet = NULL; + + nullpo_retv(sd); + nullpo_retv(c = sd->clan); + + len = sizeof(struct PACKET_ZC_CLANINFO); + fd = sd->fd; + + WFIFOHEAD(fd, len); + packet = WFIFOP(fd, 0); + + packet->PacketType = clanBasicInfo; + packet->ClanID = c->clan_id; + + safestrncpy(packet->ClanName, c->name, NAME_LENGTH); + safestrncpy(packet->MasterName, c->master, NAME_LENGTH); + + mapindex->getmapname_ext(c->map, packet->Map); + + packet->AllyCount = VECTOR_LENGTH(c->allies); + packet->AntagonistCount = VECTOR_LENGTH(c->antagonists); + + // All allies and antagonists are assumed as valid entries + // since it only gets inside the vector after the validation + // on clan->config_read + for (i = 0; i < VECTOR_LENGTH(c->allies); i++) { + struct clan_relationship *al = &VECTOR_INDEX(c->allies, i); + + if ((ally = clan->search(al->clan_id)) != NULL) { + safestrncpy(WFIFOP(fd, len), ally->name, NAME_LENGTH); + len += NAME_LENGTH; + } + } + + for (i = 0; i < VECTOR_LENGTH(c->antagonists); i++) { + struct clan_relationship *an = &VECTOR_INDEX(c->antagonists, i); + + if ((antagonist = clan->search(an->clan_id)) != NULL) { + safestrncpy(WFIFOP(fd, len), antagonist->name, NAME_LENGTH); + len += NAME_LENGTH; + } + } + + packet->PacketLength = len; + WFIFOSET(fd, len); +#endif +} + +/** + * Clan System: Updates the online and maximum player count of a clan. + * 0988 <online count>.W <maximum member amount>.W (ZC_NOTIFY_CLAN_CONNECTINFO) + */ +void clif_clan_onlinecount(struct clan *c) +{ +#if PACKETVER >= 20120716 + struct map_session_data *sd; + struct PACKET_ZC_NOTIFY_CLAN_CONNECTINFO p; + + nullpo_retv(c); + + p.PacketType = clanOnlineCount; + p.NumConnect = c->connect_member; + p.NumTotal = c->max_member; + + if ((sd = clan->getonlinesd(c)) != NULL) { + clif->send(&p, sizeof(p), &sd->bl, CLAN); + } +#endif +} + +/** +* Clan System: Notifies the client that the player has left his clan. +* 0989 (ZC_ACK_CLAN_LEAVE) +**/ +void clif_clan_leave(struct map_session_data* sd) +{ +#if PACKETVER >= 20131223 + struct PACKET_ZC_ACK_CLAN_LEAVE p; + + nullpo_retv(sd); + + p.PacketType = clanLeave; + + clif->send(&p, sizeof(p), &sd->bl, SELF); +#endif +} + +/** + * Clan System: Sends a clan message to a player + * 098e <length>.W <name>.24B <message>.?B (ZC_NOTIFY_CLAN_CHAT) + */ +void clif_clan_message(struct clan *c, const char *mes, int len) +{ +#if PACKETVER >= 20120716 + struct map_session_data *sd; + struct PACKET_ZC_NOTIFY_CLAN_CHAT *p; + unsigned int max_len = CHAT_SIZE_MAX - 5 - NAME_LENGTH; + int packet_length; + + nullpo_retv(c); + nullpo_retv(mes); + + if (len == 0) { + return; + } else if (len > max_len) { + ShowWarning("clif_clan_message: Truncated message '%s' (len=%d, max=%u, clan_id=%d).\n", mes, len, max_len, c->clan_id); + len = max_len; + } + + packet_length = sizeof(*p) + len + 1; + p = (struct PACKET_ZC_NOTIFY_CLAN_CHAT *)aMalloc(packet_length); + p->PacketType = clanMessage; + p->PacketLength = packet_length; + // p->MemberName is being ignored on the client side. + safestrncpy(p->Message, mes, len + 1); + + if ((sd = clan->getonlinesd(c)) != NULL) + clif->send(p, packet_length, &sd->bl, CLAN); + aFree(p); +#endif +} + +void clif_parse_ClanMessage(int fd, struct map_session_data *sd) __attribute__((nonnull (2))); +/** + * Clan System: Parses a clan message from a player. + * 098d <length>.W <text>.?B (<name> : <message>) (CZ_CLAN_CHAT) + */ +void clif_parse_ClanMessage(int fd, struct map_session_data *sd) +{ +#if PACKETVER >= 20120716 + const struct packet_chat_message *packet = RP2PTR(fd); + char message[CHAT_SIZE_MAX + NAME_LENGTH + 3 + 1]; + + if (clif->process_chat_message(sd, packet, message, sizeof(message)) == NULL) + return; + + clan->send_message(sd, message); +#endif +} + /* */ unsigned short clif_decrypt_cmd( int cmd, struct map_session_data *sd ) { if( sd ) { @@ -21006,4 +21183,10 @@ void clif_defaults(void) { clif->rodex_icon = clif_rodex_icon; clif->rodex_send_mails_all = clif_rodex_send_mails_all; clif->skill_scale = clif_skill_scale; + // -- Clan system + clif->clan_basicinfo = clif_clan_basicinfo; + clif->clan_onlinecount = clif_clan_onlinecount; + clif->clan_leave = clif_clan_leave; + clif->clan_message = clif_clan_message; + clif->pClanMessage = clif_parse_ClanMessage; } diff --git a/src/map/clif.h b/src/map/clif.h index 69567cc2c..c393c4f64 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -110,6 +110,8 @@ typedef enum send_target { BG_AREA_WOS, BG_QUEUE, + + CLAN, // Clan System } send_target; typedef enum broadcast_flags { @@ -1405,6 +1407,12 @@ struct clif_interface { void (*rodex_request_items) (struct map_session_data *sd, int8 opentype, int64 mail_id, int8 result); void (*rodex_icon) (int fd, bool show); void (*skill_scale) (struct block_list *bl, int src_id, int x, int y, uint16 skill_id, uint16 skill_lv, int casttime); + /* Clan System */ + void (*clan_basicinfo) (struct map_session_data *sd); + void (*clan_onlinecount) (struct clan *c); + void (*clan_leave) (struct map_session_data *sd); + void (*clan_message) (struct clan *c, const char *mes, int len); + void (*pClanMessage) (int fd, struct map_session_data* sd); }; #ifdef HERCULES_CORE diff --git a/src/map/guild.c b/src/map/guild.c index bb0484477..11609ec81 100644 --- a/src/map/guild.c +++ b/src/map/guild.c @@ -365,6 +365,11 @@ int guild_create(struct map_session_data *sd, const char *name) nullpo_ret(sd); nullpo_ret(name); + if (sd->clan != NULL) { + clif->messagecolor_self(sd->fd, COLOR_RED, "You cannot create a guild because you are in a clan."); + return 0; + } + safestrncpy(tname, name, NAME_LENGTH); trim(tname); diff --git a/src/map/intif.c b/src/map/intif.c index be6b75d96..c933ceb15 100644 --- a/src/map/intif.c +++ b/src/map/intif.c @@ -26,6 +26,7 @@ #include "map/atcommand.h" #include "map/battle.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/clif.h" #include "map/elemental.h" #include "map/guild.h" @@ -744,6 +745,81 @@ int intif_party_leaderchange(int party_id,int account_id,int char_id) return 0; } +//========================= +// Clan System +//------------------------- + +/** + * Request clan member count + * + * @param clan_id Id of the clan to have members counted + * @param kick_interval Interval of the inactivity kick + */ +int intif_clan_membercount(int clan_id, int kick_interval) +{ + if (intif->CheckForCharServer() || clan_id == 0 || kick_interval <= 0) + return 0; + + WFIFOHEAD(inter_fd, 10); + WFIFOW(inter_fd, 0) = 0x3044; + WFIFOL(inter_fd, 2) = clan_id; + WFIFOL(inter_fd, 6) = kick_interval; + WFIFOSET(inter_fd, 10); + return 1; +} + +int intif_clan_kickoffline(int clan_id, int kick_interval) +{ + if (intif->CheckForCharServer() || clan_id == 0 || kick_interval <= 0) + return 0; + + WFIFOHEAD(inter_fd, 10); + WFIFOW(inter_fd, 0) = 0x3045; + WFIFOL(inter_fd, 2) = clan_id; + WFIFOL(inter_fd, 6) = kick_interval; + WFIFOSET(inter_fd, 10); + return 1; +} + +void intif_parse_RecvClanMemberAction(int fd) +{ + struct clan *c; + int clan_id = RFIFOL(fd, 2); + int count = RFIFOL(fd, 6); + + if ((c = clan->search(clan_id)) == NULL) { + ShowError("intif_parse_RecvClanMemberAction: Received invalid clan_id '%d'\n", clan_id); + return; + } + + if (count < 0) { + ShowError("intif_parse_RecvClanMemberAction: Received invalid member count value '%d'\n", count); + return; + } + + c->received = true; + if (c->req_count_tid != INVALID_TIMER) { + timer->delete(c->req_count_tid, clan->request_membercount); + c->req_count_tid = INVALID_TIMER; + } + + c->member_count = count; + switch (c->req_state) { + case CLAN_REQ_AFTER_KICK: + if (c->req_kick_tid != INVALID_TIMER) { + timer->delete(c->req_kick_tid, clan->request_kickoffline); + c->req_kick_tid = INVALID_TIMER; + } + break; + case CLAN_REQ_RELOAD: + map->foreachpc(clan->rejoin); + break; + default: + break; + } + c->req_state = CLAN_REQ_NONE; +} + // Request a Guild creation int intif_guild_create(const char *name,const struct guild_member *master) { @@ -2744,6 +2820,9 @@ int intif_parse(int fd) case 0x3896: intif->pRodexHasNew(fd); break; case 0x3897: intif->pRodexSendMail(fd); break; case 0x3898: intif->pRodexCheckName(fd); break; + + // Clan System + case 0x3858: intif->pRecvClanMemberAction(fd); break; default: ShowError("intif_parse : unknown packet %d %x\n",fd,RFIFOW(fd,0)); return 0; @@ -2765,7 +2844,7 @@ void intif_defaults(void) { 39,-1,15,15, 14,19, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, //0x3820 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, //0x3830 -1, 0, 0,14, 0, 0, 0, 0, -1,74,-1,11, 11,-1, 0, 0, //0x3840 - -1,-1, 7, 7, 7,11, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3850 Auctions [Zephyrus] itembound[Akinari] + -1,-1, 7, 7, 7,11, 8, 0, 10, 0, 0, 0, 0, 0, 0, 0, //0x3850 Auctions [Zephyrus] itembound[Akinari] Clan System[Murilo BiO] -1, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3860 Quests [Kevin] [Inkfish] -1, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 3, 3, 0, //0x3870 Mercenaries [Zephyrus] / Elemental [pakpil] 12,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //0x3880 @@ -2857,6 +2936,9 @@ void intif_defaults(void) { intif->rodex_updatemail = intif_rodex_updatemail; intif->rodex_sendmail = intif_rodex_sendmail; intif->rodex_checkname = intif_rodex_checkname; + /* Clan System */ + intif->clan_kickoffline = intif_clan_kickoffline; + intif->clan_membercount = intif_clan_membercount; /* @accinfo */ intif->request_accinfo = intif_request_accinfo; /* */ @@ -2933,4 +3015,6 @@ void intif_defaults(void) { intif->pRodexHasNew = intif_parse_RodexNotifications; intif->pRodexSendMail = intif_parse_RodexSendMail; intif->pRodexCheckName = intif_parse_RodexCheckName; + /* Clan System */ + intif->pRecvClanMemberAction = intif_parse_RecvClanMemberAction; } diff --git a/src/map/intif.h b/src/map/intif.h index 4bca5f167..be8ff4181 100644 --- a/src/map/intif.h +++ b/src/map/intif.h @@ -138,6 +138,9 @@ struct intif_interface { int(*rodex_updatemail) (int64 mail_id, int8 flag); int(*rodex_sendmail) (struct rodex_message *msg); int(*rodex_checkname) (struct map_session_data *sd, const char *name); + /* Clan System */ + int (*clan_kickoffline) (int clan_id, int kick_interval); + int (*clan_membercount) (int clan_id, int kick_interval); /* @accinfo */ void (*request_accinfo) (int u_fd, int aid, int group_lv, char* query); /* */ @@ -212,6 +215,8 @@ struct intif_interface { void(*pRodexHasNew) (int fd); void(*pRodexSendMail) (int fd); void(*pRodexCheckName) (int fd); + /* Clan System */ + void (*pRecvClanMemberAction) (int fd); }; #ifdef HERCULES_CORE diff --git a/src/map/log.c b/src/map/log.c index 6419c4766..c3fec077e 100644 --- a/src/map/log.c +++ b/src/map/log.c @@ -82,13 +82,21 @@ char log_picktype2char(e_log_pick_type type) { } /// obtain log type character for chat logs -char log_chattype2char(e_log_chat_type type) { - switch( type ) { - case LOG_CHAT_GLOBAL: return 'O'; // Gl(O)bal - case LOG_CHAT_WHISPER: return 'W'; // (W)hisper - case LOG_CHAT_PARTY: return 'P'; // (P)arty - case LOG_CHAT_GUILD: return 'G'; // (G)uild - case LOG_CHAT_MAINCHAT: return 'M'; // (M)ain chat +char log_chattype2char(e_log_chat_type type) +{ + switch (type) { + case LOG_CHAT_GLOBAL: + return 'O'; // Gl(O)bal + case LOG_CHAT_WHISPER: + return 'W'; // (W)hisper + case LOG_CHAT_PARTY: + return 'P'; // (P)arty + case LOG_CHAT_GUILD: + return 'G'; // (G)uild + case LOG_CHAT_MAINCHAT: + return 'M'; // (M)ain chat + case LOG_CHAT_CLAN: + return 'C'; // (C)lan } // should not get here, fallback diff --git a/src/map/log.h b/src/map/log.h index 7ff36d126..e7eb72713 100644 --- a/src/map/log.h +++ b/src/map/log.h @@ -52,6 +52,7 @@ typedef enum e_log_chat_type { LOG_CHAT_PARTY = 0x04, LOG_CHAT_GUILD = 0x08, LOG_CHAT_MAINCHAT = 0x10, + LOG_CHAT_CLAN = 0x20, // all LOG_CHAT_ALL = 0xFF, } e_log_chat_type; diff --git a/src/map/map.c b/src/map/map.c index 306f3a99d..106224a47 100644 --- a/src/map/map.c +++ b/src/map/map.c @@ -30,6 +30,7 @@ #include "map/channel.h" #include "map/chat.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/clif.h" #include "map/duel.h" #include "map/elemental.h" @@ -1904,6 +1905,9 @@ int map_quit(struct map_session_data *sd) { if( sd->bg_id && !sd->bg_queue.arena ) /* TODO: dump this chunk after bg_queue is fully enabled */ bg->team_leave(sd,BGTL_QUIT); + if (sd->status.clan_id) + clan->member_offline(sd); + if (sd->state.autotrade && core->runflag != MAPSERVER_ST_SHUTDOWN && !channel->config->closing) pc->autotrade_update(sd,PAUC_REMOVE); @@ -4815,6 +4819,15 @@ bool map_zone_mf_cache(int m, char *flag, char *params) { else if( map->list[m].flag.battleground ) map_zone_mf_cache_add(m,"battleground"); } + } else if (!strcmpi(flag,"cvc")) { + if (state && map->list[m].flag.cvc) { + ;/* nothing to do */ + } else { + if (state) + map_zone_mf_cache_add(m,"cvc\toff"); + else if (map->list[m].flag.cvc) + map_zone_mf_cache_add(m,"cvc"); + } } else if (!strcmpi(flag,"noexppenalty")) { if( state && map->list[m].flag.noexppenalty ) ;/* nothing to do */ @@ -5839,6 +5852,8 @@ void read_map_zone_db(void) { zone->merge_type = MZMT_MERGEABLE; if( (zone = strdb_get(map->zone_db, MAP_ZONE_BG_NAME)) ) zone->merge_type = MZMT_MERGEABLE; + if ((zone = strdb_get(map->zone_db, MAP_ZONE_CVC_NAME))) + zone->merge_type = MZMT_MERGEABLE; } /* not supposed to go in here but in skill_final whatever */ libconfig->destroy(&map_zone_db); @@ -5999,6 +6014,7 @@ int do_final(void) { ircbot->final();/* before channel. */ channel->final(); chrif->final(); + clan->final(); clif->final(); npc->final(); quest->final(); @@ -6185,6 +6201,7 @@ void map_load_defaults(void) { battleground_defaults(); buyingstore_defaults(); channel_defaults(); + clan_defaults(); clif_defaults(); chrif_defaults(); guild_defaults(); @@ -6517,6 +6534,7 @@ int do_init(int argc, char *argv[]) ircbot->init(minimal); script->init(minimal); itemdb->init(minimal); + clan->init(minimal); skill->init(minimal); if (!minimal) map->read_zone_db();/* read after item and skill initialization */ diff --git a/src/map/map.h b/src/map/map.h index fab8839d8..facf1d921 100644 --- a/src/map/map.h +++ b/src/map/map.h @@ -352,6 +352,7 @@ STATIC_ASSERT(((MAPID_1_1_MAX - 1) | MAPID_BASEMASK) == MAPID_BASEMASK, "First c || map->list[m].flag.gvg \ || ((map->agit_flag || map->agit2_flag) && map->list[m].flag.gvg_castle) \ || map->list[m].flag.battleground \ + || map->list[m].flag.cvc \ ) // Specifies maps that have special GvG/WoE restrictions #define map_flag_gvg(m) (map->list[m].flag.gvg || ((map->agit_flag || map->agit2_flag) && map->list[m].flag.gvg_castle)) @@ -737,6 +738,7 @@ enum map_zone_merge_type { #define MAP_ZONE_PVP_NAME "PvP" #define MAP_ZONE_GVG_NAME "GvG" #define MAP_ZONE_BG_NAME "Battlegrounds" +#define MAP_ZONE_CVC_NAME "CvC" #define MAP_ZONE_PK_NAME "PK Mode" #define MAP_ZONE_MAPFLAG_LENGTH 50 @@ -823,6 +825,7 @@ struct map_data { unsigned gvg_dungeon : 1; // Celest unsigned gvg_noparty : 1; unsigned battleground : 2; // [BattleGround System] + unsigned cvc : 1; unsigned nozenypenalty : 1; unsigned notrade : 1; unsigned noskill : 1; diff --git a/src/map/mob.h b/src/map/mob.h index 98d64873b..3d1b3aadf 100644 --- a/src/map/mob.h +++ b/src/map/mob.h @@ -208,6 +208,7 @@ struct mob_data { int target_id,attacked_id; int areanpc_id; //Required in OnTouchNPC (to avoid multiple area touchs) unsigned int bg_id; // BattleGround System + int clan_id; // Clan System int64 next_walktime, last_thinktime, last_linktime, last_pcneartime, dmgtick; short move_fail_count; diff --git a/src/map/npc.c b/src/map/npc.c index d3dfb39d2..94ab57bd9 100644 --- a/src/map/npc.c +++ b/src/map/npc.c @@ -25,6 +25,7 @@ #include "map/battle.h" #include "map/chat.h" +#include "map/clan.h" #include "map/clif.h" #include "map/guild.h" #include "map/instance.h" @@ -4144,6 +4145,11 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co ShowWarning("npc_parse_mapflag: You can't set PvP and GvG flags for the same map! Removing GvG flags from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); if (retval) *retval = EXIT_FAILURE; } + if (state && map->list[m].flag.cvc) { + map->list[m].flag.cvc = 0; + ShowWarning("npc_parse_mapflag: You can't set CvC and PvP flags for the same map! Removing CvC flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) *retval = EXIT_FAILURE; + } if( state && map->list[m].flag.battleground ) { map->list[m].flag.battleground = 0; ShowWarning("npc_parse_mapflag: You can't set PvP and BattleGround flags for the same map! Removing BattleGround flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); @@ -4196,6 +4202,11 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co ShowWarning("npc_parse_mapflag: You can't set PvP and GvG flags for the same map! Removing PvP flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); if (retval) *retval = EXIT_FAILURE; } + if (state && map->list[m].flag.cvc) { + map->list[m].flag.cvc = 0; + ShowWarning("npc_parse_mapflag: You can't set CvC and GvG flags for the same map! Removing CvC flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) *retval = EXIT_FAILURE; + } if( state && map->list[m].flag.battleground ) { map->list[m].flag.battleground = 0; ShowWarning("npc_parse_mapflag: You can't set GvG and BattleGround flags for the same map! Removing BattleGround flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); @@ -4238,11 +4249,47 @@ const char *npc_parse_mapflag(const char *w1, const char *w2, const char *w3, co ShowWarning("npc_parse_mapflag: You can't set GvG and BattleGround flags for the same map! Removing GvG flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer,start-buffer)); if (retval) *retval = EXIT_FAILURE; } + if (map->list[m].flag.cvc) { + map->list[m].flag.cvc = 0; + ShowWarning("npc_parse_mapflag: You can't set CvC and BattleGround flags for the same map! Removing CvC flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) *retval = EXIT_FAILURE; + } if( state && (zone = strdb_get(map->zone_db, MAP_ZONE_BG_NAME)) != NULL && map->list[m].zone != zone ) { map->zone_change(m,zone,start,buffer,filepath); } } + else if (!strcmpi(w3, "cvc")) { + struct map_zone_data *zone; + + map->list[m].flag.cvc = state; + if (state && (map->list[m].flag.gvg || map->list[m].flag.gvg_dungeon || map->list[m].flag.gvg_castle)) { + map->list[m].flag.gvg = 0; + map->list[m].flag.gvg_dungeon = 0; + map->list[m].flag.gvg_castle = 0; + ShowWarning("npc_parse_mapflag: You can't set GvG and CvC flags for the same map! Removing GvG flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) { + *retval = EXIT_FAILURE; + } + } + if (state && map->list[m].flag.pvp) { + map->list[m].flag.pvp = 0; + ShowWarning("npc_parse_mapflag: You can't set PvP and CvC flags for the same map! Removing PvP flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) { + *retval = EXIT_FAILURE; + } + } + if (state && map->list[m].flag.battleground) { + map->list[m].flag.battleground = 0; + ShowWarning("npc_parse_mapflag: You can't set CvC and BattleGround flags for the same map! Removing BattleGround flag from %s in file '%s', line '%d'.\n", map->list[m].name, filepath, strline(buffer, start-buffer)); + if (retval) { + *retval = EXIT_FAILURE; + } + } + if (state && (zone = strdb_get(map->zone_db, MAP_ZONE_CVC_NAME)) != NULL && map->list[m].zone != zone) { + map->zone_change(m, zone, start, buffer, filepath); + } + } else if (!strcmpi(w3,"noexppenalty")) map->list[m].flag.noexppenalty=state; else if (!strcmpi(w3,"nozenypenalty")) @@ -4887,6 +4934,7 @@ int npc_reload(void) { // Reprocess npc files and reload constants itemdb->name_constants(); + clan->set_constants(); npc_process_files( npc_new_min ); instance->reload(); @@ -5026,6 +5074,7 @@ int do_init_npc(bool minimal) { // Should be loaded before npc processing, otherwise labels could overwrite constant values // and lead to undefined behavior [Panikon] itemdb->name_constants(); + clan->set_constants(); if (!minimal) { npc->timer_event_ers = ers_new(sizeof(struct timer_event_data),"clif.c::timer_event_ers",ERS_OPT_NONE); diff --git a/src/map/npc.h b/src/map/npc.h index 8bb38f252..64a2b3a51 100644 --- a/src/map/npc.h +++ b/src/map/npc.h @@ -83,6 +83,8 @@ struct npc_data { uint8 dir; uint8 area_size; + int clan_id; + unsigned size : 2; struct status_data status; diff --git a/src/map/packets.h b/src/map/packets.h index 451acce47..b90413793 100644 --- a/src/map/packets.h +++ b/src/map/packets.h @@ -2179,6 +2179,12 @@ packet(0x96e,-1,clif->ackmergeitems); packet(0x0960,5,clif->pChangeDir,2,4); // CZ_CHANGE_DIRECTION #endif +//2012-07-02 +#if PACKETVER >= 20120702 +// new packets + packet(0x098a, -1); // ZC_CLANINFO +#endif + //2012-07-10 #if PACKETVER >= 20120710 packet(0x0886,2,clif->pReqCloseBuyingStore,0); // CZ_REQ_CLOSE_BUYING_STORE @@ -2218,6 +2224,15 @@ packet(0x96e,-1,clif->ackmergeitems); packet(0x0436,4,clif->pDull); // CZ_GANGSI_RANK #endif +//2012-07-16aRagExe +#if PACKETVER >= 20120716 +// new packets + packet(0x0988, 6); // ZC_NOTIFY_CLAN_CONNECTINFO + packet(0x0989, 2); // ZC_ACK_CLAN_LEAVE + packet(0x098d, -1, clif->pClanMessage, 2, 4); // CZ_CLAN_CHAT + packet(0x098e, -1); // ZC_NOTIFY_CLAN_CHAT +#endif + // 2012-09-25aRagexe #if PACKETVER >= 20120925 // new packets (not all) diff --git a/src/map/packets_struct.h b/src/map/packets_struct.h index 1105bec96..2a65eb6cf 100644 --- a/src/map/packets_struct.h +++ b/src/map/packets_struct.h @@ -349,6 +349,14 @@ enum packet_headers { partymemberinfo = 0x01e9, partyinfo = 0x00fb, #endif +#if PACKETVER >= 20120702 + clanBasicInfo = 0x098A, ///< ZC_CLANINFO +#endif +#if PACKETVER >= 20120716 + clanOnlineCount = 0x0988, ///< ZC_NOTIFY_CLAN_CONNECTINFO + clanLeave = 0x0989, ///< ZC_ACK_CLAN_LEAVE + clanMessage = 0x098E, ///< ZC_NOTIFY_CLAN_CHAT +#endif }; #if !defined(sun) && (!defined(__NETBSD__) || __NetBSD_Version__ >= 600000000) // NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute @@ -1562,6 +1570,34 @@ struct PACKET_ZC_GROUP_LIST { struct PACKET_ZC_GROUP_LIST_SUB members[]; } __attribute__((packed)); +struct PACKET_ZC_CLANINFO { + int16 PacketType; + int16 PacketLength; + uint32 ClanID; + char ClanName[NAME_LENGTH]; + char MasterName[NAME_LENGTH]; + char Map[MAP_NAME_LENGTH_EXT]; + uint8 AllyCount; + uint8 AntagonistCount; +} __attribute__((packed)); + +struct PACKET_ZC_NOTIFY_CLAN_CONNECTINFO { + int16 PacketType; + int16 NumConnect; + int16 NumTotal; +} __attribute__((packed)); + +struct PACKET_ZC_ACK_CLAN_LEAVE { + int16 PacketType; +} __attribute__((packed)); + +struct PACKET_ZC_NOTIFY_CLAN_CHAT { + int16 PacketType; + int16 PacketLength; + char MemberName[NAME_LENGTH]; + char Message[]; +} __attribute__((packed)); + #if !defined(sun) && (!defined(__NETBSD__) || __NetBSD_Version__ >= 600000000) // NetBSD 5 and Solaris don't like pragma pack but accept the packed attribute #pragma pack(pop) #endif // not NetBSD < 6 / Solaris diff --git a/src/map/pc.c b/src/map/pc.c index 449cb25d3..c18fdee9e 100644 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -29,6 +29,7 @@ #include "map/channel.h" #include "map/chat.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/clif.h" #include "map/date.h" // is_day_of_*() #include "map/duel.h" @@ -1523,6 +1524,12 @@ int pc_reg_received(struct map_session_data *sd) status_calc_pc(sd,SCO_FIRST|SCO_FORCE); chrif->scdata_request(sd->status.account_id, sd->status.char_id); + + if (sd->status.clan_id) + clan->member_online(sd, true); + + //Auth is fully okay, update last_login + sd->status.last_login = time(NULL); // Storage Request intif->request_account_storage(sd); diff --git a/src/map/pc.h b/src/map/pc.h index df0df979d..a01152df5 100644 --- a/src/map/pc.h +++ b/src/map/pc.h @@ -443,6 +443,7 @@ END_ZEROED_BLOCK; int party_invite, party_invite_account; // for handling party invitation (holds party id and account id) int adopt_invite; // Adoption struct guild *guild;/* [Ind/Hercules] speed everything up */ + struct clan *clan; int guild_invite,guild_invite_account; int guild_emblem_id,guild_alliance,guild_alliance_account; short guild_x,guild_y; // For guildmate position display. [Skotlex] should be short [zzo] diff --git a/src/map/script.c b/src/map/script.c index fc304d4ff..36f37abb9 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -29,6 +29,7 @@ #include "map/channel.h" #include "map/chat.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/clif.h" #include "map/date.h" #include "map/elemental.h" @@ -5593,6 +5594,8 @@ int script_reload(void) itemdb->name_constants(); + clan->set_constants(); + sysinfo->vcsrevision_reload(); return 0; @@ -8627,32 +8630,45 @@ BUILDIN(readparam) { * 2 : guild_id * 3 : account_id * 4 : bg_id + * 5 : clan_id *------------------------------------------*/ BUILDIN(getcharid) { - int num; + int num = script_getnum(st, 2); struct map_session_data *sd; - - num = script_getnum(st,2); - if( script_hasdata(st,3) ) - sd=map->nick2sd(script_getstr(st,3)); + + if (script_hasdata(st, 3)) + sd = map->nick2sd(script_getstr(st, 3)); else - sd=script->rid2sd(st); + sd = script->rid2sd(st); - if(sd==NULL) { - script_pushint(st,0); //return 0, according docs + if (sd == NULL) { + script_pushint(st, 0); //return 0, according docs return true; } - switch( num ) { - case 0: script_pushint(st,sd->status.char_id); break; - case 1: script_pushint(st,sd->status.party_id); break; - case 2: script_pushint(st,sd->status.guild_id); break; - case 3: script_pushint(st,sd->status.account_id); break; - case 4: script_pushint(st,sd->bg_id); break; - default: - ShowError("buildin_getcharid: invalid parameter (%d).\n", num); - script_pushint(st,0); - break; + switch (num) { + case 0: + script_pushint(st, sd->status.char_id); + break; + case 1: + script_pushint(st, sd->status.party_id); + break; + case 2: + script_pushint(st, sd->status.guild_id); + break; + case 3: + script_pushint(st, sd->status.account_id); + break; + case 4: + script_pushint(st, sd->bg_id); + break; + case 5: + script_pushint(st, sd->status.clan_id); + break; + default: + ShowError("buildin_getcharid: invalid parameter (%d).\n", num); + script_pushint(st, 0); + break; } return true; @@ -8900,10 +8916,12 @@ BUILDIN(getguildmember) * 1 : party_name or "" * 2 : guild_name or "" * 3 : map_name + * 4 : clan_name or "" * - : "" *------------------------------------------*/ BUILDIN(strcharinfo) { + struct clan *c; struct guild* g; struct party_data* p; struct map_session_data *sd; @@ -8943,6 +8961,13 @@ BUILDIN(strcharinfo) case 3: script_pushconststr(st, map->list[sd->bl.m].name); break; + case 4: + if ((c = sd->clan) != NULL) { + script_pushstrcopy(st, c->name); + } else { + script_pushconststr(st, ""); + } + break; default: ShowWarning("script:strcharinfo: unknown parameter.\n"); script_pushconststr(st, ""); @@ -23861,6 +23886,85 @@ BUILDIN(rodex_sendmail2) } /** + * Clan System: Add a player to a clan + */ +BUILDIN(clan_join) +{ + struct map_session_data *sd = NULL; + int clan_id = script_getnum(st, 2); + + if (script_hasdata(st, 3)) + sd = map->id2sd(script_getnum(st, 3)); + else + sd = map->id2sd(st->rid); + + if (sd == NULL) { + script_pushint(st, false); + return false; + } + + if (clan->join(sd, clan_id)) + script_pushint(st, true); + else + script_pushint(st, false); + + return true; +} + +/** + * Clan System: Remove a player from clan + */ +BUILDIN(clan_leave) +{ + struct map_session_data *sd = NULL; + + if (script_hasdata(st, 2)) + sd = map->id2sd(script_getnum(st, 2)); + else + sd = map->id2sd(st->rid); + + if (sd == NULL) { + script_pushint(st, false); + return false; + } + + if (clan->leave(sd, false)) + script_pushint(st, true); + else + script_pushint(st, false); + + return true; +} + +/** + * Clan System: Show clan emblem next to npc name + */ +BUILDIN(clan_master) +{ + struct npc_data *nd = map->id2nd(st->oid); + int clan_id = script_getnum(st, 2); + + if (nd == NULL) { + script_pushint(st, false); + return false; + } else if (clan_id <= 0) { + script_pushint(st, false); + ShowError("buildin_clan_master: Received Invalid Clan ID %d\n", clan_id); + return false; + } else if (clan->search(clan_id) == NULL) { + script_pushint(st, false); + ShowError("buildin_clan_master: Received Id of a nonexistent Clan. Id: %d\n", clan_id); + return false; + } + + nd->clan_id = clan_id; + clif->sc_load(&nd->bl, nd->bl.id, AREA, status->dbs->IconChangeTable[SC_CLAN_INFO], 0, clan_id, 0); + + script_pushint(st, true); + return true; +} + +/** * Adds a built-in script function. * * @param buildin Script function data @@ -24556,6 +24660,11 @@ void script_parse_builtin(void) { /* Navigation */ BUILDIN_DEF(navigateto, "s??????"), + /* Clan System */ + BUILDIN_DEF(clan_join,"i?"), + BUILDIN_DEF(clan_leave,"?"), + BUILDIN_DEF(clan_master,"i"), + BUILDIN_DEF(channelmes, "ss"), BUILDIN_DEF(addchannelhandler, "ss"), BUILDIN_DEF(removechannelhandler, "ss"), diff --git a/src/map/skill.c b/src/map/skill.c index 4c9e83579..3d541bf76 100644 --- a/src/map/skill.c +++ b/src/map/skill.c @@ -26,6 +26,7 @@ #include "map/battle.h" #include "map/battleground.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/clif.h" #include "map/date.h" #include "map/elemental.h" @@ -17180,6 +17181,7 @@ struct skill_unit_group* skill_initunitgroup (struct block_list* src, int count, group->party_id = status->get_party_id(src); group->guild_id = status->get_guild_id(src); group->bg_id = bg->team_get_id(src); + group->clan_id = clan->get_id(src); group->group_id = skill->get_new_group_id(); CREATE(group->unit.data, struct skill_unit, count); group->unit.count = count; diff --git a/src/map/skill.h b/src/map/skill.h index c494c0e83..bd4d82df2 100644 --- a/src/map/skill.h +++ b/src/map/skill.h @@ -1774,6 +1774,7 @@ struct skill_unit_group { int party_id; int guild_id; int bg_id; + int clan_id; int map; int target_flag; //Holds BCT_* flag for battle_check_target int bl_flag; //Holds BL_* flag for map_foreachin* functions diff --git a/src/map/status.c b/src/map/status.c index 9e578bc12..3bb511970 100644 --- a/src/map/status.c +++ b/src/map/status.c @@ -25,6 +25,7 @@ #include "map/battle.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/clif.h" #include "map/elemental.h" #include "map/guild.h" @@ -1019,6 +1020,9 @@ void initChangeTables(void) // Summoner status->dbs->IconChangeTable[SC_SPRITEMABLE] = SI_SPRITEMABLE; + // Clan System + status->dbs->IconChangeTable[SC_CLAN_INFO] = SI_CLAN_INFO; + // RoDEX status->dbs->IconChangeTable[SC_DAILYSENDMAILCNT] = SI_DAILYSENDMAILCNT; @@ -1181,6 +1185,9 @@ void initChangeTables(void) status->dbs->ChangeFlagTable[SC_MVPCARD_ORCHERO] |= SCB_ALL; status->dbs->ChangeFlagTable[SC_MVPCARD_ORCLORD] |= SCB_ALL; + // Clan System + status->dbs->ChangeFlagTable[SC_CLAN_INFO] |= SCB_NONE; + // Costumes status->dbs->ChangeFlagTable[SC_DRESS_UP] |= SCB_NONE; status->dbs->ChangeFlagTable[SC_MOONSTAR] |= SCB_NONE; @@ -2685,6 +2692,12 @@ int status_calc_pc_(struct map_session_data* sd, enum e_status_calc_opt opt) status->current_equip_option_index = -1; status->current_equip_item_index = -1; + // Clan Buffs + if (sd->status.clan_id > 0) { + struct clan *c = clan->search(sd->status.clan_id); + clan->buff_start(sd, c); + } + status->calc_pc_additional(sd, opt); if( sd->pd ) { // Pet Bonus @@ -9726,6 +9739,11 @@ void status_change_start_display(struct map_session_data *sd, enum sc_type type, case SC_ALL_RIDING: dval1 = 1; break; + case SC_CLAN_INFO: + dval1 = val1; + dval2 = val2; + dval3 = val3; + break; default: /* all others: just copy val1 */ dval1 = val1; break; @@ -9745,6 +9763,9 @@ int status_get_val_flag(enum sc_type type) { int val_flag = 0; switch (type) { + case SC_CLAN_INFO: + val_flag |= 1 | 2; + break; case SC_FIGHTINGSPIRIT: val_flag |= 1 | 2; break; diff --git a/src/map/status.h b/src/map/status.h index e9c2218e8..66e773720 100644 --- a/src/map/status.h +++ b/src/map/status.h @@ -845,6 +845,9 @@ typedef enum sc_type { // Rodex SC_DAILYSENDMAILCNT, + + // Clan System + SC_CLAN_INFO, #ifndef SC_MAX SC_MAX, //Automatically updated max, used in for's to check we are within bounds. #endif diff --git a/src/map/unit.c b/src/map/unit.c index 0b5b21caf..6e98fc093 100644 --- a/src/map/unit.c +++ b/src/map/unit.c @@ -27,6 +27,7 @@ #include "map/battleground.h" #include "map/chat.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/clif.h" #include "map/duel.h" #include "map/elemental.h" @@ -2725,6 +2726,7 @@ int unit_free(struct block_list *bl, clr_type clrtype) map->foreachpc(clif->friendslist_toggle_sub, sd->status.account_id, sd->status.char_id, 0); party->send_logout(sd); guild->send_memberinfoshort(sd,0); + clan->member_offline(sd); pc->cleareventtimer(sd); pc->inventory_rental_clear(sd); pc->delspiritball(sd,sd->spiritball,1); diff --git a/src/plugins/HPMHooking.c b/src/plugins/HPMHooking.c index 6d6184402..99b4e63e7 100644 --- a/src/plugins/HPMHooking.c +++ b/src/plugins/HPMHooking.c @@ -46,6 +46,7 @@ PRAGMA_GCC5(GCC diagnostic ignored "-Wdiscarded-qualifiers") #include "char/char.h" #include "char/geoip.h" #include "char/int_auction.h" +#include "char/int_clan.h" #include "char/int_elemental.h" #include "char/int_guild.h" #include "char/int_homun.h" @@ -73,6 +74,7 @@ PRAGMA_GCC5(GCC diagnostic ignored "-Wdiscarded-qualifiers") #include "map/channel.h" #include "map/chat.h" #include "map/chrif.h" +#include "map/clan.h" #include "map/clif.h" #include "map/duel.h" #include "map/elemental.h" |