New macro detection features (#7359)

* Adds battle config macro_detection_punishment to ban or jail characters.
* Adds battle config macro_detection_punishment_time to adjust the duration of ban/jail for players who fail the captcha challenges.
* Adds script command macro_detector which invokes the captcha challenge.
* General cleanups to the jail functions to remove duplicate code.
* General cleanups to the macro punishment calls to remove duplicate code.
* Ending SC_JAILED will now properly remove the jail status rather than trying to reset the timer to 1 second resolving any possibility of players getting stuck.
Thanks to @Lemongrass3110!
This commit is contained in:
Aleos 2022-12-13 15:44:42 -05:00 committed by GitHub
parent 5a533a7a12
commit ee2dcf816e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 154 additions and 55 deletions

View File

@ -158,3 +158,14 @@ macro_detection_retry: 3
// Amount of time in milliseconds before the macro detection will fail and the user will be banned.
// Official: 60000
macro_detection_timeout: 60000
// Macro Detector punishment type
// 0 - Ban
// 1 - Jail
// Official: 0
macro_detection_punishment: 0
// Macro Detector punishment duration
// Amount of time in minutes that the punishment type is active for. Use 0 for infinite.
// Official: 0
macro_detection_punishment_time: 0

View File

@ -6579,6 +6579,28 @@ Examples:
---------------------------------------
macro_detector({<account ID>});
macro_detector({"<character name>"});
This command will display the captcha UI challenge onto the invoking character or the given <account ID>/<character name>.
Example:
// Use 'getareaunits' to gather an area of players to test.
// Build an int array of the account IDs.
.@num = getareaunits(BL_PC, "prontera", 150, 150, 160, 160, .@array[0]);
mes "The number of Players in Prontera in between 150x150 and 160x160 is " + .@num + " .";
mes "Players to challenge:";
freeloop(1); // If the list is too big
for(.@i = 0; .@i < getarraysize(.@array); .@i++) {
mes (.@i + 1) + " " + convertpcinfo(.@array[.@i], CPC_NAME);
macro_detector .@array[.@i];
}
freeloop(0);
end;
---------------------------------------
==================================
|5.- Mob / NPC -related commands.|
==================================

View File

@ -5218,8 +5218,7 @@ ACMD_FUNC(servertime)
ACMD_FUNC(jail)
{
struct map_session_data *pl_sd;
int x, y;
unsigned short m_index;
nullpo_retr(-1, sd);
memset(atcmd_player_name, '\0', sizeof(atcmd_player_name));
@ -5244,21 +5243,7 @@ ACMD_FUNC(jail)
return -1;
}
switch(rnd() % 2) { //Jail Locations
case 0:
m_index = mapindex_name2id(MAP_JAIL);
x = 24;
y = 75;
break;
default:
m_index = mapindex_name2id(MAP_JAIL);
x = 49;
y = 75;
break;
}
//Duration of INT_MAX to specify infinity.
sc_start4(NULL,&pl_sd->bl,SC_JAILED,100,INT_MAX,m_index,x,y,1000);
pc_jail(*pl_sd);
clif_displaymessage(pl_sd->fd, msg_txt(sd,117)); // GM has send you in jails.
clif_displaymessage(fd, msg_txt(sd,118)); // Player warped in jails.
return 0;
@ -5296,7 +5281,7 @@ ACMD_FUNC(unjail)
}
//Reset jail time to 1 sec.
sc_start(NULL,&pl_sd->bl,SC_JAILED,100,1,1000);
pc_jail(*pl_sd, 0);
clif_displaymessage(pl_sd->fd, msg_txt(sd,120)); // A GM has discharged you from jail.
clif_displaymessage(fd, msg_txt(sd,121)); // Player unjailed.
return 0;
@ -5305,8 +5290,8 @@ ACMD_FUNC(unjail)
ACMD_FUNC(jailfor) {
struct map_session_data *pl_sd = NULL;
char * modif_p;
int jailtime = 0,x,y;
short m_index = 0;
int jailtime = 0;
nullpo_retr(-1, sd);
memset(atcmd_output, '\0', sizeof(atcmd_output));
@ -5363,19 +5348,8 @@ ACMD_FUNC(jailfor) {
return -1;
}
// Jail locations, add more as you wish.
switch(rnd()%2) {
case 1: // Jail #1
m_index = mapindex_name2id(MAP_JAIL);
x = 49; y = 75;
break;
default: // Default Jail
m_index = mapindex_name2id(MAP_JAIL);
x = 24; y = 75;
break;
}
pc_jail(*pl_sd, jailtime);
sc_start4(NULL,&pl_sd->bl,SC_JAILED,100,jailtime,m_index,x,y,jailtime?60000:1000); //jailtime = 0: Time was reset to 0. Wait 1 second to warp player out (since it's done in status_change_timer).
return 0;
}

View File

@ -10271,6 +10271,8 @@ static const struct _battle_data {
{ "break_mob_equip", &battle_config.break_mob_equip, 0, 0, 1, },
{ "macro_detection_retry", &battle_config.macro_detection_retry, 3, 1, INT_MAX, },
{ "macro_detection_timeout", &battle_config.macro_detection_timeout, 60000, 0, INT_MAX, },
{ "macro_detection_punishment", &battle_config.macro_detection_punishment, 0, 0, 1, },
{ "macro_detection_punishment_time", &battle_config.macro_detection_punishment_time, 0, 0, INT_MAX, },
{ "feature.dynamicnpc_timeout", &battle_config.feature_dynamicnpc_timeout, 1000, 60000, INT_MAX, },
{ "feature.dynamicnpc_rangex", &battle_config.feature_dynamicnpc_rangex, 2, 0, INT_MAX, },

View File

@ -714,6 +714,8 @@ struct Battle_Config
int break_mob_equip;
int macro_detection_retry;
int macro_detection_timeout;
int macro_detection_punishment;
int macro_detection_punishment_time;
int feature_dynamicnpc_timeout;
int feature_dynamicnpc_rangex;

View File

@ -24776,7 +24776,7 @@ void clif_parse_macro_reporter_ack(int fd, map_session_data *sd) {
return;
}
pc_macro_reporter_process(*sd, *tsd);
pc_macro_reporter_process(*tsd, sd->status.account_id);
clif_macro_reporter_status(*sd, MCR_MONITORING);
#endif
}

View File

@ -15235,6 +15235,76 @@ void pc_attendance_claim_reward( struct map_session_data* sd ){
clif_attendence_response( sd, attendance_counter );
}
/**
* Send a player to jail and determine the location to send in jail.
* @param sd: Player data
* @param duration: Duration in minutes (default INT_MAX = infinite)
*/
void pc_jail(map_session_data &sd, int32 duration) {
uint16 m_index = mapindex_name2id(MAP_JAIL);
int16 x, y;
switch (rnd() % 2) { // Jail Locations
case 0: // Jail #1
x = 49;
y = 75;
break;
default: // Default Jail
x = 24;
y = 75;
break;
}
duration = i32max(0, duration); // Can't be less than 0 seconds.
// If duration > 0 then triggered via jailfor which checks every minute.
// If duration == INT_MAX then triggered via jail for infinite duration.
// If duration == 0 then triggered via unjail and end status.
if (duration > 0)
sc_start4(nullptr, &sd.bl, SC_JAILED, 100, duration, m_index, x, y, 60000);
else
status_change_end(&sd.bl, SC_JAILED);
}
/**
* Determine the punishment type when failing macro checks.
* @param sd: Player data
* @param stype: Macro detection status type (for banning)
*/
static void pc_macro_punishment(map_session_data &sd, e_macro_detect_status stype) {
int32 duration = 0;
// Determine if there's a unique duration
if (battle_config.macro_detection_punishment_time > 0) {
char time[13];
safesnprintf(time, 13, "+%dnm", battle_config.macro_detection_punishment_time);
duration = static_cast<int32>(solve_time(time));
}
// Delete the timer
if (sd.macro_detect.timer != INVALID_TIMER)
delete_timer(sd.macro_detect.timer, pc_macro_detector_timeout);
// Clear the macro detect data
sd.macro_detect = {};
sd.macro_detect.timer = INVALID_TIMER;
// Unblock all actions for the player
sd.state.block_action &= ~PCBLOCK_ALL;
sd.state.block_action &= ~PCBLOCK_IMMUNE;
if (battle_config.macro_detection_punishment == 0) { // Ban
clif_macro_detector_status(sd, stype);
chrif_req_login_operation(sd.macro_detect.reporter_aid, sd.status.name, (duration == 0 ? CHRIF_OP_LOGIN_BLOCK : CHRIF_OP_LOGIN_BAN), duration, 0, 0);
} else { // Jail
// Send success to close the window without closing the client
clif_macro_detector_status(sd, MCD_GOOD);
pc_jail(sd, (duration == 0 ? INT_MAX : duration / 60));
}
}
/**
* Save a captcha image to memory via /macro_register.
* @param sd: Player data
@ -15320,9 +15390,8 @@ TIMER_FUNC(pc_macro_detector_timeout) {
sd->macro_detect.retry -= 1;
if (sd->macro_detect.retry == 0) {
// All attempts have been exhausted block the user
clif_macro_detector_status(*sd, MCD_TIMEOUT);
chrif_req_login_operation(sd->macro_detect.reporter_aid, sd->status.name, CHRIF_OP_LOGIN_BLOCK, 0, 0, 0);
// All attempts have been exhausted, punish the user
pc_macro_punishment(*sd, MCD_TIMEOUT);
} else {
// Update the client
clif_macro_detector_request_show(*sd);
@ -15373,10 +15442,9 @@ void pc_macro_detector_process_answer(map_session_data &sd, char captcha_answer[
// Deduct an answering attempt
sd.macro_detect.retry -= 1;
// All attempts have been exhausted block the user
// All attempts have been exhausted, punish the user
if (sd.macro_detect.retry <= 0) {
clif_macro_detector_status(sd, MCD_INCORRECT);
chrif_req_login_operation(sd.macro_detect.reporter_aid, sd.status.name, CHRIF_OP_LOGIN_BLOCK, 0, 0, 0);
pc_macro_punishment(sd, MCD_INCORRECT);
return;
}
@ -15399,9 +15467,9 @@ void pc_macro_detector_disconnect(map_session_data &sd) {
sd.macro_detect.timer = INVALID_TIMER;
}
// If the player disconnects before clearing the challenge the account is banned.
// If the player disconnects before clearing the challenge the player is punished.
if (sd.macro_detect.retry != 0)
chrif_req_login_operation(sd.macro_detect.reporter_aid, sd.status.name, CHRIF_OP_LOGIN_BLOCK, 0, 0, 0);
pc_macro_punishment(sd, MCD_TIMEOUT);
}
/**
@ -15438,10 +15506,10 @@ void pc_macro_reporter_area_select(map_session_data &sd, const int16 x, const in
/**
* Send out captcha check to player.
* @param ssd: Source player data
* @param tsd: Target player data
* @param sd: Target player data
* @param reporter_account_id: Account ID of reporter
*/
void pc_macro_reporter_process(map_session_data &ssd, map_session_data &tsd) {
void pc_macro_reporter_process(map_session_data &sd, int32 reporter_account_id) {
if (captcha_db.empty())
return;
@ -15449,18 +15517,18 @@ void pc_macro_reporter_process(map_session_data &ssd, map_session_data &tsd) {
const std::shared_ptr<s_captcha_data> cd = captcha_db.random();
// Set macro detection data.
tsd.macro_detect.cd = cd;
tsd.macro_detect.reporter_aid = ssd.status.account_id;
tsd.macro_detect.retry = battle_config.macro_detection_retry;
sd.macro_detect.cd = cd;
sd.macro_detect.reporter_aid = reporter_account_id;
sd.macro_detect.retry = battle_config.macro_detection_retry;
// Block all actions for the target player.
tsd.state.block_action |= (PCBLOCK_ALL | PCBLOCK_IMMUNE);
sd.state.block_action |= (PCBLOCK_ALL | PCBLOCK_IMMUNE);
// Open macro detect client side.
clif_macro_detector_request(tsd);
clif_macro_detector_request(sd);
// Start the timeout timer.
tsd.macro_detect.timer = add_timer(gettick() + battle_config.macro_detection_timeout, pc_macro_detector_timeout, tsd.bl.id, 0);
sd.macro_detect.timer = add_timer(gettick() + battle_config.macro_detection_timeout, pc_macro_detector_timeout, sd.bl.id, 0);
}
/**

View File

@ -1722,17 +1722,20 @@ bool pc_attendance_enabled();
int32 pc_attendance_counter( struct map_session_data* sd );
void pc_attendance_claim_reward( struct map_session_data* sd );
void pc_jail(map_session_data &sd, int32 duration = INT_MAX);
// Captcha Register
void pc_macro_captcha_register(map_session_data &sd, uint16 image_size, char captcha_answer[CAPTCHA_ANSWER_SIZE]);
void pc_macro_captcha_register_upload(map_session_data & sd, uint16 upload_size, char *upload_data);
// Macro Detector
TIMER_FUNC(pc_macro_detector_timeout);
void pc_macro_detector_process_answer(map_session_data &sd, char captcha_answer[CAPTCHA_ANSWER_SIZE]);
void pc_macro_detector_disconnect(map_session_data &sd);
// Macro Reporter
void pc_macro_reporter_area_select(map_session_data &sd, const int16 x, const int16 y, const int8 radius);
void pc_macro_reporter_process(map_session_data &ssd, map_session_data &tsd);
void pc_macro_reporter_process(map_session_data &sd, int32 reporter_account_id = -1);
#ifdef MAP_GENERATOR
void pc_reputation_generate();

View File

@ -26829,6 +26829,25 @@ BUILDIN_FUNC(isdead) {
return SCRIPT_CMD_SUCCESS;
}
BUILDIN_FUNC(macro_detector) {
map_session_data *sd;
if (script_hasdata(st, 2) && script_isstring(st, 2)) { // Character Name
if (!script_nick2sd(2, sd)) {
return SCRIPT_CMD_FAILURE;
}
} else { // Account ID
if (!script_accid2sd(2, sd)) {
return SCRIPT_CMD_FAILURE;
}
}
// Reporter Account ID as -1 for server.
pc_macro_reporter_process(*sd);
return SCRIPT_CMD_SUCCESS;
}
#include "../custom/script.inc"
// declarations that were supposed to be exported from npc_chat.cpp
@ -27582,6 +27601,8 @@ struct script_function buildin_func[] = {
BUILDIN_DEF(getfame, "?"),
BUILDIN_DEF(getfamerank, "?"),
BUILDIN_DEF(isdead, "?"),
BUILDIN_DEF(macro_detector, "?"),
#include "../custom/script_def.inc"
{NULL,NULL,NULL},

View File

@ -11257,7 +11257,6 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
break;
case SC_JAILED:
// Val1 is duration in minutes. Use INT_MAX to specify 'unlimited' time.
tick = val1>0?1000:250;
if (sd) {
if (sd->mapindex != val2) {
int pos = (bl->x&0xFFFF)|(bl->y<<16), // Current Coordinates
@ -13275,9 +13274,6 @@ int status_change_end(struct block_list* bl, enum sc_type type, int tid)
}
break;
case SC_JAILED:
if(tid == INVALID_TIMER)
break;
// Natural expiration.
if(sd && sd->mapindex == sce->val2)
pc_setpos(sd,(unsigned short)sce->val3,sce->val4&0xFFFF, sce->val4>>16, CLR_TELEPORT);
break; // Guess hes not in jail :P