From 2b94f0aeff3d60f97d1a7d3f39942769d8fa6547 Mon Sep 17 00:00:00 2001 From: Aleos Date: Sat, 27 Feb 2021 17:14:10 -0500 Subject: [PATCH] Implements autoloot support for Mercenary (#5594) * Fixed #5570. * Adds the ability for mercenary to trigger autoloot. * Adds idle sharing and idle option configs for granular settings. Thanks to @saya9200's suggestion and @Lemongrass3110! Co-authored-by: Lemongrass3110 --- conf/battle/drops.conf | 32 ++++++++++++++++++++++++++++++++ doc/script_commands.txt | 8 ++++++++ src/map/atcommand.cpp | 2 ++ src/map/battle.cpp | 3 +++ src/map/battle.hpp | 3 +++ src/map/clif.cpp | 24 ++++++++++++++++++++++++ src/map/mob.cpp | 20 +++++++++++--------- src/map/pc.hpp | 2 ++ src/map/script.cpp | 12 ++++++++++++ 9 files changed, 97 insertions(+), 9 deletions(-) diff --git a/conf/battle/drops.conf b/conf/battle/drops.conf index e8ae97cb34..a98c73f8f6 100644 --- a/conf/battle/drops.conf +++ b/conf/battle/drops.conf @@ -153,3 +153,35 @@ rare_drop_announce: 0 // If RENEWAL_DROP, Bubble Gum, or any other modifiers are active autoloot // will take them into account. autoloot_adjust: 0 + +// Does autoloot work when a monster is killed by mercenary only? +mercenary_autoloot: no + +// Is getting items from a mercenary disabled when their master's idle? +// Set to no, or the amount of seconds (NOT milliseconds) that need to pass before considering +// a character idle. +// Characters in a chat/vending are always considered idle. +// A character's idle status is reset upon item use/skill use/attack (auto attack counts too)/movement. +// Their master will only receive items if 'mercenary_autoloot' is activated, +// otherwise they will be dropped on the ground as usual. +// NOTE: This option uses a special timer to track idle time, separated from the normal idle timer. +mer_idle_no_share: no + +// How the server should measure the mercenary master's idle time? (Note 3) +// (This will only work if 'mer_idle_no_share' is enabled). +// 0x001 - Walk Request +// 0x002 - UseSkillToID Request (Targetted skill use attempt) +// 0x004 - UseSkillToPos Request (AoE skill use attempt) +// 0x008 - UseItem Request (Including equip/unequip) +// 0x010 - Attack Request +// 0x020 - Chat Request (Whisper, Party, Guild, Battlegrounds, etc) +// 0x040 - Sit/Standup Request +// 0x080 - Emotion Request +// 0x100 - DropItem Request +// 0x200 - @/#Command Request +// Please note that at least 1 option has to be enabled. +// Be mindful that the more options used, the easier it becomes to cheat this features. +// Default: walk (0x1) + useskilltoid (0x2) + useskilltopos (0x4) + useitem (0x8) + attack (0x10) = 0x1F +// NOTE: This allows you to configure different settings for mercenary, separated from normal idle timer and 'idletime_option'. +// It will only apply to mercenary-only kills and it will not affect normal autoloot and party share options. +idletime_mer_option: 0x1F diff --git a/doc/script_commands.txt b/doc/script_commands.txt index e87b3bb0ec..9173200b14 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -3939,6 +3939,14 @@ This will only work if 'hom_idle_no_share' and 'idletime_hom_option' are enabled --------------------------------------- +*checkidlemer({""}) + +Returns the time, in seconds, that the specified player has been idle for mercenary item share. +Name is optional, and defaults to the attached player if omitted. +This will only work if 'mer_idle_no_share' and 'idletime_mer_option' are enabled (see '/conf/battle/drops.conf'). + +--------------------------------------- + *agitcheck() *agitcheck2() *agitcheck3() diff --git a/src/map/atcommand.cpp b/src/map/atcommand.cpp index b8df7c4150..080a740392 100644 --- a/src/map/atcommand.cpp +++ b/src/map/atcommand.cpp @@ -10860,6 +10860,8 @@ bool is_atcommand(const int fd, struct map_session_data* sd, const char* message sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_ATCOMMAND) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_ATCOMMAND) + sd->idletime_mer = last_tick; //Clearing these to be used once more. memset(command, '\0', sizeof(command)); diff --git a/src/map/battle.cpp b/src/map/battle.cpp index edcc70b68c..6db7af24df 100644 --- a/src/map/battle.cpp +++ b/src/map/battle.cpp @@ -9125,6 +9125,9 @@ static const struct _battle_data { { "homunculus_starving_delay", &battle_config.homunculus_starving_delay, 20000, 0, INT_MAX, }, { "drop_connection_on_quit", &battle_config.drop_connection_on_quit, 0, 0, 1, }, { "mob_spawn_variance", &battle_config.mob_spawn_variance, 1, 0, 3, }, + { "mercenary_autoloot", &battle_config.mercenary_autoloot, 0, 0, 1, }, + { "mer_idle_no_share" , &battle_config.mer_idle_no_share, 0, 0, INT_MAX, }, + { "idletime_mer_option", &battle_config.idletime_mer_option, 0x1F, 0x1, 0xFFF, }, #include "../custom/battle_config_init.inc" }; diff --git a/src/map/battle.hpp b/src/map/battle.hpp index 5060d6d711..7031eecf65 100644 --- a/src/map/battle.hpp +++ b/src/map/battle.hpp @@ -689,6 +689,9 @@ struct Battle_Config int homunculus_starving_delay; int drop_connection_on_quit; int mob_spawn_variance; + int mercenary_autoloot; + int mer_idle_no_share; + int idletime_mer_option; #include "../custom/battle_config_struct.inc" }; diff --git a/src/map/clif.cpp b/src/map/clif.cpp index 00af265c8a..12a7b9da1e 100644 --- a/src/map/clif.cpp +++ b/src/map/clif.cpp @@ -10336,6 +10336,8 @@ static bool clif_process_message(struct map_session_data* sd, bool whisperFormat sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_CHAT) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_CHAT) + sd->idletime_mer = last_tick; //achievement_update_objective(sd, AG_CHATTING, 1, 1); // !TODO: Confirm how this achievement is triggered @@ -11150,6 +11152,8 @@ void clif_parse_WalkToXY(int fd, struct map_session_data *sd) sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_WALK) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_WALK) + sd->idletime_mer = last_tick; unit_walktoxy(&sd->bl, x, y, 4); } @@ -11369,6 +11373,8 @@ void clif_parse_Emotion(int fd, struct map_session_data *sd) sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_EMOTION) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_EMOTION) + sd->idletime_mer = last_tick; if (sd->state.block_action & PCBLOCK_EMOTION) { clif_skill_fail(sd, 1, USESKILL_FAIL_LEVEL, 1); @@ -11455,6 +11461,8 @@ void clif_parse_ActionRequest_sub(struct map_session_data *sd, int action_type, sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_ATTACK) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_ATTACK) + sd->idletime_mer = last_tick; unit_attack(&sd->bl, target_id, action_type != 0); break; case 0x02: // sitdown @@ -11487,6 +11495,8 @@ void clif_parse_ActionRequest_sub(struct map_session_data *sd, int action_type, sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_SIT) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_SIT) + sd->idletime_mer = last_tick; pc_setsit(sd); skill_sit(sd, true); @@ -11512,6 +11522,8 @@ void clif_parse_ActionRequest_sub(struct map_session_data *sd, int action_type, sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_SIT) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_SIT) + sd->idletime_mer = last_tick; skill_sit(sd, false); clif_standing(&sd->bl); } @@ -11771,6 +11783,8 @@ void clif_parse_DropItem(int fd, struct map_session_data *sd){ sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_DROPITEM) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_DROPITEM) + sd->idletime_mer = last_tick; return; } @@ -11803,6 +11817,8 @@ void clif_parse_UseItem(int fd, struct map_session_data *sd) sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_USEITEM) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_USEITEM) + sd->idletime_mer = last_tick; n = RFIFOW(fd,packet_db[RFIFOW(fd,0)].pos[0])-2; if(n <0 || n >= MAX_INVENTORY) @@ -11852,6 +11868,8 @@ void clif_parse_EquipItem(int fd,struct map_session_data *sd) sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_USEITEM) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_USEITEM) + sd->idletime_mer = last_tick; //Client doesn't send the position for ammo. if(sd->inventory_data[index]->type == IT_AMMO) @@ -11893,6 +11911,8 @@ void clif_parse_UnequipItem(int fd,struct map_session_data *sd) sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_USEITEM) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_USEITEM) + sd->idletime_mer = last_tick; pc_unequipitem(sd,index,1); } @@ -12550,6 +12570,8 @@ void clif_parse_skill_toid( struct map_session_data* sd, uint16 skill_id, uint16 sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_USESKILLTOID) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_USESKILLTOID) + sd->idletime_mer = last_tick; if( sd->npc_id ){ if( pc_hasprogress( sd, WIP_DISABLE_SKILLITEM ) || !sd->npc_item_flag || !( inf & INF_SELF_SKILL ) ){ @@ -12680,6 +12702,8 @@ static void clif_parse_UseSkillToPosSub(int fd, struct map_session_data *sd, uin sd->idletime = last_tick; if (battle_config.hom_idle_no_share && sd->hd && battle_config.idletime_hom_option&IDLE_USESKILLTOPOS) sd->idletime_hom = last_tick; + if (battle_config.mer_idle_no_share && sd->md && battle_config.idletime_mer_option&IDLE_USESKILLTOPOS) + sd->idletime_mer = last_tick; if( skill_isNotOk(skill_id, sd) ) return; diff --git a/src/map/mob.cpp b/src/map/mob.cpp index 8cca6e3a15..7745bb5bc2 100644 --- a/src/map/mob.cpp +++ b/src/map/mob.cpp @@ -2239,9 +2239,9 @@ static TIMER_FUNC(mob_delay_item_drop){ * Sets the item_drop into the item_drop_list. * Also performs logging and autoloot if enabled. * rate is the drop-rate of the item, required for autoloot. - * flag : Killed only by homunculus? + * flag : Killed only by homunculus/mercenary? *------------------------------------------*/ -static void mob_item_drop(struct mob_data *md, struct item_drop_list *dlist, struct item_drop *ditem, int loot, int drop_rate, unsigned short flag) +static void mob_item_drop(struct mob_data *md, struct item_drop_list *dlist, struct item_drop *ditem, int loot, int drop_rate, bool flag) { TBL_PC* sd; bool test_autoloot; @@ -2253,7 +2253,7 @@ static void mob_item_drop(struct mob_data *md, struct item_drop_list *dlist, str if( sd == NULL ) sd = map_charid2sd(dlist->third_charid); test_autoloot = sd && (drop_rate <= sd->state.autoloot || pc_isautolooting(sd, ditem->item_data.nameid)) - && (flag?(battle_config.homunculus_autoloot?(battle_config.hom_idle_no_share == 0 || !pc_isidle_hom(sd)):0): + && (flag ? ((battle_config.homunculus_autoloot ? (battle_config.hom_idle_no_share == 0 || !pc_isidle_hom(sd)) : 0) || (battle_config.mercenary_autoloot ? (battle_config.mer_idle_no_share == 0 || !pc_isidle_mer(sd)) : 0)) : (battle_config.idle_no_autoloot == 0 || DIFF_TICK(last_tick, sd->idletime) < battle_config.idle_no_autoloot)); #ifdef AUTOLOOT_DISTANCE test_autoloot = test_autoloot && sd->bl.m == md->bl.m @@ -2515,7 +2515,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) int dmgbltypes = 0; // bitfield of all bl types, that caused damage to the mob and are elligible for exp distribution unsigned int mvp_damage; t_tick tick = gettick(); - bool rebirth, homkillonly; + bool rebirth, homkillonly, merckillonly; status = &md->status; @@ -2581,6 +2581,8 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) // determines, if the monster was killed by homunculus' damage only homkillonly = (bool)( ( dmgbltypes&BL_HOM ) && !( dmgbltypes&~BL_HOM ) ); + // determines if the monster was killed by mercenary damage only + merckillonly = (bool)((dmgbltypes & BL_MER) && !(dmgbltypes & ~BL_MER)); if(!battle_config.exp_calc_type && count > 1) { //Apply first-attacker 200% exp share bonus //TODO: Determine if this should go before calculating the MVP player instead of after. @@ -2848,7 +2850,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) } // Announce first, or else ditem will be freed. [Lance] // By popular demand, use base drop rate for autoloot code. [Skotlex] - mob_item_drop(md, dlist, ditem, 0, battle_config.autoloot_adjust ? drop_rate : md->db->dropitem[i].rate, homkillonly); + mob_item_drop(md, dlist, ditem, 0, battle_config.autoloot_adjust ? drop_rate : md->db->dropitem[i].rate, homkillonly || merckillonly); } // Ore Discovery [Celest] @@ -2857,7 +2859,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) memset(&mobdrop, 0, sizeof(struct s_mob_drop)); mobdrop.nameid = itemdb_searchrandomid(IG_FINDINGORE,1); ditem = mob_setdropitem(&mobdrop, 1, md->mob_id); - mob_item_drop(md, dlist, ditem, 0, battle_config.finding_ore_rate/10, homkillonly); + mob_item_drop(md, dlist, ditem, 0, battle_config.finding_ore_rate/10, homkillonly || merckillonly); } if(sd) { @@ -2889,7 +2891,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) memset(&mobdrop, 0, sizeof(struct s_mob_drop)); mobdrop.nameid = dropid; - mob_item_drop(md, dlist, mob_setdropitem(&mobdrop,1,md->mob_id), 0, drop_rate, homkillonly); + mob_item_drop(md, dlist, mob_setdropitem(&mobdrop,1,md->mob_id), 0, drop_rate, homkillonly || merckillonly); } } @@ -2904,7 +2906,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) // process items looted by the mob if (md->lootitems) { for (i = 0; i < md->lootitem_count; i++) - mob_item_drop(md, dlist, mob_setlootitem(&md->lootitems[i], md->mob_id), 1, 10000, homkillonly); + mob_item_drop(md, dlist, mob_setlootitem(&md->lootitems[i], md->mob_id), 1, 10000, homkillonly || merckillonly); } if (dlist->item) //There are drop items. add_timer(tick + (!battle_config.delay_battle_damage?500:0), mob_delay_item_drop, 0, (intptr_t)dlist); @@ -2920,7 +2922,7 @@ int mob_dead(struct mob_data *md, struct block_list *src, int type) dlist->third_charid = (third_sd ? third_sd->status.char_id : 0); dlist->item = NULL; for (i = 0; i < md->lootitem_count; i++) - mob_item_drop(md, dlist, mob_setlootitem(&md->lootitems[i], md->mob_id), 1, 10000, homkillonly); + mob_item_drop(md, dlist, mob_setlootitem(&md->lootitems[i], md->mob_id), 1, 10000, homkillonly || merckillonly); add_timer(tick + (!battle_config.delay_battle_damage?500:0), mob_delay_item_drop, 0, (intptr_t)dlist); } diff --git a/src/map/pc.hpp b/src/map/pc.hpp index a79894d60f..0ca778eda6 100644 --- a/src/map/pc.hpp +++ b/src/map/pc.hpp @@ -390,6 +390,7 @@ struct map_session_data { unsigned int chatID; time_t idletime; time_t idletime_hom; + time_t idletime_mer; struct s_progressbar { int npc_id; @@ -973,6 +974,7 @@ extern struct s_job_info job_info[CLASS_COUNT]; #define pc_issit(sd) ( (sd)->vd.dead_sit == 2 ) #define pc_isidle_party(sd) ( (sd)->chatID || (sd)->state.vending || (sd)->state.buyingstore || DIFF_TICK(last_tick, (sd)->idletime) >= battle_config.idle_no_share ) #define pc_isidle_hom(sd) ( (sd)->hd && ( (sd)->chatID || (sd)->state.vending || (sd)->state.buyingstore || DIFF_TICK(last_tick, (sd)->idletime_hom) >= battle_config.hom_idle_no_share ) ) +#define pc_isidle_mer(sd) ( (sd)->md && ( (sd)->chatID || (sd)->state.vending || (sd)->state.buyingstore || DIFF_TICK(last_tick, (sd)->idletime_mer) >= battle_config.mer_idle_no_share ) ) #define pc_istrading(sd) ( (sd)->npc_id || (sd)->state.vending || (sd)->state.buyingstore || (sd)->state.trading ) #define pc_cant_act(sd) ( (sd)->npc_id || (sd)->state.vending || (sd)->state.buyingstore || (sd)->chatID || ((sd)->sc.opt1 && (sd)->sc.opt1 != OPT1_BURNING) || (sd)->state.trading || (sd)->state.storage_flag || (sd)->state.prevend ) diff --git a/src/map/script.cpp b/src/map/script.cpp index 6930fe3524..b11726ba5a 100644 --- a/src/map/script.cpp +++ b/src/map/script.cpp @@ -17643,6 +17643,17 @@ BUILDIN_FUNC(checkidlehom) return SCRIPT_CMD_SUCCESS; } +BUILDIN_FUNC(checkidlemer) +{ + TBL_PC *sd; + + if( script_nick2sd(2,sd) ) + script_pushint(st, DIFF_TICK(last_tick, sd->idletime_mer)); + else + script_pushint(st, 0); + return SCRIPT_CMD_SUCCESS; +} + BUILDIN_FUNC(searchitem) { struct script_data* data = script_getdata(st, 2); @@ -25476,6 +25487,7 @@ struct script_function buildin_func[] = { BUILDIN_DEF(checkchatting,"?"), BUILDIN_DEF(checkidle,"?"), BUILDIN_DEF(checkidlehom,"?"), + BUILDIN_DEF(checkidlemer,"?"), BUILDIN_DEF(openmail,"?"), BUILDIN_DEF(openauction,"?"), BUILDIN_DEF(checkcell,"siii"),