// TMW2 functions.
// Author:
// Jesusalva
// Description:
// Random CAPTCHA check against AFK robots.
// TODO: Put this in a blackbox for bots which sniff text chat.
// Variables:
// gettimetick(2) variables: CAPTCHA_TIME | CAPTCHA_OK
// CAPTCHA_1 | CAPTCHA_2 | CAPTCHA_ANSWER => The correct captcha reply
// CAPTCHA_OP$ => The operation, defaults to "+"
// $@BOTCHECK_TARGET => The account ID of the char being probed
// @captcha_cooldown => anti-flood
// CaptchName, names the number
function script CaptchName {
switch (getarg(0)) {
case 0: return l("zero");
case 1: return l("one");
case 2: return l("two");
case 3: return l("three");
case 4: return l("four");
case 5: return l("five");
case 6: return l("six");
case 7: return l("seven");
case 8: return l("eight");
case 9: return l("nine");
case 10: return l("ten");
case 11: return l("eleven");
case 12: return l("twelve");
case 13: return l("thirteen");
case 14: return l("fourteen");
case 15: return l("fifteen");
case 16: return l("sixteen");
case 17: return l("seventeen");
case 18: return l("eighteen");
case 19: return l("nineteen");
case 20: return l("twenty");
}
return getarg(0);
}
// MakeCaptch, makes a captcha and saves it
function script MakeCaptch {
CAPTCHA_TIME=gettimetick(2);
CAPTCHA_1=rand2(21);
CAPTCHA_2=rand2(20);
// select a operation
switch (rand2(1)) {
case 0:
CAPTCHA_OP$="-";
CAPTCHA_ANSWER=CAPTCHA_1-CAPTCHA_2;
break;
default:
CAPTCHA_OP$="+";
CAPTCHA_ANSWER=CAPTCHA_1+CAPTCHA_2;
break;
}
return;
}
function script CaptchVal {
// Make it nice
.@c$=any(
CAPTCHA_1+CAPTCHA_OP$+CAPTCHA_2,
CaptchName(CAPTCHA_1)+CAPTCHA_OP$+CAPTCHA_2,
CAPTCHA_1+CAPTCHA_OP$+CaptchName(CAPTCHA_2),
CaptchName(CAPTCHA_1)+CAPTCHA_OP$+CaptchName(CAPTCHA_2),
CAPTCHA_1+" "+CAPTCHA_OP$+" "+CAPTCHA_2,
CaptchName(CAPTCHA_1)+" "+CAPTCHA_OP$+" "+CAPTCHA_2,
CAPTCHA_1+" "+CAPTCHA_OP$+" "+CaptchName(CAPTCHA_2),
CaptchName(CAPTCHA_1)+" "+CAPTCHA_OP$+" "+CaptchName(CAPTCHA_2));
return .@c$;
}
function script CaptchExample {
if (!CAPTCHA_TIME || getarg(0, false)) {
dispbottom("##1TO REPLY TO CAPTCHAS: @capcha <numeric answer>##1");
dispbottom l("Example: Give the answer for the following: one+1");
dispbottom l("Reply: %s", b("@captcha 2"));
dispbottom b(l("This example will not be shown again."));
}
return;
}
- script @captcha 32767,{
function captchaProbe;
end;
OnCall:
// Not you, ignore
if (getcharid(3) != $@BOTCHECK_TARGET)
end;
// Attempt cooldown
if (@captcha_cooldown > gettimetick(2)) {
dispbottom l("CAPTCHA: Cooldown in effect."); end;}
// Process answer
@captcha_cooldown=gettimetick(2)+.antiflood;
// Lets be reasonable
if (gettimetick(2)+2 > CAPTCHA_TIME) {
dispbottom l("CAPTCHA: An error happened, try again."); end;}
// Verify answer
.@ans$ = implode(.@atcmd_parameters$, " ");
.@ans=atoi(.@ans$);
if (.@ans == CAPTCHA_ANSWER) {
CAPTCHA_OK=gettimetick(2)+.cooldown;
$@BOTCHECK_TARGET=0;
dispbottom any(
l("captcha successful"),
l("captcha ok"),
l("correct"),
l("understood"),
l("not bad"),
l("hmpf. That'll do."),
l("a bit longer and I would have jailed you %%\\ "),
l("%%\\ that'll do."),
l("%%N"));
dispbottom l("Remember: Players can also help enforcing no-AFK-bot rule!");
//dispbottom l("Remember: Never lend your toothbrush to a slime!");
} else {
dispbottom l("CAPTCHA: Incorrect answer. Wait %ds and try again.", .antiflood); // Max 10 attempts total
}
end;
OnInit:
.thr=180; // Seconds to reply
.cooldown=3600; // Captcha Immunity
.antiflood=18; // Seconds between captcha failed attempts
bindatcmd "captcha", "@captcha::OnCall", 0, 0, 0;
initnpctimer;
end;
// Pick a random target for captcha checks
OnTimer5000:
if ($@BOTCHECK_TARGET) captchaProbe();
// Script disabled by admins
if (!$CAPTCHA) {
initnpctimer;
end;
}
// Maybe we will conduct a captcha
if (rand2(10) < 3) {
// This can be slow, beware
.@c = getunits(BL_PC, .@players, MAX_CYCLE_PC);
for (.@i = 0; .@i < .@c; .@i++) {
// Too lazy
if (rand2(100) > 5)
continue;
// Okay, lets do it
attachrid(.@players[.@i]);
// 1. Player in immunity, who is next one
if (CAPTCHA_OK > gettimetick(2)) {
detachrid();
continue;
}
// 2. Player must be jailed, and we continue
if (CAPTCHA_TIME < CAPTCHA_OK) {
atcommand("@jailfor 40mn "+strcharinfo(0));
dispbottom l("You failed to reply captcha in time and was arrested for AFK Botting. You can use @jailtime to keep track of time left.");
CAPTCHA_OK=CAPTCHA_TIME;
detachrid();
continue;
}
// 3. This is a good target, lets do this
.@g$="";
CaptchExample();
MakeCaptch();
.@g$=any(
"CAPTCHA: Please reply the following: "+CaptchVal(),
"BOTCHECK: Please reply the following: "+CaptchVal(),
"CAPTCHA: You must answer this: "+CaptchVal(),
"BOTCHECK: You must answer this: "+CaptchVal());
// 4. Find a random method
switch (rand2(2)) {
case 0:
message(getcharid(3), .@g$);
message(getcharid(3), "Example for one + one: @captcha 2");
break;
case 1:
announce(.@g$, bc_self|bc_pc);
announce("Example for one + one: @captcha 2", bc_self|bc_pc);
break;
default:
dispbottom(.@g$);
dispbottom("Example for one + one: @captcha 2");
break;
}
// 5. Detach rid, target is set
$@BOTCHECK_TARGET=getcharid(3);
detachrid();
break;
}
}
// Continue this timer forever
initnpctimer;
end;
function captchaProbe {
// Attach rid
.@online=attachrid($@BOTCHECK_TARGET);
// User disconnected, next captcha they'll be arrested because timer will expire
if (!.@online) {
$@BOTCHECK_TARGET=false;
CAPTCHA_OK=false;
}
// Timer expired? Ban hammer
if (CAPTCHA_TIME+.thr > gettimetick(2)) {
atcommand("@jailfor 30mn "+strcharinfo(0));
dispbottom l("You failed to reply captcha in time and was arrested for AFK Botting. You can use @jailtime to keep track of time left.");
CaptchExample(true);
$@BOTCHECK_TARGET=false;
CAPTCHA_OK=CAPTCHA_TIME;
}
// Nothing happened, lets wait
return;
}
}