summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--npc/005-7/trainer.txt8
-rw-r--r--npc/functions/captcha.txt233
-rw-r--r--npc/scripts.conf1
3 files changed, 242 insertions, 0 deletions
diff --git a/npc/005-7/trainer.txt b/npc/005-7/trainer.txt
index 3dfa9a999..c1dc37c8b 100644
--- a/npc/005-7/trainer.txt
+++ b/npc/005-7/trainer.txt
@@ -541,6 +541,14 @@ L_Regras:
mes "";
callfunc "GameRules";
next;
+ mes ".:: "+l("Automatic Botchecks")+" ::.";
+ mes l("If enabled, automatic captchas may be sent to you every once in a while.");
+ mes l("To answer them, use: @captcha <num_value>");
+ next;
+ CaptchExample();
+ mes l("Example: Give the answer for the following: one+1");
+ mes l("Reply: %s", b("@captcha 2"));
+ next;
goto L_PreMenu;
L_Menu_gap:
diff --git a/npc/functions/captcha.txt b/npc/functions/captcha.txt
new file mode 100644
index 000000000..776501c57
--- /dev/null
+++ b/npc/functions/captcha.txt
@@ -0,0 +1,233 @@
+// 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;
+ }
+
+}
+
+
diff --git a/npc/scripts.conf b/npc/scripts.conf
index bc1cbb246..193e9160b 100644
--- a/npc/scripts.conf
+++ b/npc/scripts.conf
@@ -143,6 +143,7 @@
"npc/functions/mkbot.txt",
"npc/functions/gmbot.txt",
"npc/functions/instances.txt",
+"npc/functions/captcha.txt",
// Maps specific scripts
@include "npc/_import.txt"