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.
This commit is contained in:
Lemongrass3110 2018-07-16 21:39:42 +02:00 committed by GitHub
parent 030443c9d3
commit a5588dd9ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 534 additions and 37 deletions

View File

@ -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

View File

@ -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
}
},
{

View File

@ -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

View File

@ -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: <MSG>3455</MSG>
// Mail title: %dday attendance has been paid.
789: <MSG>3456,%d</MSG>
// Mail body: %dday attendance has been paid.
790: <MSG>3456,%d</MSG>
791: You are not allowed to use the attendance system.
//792-899 free
//------------------------------------
// More atcommands message

View File

@ -0,0 +1,3 @@
Header:
Type: ATTENDANCE_CONF
Version: 1

3
db/pre-re/attendance.yml Normal file
View File

@ -0,0 +1,3 @@
Header:
Type: ATTENDANCE_CONF
Version: 1

61
db/re/attendance.yml Normal file
View File

@ -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

View File

@ -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
//===================================================================

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -8,6 +8,8 @@
#include <string>
#include <map>
#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 <typename K, typename V> bool map_exists( std::map<K,V>& map, K key ){
return map.find( key ) != map.end();
}
template <typename K, typename V> V* map_find( std::map<K,V>& map, K key ){
auto it = map.find( key );

View File

@ -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");

View File

@ -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"
};

View File

@ -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 <msg id>.W <color>.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 - <packet id>.w <packet len>.w (<name> : <message>).?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 <type>.B <data>.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 <type>.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 <unknown>.L <data>.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
*------------------------------------------*/

View File

@ -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_ */

View File

@ -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

View File

@ -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;
}

View File

@ -41,6 +41,7 @@ enum e_date_type{
DT_MONTH,
DT_YEAR,
DT_DAYOFYEAR,
DT_YYYYMMDD,
DT_MAX
};

View File

@ -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,

View File

@ -292,6 +292,7 @@
<Copy SourceFiles="$(SolutionDir)conf\msg_conf\import-tmpl\map_msg_tha_conf.txt" DestinationFolder="$(SolutionDir)conf\msg_conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\msg_conf\import\map_msg_tha_conf.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\abra_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\abra_db.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\achievement_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\achievement_db.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\attendance.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\attendance.yml')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\attr_fix.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\attr_fix.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\castle_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\castle_db.txt')" />
<Copy SourceFiles="$(SolutionDir)db\import-tmpl\const.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\const.txt')" />

View File

@ -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;

View File

@ -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

View File

@ -3,9 +3,14 @@
#include "pc.hpp"
#include <map>
#include <vector>
#include <math.h>
#include <stdlib.h>
#include <yaml-cpp/yaml.h>
#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<int,struct s_attendance_reward> rewards;
};
std::vector<struct s_attendance_period> 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 &period;
}
}
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>();
uint32 end = endNode.as<uint32>();
// 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<uint32>();
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<uint16>();
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<uint16>();
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");

View File

@ -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_ */

View File

@ -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 },
};

View File

@ -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);