From a5588dd9ab00d33e9e09c8cc4b34a83710dcc3c6 Mon Sep 17 00:00:00 2001 From: Lemongrass3110 Date: Mon, 16 Jul 2018 21:39:42 +0200 Subject: [PATCH] Initial Release of Attendance Feature (#3297) Thanks to @secretdataz and @aleos89 for their help. Thanks to @Haikenz and @admkakaroto for testing. Thanks to @Daegaladh for his ideas. --- conf/battle/feature.conf | 4 + conf/groups.conf | 2 + conf/map_athena.conf | 4 +- conf/msg_conf/map_msg.conf | 11 +- db/import-tmpl/attendance.yml | 3 + db/pre-re/attendance.yml | 3 + db/re/attendance.yml | 61 ++++++++ db/re/item_db.txt | 14 +- db/re/item_package.txt | 34 ++-- db/re/item_trade.txt | 11 +- doc/script_commands.txt | 1 + src/common/utilities.hpp | 6 + src/map/battle.cpp | 8 + src/map/battle.hpp | 1 + src/map/clif.cpp | 65 ++++++++ src/map/clif.hpp | 13 ++ src/map/clif_packetdb.hpp | 5 +- src/map/date.cpp | 2 + src/map/date.hpp | 1 + src/map/itemdb.hpp | 2 +- src/map/map-server.vcxproj | 1 + src/map/map.cpp | 2 +- src/map/map.hpp | 23 +-- src/map/pc.cpp | 283 ++++++++++++++++++++++++++++++++++ src/map/pc.hpp | 6 + src/map/pc_groups.hpp | 2 + src/map/script_constants.hpp | 3 +- 27 files changed, 534 insertions(+), 37 deletions(-) create mode 100644 db/import-tmpl/attendance.yml create mode 100644 db/pre-re/attendance.yml create mode 100644 db/re/attendance.yml diff --git a/conf/battle/feature.conf b/conf/battle/feature.conf index cee0dad97a..f451103b32 100644 --- a/conf/battle/feature.conf +++ b/conf/battle/feature.conf @@ -70,3 +70,7 @@ feature.achievement: on // Homunculues Autofeeding (Note 1) // Requires: 2017-09-20bRagexeRE or later feature.homunculus_autofeed: off + +// Attendance System (Note 1) +// Requires: 2018-03-07bRagexeRE or later +feature.attendance: off diff --git a/conf/groups.conf b/conf/groups.conf index ccdde95ff8..75198f7594 100644 --- a/conf/groups.conf +++ b/conf/groups.conf @@ -94,6 +94,7 @@ groups: ( can_trade: true can_party: true command_enable: true + attendance: true } }, { @@ -135,6 +136,7 @@ groups: ( langtype: true } permissions: { + attendance: false } }, { diff --git a/conf/map_athena.conf b/conf/map_athena.conf index 8c2a56522a..b2e6d683a9 100644 --- a/conf/map_athena.conf +++ b/conf/map_athena.conf @@ -105,11 +105,13 @@ minsave_time: 100 // 32: After successfully submitting an item for auction // 64: After successfully get/delete/complete a quest // 128: After every bank transaction (deposit/withdraw) +// 256: After every attendance reward +// 4095: Always // NOTE: These settings decrease the chance of dupes/lost items when there's a // server crash at the expense of increasing the map/char server lag. If your // server rarely crashes, but experiences interserver lag, you may want to set // these off. -save_settings: 255 +save_settings: 4095 // Message of the day file, when a character logs on, this message is displayed. motd_txt: conf/motd.txt diff --git a/conf/msg_conf/map_msg.conf b/conf/msg_conf/map_msg.conf index 8e2b5b1478..9cf4262d80 100644 --- a/conf/msg_conf/map_msg.conf +++ b/conf/msg_conf/map_msg.conf @@ -843,7 +843,16 @@ 786: The guild does not have a guild storage. 787: You do not have permission to use the guild storage. -//788-899 free +// Attendance +// Mail sender: Officer +788: 3455 +// Mail title: %dday attendance has been paid. +789: 3456,%d +// Mail body: %dday attendance has been paid. +790: 3456,%d +791: You are not allowed to use the attendance system. + +//792-899 free //------------------------------------ // More atcommands message diff --git a/db/import-tmpl/attendance.yml b/db/import-tmpl/attendance.yml new file mode 100644 index 0000000000..338333625c --- /dev/null +++ b/db/import-tmpl/attendance.yml @@ -0,0 +1,3 @@ +Header: + Type: ATTENDANCE_CONF + Version: 1 diff --git a/db/pre-re/attendance.yml b/db/pre-re/attendance.yml new file mode 100644 index 0000000000..338333625c --- /dev/null +++ b/db/pre-re/attendance.yml @@ -0,0 +1,3 @@ +Header: + Type: ATTENDANCE_CONF + Version: 1 diff --git a/db/re/attendance.yml b/db/re/attendance.yml new file mode 100644 index 0000000000..6de2f414d4 --- /dev/null +++ b/db/re/attendance.yml @@ -0,0 +1,61 @@ +Header: + Type: ATTENDANCE_CONF + Version: 1 + +Attendance: + - Start: 20180502 + End: 20180529 + Rewards: + - Day: 1 + ItemId: 22979 + - Day: 2 + ItemId: 6316 + - Day: 3 + ItemId: 12265 + Amount: 5 + - Day: 4 + ItemId: 23047 + Amount: 5 + - Day: 5 + ItemId: 23038 + - Day: 6 + ItemId: 23043 + - Day: 7 + ItemId: 23340 + Amount: 3 + - Day: 8 + ItemId: 12516 + Amount: 5 + - Day: 9 + ItemId: 23307 + Amount: 5 + - Day: 10 + ItemId: 12610 + - Day: 11 + ItemId: 14533 + Amount: 2 + - Day: 12 + ItemId: 23012 + Amount: 3 + - Day: 13 + ItemId: 23048 + Amount: 5 + - Day: 14 + ItemId: 12264 + Amount: 5 + - Day: 15 + ItemId: 23046 + Amount: 5 + - Day: 16 + ItemId: 12515 + Amount: 5 + - Day: 17 + ItemId: 12522 + Amount: 5 + - Day: 18 + ItemId: 12523 + Amount: 5 + - Day: 19 + ItemId: 6234 + - Day: 20 + ItemId: 22845 diff --git a/db/re/item_db.txt b/db/re/item_db.txt index ede944a738..3d8f807269 100644 --- a/db/re/item_db.txt +++ b/db/re/item_db.txt @@ -6827,6 +6827,7 @@ 12607,Lolli_Pop_Box,Delicious Lollipop Box,11,20,,10,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} 12608,Splendid_Box2,Splendid Box2,11,20,,100,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} 12609,Old_Ore_Box,Old Ore Box,2,20,,100,,,,,0xFFFFFFFF,63,2,,,,,,{ getgroupitem(IG_Old_Ore_Box); },{},{} +12610,Mysterious_Egg,Mysterious Egg,2,,,10,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} 12612,Old_Coin_Pocket,Old Coin Bag,2,20,,10,,,,,0xFFFFFFFF,63,2,,,,,,{ getgroupitem(IG_Old_Coin_Pocket); },{},{} 12613,High_Coin_Pocket,Improved Coin Bag,2,20,,10,,,,,0xFFFFFFFF,63,2,,,,,,{ getgroupitem(IG_High_Coin_Pocket); },{},{} 12614,Mid_Coin_Pocket,Intermediate Coin Bag,2,20,,10,,,,,0xFFFFFFFF,63,2,,,,,,{ getgroupitem(IG_Mid_Coin_Pocket); },{},{} @@ -9277,7 +9278,7 @@ 17181,Jan_Groove_Box,Jan Groove Box,2,20,,10,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} 17184,3rd_Test_Pass_Box,3rd Test Pass Box,18,0,,0,,,,,0xFFFFFFFF,63,2,,,,,,{ getitem 6583,1; },{},{} 17203,Free_Pass_Box,Free Pass Box,2,20,,10,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} -17204,Mysterious_Egg,Shining Egg,18,10,,10,,,,0,0xFFFFFFFF,63,2,,,1,,,{ getgroupitem(IG_Mysterious_Egg); },{},{} +17204,Shining_Egg,Shining Egg,18,10,,10,,,,0,0xFFFFFFFF,63,2,,,1,,,{ getgroupitem(IG_Shining_Egg); },{},{} 17207,Idn_Heart_Scroll,Idn Heart Scroll,2,20,,10,,,,,0xFFFFFFFF,63,2,,,,,,{ getgroupitem(IG_Idn_Heart_Scroll); },{},{} 17209,Tw_Rainbow_Scroll,Tw Rainbow Scroll,2,20,,10,,,,,0xFFFFFFFF,63,2,,,,,,{ getgroupitem(IG_Tw_Rainbow_Scroll); },{},{} 17210,Tw_Red_Scroll,Tw Red Scroll,2,20,,10,,,,,0xFFFFFFFF,63,2,,,,,,{ getgroupitem(IG_Tw_Red_Scroll); },{},{} @@ -11155,6 +11156,7 @@ 22782,PC_Bang_Wooden_Box,PC Bang Wooden Box,2,10,,200,,,,0,0xFFFFFFFF,63,2,,,1,,,{ getitem 547,30; /*No Info*/},{},{} 22783,PC_Bang_Golden_Box,PC Bang Golden Box,2,10,,200,,,,0,0xFFFFFFFF,63,2,,,1,,,{ getitem 547,1; getitem 985,10; /*No Info*/},{},{} 22784,PC_Bang_Platinum_Box,PC Bang Platinum Box,2,10,,200,,,,0,0xFFFFFFFF,63,2,,,1,,,{ getitem 547,1; getitem 12017,10; getitem 678,12; /*No Info*/},{},{} +22979,C_Battle_Gum_2,[Sale] Battle Manual and Bubble Gum,2,,,0,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} 22802,Safe_to_6_Equipment_Certificate,Safe to 6 Equipment Certificate,3,10,,10,,,,,,,,,,,,,{},{},{} 22808,Special_Gift_Box,Special Gift Box,2,10,,100,,,,,,,,,,,,,{},{},{} 22812,Sealed_Dracula_Scroll,Sealed Dracula Scroll,2,10,,10,,,,0,0xFFFFFFFF,63,2,,,1,,,{ getitem callfunc("F_Rand",6228,6232,22813,19937,17314, 6635),1; },{},{} @@ -11213,6 +11215,13 @@ 22984,Kahluna_Milk,Kahluna Milk,0,6,,10,,,,,0xFFFFFFFF,63,2,,,,,,{ sc_start SC_DORAM_BUF_01, 180000, 0; },{},{} 22985,Basil,Basil,0,10,,10,,,,,0xFFFFFFFF,63,2,,,,,,{ sc_start SC_DORAM_BUF_02, 180000, 0; },{},{} // +23012,S_Small_Mana_Potion,[Sale] Small Mana Potion,2,,,10,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} +23038,S_Slim_White_Box,[Sale] Slim White Potion Box,2,,,0,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} +23043,S_Seed_Of_Yggdrasil_Box,[Sale] Yggdrasil Seed Box,2,,,0,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} +23046,S_Mystic_Powder,[Sale] Mystic Powder,2,,,0,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} +23047,S_Blessing_Tyr,[Sale] Blessing of Tyr,2,,,10,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} +23048,S_Resilience_Potion,[Sale] Resilience Enhancement Potion,2,,,10,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} +// 23123,Bullet_Case_Flare,Flare Bullet Cartridge,2,10,,250,,,,,0xFFFFFFFF,63,2,,,,,,{ getitem 13228,500; },{},{} 23124,Bullet_Case_Lighting,Lightning Bullet Cartridge,2,10,,250,,,,,0xFFFFFFFF,63,2,,,,,,{ getitem 13229,500; },{},{} 23125,Bullet_Case_Ice,Ice Bullet Cartridge,2,10,,250,,,,,0xFFFFFFFF,63,2,,,,,,{ getitem 13230,500; },{},{} @@ -11227,6 +11236,9 @@ 23196,Agust_Lucky_Scroll,Shining Blue Lucky Egg,18,10,,10,,,,0,0xFFFFFFFF,63,2,,,1,,,{ getgroupitem(IG_Agust_Lucky_Scroll); },{},{} // 23277,Mado_Box,Emergency Magic Gear,2,10000,,3000,,,,,0x00000400,56,2,,,100,,,{ setmadogear 1; },{},{} +// +23307,S_Shining_Def_Scroll,[Sale] Shining Defense Scroll,2,,,10,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} +23340,S_Megaphone,[Sale] Megaphone,2,,,10,,,,,0xFFFFFFFF,63,2,,,,,,{},{},{} //=================================================================== // Shadow Equipments //=================================================================== diff --git a/db/re/item_package.txt b/db/re/item_package.txt index f492f6f40a..42b8388a2b 100644 --- a/db/re/item_package.txt +++ b/db/re/item_package.txt @@ -4473,23 +4473,23 @@ IG_Something_Candy_Holder,22067,1,1 // 1x Witch Shoes IG_Something_Candy_Holder,22669,5,1 // 1x October Spooky Trade Box IG_Something_Candy_Holder,22670,1,1 // 1x DARK INVITATION -// Mysterious_Egg -IG_Mysterious_Egg,12259,200,1,1,0,0,0 // 1x Miracle_Medicine -IG_Mysterious_Egg,5374,1,1,1,0,0,0 // 1x L_Magestic_Goat -IG_Mysterious_Egg,5254,99,1,1,0,0,0 // 1x Deviling_Hat -IG_Mysterious_Egg,12246,99,1,1,0,0,0 // 1x Magic_Card_Album -IG_Mysterious_Egg,4302,1,1,1,0,0,0 // 1x Tao_Gunka_Card -IG_Mysterious_Egg,5474,200,1,1,0,0,0 // 1x Notice_Board -IG_Mysterious_Egg,2554,1,1,1,0,0,0 // 1x Piece_Of_Angent_Skin -IG_Mysterious_Egg,17001,1099,1,1,0,0,0 // 1x Wander_Man_Box10 -IG_Mysterious_Egg,12903,1000,1,1,0,0,0 // 1x Str_Dish_Box -IG_Mysterious_Egg,12922,1100,1,1,0,0,0 // 1x Token_Of_Siegfried_Box -IG_Mysterious_Egg,16755,1200,1,1,0,0,0 // 1x Unbreak_Def_Box -IG_Mysterious_Egg,12909,800,1,1,0,0,0 // 1x Kafra_Card_Box -IG_Mysterious_Egg,14232,800,1,1,0,0,0 // 1x Yggdrasilberry_Box_ -IG_Mysterious_Egg,12361,1000,2,1,0,0,0 // 2x Delicious_Shaved_Ice -IG_Mysterious_Egg,12910,1100,1,1,0,0,0 // 1x Giant_Fly_Wing_Box -IG_Mysterious_Egg,16753,1300,1,1,0,0,0 // 1x Unbreak_Weap_Box +// Shining Egg +IG_Shining_Egg,12259,200,1,1,0,0,0 // 1x Miracle_Medicine +IG_Shining_Egg,5374,1,1,1,0,0,0 // 1x L_Magestic_Goat +IG_Shining_Egg,5254,99,1,1,0,0,0 // 1x Deviling_Hat +IG_Shining_Egg,12246,99,1,1,0,0,0 // 1x Magic_Card_Album +IG_Shining_Egg,4302,1,1,1,0,0,0 // 1x Tao_Gunka_Card +IG_Shining_Egg,5474,200,1,1,0,0,0 // 1x Notice_Board +IG_Shining_Egg,2554,1,1,1,0,0,0 // 1x Piece_Of_Angent_Skin +IG_Shining_Egg,17001,1099,1,1,0,0,0 // 1x Wander_Man_Box10 +IG_Shining_Egg,12903,1000,1,1,0,0,0 // 1x Str_Dish_Box +IG_Shining_Egg,12922,1100,1,1,0,0,0 // 1x Token_Of_Siegfried_Box +IG_Shining_Egg,16755,1200,1,1,0,0,0 // 1x Unbreak_Def_Box +IG_Shining_Egg,12909,800,1,1,0,0,0 // 1x Kafra_Card_Box +IG_Shining_Egg,14232,800,1,1,0,0,0 // 1x Yggdrasilberry_Box_ +IG_Shining_Egg,12361,1000,2,1,0,0,0 // 2x Delicious_Shaved_Ice +IG_Shining_Egg,12910,1100,1,1,0,0,0 // 1x Giant_Fly_Wing_Box +IG_Shining_Egg,16753,1300,1,1,0,0,0 // 1x Unbreak_Weap_Box // Agust_Lucky_Scroll IG_Agust_Lucky_Scroll,4365,1,1,1,1,0,0 // 1x B_Katrinn_Card diff --git a/db/re/item_trade.txt b/db/re/item_trade.txt index f3e67e6b98..045009b5dc 100644 --- a/db/re/item_trade.txt +++ b/db/re/item_trade.txt @@ -1440,7 +1440,7 @@ 12596,475,100 // Magic_Candy 12600,507,100 // Treasure_Box_Scroll 12607,507,100 // Lolli_Pop_Box -//12610,475,100 // +12610,475,100 // Mysterious_Egg 12622,507,100 // Boarding_Halter 12625,475,100 // Sapa_Feat_Cert_Pack 12633,475,100 // Malang_Cat_Can @@ -3869,6 +3869,15 @@ //22950,475,100 // //22951,475,100 // //22952,475,100 // +22979,475,100 // C_Battle_Gum_2 +23012,475,100 // S_Small_Mana_Potion +23038,475,100 // S_Slim_White_Box +23043,475,100 // S_Seed_Of_Yggdrasil_Box +23046,475,100 // S_Mystic_Powder +23047,475,100 // S_Blessing_Tyr +23048,475,100 // S_Resilience_Potion +23307,475,100 // S_Shining_Def_Scroll +23340,475,100 // S_Megaphone 23177,475,100 // Kafra_Card_ 23196,475,100 // Shining_Blue_Lucky_Egg 25043,499,100 // Thorny_Vine_Flute diff --git a/doc/script_commands.txt b/doc/script_commands.txt index 286bf7035d..779b0d7bc3 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -3135,6 +3135,7 @@ DT_DAYOFMONTH - Day of the current month DT_MONTH - Month (constants for JANUARY to DECEMBER are available) DT_YEAR - Year DT_DAYOFYEAR - Day of the year +DT_YYYYMMDD - current date in the form YYYYMMDD It will only return numbers. If another type is supplied -1 will be returned. diff --git a/src/common/utilities.hpp b/src/common/utilities.hpp index 022a29198e..18fa5cb646 100644 --- a/src/common/utilities.hpp +++ b/src/common/utilities.hpp @@ -8,6 +8,8 @@ #include #include +#include "cbasetypes.hpp" + // Class used to perform time measurement class cScopeTimer { struct sPimpl; //this is to avoid long compilation time @@ -20,6 +22,10 @@ int levenshtein( const std::string &s1, const std::string &s2 ); namespace rathena { namespace util { + template bool map_exists( std::map& map, K key ){ + return map.find( key ) != map.end(); + } + template V* map_find( std::map& map, K key ){ auto it = map.find( key ); diff --git a/src/map/battle.cpp b/src/map/battle.cpp index 3ed9cdfb2a..29d04b86f1 100644 --- a/src/map/battle.cpp +++ b/src/map/battle.cpp @@ -8562,6 +8562,7 @@ static const struct _battle_data { { "feature.homunculus_autofeed", &battle_config.feature_homunculus_autofeed, 1, 0, 1, }, { "summoner_trait", &battle_config.summoner_trait, 3, 0, 3, }, { "homunculus_autofeed_always", &battle_config.homunculus_autofeed_always, 1, 0, 1, }, + { "feature.attendance", &battle_config.feature_attendance, 1, 0, 1, }, #include "../custom/battle_config_init.inc" }; @@ -8699,6 +8700,13 @@ void battle_adjust_conf() } #endif +#if PACKETVER < 20180307 + if( battle_config.feature_attendance ){ + ShowWarning("conf/battle/feature.conf attendance system is enabled but it requires PACKETVER 2018-03-07 or newer, disabling...\n"); + battle_config.feature_attendance = 0; + } +#endif + #ifndef CELL_NOSTACK if (battle_config.custom_cell_stack_limit != 1) ShowWarning("Battle setting 'custom_cell_stack_limit' takes no effect as this server was compiled without Cell Stack Limit support.\n"); diff --git a/src/map/battle.hpp b/src/map/battle.hpp index ca4e38e1fb..2ad3fb650e 100644 --- a/src/map/battle.hpp +++ b/src/map/battle.hpp @@ -643,6 +643,7 @@ struct Battle_Config int feature_homunculus_autofeed; int summoner_trait; int homunculus_autofeed_always; + int feature_attendance; #include "../custom/battle_config_struct.inc" }; diff --git a/src/map/clif.cpp b/src/map/clif.cpp index f058e336f5..2faa9299fd 100644 --- a/src/map/clif.cpp +++ b/src/map/clif.cpp @@ -9930,6 +9930,21 @@ void clif_msg_skill(struct map_session_data* sd, uint16 skill_id, int msg_id) WFIFOSET(fd, packet_len(0x7e6)); } +/// Displays msgstringtable.txt string in a color. (ZC_MSG_COLOR). +/// 09cd .W .L +void clif_msg_color( struct map_session_data *sd, uint16 msg_id, uint32 color ){ + nullpo_retv(sd); + + int fd = sd->fd; + + WFIFOHEAD(fd, packet_len(0x9cd)); + WFIFOW(fd, 0) = 0x9cd; + WFIFOW(fd, 2) = msg_id; + WFIFOL(fd, 4) = color; + + WFIFOSET(fd, packet_len(0x9cd)); +} + /// Validates one global/guild/party/whisper message packet and tries to recognize its components. /// Returns true if the packet was parsed successfully. /// Formats: false - .w .w ( : ).?B 00 @@ -20285,6 +20300,56 @@ void clif_parse_changedress( int fd, struct map_session_data* sd ){ #endif } +/// Opens an UI window of the given type and initializes it with the given data +/// 0AE2 .B .L +void clif_ui_open( struct map_session_data *sd, enum out_ui_type ui_type, int32 data ){ + nullpo_retv(sd); + + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xae2)); + WFIFOW(fd,0) = 0xae2; + WFIFOB(fd,2) = ui_type; + WFIFOL(fd,3) = data; + WFIFOSET(fd,packet_len(0xae2)); +} + +/// Request to open an UI window of the given type +/// 0A68 .B +void clif_parse_open_ui( int fd, struct map_session_data* sd ){ + switch( RFIFOB(fd,2) ){ + case IN_UI_ATTENDANCE: + if( !pc_has_permission( sd, PC_PERM_ATTENDANCE ) ){ + clif_messagecolor( &sd->bl, color_table[COLOR_RED], msg_txt( sd, 791 ), false, SELF ); // You are not allowed to use the attendance system. + }else if( pc_attendance_enabled() ){ + clif_ui_open( sd, OUT_UI_ATTENDANCE, pc_attendance_counter( sd ) ); + }else{ + clif_msg_color( sd, MSG_ATTENDANCE_DISABLED, color_table[COLOR_RED] ); + } + break; + } +} + +/// Response for attedance request +/// 0AF0 .L .L +void clif_attendence_response( struct map_session_data *sd, int32 data ){ + nullpo_retv(sd); + + int fd = sd->fd; + + WFIFOHEAD(fd,packet_len(0xAF0)); + WFIFOW(fd,0) = 0xAF0; + WFIFOL(fd,2) = 0; + WFIFOL(fd,6) = data; + WFIFOSET(fd,packet_len(0xAF0)); +} + +/// Request from the client to retrieve today's attendance reward +/// 0AEF +void clif_parse_attendance_request( int fd, struct map_session_data* sd ){ + pc_attendance_claim_reward(sd); +} + /*========================================== * Main client packet processing function *------------------------------------------*/ diff --git a/src/map/clif.hpp b/src/map/clif.hpp index 5c27592f22..61b4b87c9e 100644 --- a/src/map/clif.hpp +++ b/src/map/clif.hpp @@ -510,6 +510,7 @@ enum clif_messages : uint16_t { SKILL_NEED_REVOLVER = 0x9fd, SKILL_NEED_HOLY_BULLET = 0x9fe, SKILL_NEED_GRENADE = 0xa01, + MSG_ATTENDANCE_DISABLED = 0xd92, }; enum e_personalinfo : uint8_t { @@ -1075,4 +1076,16 @@ void clif_achievement_list_all(struct map_session_data *sd); void clif_achievement_update(struct map_session_data *sd, struct achievement *ach, int count); void clif_achievement_reward_ack(int fd, unsigned char result, int ach_id); +/// Attendance System +enum in_ui_type : int8 { + IN_UI_ATTENDANCE = 5 +}; + +enum out_ui_type : int8 { + OUT_UI_ATTENDANCE = 7 +}; + +void clif_ui_open( struct map_session_data *sd, enum out_ui_type ui_type, int32 data ); +void clif_attendence_response( struct map_session_data *sd, int32 data ); + #endif /* _CLIF_HPP_ */ diff --git a/src/map/clif_packetdb.hpp b/src/map/clif_packetdb.hpp index e4a8293b68..8620d11937 100644 --- a/src/map/clif_packetdb.hpp +++ b/src/map/clif_packetdb.hpp @@ -2143,6 +2143,7 @@ parseable_packet(0x096E,-1,clif_parse_merge_item_req,2,4); // CZ_REQ_MERGE_ITEM ack_packet(ZC_ACK_MERGE_ITEM,0x096F,7,2,4,6,7); // ZC_ACK_MERGE_ITEM parseable_packet(0x0974,2,clif_parse_merge_item_cancel,0); // CZ_CANCEL_MERGE_ITEM + packet(0x9CD,8); // ZC_MSG_COLOR #endif // 2013-08-21bRagexe @@ -2381,9 +2382,9 @@ // 2018-03-07bRagexeRE #if PACKETVER >= 20180307 - parseable_packet(0x0A68,3,clif_parse_dull,0); + parseable_packet(0x0A68,3,clif_parse_open_ui,2); packet(0x0AE2,7); - parseable_packet(0x0AEF,2,clif_parse_dull,0); + parseable_packet(0x0AEF,2,clif_parse_attendance_request,0); packet(0x0AF0,10); #endif diff --git a/src/map/date.cpp b/src/map/date.cpp index b93399ccf8..f9fa71d3c3 100644 --- a/src/map/date.cpp +++ b/src/map/date.cpp @@ -123,6 +123,8 @@ int date_get( enum e_date_type type ) return date_get_year(); case DT_DAYOFYEAR: return date_get_dayofyear(); + case DT_YYYYMMDD: + return date_get( DT_YEAR ) * 10000 + date_get( DT_MONTH ) * 100 + date_get(DT_DAYOFMONTH); default: return -1; } diff --git a/src/map/date.hpp b/src/map/date.hpp index 96016ebcac..46cd0c7114 100644 --- a/src/map/date.hpp +++ b/src/map/date.hpp @@ -41,6 +41,7 @@ enum e_date_type{ DT_MONTH, DT_YEAR, DT_DAYOFYEAR, + DT_YYYYMMDD, DT_MAX }; diff --git a/src/map/itemdb.hpp b/src/map/itemdb.hpp index bf37742233..71820e8d1d 100644 --- a/src/map/itemdb.hpp +++ b/src/map/itemdb.hpp @@ -714,7 +714,7 @@ enum e_random_item_group { IG_COSTAMA_EGG29, IG_INK_BALL, IG_SOMETHING_CANDY_HOLDER, - IG_MYSTERIOUS_EGG, + IG_SHINING_EGG, IG_AGUST_LUCKY_SCROLL, IG_ELEMENT, IG_POISON, diff --git a/src/map/map-server.vcxproj b/src/map/map-server.vcxproj index 4c634905d9..9d36de1b38 100644 --- a/src/map/map-server.vcxproj +++ b/src/map/map-server.vcxproj @@ -292,6 +292,7 @@ + diff --git a/src/map/map.cpp b/src/map/map.cpp index 10ed9ab143..91a26f4388 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -125,7 +125,7 @@ int map_port=0; int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; int minsave_interval = 100; -unsigned char save_settings = CHARSAVE_ALL; +int16 save_settings = CHARSAVE_ALL; bool agit_flag = false; bool agit2_flag = false; bool agit3_flag = false; diff --git a/src/map/map.hpp b/src/map/map.hpp index a579f4d728..444b265da5 100644 --- a/src/map/map.hpp +++ b/src/map/map.hpp @@ -753,7 +753,7 @@ extern int map_num; extern int autosave_interval; extern int minsave_interval; -extern unsigned char save_settings; +extern int16 save_settings; extern int night_flag; // 0=day, 1=night [Yor] extern int enable_spy; //Determines if @spy commands are active. @@ -780,16 +780,17 @@ extern struct s_map_default map_default; /// Type of 'save_settings' enum save_settings_type { - CHARSAVE_NONE = 0, - CHARSAVE_TRADE = 0x01, /// After trading - CHARSAVE_VENDING = 0x02, /// After vending (open/transaction) - CHARSAVE_STORAGE = 0x04, /// After closing storage/guild storage. - CHARSAVE_PET = 0x08, /// After hatching/returning to egg a pet. - CHARSAVE_MAIL = 0x10, /// After successfully sending a mail with attachment - CHARSAVE_AUCTION = 0x20, /// After successfully submitting an item for auction - CHARSAVE_QUEST = 0x40, /// After successfully get/delete/complete a quest - CHARSAVE_BANK = 0x80, /// After every bank transaction (deposit/withdraw) - CHARSAVE_ALL = 0xFF, + CHARSAVE_NONE = 0x000, /// Never + CHARSAVE_TRADE = 0x001, /// After trading + CHARSAVE_VENDING = 0x002, /// After vending (open/transaction) + CHARSAVE_STORAGE = 0x004, /// After closing storage/guild storage. + CHARSAVE_PET = 0x008, /// After hatching/returning to egg a pet. + CHARSAVE_MAIL = 0x010, /// After successfully sending a mail with attachment + CHARSAVE_AUCTION = 0x020, /// After successfully submitting an item for auction + CHARSAVE_QUEST = 0x040, /// After successfully get/delete/complete a quest + CHARSAVE_BANK = 0x080, /// After every bank transaction (deposit/withdraw) + CHARSAVE_ATTENDANCE = 0x100, /// After every attendence reward + CHARSAVE_ALL = 0xFFF, /// Always }; // users diff --git a/src/map/pc.cpp b/src/map/pc.cpp index f2a48bf93c..6a7f686723 100755 --- a/src/map/pc.cpp +++ b/src/map/pc.cpp @@ -3,9 +3,14 @@ #include "pc.hpp" +#include +#include + #include #include +#include + #include "../common/cbasetypes.hpp" #include "../common/core.hpp" // get_svn_revision() #include "../common/ers.hpp" // ers_destroy @@ -17,6 +22,7 @@ #include "../common/socket.hpp" // session[] #include "../common/strlib.hpp" // safestrncpy() #include "../common/timer.hpp" +#include "../common/utilities.hpp" #include "../common/utils.hpp" #include "achievement.hpp" @@ -53,7 +59,10 @@ #include "unit.hpp" // unit_stop_attack(), unit_stop_walking() #include "vending.hpp" // struct s_vending +using namespace rathena; + int pc_split_atoui(char* str, unsigned int* val, char sep, int max); +static inline bool pc_attendance_rewarded_today( struct map_session_data* sd ); #define PVP_CALCRANK_INTERVAL 1000 // PVP calculation interval #define MAX_LEVEL_BASE_EXP 99999999 ///< Max Base EXP for player on Max Base Level @@ -82,6 +91,19 @@ struct fame_list taekwon_fame_list[MAX_FAME_LIST]; struct s_job_info job_info[CLASS_COUNT]; +struct s_attendance_reward{ + uint16 item_id; + uint16 amount; +}; + +struct s_attendance_period{ + uint32 start; + uint32 end; + std::map rewards; +}; + +std::vector attendance_periods; + #define MOTD_LINE_SIZE 128 static char motd_text[MOTD_LINE_SIZE][CHAT_SIZE_MAX]; // Message of the day buffer [Valaris] @@ -11724,6 +11746,10 @@ void pc_damage_log_clear(struct map_session_data *sd, int id) void pc_scdata_received(struct map_session_data *sd) { pc_inventory_rentals(sd); // Needed here to remove rentals that have Status Changes after chrif_load_scdata has finished + if( pc_has_permission( sd, PC_PERM_ATTENDANCE ) && pc_attendance_enabled() && !pc_attendance_rewarded_today( sd ) ){ + clif_ui_open( sd, OUT_UI_ATTENDANCE, pc_attendance_counter( sd ) ); + } + sd->state.pc_loaded = true; if (sd->state.connect_new == 0 && sd->fd) { // Character already loaded map! Gotta trigger LoadEndAck manually. @@ -12531,6 +12557,260 @@ void pc_set_costume_view(struct map_session_data *sd) { clif_changelook(&sd->bl, LOOK_ROBE, sd->status.robe); } +struct s_attendance_period* pc_attendance_period(){ + uint32 date = date_get(DT_YYYYMMDD); + + for( struct s_attendance_period& period : attendance_periods ){ + if( period.start <= date && period.end >= date ){ + return . + } + } + + return nullptr; +} + +bool pc_attendance_enabled(){ + // Check if the attendance feature is disabled + if( !battle_config.feature_attendance ){ + return false; + } + + // Check if there is a running attendance period + return pc_attendance_period() != nullptr; +} + +static inline bool pc_attendance_rewarded_today( struct map_session_data* sd ){ + return pc_readreg2( sd, ATTENDANCE_DATE_VAR ) >= date_get(DT_YYYYMMDD); +} + +int32 pc_attendance_counter( struct map_session_data* sd ){ + struct s_attendance_period* period = pc_attendance_period(); + + // No running attendance period + if( period == nullptr ){ + return 0; + } + + // Get the counter for the current period + int counter = pc_readreg2( sd, ATTENDANCE_COUNT_VAR ); + + // Check if we have a remaining counter from a previous period + if( counter > 0 && pc_readreg2( sd, ATTENDANCE_DATE_VAR ) < period->start ){ + // Reset the counter to zero + pc_setreg2( sd, ATTENDANCE_COUNT_VAR, 0 ); + + return 0; + } + + return 10 * counter + ( ( pc_attendance_rewarded_today(sd) ) ? 1 : 0 ); +} + +void pc_attendance_claim_reward( struct map_session_data* sd ){ + // If the user's group does not have the permission + if( !pc_has_permission( sd, PC_PERM_ATTENDANCE ) ){ + return; + } + + // Check if the attendance feature is disabled + if( !pc_attendance_enabled() ){ + return; + } + + // Check if the user already got his reward today + if( pc_attendance_rewarded_today( sd ) ){ + return; + } + + int32 attendance_counter = pc_readreg2( sd, ATTENDANCE_COUNT_VAR ); + + attendance_counter += 1; + + struct s_attendance_period* period = pc_attendance_period(); + + if( period == nullptr ){ + return; + } + + if( period->rewards.size() < attendance_counter ){ + return; + } + + pc_setreg2( sd, ATTENDANCE_DATE_VAR, date_get(DT_YYYYMMDD) ); + pc_setreg2( sd, ATTENDANCE_COUNT_VAR, attendance_counter ); + + if( save_settings&CHARSAVE_ATTENDANCE ) + chrif_save(sd, CSAVE_NORMAL); + + struct s_attendance_reward& reward = period->rewards.at( attendance_counter - 1 ); + + struct mail_message msg; + + memset( &msg, 0, sizeof( struct mail_message ) ); + + msg.dest_id = sd->status.char_id; + safestrncpy( msg.send_name, msg_txt( sd, 788 ), NAME_LENGTH ); + safesnprintf( msg.title, MAIL_TITLE_LENGTH, msg_txt( sd, 789 ), attendance_counter ); + safesnprintf( msg.body, MAIL_BODY_LENGTH, msg_txt( sd, 790 ), attendance_counter ); + + msg.item[0].nameid = reward.item_id; + msg.item[0].amount = reward.amount; + msg.item[0].identify = 1; + + msg.status = MAIL_NEW; + msg.type = MAIL_INBOX_NORMAL; + msg.timestamp = time(NULL); + + intif_Mail_send(0, &msg); + + clif_attendence_response( sd, attendance_counter ); +} + +void pc_attendance_load( std::string path ){ + YAML::Node root; + + try{ + root = YAML::LoadFile( path ); + }catch( ... ){ + ShowError( "pc_attendance_load: Failed to read attendance configuration file \"%s\".\n", path.c_str() ); + return; + } + + if( root["Attendance"] ){ + YAML::Node attendance = root["Attendance"]; + + for( const auto &periodNode : attendance ){ + if( !periodNode["Start"].IsDefined() ){ + ShowError( "pc_attendance_load: Missing \"Start\" for period in line %d.\n", periodNode.Mark().line ); + continue; + } + + YAML::Node startNode = periodNode["Start"]; + + if( !periodNode["End"].IsDefined() ){ + ShowError( "pc_attendance_load: Missing \"End\" for period in line %d.\n", periodNode.Mark().line ); + continue; + } + + YAML::Node endNode = periodNode["End"]; + + if( !periodNode["Rewards"].IsDefined() ){ + ShowError( "pc_attendance_load: Missing \"Rewards\" for period in line %d.\n", periodNode.Mark().line ); + continue; + } + + YAML::Node rewardsNode = periodNode["Rewards"]; + + uint32 start = startNode.as(); + uint32 end = endNode.as(); + + // If the period is outdated already, we do not even bother parsing + if( end < date_get( DT_YYYYMMDD ) ){ + continue; + } + + // Collision detection + bool collision = false; + + for( struct s_attendance_period& period : attendance_periods ){ + // Check if start is inside another period + if( period.start <= start && start <= period.end ){ + ShowError( "pc_attendance_load: period start %u intersects with period %u-%u.\n", start, period.start, period.end ); + collision = true; + break; + } + + // Check if end is inside another period + if( period.start <= end && end <= period.end ){ + ShowError( "pc_attendance_load: period end %u intersects with period %u-%u.\n", start, period.start, period.end ); + collision = true; + break; + } + } + + if( collision ){ + continue; + } + + struct s_attendance_period period; + + period.start = start; + period.end = end; + + for( const auto& rewardNode : rewardsNode ){ + if( !rewardNode["Day"].IsDefined() ){ + ShowError( "pc_attendance_load: No day defined for node in line %d.\n", rewardNode.Mark().line ); + continue; + } + + uint32 day = rewardNode["Day"].as(); + + if( !rewardNode["ItemId"].IsDefined() ){ + ShowError( "pc_attendance_load: No reward defined for day %d.\n", day ); + continue; + } + + YAML::Node itemNode = rewardNode["ItemId"]; + + uint16 item_id = itemNode.as(); + + if( item_id == 0 || !itemdb_exists( item_id ) ){ + ShowError( "pc_attendance_load: Unknown item ID %hu for day %d.\n", item_id, day ); + continue; + } + + uint16 amount; + + if( rewardNode["Amount"] ){ + amount = rewardNode["Amount"].as(); + + if( amount == 0 ){ + ShowError( "pc_attendance_load: Invalid reward count %hu for day %d. Defaulting to 1...\n", amount, day ); + amount = 1; + }else if( amount > MAX_AMOUNT ){ + ShowError( "pc_attendance_load: Reward count %hu above maximum %hu for day %d. Defaulting to %hu...\n", amount, MAX_AMOUNT, day, MAX_AMOUNT ); + amount = MAX_AMOUNT; + } + }else{ + amount = 1; + } + + struct s_attendance_reward* reward = &period.rewards[day - 1]; + + reward->item_id = item_id; + reward->amount = amount; + } + + bool missing_day = false; + + for( int day = 0; day < period.rewards.size(); day++ ){ + if( !util::map_exists( period.rewards, day ) ){ + ShowError( "pc_attendance_load: Reward for day %d is missing.\n", day + 1 ); + missing_day = true; + break; + } + } + + if( missing_day ){ + continue; + } + + attendance_periods.push_back( period ); + } + } +} + +void pc_read_attendance(){ + char path[1024]; + + sprintf( path, "%s/%sattendance.yml", db_path, DBPATH ); + + pc_attendance_load( path ); + + sprintf( path, "%s/%s/attendance.yml", db_path, DBIMPORT ); + + pc_attendance_load( path ); +} + /*========================================== * pc Init/Terminate *------------------------------------------*/ @@ -12542,6 +12822,8 @@ void do_final_pc(void) { ers_destroy(pc_itemgrouphealrate_ers); ers_destroy(num_reg_ers); ers_destroy(str_reg_ers); + + attendance_periods.clear(); } void do_init_pc(void) { @@ -12550,6 +12832,7 @@ void do_init_pc(void) { pc_readdb(); pc_read_motd(); // Read MOTD [Valaris] + pc_read_attendance(); add_timer_func_list(pc_invincible_timer, "pc_invincible_timer"); add_timer_func_list(pc_eventtimer, "pc_eventtimer"); diff --git a/src/map/pc.hpp b/src/map/pc.hpp index 9b0c6d0f6b..fa724b4ec2 100644 --- a/src/map/pc.hpp +++ b/src/map/pc.hpp @@ -47,6 +47,8 @@ enum sc_type : int16; #define JOBCHANGE3RD_VAR "jobchange_level_3rd" #define TKMISSIONID_VAR "TK_MISSION_ID" #define TKMISSIONCOUNT_VAR "TK_MISSION_COUNT" +#define ATTENDANCE_DATE_VAR "#AttendanceDate" +#define ATTENDANCE_COUNT_VAR "#AttendanceCounter" //Update this max as necessary. 55 is the value needed for Super Baby currently //Raised to 85 since Expanded Super Baby needs it. @@ -1338,4 +1340,8 @@ bool pc_job_can_entermap(enum e_job jobid, int m, int group_lv); int pc_level_penalty_mod(int level_diff, uint32 mob_class, enum e_mode mode, int type); #endif +bool pc_attendance_enabled(); +int32 pc_attendance_counter( struct map_session_data* sd ); +void pc_attendance_claim_reward( struct map_session_data* sd ); + #endif /* _PC_HPP_ */ diff --git a/src/map/pc_groups.hpp b/src/map/pc_groups.hpp index 27f00e2fc3..6420442988 100644 --- a/src/map/pc_groups.hpp +++ b/src/map/pc_groups.hpp @@ -51,6 +51,7 @@ enum e_pc_permission : uint32 { PC_PERM_ENABLE_COMMAND = 0x01000000, PC_PERM_BYPASS_STAT_ONCLONE = 0x02000000, PC_PERM_BYPASS_MAX_STAT = 0x04000000, + PC_PERM_ATTENDANCE = 0x08000000, //.. add other here PC_PERM_ALLPERMISSION = 0xFFFFFFFF, }; @@ -86,6 +87,7 @@ static const struct s_pcg_permission_name { { "command_enable",PC_PERM_ENABLE_COMMAND }, { "bypass_stat_onclone",PC_PERM_BYPASS_STAT_ONCLONE }, { "bypass_max_stat",PC_PERM_BYPASS_MAX_STAT }, + { "attendance",PC_PERM_ATTENDANCE }, { "all_permission", PC_PERM_ALLPERMISSION }, }; diff --git a/src/map/script_constants.hpp b/src/map/script_constants.hpp index 5e5fb15f4b..4b5f055738 100644 --- a/src/map/script_constants.hpp +++ b/src/map/script_constants.hpp @@ -4451,6 +4451,7 @@ export_constant(DT_MONTH); export_constant(DT_YEAR); export_constant(DT_DAYOFYEAR); + export_constant(DT_YYYYMMDD); /* instance info */ export_constant(IIT_ID); @@ -4940,7 +4941,7 @@ export_constant(IG_COSTAMA_EGG29); export_constant(IG_INK_BALL); export_constant(IG_SOMETHING_CANDY_HOLDER); - export_constant(IG_MYSTERIOUS_EGG); + export_constant(IG_SHINING_EGG); export_constant(IG_AGUST_LUCKY_SCROLL); export_constant(IG_ELEMENT); export_constant(IG_POISON);