diff options
author | smokexyz <sagunkho@hotmail.com> | 2018-06-30 04:20:03 +0100 |
---|---|---|
committer | Asheraf <acheraf1998@gmail.com> | 2018-07-24 21:41:20 +0100 |
commit | 19aa33a5f61f0996d76d19db7dbe9d81f5daa090 (patch) | |
tree | b54e07d42a58e34820e3131f750b56b7130cb461 /src | |
parent | 78c75cade7f445231f11395a6faef9b0a55568dc (diff) | |
download | hercules-19aa33a5f61f0996d76d19db7dbe9d81f5daa090.tar.gz hercules-19aa33a5f61f0996d76d19db7dbe9d81f5daa090.tar.bz2 hercules-19aa33a5f61f0996d76d19db7dbe9d81f5daa090.tar.xz hercules-19aa33a5f61f0996d76d19db7dbe9d81f5daa090.zip |
Implementation of the official Achievement System.
Source: http://ro.gnjoy.com/news/update/View.asp?seq=163&curpage=1
Script Commands -
```
achievement_progress(<ach_id>,<obj_idx>,<progress>,<incremental?>{,<char_id>});
```
Includes an achievement_db.conf generator that reads from the item_db, mob_db (server side) and achievement_list.lub files to determine valid achievement entries based on item/monster availability. Achievements containing unsupported entries are commented out.
This feature, although renewal-only in official servers, is capable of being used in pre-renewal mode on Hercules.
Does not include the title system yet.
A big thanks to -
@MishimaHaruna for constantly reviewing.
@4144 for all the support.
@Asheraf for a lot of official information.
Co-authored-by: "Dastgir" <dastgirp@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/char/HPMchar.c | 2 | ||||
-rw-r--r-- | src/char/Makefile.in | 2 | ||||
-rw-r--r-- | src/char/char.c | 14 | ||||
-rw-r--r-- | src/char/char.h | 1 | ||||
-rw-r--r-- | src/char/inter.c | 6 | ||||
-rw-r--r-- | src/char/mapif.c | 120 | ||||
-rw-r--r-- | src/char/mapif.h | 5 | ||||
-rw-r--r-- | src/common/mmo.h | 18 | ||||
-rw-r--r-- | src/map/HPMmap.c | 1 | ||||
-rw-r--r-- | src/map/atcommand.c | 6 | ||||
-rw-r--r-- | src/map/chat.c | 5 | ||||
-rw-r--r-- | src/map/chrif.c | 2 | ||||
-rw-r--r-- | src/map/clif.c | 206 | ||||
-rw-r--r-- | src/map/clif.h | 6 | ||||
-rw-r--r-- | src/map/intif.c | 102 | ||||
-rw-r--r-- | src/map/intif.h | 5 | ||||
-rw-r--r-- | src/map/map.c | 4 | ||||
-rw-r--r-- | src/map/map.h | 11 | ||||
-rw-r--r-- | src/map/mob.c | 7 | ||||
-rw-r--r-- | src/map/npc.c | 4 | ||||
-rw-r--r-- | src/map/packets.h | 2 | ||||
-rw-r--r-- | src/map/packets_struct.h | 40 | ||||
-rw-r--r-- | src/map/party.c | 4 | ||||
-rw-r--r-- | src/map/pc.c | 48 | ||||
-rw-r--r-- | src/map/pc.h | 6 | ||||
-rw-r--r-- | src/map/pet.c | 7 | ||||
-rw-r--r-- | src/map/script.c | 86 | ||||
-rw-r--r-- | src/map/unit.c | 1 | ||||
-rw-r--r-- | src/plugins/HPMHooking.c | 2 |
29 files changed, 711 insertions, 12 deletions
diff --git a/src/char/HPMchar.c b/src/char/HPMchar.c index 9f075d909..db2c3702e 100644 --- a/src/char/HPMchar.c +++ b/src/char/HPMchar.c @@ -27,6 +27,7 @@ #include "char/char.h" #include "char/geoip.h" #include "char/inter.h" +#include "char/int_achievement.h" #include "char/int_auction.h" #include "char/int_clan.h" #include "char/int_elemental.h" @@ -42,6 +43,7 @@ #include "char/loginif.h" #include "char/mapif.h" #include "char/pincode.h" + #include "common/HPMi.h" #include "common/conf.h" #include "common/console.h" diff --git a/src/char/Makefile.in b/src/char/Makefile.in index b79958679..95c8df813 100644 --- a/src/char/Makefile.in +++ b/src/char/Makefile.in @@ -44,7 +44,7 @@ CHAR_C = char.c HPMchar.c loginif.c mapif.c geoip.c inter.c int_achievement.c in 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_achievement.h int_auction.h int_clan.h int_elemental.h +CHAR_H = char.h HPMchar.h loginif.h mapif.h geoip.h inter.h int_achievement.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 99198fa50..321e386ae 100644 --- a/src/char/char.c +++ b/src/char/char.c @@ -37,6 +37,7 @@ #include "char/int_quest.h" #include "char/int_rodex.h" #include "char/int_storage.h" +#include "char/int_achievement.h" #include "char/inter.h" #include "char/loginif.h" #include "char/mapif.h" @@ -112,6 +113,7 @@ char acc_reg_num_db[32] = "acc_reg_num_db"; char acc_reg_str_db[32] = "acc_reg_str_db"; char char_reg_str_db[32] = "char_reg_str_db"; char char_reg_num_db[32] = "char_reg_num_db"; +char char_achievement_db[256] = "char_achievements"; static struct char_interface char_s; struct char_interface *chr; @@ -291,12 +293,18 @@ static void char_set_char_offline(int char_id, int account_id) } else { - struct mmo_charstatus* cp = (struct mmo_charstatus*) idb_get(chr->char_db_,char_id); + struct mmo_charstatus *cp = (struct mmo_charstatus*) idb_get(chr->char_db_, char_id); + /* Character Achievements */ + struct char_achievements *c_ach = (struct char_achievements *) idb_get(inter_achievement->char_achievements, char_id); inter_guild->CharOffline(char_id, cp?cp->guild_id:-1); - if (cp) + if (cp != NULL) idb_remove(chr->char_db_,char_id); + if (c_ach != NULL) { + VECTOR_CLEAR(*c_ach); + idb_remove(inter_achievement->char_achievements, char_id); + } if( SQL_ERROR == SQL->Query(inter->sql_handle, "UPDATE `%s` SET `online`='0' WHERE `char_id`='%d' LIMIT 1", char_db, char_id) ) Sql_ShowDebug(inter->sql_handle); @@ -5443,6 +5451,7 @@ static bool char_sql_config_read_pc(const char *filename, const struct config_t libconfig->setting_lookup_mutable_string(setting, "hotkey_db", hotkey_db, sizeof(hotkey_db)); libconfig->setting_lookup_mutable_string(setting, "scdata_db", scdata_db, sizeof(scdata_db)); libconfig->setting_lookup_mutable_string(setting, "inventory_db", inventory_db, sizeof(inventory_db)); + libconfig->setting_lookup_mutable_string(setting, "achievement_db", char_achievement_db, sizeof(char_achievement_db)); libconfig->setting_lookup_mutable_string(setting, "cart_db", cart_db, sizeof(cart_db)); libconfig->setting_lookup_mutable_string(setting, "charlog_db", charlog_db, sizeof(charlog_db)); libconfig->setting_lookup_mutable_string(setting, "storage_db", storage_db, sizeof(storage_db)); @@ -6319,6 +6328,7 @@ void char_load_defaults(void) inter_quest_defaults(); inter_storage_defaults(); inter_rodex_defaults(); + inter_achievement_defaults(); inter_defaults(); geoip_defaults(); } diff --git a/src/char/char.h b/src/char/char.h index 4d816583a..81cab1eaf 100644 --- a/src/char/char.h +++ b/src/char/char.h @@ -340,6 +340,7 @@ extern char acc_reg_num_db[32]; extern char acc_reg_str_db[32]; extern char char_reg_str_db[32]; extern char char_reg_num_db[32]; +extern char char_achievement_db[256]; extern int guild_exp_rate; diff --git a/src/char/inter.c b/src/char/inter.c index 7269009a7..418c9b0a1 100644 --- a/src/char/inter.c +++ b/src/char/inter.c @@ -36,6 +36,7 @@ #include "char/int_quest.h" #include "char/int_rodex.h" #include "char/int_storage.h" +#include "char/int_achievement.h" #include "char/mapif.h" #include "common/cbasetypes.h" #include "common/conf.h" @@ -70,7 +71,7 @@ int party_share_level = 10; // recv. packet list static int inter_recv_packet_length[] = { -1,-1, 7,-1, -1,13,36, (2 + 4 + 4 + 4 + NAME_LENGTH), 0, 0, 0, 0, 0, 0, 0, 0, // 3000- - 6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, // 3010- Account Storage [Smokexyz] + 6,-1, 6,-1, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, // 3010- Account Storage, Achievements [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, 10,10, 0, 0, 7, 6,10,10, 10,-1, 0, 0, // 3040- Clan System(3044-3045) @@ -974,6 +975,7 @@ static int inter_init_sql(const char *file) inter_mail->sql_init(); inter_auction->sql_init(); inter_rodex->sql_init(); + inter_achievement->sql_init(); geoip->init(); inter->msg_config_read("conf/messages.conf", false); @@ -995,6 +997,7 @@ static void inter_final(void) inter_mail->sql_final(); inter_auction->sql_final(); inter_rodex->sql_final(); + inter_achievement->sql_final(); geoip->final(true); inter->do_final_msg(); @@ -1133,6 +1136,7 @@ static int inter_parse_frommap(int fd) || inter_quest->parse_frommap(fd) || inter_rodex->parse_frommap(fd) || inter_clan->parse_frommap(fd) + || inter_achievement->parse_frommap(fd) ) break; else diff --git a/src/char/mapif.c b/src/char/mapif.c index 30f8c1178..dc5735550 100644 --- a/src/char/mapif.c +++ b/src/char/mapif.c @@ -24,6 +24,7 @@ #include "mapif.h" #include "char/char.h" +#include "char/int_achievement.h" #include "char/int_auction.h" #include "char/int_clan.h" #include "char/int_guild.h" @@ -2346,6 +2347,120 @@ static int mapif_parse_ClanMemberCount(int fd, int clan_id, int kick_interval) return 0; } +// Achievement System +/** + * Parse achievement load request from the map server + * @param[in] fd socket descriptor. + */ +static void mapif_parse_load_achievements(int fd) +{ + int char_id = 0; + + /* Read received information from map-server. */ + RFIFOHEAD(fd); + char_id = RFIFOL(fd, 2); + + /* Load and send achievements to map */ + mapif->achievement_load(fd, char_id); +} + +/** + * Loads achievements and sends to the map server. + * @param[in] fd socket descriptor + * @param[in] char_id character Id. + */ +static void mapif_achievement_load(int fd, int char_id) +{ + struct char_achievements *cp = NULL; + + /* Ensure data exists */ + cp = idb_ensure(inter_achievement->char_achievements, char_id, inter_achievement->ensure_char_achievements); + + /* Load storage for char-server. */ + inter_achievement->fromsql(char_id, cp); + + /* Send Achievements to map server. */ + mapif->sAchievementsToMap(fd, char_id, cp); +} + +/** + * Sends achievement data of a character to the map server. + * @packet[out] 0x3810 <packet_id>.W <payload_size>.W <char_id>.L <char_achievements[]>.P + * @param[in] fd socket descriptor. + * @param[in] char_id Character ID. + * @param[in] cp Pointer to character's achievement data vector. + */ +static void mapif_send_achievements_to_map(int fd, int char_id, const struct char_achievements *cp) +{ + int i = 0; + int data_size = 0; + + nullpo_retv(cp); + + data_size = sizeof(struct achievement) * VECTOR_LENGTH(*cp); + +STATIC_ASSERT((sizeof(struct achievement) * MAX_ACHIEVEMENT_DB + 8 <= UINT16_MAX), + "The achievements data can potentially be larger than the maximum packet size. This may cause errors at run-time."); + + /* Send to the map server. */ + WFIFOHEAD(fd, (8 + data_size)); + WFIFOW(fd, 0) = 0x3810; + WFIFOW(fd, 2) = (8 + data_size); + WFIFOL(fd, 4) = char_id; + for (i = 0; i < VECTOR_LENGTH(*cp); i++) + memcpy(WFIFOP(fd, 8 + i * sizeof(struct achievement)), &VECTOR_INDEX(*cp, i), sizeof(struct achievement)); + WFIFOSET(fd, 8 + data_size); +} + +/** + * Handles achievement request and saves data from map server. + * @packet[in] 0x3013 <packet_size>.W <char_id>.L <char_achievement>.P + * @param[in] fd session socket descriptor. + */ +static void mapif_parse_save_achievements(int fd) +{ + int size = 0, char_id = 0, payload_count = 0, i = 0; + struct char_achievements p = { 0 }; + + RFIFOHEAD(fd); + size = RFIFOW(fd, 2); + char_id = RFIFOL(fd, 4); + + payload_count = (size - 8) / sizeof(struct achievement); + + VECTOR_INIT(p); + VECTOR_ENSURE(p, payload_count, 1); + + for (i = 0; i < payload_count; i++) { + struct achievement ach = { 0 }; + memcpy(&ach, RFIFOP(fd, 8 + i * sizeof(struct achievement)), sizeof(struct achievement)); + VECTOR_PUSH(p, ach); + } + + mapif->achievement_save(char_id, &p); + + VECTOR_CLEAR(p); +} + +/** + * Handles inter-server achievement db ensuring + * and saves current achievements to sql. + * @param[in] char_id character identifier. + * @param[out] p pointer to character achievements vector. + */ +static void mapif_achievement_save(int char_id, struct char_achievements *p) +{ + struct char_achievements *cp = NULL; + + nullpo_retv(p); + + /* Get loaded achievements. */ + cp = idb_ensure(inter_achievement->char_achievements, char_id, inter_achievement->ensure_char_achievements); + + if (VECTOR_LENGTH(*p)) /* Save current achievements. */ + inter_achievement->tosql(char_id, cp, p); +} + void mapif_defaults(void) { mapif = &mapif_s; @@ -2361,6 +2476,11 @@ void mapif_defaults(void) mapif->sendallwos = mapif_sendallwos; mapif->send = mapif_send; mapif->send_users_count = mapif_send_users_count; + mapif->pLoadAchievements = mapif_parse_load_achievements; + mapif->sAchievementsToMap = mapif_send_achievements_to_map; + mapif->pSaveAchievements = mapif_parse_save_achievements; + mapif->achievement_load = mapif_achievement_load; + mapif->achievement_save = mapif_achievement_save; mapif->auction_message = mapif_auction_message; mapif->auction_sendlist = mapif_auction_sendlist; mapif->parse_auction_requestlist = mapif_parse_auction_requestlist; diff --git a/src/char/mapif.h b/src/char/mapif.h index d67ce1c79..bfdefe4ea 100644 --- a/src/char/mapif.h +++ b/src/char/mapif.h @@ -40,6 +40,11 @@ struct mapif_interface { int (*sendallwos) (int sfd, unsigned char *buf, unsigned int len); int (*send) (int fd, unsigned char *buf, unsigned int len); void (*send_users_count) (int users); + void (*pLoadAchievements) (int fd); + void (*sAchievementsToMap) (int fd, int char_id, const struct char_achievements *p); + void (*pSaveAchievements) (int fd); + void (*achievement_load) (int fd, int char_id); + void (*achievement_save) (int char_id, struct char_achievements *p); void (*auction_message) (int char_id, unsigned char result); void (*auction_sendlist) (int fd, int char_id, short count, short pages, unsigned char *buf); void (*parse_auction_requestlist) (int fd); diff --git a/src/common/mmo.h b/src/common/mmo.h index 1b9562e9d..69717c972 100644 --- a/src/common/mmo.h +++ b/src/common/mmo.h @@ -218,6 +218,16 @@ #define MAX_QUEST_OBJECTIVES 3 // Max quest objectives for a quest #endif +// Achievements [Smokexyz/Hercules] +#ifndef MAX_ACHIEVEMENT_DB +#define MAX_ACHIEVEMENT_DB 360 // Maximum number of achievements +#define MAX_ACHIEVEMENT_OBJECTIVES 10 // Maximum number of achievement objectives +STATIC_ASSERT(MAX_ACHIEVEMENT_OBJECTIVES <= 10, "This value is limited by the client and database layout and should only be increased if you know the consequences."); +#define MAX_ACHIEVEMENT_RANKS 20 // Achievement Ranks +STATIC_ASSERT(MAX_ACHIEVEMENT_RANKS <= 255, "This value is limited by the client and database layout and should only be increased if you know the consequences."); +#define MAX_ACHIEVEMENT_ITEM_REWARDS 10 // Achievement Rewards +#endif + // for produce #define MIN_ATTRIBUTE 0 #define MAX_ATTRIBUTE 4 @@ -616,6 +626,14 @@ struct hotkey { #endif }; +struct achievement { // Achievements [Smokexyz/Hercules] + int id; + int objective[MAX_ACHIEVEMENT_OBJECTIVES]; + time_t completed_at, rewarded_at; +}; + +VECTOR_STRUCT_DECL(char_achievements, struct achievement); + struct mmo_charstatus { int char_id; int account_id; diff --git a/src/map/HPMmap.c b/src/map/HPMmap.c index e4640d09d..091a53311 100644 --- a/src/map/HPMmap.c +++ b/src/map/HPMmap.c @@ -47,6 +47,7 @@ #include "common/sysinfo.h" #include "common/timer.h" #include "common/utils.h" +#include "map/achievement.h" #include "map/atcommand.h" #include "map/battle.h" #include "map/battleground.h" diff --git a/src/map/atcommand.c b/src/map/atcommand.c index 79bd92213..6dff3f698 100644 --- a/src/map/atcommand.c +++ b/src/map/atcommand.c @@ -55,6 +55,7 @@ #include "map/storage.h" #include "map/trade.h" #include "map/unit.h" +#include "map/achievement.h" #include "common/cbasetypes.h" #include "common/conf.h" #include "common/core.h" @@ -1421,6 +1422,10 @@ ACMD(baselevelup) clif->updatestatus(sd, SP_BASEEXP); clif->updatestatus(sd, SP_NEXTBASEEXP); pc->baselevelchanged(sd); + + // achievements + achievement->validate_stats(sd, SP_BASELEVEL, sd->status.base_level); + if(sd->status.party_id) party->send_levelup(sd); @@ -2526,6 +2531,7 @@ ACMD(param) clif->updatestatus(sd, SP_USTR + i); status_calc_pc(sd, SCO_FORCE); clif->message(fd, msg_fd(fd,42)); // Stat changed. + achievement->validate_stats(sd, SP_STR + i, new_value); // Achievements [Smokexyz/Hercules] } else { if (value < 0) clif->message(fd, msg_fd(fd,41)); // Unable to decrease the number/value. diff --git a/src/map/chat.c b/src/map/chat.c index 9852131be..d9b642219 100644 --- a/src/map/chat.c +++ b/src/map/chat.c @@ -29,6 +29,7 @@ #include "map/npc.h" // npc_event_do() #include "map/pc.h" #include "map/skill.h" // ext_skill_unit_onplace() +#include "map/achievement.h" #include "common/cbasetypes.h" #include "common/memmgr.h" #include "common/mmo.h" @@ -126,6 +127,7 @@ static bool chat_createpcchat(struct map_session_data *sd, const char *title, co pc_stop_attack(sd); clif->createchat(sd,0); // 0 = success clif->dispchat(cd,0); + achievement->validate_chatroom_create(sd); // Achievements [Smokexyz/Hercules] return true; } clif->createchat(sd,1); // 1 = Room limit exceeded @@ -181,6 +183,9 @@ static bool chat_joinchat(struct map_session_data *sd, int chatid, const char *p cd->usersd[cd->users] = sd; cd->users++; + if (cd->owner->type == BL_PC) + achievement->validate_chatroom_members(BL_UCAST(BL_PC, cd->owner), cd->users); + pc_setchatid(sd,cd->bl.id); clif->joinchatok(sd, cd); //To the person who newly joined the list of all diff --git a/src/map/chrif.c b/src/map/chrif.c index cd24c5fea..609c856c6 100644 --- a/src/map/chrif.c +++ b/src/map/chrif.c @@ -336,6 +336,8 @@ static bool chrif_save(struct map_session_data *sd, int flag) elemental->save(sd->ed); if( sd->save_quest ) intif->quest_save(sd); + if (VECTOR_LENGTH(sd->achievement) > 0) + intif->achievements_save(sd); if (sd->storage.received == true && sd->storage.save == true) intif->send_account_storage(sd); diff --git a/src/map/clif.c b/src/map/clif.c index 2a2d87ccc..c71c9d38d 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -56,13 +56,14 @@ #include "map/trade.h" #include "map/unit.h" #include "map/vending.h" +#include "map/achievement.h" #include "common/HPM.h" #include "common/cbasetypes.h" #include "common/conf.h" #include "common/ers.h" #include "common/grfio.h" #include "common/memmgr.h" -#include "common/mmo.h" // NEW_CARTS +#include "common/mmo.h" // NEW_CARTS, char_achievements #include "common/nullpo.h" #include "common/random.h" #include "common/showmsg.h" @@ -14786,6 +14787,7 @@ static void clif_parse_FriendsListReply(int fd, struct map_session_data *sd) f_sd->status.friends[i].char_id = sd->status.char_id; memcpy(f_sd->status.friends[i].name, sd->status.name, NAME_LENGTH); clif->friendslist_reqack(f_sd, sd, 0); + achievement->validate_friend_add(f_sd); // Achievements [Smokexyz/Hercules] if (battle_config.friend_auto_add) { // Also add f_sd to sd's friendlist. @@ -14804,6 +14806,7 @@ static void clif_parse_FriendsListReply(int fd, struct map_session_data *sd) sd->status.friends[i].char_id = f_sd->status.char_id; memcpy(sd->status.friends[i].name, f_sd->status.name, NAME_LENGTH); clif->friendslist_reqack(sd, f_sd, 0); + achievement->validate_friend_add(sd); // Achievements [Smokexyz/Hercules] } } } @@ -20223,6 +20226,201 @@ static unsigned short clif_parse_cmd_optional(int fd, struct map_session_data *s return cmd; } +/** + * Send all of a session's achievement information to client. + * Called only once on login/char-loading. (PACKET_ZC_ALL_ACH_LIST) + * @packet [out] 0x0A23 <ID>.W <Length>.W <ach_count>.L <total_points>.L <rank>.W <current_rank_points>.L <next_rank_points>.L <struct ach_list_info *[]>.P + * @param fd socket descriptor + * @param sd pointer to map_session_data + */ +static void clif_achievement_send_list(int fd, struct map_session_data *sd) +{ +#if PACKETVER >= 20141016 + int i = 0, count = 0, curr_exp_tmp = 0; + struct packet_achievement_list p = { 0 }; + + nullpo_retv(sd); + + /* Browse through the session's achievement list and gather their values. */ + for (i = 0; i < VECTOR_LENGTH(sd->achievement); i++) { + int j = 0; + struct achievement *a = &VECTOR_INDEX(sd->achievement, i); + const struct achievement_data *ad = NULL; + + /* Sanity check for nonull pointers. */ + if (a == NULL || (ad = achievement->get(a->id)) == NULL) + continue; + + p.ach[count].ach_id = a->id; + + for (j = 0; j < VECTOR_LENGTH(ad->objective); j++) + p.ach[count].objective[j] = a->objective[j]; + + if (a->completed_at) { + p.ach[count].completed = 1; + p.ach[count].completed_at = (uint32) a->completed_at; + p.ach[count].reward = a->rewarded_at ? 1 : 0; + p.total_points += ad->points; + } + + count++; + } + + p.packet_id = achievementListType; + p.packet_len = sizeof(struct ach_list_info) * count + 22; + p.total_achievements = count; + /* Determine Achievement Rank and current exp */ + curr_exp_tmp = p.total_points; + for (i = 0; curr_exp_tmp && i < MAX_ACHIEVEMENT_RANKS && i < VECTOR_LENGTH(achievement->rank_exp); i++) { + if (curr_exp_tmp >= VECTOR_INDEX(achievement->rank_exp, i)) { + curr_exp_tmp -= VECTOR_INDEX(achievement->rank_exp, i); + p.rank++; + + // Validate achievement rank type achievements. + achievement->validate_achievement_rank(sd, p.rank); + } + } + /* Determine Achievement Rank Exp */ + p.current_rank_points = curr_exp_tmp; + p.next_rank_points = VECTOR_INDEX(achievement->rank_exp, p.rank); + /* Send payload */ + clif->send(&p, p.packet_len, &sd->bl, SELF); +#endif // PACKETVER >= 20141016 +} + +/** + * Sends achievement information for a single achievement. + * Called on objective progress updates/completion. (PACKET_ZC_ACH_UPDATE) + * @packet [out] 0x0A24 <ID>.W <total_points>.L <rank>.W <current_rank_points>.L <next_rank_points>.L <struct ach_list_info *>.P + * @param fd socket descriptor + * @param sd pointer to struct map_session_data + * @param ad const pointer to struct achievement_data from the achievement db. + */ +static void clif_achievement_send_update(int fd, struct map_session_data *sd, const struct achievement_data *ad) +{ +#if PACKETVER >= 20141016 + struct packet_achievement_update p = { 0 }; + struct achievement *a = NULL; + int i = 0, points = 0, rank = 0, curr_rank_points = 0; + + nullpo_retv(sd); + nullpo_retv(ad); + + /* Get Session Achievement Data */ + if ((a = achievement->ensure(sd, ad)) == NULL) + return; + + /* Get total points, current rank and current rank points from the session. */ + achievement->calculate_totals(sd, &points, NULL, &rank, &curr_rank_points); + + p.packet_id = achievementUpdateType; + + /* Determine Total Achievement Points */ + p.total_points = points; + + /* Determine Achievement Rank */ + p.rank = rank; + + /* Determine Achievement Rank Exp */ + p.current_rank_points = curr_rank_points; + p.next_rank_points = VECTOR_INDEX(achievement->rank_exp, p.rank); + + p.ach.ach_id = ad->id; + p.ach.completed = (uint8) achievement->check_complete(sd, ad); + + for (i = 0; i < VECTOR_LENGTH(ad->objective); i++) + p.ach.objective[i] = a->objective[i]; + + p.ach.completed_at = (uint32) a->completed_at; + + p.ach.reward = a->rewarded_at ? 1 : 0; + + clif->send(&p, packet_len(achievementUpdateType), &sd->bl, SELF); + + /* Validate rank-related achievements */ + achievement->validate_achievement_rank(sd, rank); + +#endif // PACKETVER >= 20141016 +} + +/** + * Parses achievement reward packet from session. + * @packet [in] 0x0A25 <ach_id>.L + * @param fd socket descriptor. + * @param sd ptr to session data. + */ +static void clif_parse_achievement_get_reward(int fd, struct map_session_data *sd) __attribute__((nonnull(2))); +static void clif_parse_achievement_get_reward(int fd, struct map_session_data *sd) +{ +#if PACKETVER >= 20141016 + int ach_id = RFIFOL(fd, 2); + const struct achievement_data *ad = NULL; + struct achievement *ach = NULL; + + if (ach_id <= 0 || (ad = achievement->get(ach_id)) == NULL) + return; + + if ((ach = achievement->ensure(sd, ad)) == NULL) + return; + + if (achievement->check_complete(sd, ad) && ach->completed_at && ach->rewarded_at == 0) { + int i = 0; + /* Buff */ + if (ad->rewards.bonus != NULL) + script->run(ad->rewards.bonus, 0, sd->bl.id, 0); + + /* Give Items */ + for (i = 0; i < VECTOR_LENGTH(ad->rewards.item); i++) { + struct item it = { 0 }; + int total = 0; + + it.nameid = VECTOR_INDEX(ad->rewards.item, i).id; + total = VECTOR_INDEX(ad->rewards.item, i).amount; + + it.identify = 1; + + //Check if it's stackable. + if (!itemdb->isstackable(it.nameid)) { + int j = 0; + for (j = 0; j < total; ++j) + pc->additem(sd, &it, (it.amount = 1), LOG_TYPE_SCRIPT); + } else { + pc->additem(sd, &it, (it.amount = total), LOG_TYPE_SCRIPT); + } + } + + /* @TODO TitleId */ + + ach->rewarded_at = time(NULL); + + clif->achievement_send_update(fd, sd, ad); // send update. + + clif->achievement_reward_ack(fd, sd, ad); + } +#endif // PACKETVER >= 20141016 +} + +/** + * Sends achievement reward collection acknowledgement to the client. + * @packet [out] 0x0A26 <packet_id>.W <received + */ +static void clif_achievement_reward_ack(int fd, struct map_session_data *sd, const struct achievement_data *ad) +{ +#if PACKETVER >= 20141016 + struct packet_achievement_reward_ack p = { 0 }; + + nullpo_retv(sd); + nullpo_retv(ad); + + p.packet_id = achievementRewardAckType; + p.received = 1; + p.ach_id = ad->id; + + clif->send(&p, packet_len(achievementRewardAckType), &sd->bl, SELF); +#endif // PACKETVER >= 20141016 +} +// End of Achievement System + /*========================================== * RoDEX *------------------------------------------*/ @@ -22159,6 +22357,11 @@ void clif_defaults(void) clif->isdisguised = clif_isdisguised; clif->navigate_to = clif_navigate_to; clif->bl_type = clif_bl_type; + /* Achievement System */ + clif->achievement_send_list = clif_achievement_send_list; + clif->achievement_send_update = clif_achievement_send_update; + clif->pAchievementGetReward = clif_parse_achievement_get_reward; + clif->achievement_reward_ack = clif_achievement_reward_ack; /*------------------------ *- Parse Incoming Packet @@ -22406,6 +22609,7 @@ void clif_defaults(void) clif->pHotkeyRowShift = clif_parse_HotkeyRowShift; clif->dressroom_open = clif_dressroom_open; clif->pOneClick_ItemIdentify = clif_parse_OneClick_ItemIdentify; + /* Achievements [Smokexyz/Hercules] */ clif->get_bl_name = clif_get_bl_name; /* RODEX */ clif->pRodexOpenWriteMail = clif_parse_rodex_open_write_mail; diff --git a/src/map/clif.h b/src/map/clif.h index 0077566b5..7a2fbaa68 100644 --- a/src/map/clif.h +++ b/src/map/clif.h @@ -54,6 +54,7 @@ struct skill_cd; struct skill_unit; struct unit_data; struct view_data; +struct achievement_data; // map/achievement.h enum clif_messages; enum rodex_add_item; @@ -1197,6 +1198,11 @@ struct clif_interface { bool (*isdisguised) (struct block_list* bl); void (*navigate_to) (struct map_session_data *sd, const char* mapname, uint16 x, uint16 y, uint8 flag, bool hideWindow, uint16 mob_id); unsigned char (*bl_type) (struct block_list *bl); + /* Achievement System */ + void (*achievement_send_list) (int fd, struct map_session_data *sd); + void (*achievement_send_update) (int fd, struct map_session_data *sd, const struct achievement_data *ad); + void (*pAchievementGetReward) (int fd, struct map_session_data *sd); + void (*achievement_reward_ack) (int fd, struct map_session_data *sd, const struct achievement_data *ad); /*------------------------ *- Parse Incoming Packet *------------------------*/ diff --git a/src/map/intif.c b/src/map/intif.c index 393058a8a..a5dc9c575 100644 --- a/src/map/intif.c +++ b/src/map/intif.c @@ -41,6 +41,8 @@ #include "map/quest.h" #include "map/rodex.h" #include "map/storage.h" +#include "map/achievement.h" + #include "common/memmgr.h" #include "common/nullpo.h" #include "common/showmsg.h" @@ -1730,6 +1732,98 @@ static void intif_parse_DeleteHomunculusOk(int fd) ShowError("Homunculus data delete failure\n"); } +/*************************************** + * ACHIEVEMENT SYSTEM FUNCTIONS + ***************************************/ +/** + * Sends a request to the inter-server to load + * and send a character's achievement data. + * @packet [out] 0x3012 <char_id>.L + * @param sd pointer to map_session_data. + */ +static void intif_achievements_request(struct map_session_data *sd) +{ + nullpo_retv(sd); + + if (intif->CheckForCharServer()) + return; + + WFIFOHEAD(inter_fd, 6); + WFIFOW(inter_fd, 0) = 0x3012; + WFIFOL(inter_fd, 2) = sd->status.char_id; + WFIFOSET(inter_fd, 6); +} + +/** + * Handles reception of achievement data for a character from the inter-server. + * @packet [in] 0x3810 <packet_len>.W <char_id>.L <char_achievements[]>.P + * @param fd socket descriptor. + */ +static void intif_parse_achievements_load(int fd) +{ + int size = RFIFOW(fd, 2); + int char_id = RFIFOL(fd, 4); + int payload_count = (size - 8) / sizeof(struct achievement); + struct map_session_data *sd = map->charid2sd(char_id); + int i = 0; + + if (sd == NULL) { + ShowError("intif_parse_achievements_load: Parse request for achievements received but character is offline!\n"); + return; + } + + if (VECTOR_LENGTH(sd->achievement) > 0) { + ShowError("intif_parse_achievements_load: Achievements already loaded! Possible multiple calls from the inter-server received.\n"); + return; + } + + VECTOR_ENSURE(sd->achievement, payload_count, 1); + + for (i = 0; i < payload_count; i++) { + struct achievement t_ach = { 0 }; + + memcpy(&t_ach, RFIFOP(fd, 8 + i * sizeof(struct achievement)), sizeof(struct achievement)); + + if (achievement->get(t_ach.id) == NULL) { + ShowError("intif_parse_achievements_load: Invalid Achievement %d received from character %d. Ignoring...\n", t_ach.id, char_id); + continue; + } + + VECTOR_PUSH(sd->achievement, t_ach); + } + + clif->achievement_send_list(fd, sd); + sd->achievements_received = true; +} + +/** + * Send character's achievement data to the inter-server. + * @packet 0x3013 <packet_len>.W <char_id>.L <achievements[]>.P + * @param sd pointer to map session data. + */ +static void intif_achievements_save(struct map_session_data *sd) +{ + int packet_len = 0, payload_size = 0, i = 0; + + nullpo_retv(sd); + /* check for character server. */ + if (intif->CheckForCharServer()) + return; + /* Return if no data. */ + if (!(payload_size = VECTOR_LENGTH(sd->achievement) * sizeof(struct achievement))) + return; + + packet_len = payload_size + 8; + + WFIFOHEAD(inter_fd, packet_len); + WFIFOW(inter_fd, 0) = 0x3013; + WFIFOW(inter_fd, 2) = packet_len; + WFIFOL(inter_fd, 4) = sd->status.char_id; + for (i = 0; i < VECTOR_LENGTH(sd->achievement); i++) + memcpy(WFIFOP(inter_fd, 8 + i * sizeof(struct achievement)), &VECTOR_INDEX(sd->achievement, i), sizeof(struct achievement)); + WFIFOSET(inter_fd, packet_len); +} + /************************************** * QUESTLOG SYSTEM FUNCTIONS * **************************************/ @@ -2799,6 +2893,7 @@ static int intif_parse(int fd) case 0x3806: intif->pChangeNameOk(fd); break; case 0x3807: intif->pMessageToFD(fd); break; case 0x3808: intif->pAccountStorageSaveAck(fd); break; + case 0x3810: intif->pAchievementsLoad(fd); break; case 0x3818: intif->pLoadGuildStorage(fd); break; case 0x3819: intif->pSaveGuildStorage(fd); break; case 0x3820: intif->pPartyCreated(fd); break; @@ -2896,7 +2991,7 @@ void intif_defaults(void) { const int packet_len_table [INTIF_PACKET_LEN_TABLE_SIZE] = { -1,-1,27,-1, -1,-1,37,-1, 7, 0, 0, 0, 0, 0, 0, 0, //0x3800-0x380f - 0, 0, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, //0x3810 + -1, 0, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, //0x3810 Achievements [Smokexyz/Hercules] 39,-1,15,15, 14,19, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, //0x3820 10,-1,15, 0, 79,23, 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 @@ -3001,6 +3096,9 @@ void intif_defaults(void) intif->CheckForCharServer = CheckForCharServer; /* */ intif->itembound_req = intif_itembound_req; + /* Achievement System */ + intif->achievements_request = intif_achievements_request; + intif->achievements_save = intif_achievements_save; /* parse functions */ intif->pWisMessage = intif_parse_WisMessage; intif->pWisEnd = intif_parse_WisEnd; @@ -3073,4 +3171,6 @@ void intif_defaults(void) intif->pRodexCheckName = intif_parse_RodexCheckName; /* Clan System */ intif->pRecvClanMemberAction = intif_parse_RecvClanMemberAction; + /* Achievement System */ + intif->pAchievementsLoad = intif_parse_achievements_load; } diff --git a/src/map/intif.h b/src/map/intif.h index c75b93201..1e98d11f8 100644 --- a/src/map/intif.h +++ b/src/map/intif.h @@ -145,6 +145,9 @@ struct intif_interface { void (*request_accinfo) (int u_fd, int aid, int group_lv, char* query); /* */ int (*CheckForCharServer) (void); + /* Achievement System [Smokexyz/Hercules] */ + void(*achievements_request) (struct map_session_data *sd); + void(*achievements_save) (struct map_session_data *sd); /* */ void (*pWisMessage) (int fd); void (*pWisEnd) (int fd); @@ -217,6 +220,8 @@ struct intif_interface { void(*pRodexCheckName) (int fd); /* Clan System */ void (*pRecvClanMemberAction) (int fd); + /* Achievements */ + void (*pAchievementsLoad) (int fd); }; #ifdef HERCULES_CORE diff --git a/src/map/map.c b/src/map/map.c index 0124a3035..ce8f4cdf5 100644 --- a/src/map/map.c +++ b/src/map/map.c @@ -59,6 +59,7 @@ #include "map/rodex.h" #include "map/trade.h" #include "map/unit.h" +#include "map/achievement.h" #include "common/HPM.h" #include "common/cbasetypes.h" #include "common/conf.h" @@ -6134,6 +6135,7 @@ int do_final(void) map->list_final(); vending->final(); rodex->final(); + achievement->final(); HPM_map_do_final(); @@ -6338,6 +6340,7 @@ static void map_load_defaults(void) pet_defaults(); path_defaults(); quest_defaults(); + achievement_defaults(); npc_chat_defaults(); rodex_defaults(); } @@ -6653,6 +6656,7 @@ int do_init(int argc, char *argv[]) mercenary->init(minimal); elemental->init(minimal); quest->init(minimal); + achievement->init(minimal); npc->init(minimal); unit->init(minimal); bg->init(minimal); diff --git a/src/map/map.h b/src/map/map.h index 04525b4d5..207fef2ce 100644 --- a/src/map/map.h +++ b/src/map/map.h @@ -66,6 +66,16 @@ enum E_MAPSERVER_ST { #define block_free_max 1048576 #define BL_LIST_MAX 1048576 +// The following system marks a different job ID system used by the map server, +// which makes a lot more sense than the normal one. [Skotlex] +// These marks the "level" of the job. +#define JOBL_2_1 0x0100 +#define JOBL_2_2 0x0200 +#define JOBL_2 0x0300 // JOBL_2_1 | JOBL_2_2 +#define JOBL_UPPER 0x1000 +#define JOBL_BABY 0x2000 +#define JOBL_THIRD 0x4000 + // For filtering and quick checking. #define MAPID_BASEMASK 0x00ff #define MAPID_UPPERMASK 0x0fff @@ -537,6 +547,7 @@ struct flooritem_data { }; enum status_point_types { //we better clean up this enum and change it name [Hemagx] + SP_NONE = -1, SP_SPEED,SP_BASEEXP,SP_JOBEXP,SP_KARMA,SP_MANNER,SP_HP,SP_MAXHP,SP_SP, // 0-7 SP_MAXSP,SP_STATUSPOINT,SP_0a,SP_BASELEVEL,SP_SKILLPOINT,SP_STR,SP_AGI,SP_VIT, // 8-15 SP_INT,SP_DEX,SP_LUK,SP_CLASS,SP_ZENY,SP_SEX,SP_NEXTBASEEXP,SP_NEXTJOBEXP, // 16-23 diff --git a/src/map/mob.c b/src/map/mob.c index 0dbff9211..27039490c 100644 --- a/src/map/mob.c +++ b/src/map/mob.c @@ -44,6 +44,7 @@ #include "map/script.h" #include "map/skill.h" #include "map/status.h" +#include "map/achievement.h" #include "common/HPM.h" #include "common/cbasetypes.h" #include "common/conf.h" @@ -2188,6 +2189,10 @@ static void mob_damage(struct mob_data *md, struct block_list *src, int damage) if (src) mob->log_damage(md, src, damage); md->dmgtick = timer->gettick(); + + // Achievements [Smokexyz/Hercules] + if (src != NULL && src->type == BL_PC) + achievement->validate_mob_damage(BL_UCAST(BL_PC, src), damage, false); } if (battle_config.show_mob_info&3) @@ -2420,6 +2425,8 @@ static int mob_dead(struct mob_data *md, struct block_list *src, int type) } if(zeny) // zeny from mobs [Valaris] pc->getzeny(tmpsd[i], zeny, LOG_TYPE_PICKDROP_MONSTER, NULL); + + achievement->validate_mob_kill(tmpsd[i], md->db->mob_id); // Achievements [Smokexyz/Hercules] } } diff --git a/src/map/npc.c b/src/map/npc.c index 383558eaf..3a00b38ea 100644 --- a/src/map/npc.c +++ b/src/map/npc.c @@ -40,6 +40,7 @@ #include "map/skill.h" #include "map/status.h" #include "map/unit.h" +#include "map/achievement.h" #include "common/HPM.h" #include "common/cbasetypes.h" #include "common/db.h" @@ -2272,6 +2273,9 @@ static int npc_selllist(struct map_session_data *sd, struct itemlist *item_list) } pc->delitem(sd, idx, entry->amount, 0, DELITEM_SOLD, LOG_TYPE_NPC); + + // Achievements [Smokexyz/Hercules] + achievement->validate_item_sell(sd, sd->status.inventory[idx].nameid, entry->amount); } if (z + sd->status.zeny > MAX_ZENY && nd->master_nd == NULL) diff --git a/src/map/packets.h b/src/map/packets.h index d4f87f5a0..a0459e94c 100644 --- a/src/map/packets.h +++ b/src/map/packets.h @@ -3209,7 +3209,7 @@ packet(0x96e,-1,clif->ackmergeitems); packet(0x0a22,3); // ZC_RECV_ROULETTE_ITEM packet(0x0a23,-1); // ZC_ALL_ACH_LIST packet(0x0a24,35); // ZC_ACH_UPDATE - packet(0x0a25,6,clif->pDull/*,XXX*/); // CZ_REQ_ACH_REWARD + packet(0x0a25,6,clif->pAchievementGetReward, 2); // CZ_REQ_ACH_REWARD packet(0x0a26,7); // ZC_REQ_ACH_REWARD_ACK // changed packet sizes packet(0x0a18,14); // ZC_ACCEPT_ENTER3 diff --git a/src/map/packets_struct.h b/src/map/packets_struct.h index 9ae99afda..ef78f76aa 100644 --- a/src/map/packets_struct.h +++ b/src/map/packets_struct.h @@ -302,6 +302,11 @@ enum packet_headers { rouletteinfoackType = 0xa1c, roulettgenerateackType = 0xa20, roulettercvitemackType = 0xa22, +#if PACKETVER >= 20141016 + achievementListType = 0xa23, + achievementUpdateType = 0xa24, + achievementRewardAckType = 0xa26, +#endif // PACKETVER >= 20141016 #if PACKETVER >= 20150513 // [4144] 0x09f8 handling in client from 2014-10-29aRagexe and 2014-03-26cRagexeRE questListType = 0x9f8, ///< ZC_ALL_QUEST_LIST3 #elif PACKETVER >= 20141022 @@ -2586,6 +2591,41 @@ struct PACKET_ZC_SEARCH_STORE_INFO_ACK { struct PACKET_ZC_SEARCH_STORE_INFO_ACK_sub items[]; } __attribute__((packed)); +/* Achievement System */ +struct ach_list_info { + uint32 ach_id; + uint8 completed; + uint32 objective[MAX_ACHIEVEMENT_OBJECTIVES]; + uint32 completed_at; + uint8 reward; +} __attribute__((packed)); + +struct packet_achievement_list { + uint16 packet_id; + uint16 packet_len; + uint32 total_achievements; + uint32 total_points; + uint16 rank; + uint32 current_rank_points; + uint32 next_rank_points; + struct ach_list_info ach[MAX_ACHIEVEMENT_DB]; +} __attribute__((packed)); + +struct packet_achievement_update { + uint16 packet_id; + uint32 total_points; + uint16 rank; + uint32 current_rank_points; + uint32 next_rank_points; + struct ach_list_info ach; +} __attribute__((packed)); + +struct packet_achievement_reward_ack { + uint16 packet_id; + uint8 received; + uint32 ach_id; +} __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/party.c b/src/map/party.c index 9024b1c19..e4fb18c23 100644 --- a/src/map/party.c +++ b/src/map/party.c @@ -36,6 +36,7 @@ #include "map/pc.h" #include "map/skill.h" #include "map/status.h" +#include "map/achievement.h" #include "common/HPM.h" #include "common/cbasetypes.h" #include "common/memmgr.h" @@ -189,6 +190,9 @@ static int party_create(struct map_session_data *sd, const char *name, int item, party->fill_member(&leader, sd, 1); intif->create_party(&leader,name,item,item2); + + achievement->validate_party_create(sd); //Achievements (Smokexyz) + return 0; } diff --git a/src/map/pc.c b/src/map/pc.c index f2cff8ab3..6f9347113 100644 --- a/src/map/pc.c +++ b/src/map/pc.c @@ -55,6 +55,7 @@ #include "map/skill.h" #include "map/status.h" // struct status_data #include "map/storage.h" +#include "map/achievement.h" #include "common/cbasetypes.h" #include "common/conf.h" #include "common/core.h" // get_svn_revision() @@ -1053,6 +1054,11 @@ static bool pc_adoption(struct map_session_data *p1_sd, struct map_session_data pc->skill(p1_sd, WE_CALLBABY, 1, SKILL_GRANT_PERMANENT); pc->skill(p2_sd, WE_CALLBABY, 1, SKILL_GRANT_PERMANENT); + // Achievements [Smokexyz/Hercules] + achievement->validate_adopt(p1_sd, true); // Parent 1 + achievement->validate_adopt(p2_sd, true); // Parent 2 + achievement->validate_adopt(b_sd, false); // Baby + return true; } @@ -1325,6 +1331,7 @@ static bool pc_authok(struct map_session_data *sd, int login_id2, time_t expirat sd->bg_queue.type = 0; VECTOR_INIT(sd->script_queues); + VECTOR_INIT(sd->achievement); // Achievements [Smokexyz/Hercules] VECTOR_INIT(sd->storage.item); // initialize storage item vector. VECTOR_INIT(sd->hatEffectId); @@ -1384,6 +1391,7 @@ static bool pc_authok(struct map_session_data *sd, int login_id2, time_t expirat " Group '"CL_WHITE"%d"CL_RESET"').\n", sd->status.name, sd->status.account_id, sd->status.char_id, CONVIP(ip), sd->group_id); + // Send friends list clif->friendslist_send(sd); @@ -1474,7 +1482,6 @@ static int pc_reg_received(struct map_session_data *sd) nullpo_ret(sd); sd->vars_ok = true; - sd->change_level_2nd = pc_readglobalreg(sd,script->add_str("jobchange_level")); sd->change_level_3rd = pc_readglobalreg(sd,script->add_str("jobchange_level_3rd")); sd->die_counter = pc_readglobalreg(sd,script->add_str("PC_DIE_COUNTER")); @@ -1593,6 +1600,9 @@ static int pc_reg_received(struct map_session_data *sd) if( npc->motd ) /* [Ind/Hercules] */ script->run(npc->motd->u.scr.script, 0, sd->bl.id, npc->fake_nd->bl.id); + // Achievements [Smokexyz/Hercules] + intif->achievements_request(sd); + return 1; } @@ -4500,6 +4510,8 @@ static int pc_payzeny(struct map_session_data *sd, int zeny, enum e_log_pick_typ sd->status.zeny -= zeny; clif->updatestatus(sd,SP_ZENY); + achievement->validate_zeny(sd, -zeny); // Achievements [Smokexyz/Hercules] + if(!tsd) tsd = sd; logs->zeny(sd, type, tsd, -zeny); if( zeny > 0 && sd->state.showzeny ) { @@ -4636,6 +4648,8 @@ static int pc_getzeny(struct map_session_data *sd, int zeny, enum e_log_pick_typ sd->status.zeny += zeny; clif->updatestatus(sd,SP_ZENY); + achievement->validate_zeny(sd, zeny); // Achievements [Smokexyz/Hercules] + if(!tsd) tsd = sd; logs->zeny(sd, type, tsd, zeny); if( zeny > 0 && sd->state.showzeny ) { @@ -4760,6 +4774,7 @@ static int pc_additem(struct map_session_data *sd, struct item *item_data, int a sd->status.inventory[i].amount = amount; sd->inventory_data[i] = data; clif->additem(sd,i,amount,0); + } if( ( !itemdb->isstackable2(data) || data->flag.force_serial || data->type == IT_CASH) && !item_data->unique_id ) @@ -4767,6 +4782,8 @@ static int pc_additem(struct map_session_data *sd, struct item *item_data, int a logs->pick_pc(sd, log_type, amount, &sd->status.inventory[i],sd->inventory_data[i]); + achievement->validate_item_get(sd, sd->status.inventory[i].nameid, sd->status.inventory[i].amount); // Achievements [Smokexyz/Hercules] + sd->weight += w; clif->updatestatus(sd,SP_WEIGHT); //Auto-equip @@ -6879,11 +6896,15 @@ static int pc_checkbaselevelup(struct map_session_data *sd) clif->misceffect(&sd->bl,0); npc->script_event(sd, NPCE_BASELVUP); //LORDALFA - LVLUPEVENT - if(sd->status.party_id) + if (sd->status.party_id) party->send_levelup(sd); pc->baselevelchanged(sd); + quest->questinfo_refresh(sd); + + achievement->validate_stats(sd, SP_BASELEVEL, sd->status.base_level); + return 1; } @@ -6946,7 +6967,11 @@ static int pc_checkjoblevelup(struct map_session_data *sd) clif->status_change(&sd->bl,SI_DEVIL1, 1, 0, 0, 0, 1); //Permanent blind effect from SG_DEVIL. npc->script_event(sd, NPCE_JOBLVUP); + quest->questinfo_refresh(sd); + + achievement->validate_stats(sd, SP_BASELEVEL, sd->status.job_level); + return 1; } @@ -7243,6 +7268,8 @@ static int pc_setstat(struct map_session_data *sd, int type, int val) return -1; } + achievement->validate_stats(sd, type, val); // Achievements [Smokexyz/Hercules] + return val; } @@ -7974,6 +8001,14 @@ static void pc_damage(struct map_session_data *sd, struct block_list *src, unsig if (battle_config.prevent_logout_trigger & PLT_DAMAGE) sd->canlog_tick = timer->gettick(); + + // Achievements [Smokexyz/Hercules] + if (src != NULL) { + if (src->type == BL_PC) + achievement->validate_pc_damage(BL_UCAST(BL_PC, src), sd, hp); + else if (src->type == BL_MOB) + achievement->validate_mob_damage(sd, hp, true); + } } /*========================================== @@ -8124,6 +8159,8 @@ static int pc_dead(struct map_session_data *sd, struct block_list *src) pc->setparam(ssd, SP_KILLEDRID, sd->bl.id); npc->script_event(ssd, NPCE_KILLPC); + achievement->validate_pc_kill(ssd, sd); // Achievements [Smokexyz/Hercules] + if (battle_config.pk_mode&2) { ssd->status.manner -= 5; if(ssd->status.manner < 0) @@ -9042,6 +9079,8 @@ static int pc_jobchange(struct map_session_data *sd, int class, int upper) } quest->questinfo_refresh(sd); + achievement->validate_jobchange(sd); // Achievements [Smokexyz/Hercules] + return 0; } @@ -10682,6 +10721,11 @@ static int pc_marriage(struct map_session_data *sd, struct map_session_data *dst return -1; sd->status.partner_id = dstsd->status.char_id; dstsd->status.partner_id = sd->status.char_id; + + // Achievements [Smokexyz/Hercules] + achievement->validate_marry(sd); + achievement->validate_marry(dstsd); + return 0; } diff --git a/src/map/pc.h b/src/map/pc.h index 622dcf3f7..9839258e4 100644 --- a/src/map/pc.h +++ b/src/map/pc.h @@ -37,7 +37,7 @@ #include "common/db.h" #include "common/ers.h" // struct eri #include "common/hercules.h" -#include "common/mmo.h" // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus, NEW_CARTS +#include "common/mmo.h" // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus, NEW_CARTS, struct s_achievement /** * Defines @@ -631,6 +631,10 @@ END_ZEROED_BLOCK; unsigned sitstand : 1; unsigned commands : 1; } block_action; + + /* Achievement System */ + struct char_achievements achievement; + bool achievements_received; }; #define EQP_WEAPON EQP_HAND_R diff --git a/src/map/pet.c b/src/map/pet.c index e544905c0..678972f14 100644 --- a/src/map/pet.c +++ b/src/map/pet.c @@ -23,6 +23,7 @@ #include "config/core.h" // DBPATH #include "pet.h" +#include "map/achievement.h" #include "map/atcommand.h" // msg_txt() #include "map/battle.h" #include "map/chrif.h" @@ -576,8 +577,8 @@ static int pet_catch_process2(struct map_session_data *sd, int target_id) pet_catch_rate = (pet->db[i].capture + (sd->status.base_level - md->level)*30 + sd->battle_status.luk*20)*(200 - get_percentage(md->status.hp, md->status.max_hp))/100; if(pet_catch_rate < 1) pet_catch_rate = 1; - if(battle_config.pet_catch_rate != 100) - pet_catch_rate = (pet_catch_rate*battle_config.pet_catch_rate)/100; + if(battle->bc->pet_catch_rate != 100) + pet_catch_rate = (pet_catch_rate*battle->bc->pet_catch_rate)/100; if(rnd()%10000 < pet_catch_rate) { @@ -586,6 +587,8 @@ static int pet_catch_process2(struct map_session_data *sd, int target_id) clif->pet_roulette(sd,1); intif->create_pet(sd->status.account_id,sd->status.char_id,pet->db[i].class_,mob->db(pet->db[i].class_)->lv, pet->db[i].EggID,0,pet->db[i].intimate,100,0,1,pet->db[i].jname); + + achievement->validate_taming(sd, pet->db[i].class_); } else { diff --git a/src/map/script.c b/src/map/script.c index 9a5d33604..200a43ba0 100644 --- a/src/map/script.c +++ b/src/map/script.c @@ -57,6 +57,7 @@ #include "map/status.h" #include "map/storage.h" #include "map/unit.h" +#include "map/achievement.h" #include "common/cbasetypes.h" #include "common/conf.h" #include "common/db.h" @@ -9574,6 +9575,11 @@ static BUILDIN(successrefitem) clif->additem(sd,i,1,0); pc->equipitem(sd,i,ep); clif->misceffect(&sd->bl,3); + + achievement->validate_refine(sd, i, true); // Achievements [Smokexyz/Hercules] + + /* The following check is exclusive to characters (possibly only whitesmiths) + * that create equipments and refine them to level 10. */ if(sd->status.inventory[i].refine == 10 && sd->status.inventory[i].card[0] == CARD0_FORGE && sd->status.char_id == (int)MakeDWord(sd->status.inventory[i].card[2],sd->status.inventory[i].card[3]) @@ -9611,6 +9617,9 @@ static BUILDIN(failedrefitem) if (num > 0 && num <= ARRAYLENGTH(script->equip)) i=pc->checkequip(sd,script->equip[num-1]); if(i >= 0) { + // Call before changing refine to 0. + achievement->validate_refine(sd, i, false); + sd->status.inventory[i].refine = 0; pc->unequipitem(sd, i, PCUNEQUIPITEM_RECALC|PCUNEQUIPITEM_FORCE); //recalculate bonus clif->refine(sd->fd,1,i,sd->status.inventory[i].refine); //notify client of failure @@ -9658,6 +9667,9 @@ static BUILDIN(downrefitem) clif->additem(sd,i,1,0); pc->equipitem(sd,i,ep); + + achievement->validate_refine(sd, i, false); // Achievements [Smokexyz/Hercules] + clif->misceffect(&sd->bl,2); } @@ -21246,6 +21258,78 @@ static BUILDIN(showevent) } /*========================================== + * Achievement System [Smokexyz/Hercules] + *-----------------------------------------*/ +/** + * Validates an objective index for the given achievement. + * Can be used for any achievement type. + * @command achievement_progress(<ach_id>,<obj_idx>,<progress>,<incremental?>{,<char_id>}); + * @param aid - achievement ID + * @param obj_idx - achievement objective index. + * @param progress - objective progress towards goal. + * @Param incremental - (boolean) true to add the progress towards the goal, + * false to use the progress only as a comparand. + * @param account_id - (optional) character ID to perform on. + * @return true on success, false on failure. + * @push 1 on success, 0 on failure. + */ +static BUILDIN(achievement_progress) +{ + struct map_session_data *sd = script->rid2sd(st); + int aid = script_getnum(st, 2); + int obj_idx = script_getnum(st, 3); + int progress = script_getnum(st, 4); + int incremental = script_getnum(st, 5); + int account_id = script_hasdata(st, 6) ? script_getnum(st, 6) : 0; + const struct achievement_data *ad = NULL; + + if ((ad = achievement->get(aid)) == NULL) { + ShowError("buildin_achievement_progress: Invalid achievement ID %d received.\n", aid); + script_pushint(st, 0); + return false; + } + + if (obj_idx <= 0 || obj_idx > VECTOR_LENGTH(ad->objective)) { + ShowError("buildin_achievement_progress: Invalid objective index %d received. (min: %d, max: %d)\n", obj_idx, 0, VECTOR_LENGTH(ad->objective)); + script_pushint(st, 0); + return false; + } + + obj_idx--; // convert to array index. + + if (progress <= 0 || progress > VECTOR_INDEX(ad->objective, obj_idx).goal) { + ShowError("buildin_achievement_progress: Progress exceeds goal limit for achievement id %d.\n", aid); + script_pushint(st, 0); + return false; + } + + if (incremental < 0 || incremental > 1) { + ShowError("buildin_achievement_progress: Argument 4 expects boolean (0/1). provided value: %d\n", incremental); + script_pushint(st, 0); + return false; + } + + if (script_hasdata(st, 6)) { + if (account_id <= 0) { + ShowError("buildin_achievement_progress: Invalid Account id %d provided.\n", account_id); + script_pushint(st, 0); + return false; + } else if ((sd = map->id2sd(account_id)) == NULL) { + ShowError("buildin_achievement_progress: Account with id %d was not found.\n", account_id); + script_pushint(st, 0); + return false; + } + } + + if (achievement->validate(sd, aid, obj_idx, progress, incremental ? true : false)) + script_pushint(st, progress); + else + script_pushint(st, 0); + + return true; +} + +/*========================================== * BattleGround System *------------------------------------------*/ static BUILDIN(waitingroom2bg) @@ -25124,6 +25208,8 @@ static void script_parse_builtin(void) BUILDIN_DEF(agitstart2,""), BUILDIN_DEF(agitend2,""), BUILDIN_DEF(agitcheck2,""), + // Achievements [Smokexyz/Hercules] + BUILDIN_DEF(achievement_progress, "iiii?"), // BattleGround BUILDIN_DEF(waitingroom2bg,"siiss?"), BUILDIN_DEF(waitingroom2bg_single,"isiis"), diff --git a/src/map/unit.c b/src/map/unit.c index 2fd8d6efb..91f7e0acd 100644 --- a/src/map/unit.c +++ b/src/map/unit.c @@ -2765,6 +2765,7 @@ static int unit_free(struct block_list *bl, clr_type clrtype) sd->instance = NULL; } VECTOR_CLEAR(sd->script_queues); + VECTOR_CLEAR(sd->achievement); // Achievement [Smokexyz/Hercules] VECTOR_CLEAR(sd->storage.item); VECTOR_CLEAR(sd->hatEffectId); sd->storage.received = false; diff --git a/src/plugins/HPMHooking.c b/src/plugins/HPMHooking.c index ca64aca6a..b477cb5c3 100644 --- a/src/plugins/HPMHooking.c +++ b/src/plugins/HPMHooking.c @@ -48,6 +48,7 @@ PRAGMA_GCC5(GCC diagnostic ignored "-Wdiscarded-qualifiers") #define HPM_SOURCES_INCLUDE "HPMHooking/HPMHooking_char.sources.inc" #include "char/char.h" #include "char/geoip.h" +#include "char/int_achievement.h" #include "char/int_auction.h" #include "char/int_clan.h" #include "char/int_elemental.h" @@ -71,6 +72,7 @@ PRAGMA_GCC5(GCC diagnostic ignored "-Wdiscarded-qualifiers") #define HPM_HOOKS_INCLUDE "HPMHooking/HPMHooking_map.Hooks.inc" #define HPM_POINTS_INCLUDE "HPMHooking/HPMHooking_map.HookingPoints.inc" #define HPM_SOURCES_INCLUDE "HPMHooking/HPMHooking_map.sources.inc" +#include "map/achievement.h" #include "map/atcommand.h" #include "map/battle.h" #include "map/battleground.h" |