From 191335858bbc1add4afbe965e6c490d730019352 Mon Sep 17 00:00:00 2001 From: Pazdikan Date: Tue, 17 Sep 2024 14:19:29 +0200 Subject: [PATCH 1/3] WIP code refactor into multiple files --- src/config.py | 29 ++++++++++++ src/core/behavior.py | 17 +++++++ src/core/brain.py | 100 ++++++++++++++++++++++++++++++++++++++++ src/killer/trapper.py | 12 +++++ src/killer/universal.py | 20 ++++++++ src/main.py | 20 ++++++++ src/util/console.py | 3 ++ src/util/screenshot.py | 54 ++++++++++++++++++++++ 8 files changed, 255 insertions(+) create mode 100644 src/config.py create mode 100644 src/core/behavior.py create mode 100644 src/core/brain.py create mode 100644 src/killer/trapper.py create mode 100644 src/killer/universal.py create mode 100644 src/main.py create mode 100644 src/util/console.py create mode 100644 src/util/screenshot.py diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..a3edf02 --- /dev/null +++ b/src/config.py @@ -0,0 +1,29 @@ +from time import time +from enum import Enum + +# THIS IS NOT THE CONFIGURATION YOU ARE LOOKING FOR +# SCROLL DOWN TO FIND THE CONFIGURATION FOR USERS + +ss = None + +State = Enum('State', ['INGAME', 'INLOBBY', 'INQUEUE']) +Killer = Enum('Killer', ['OTHER', "TRAPPER"]) + +current_state = State.INLOBBY + +games = 0 +xp = 0 +total_time_in_game = 0 + +script_start_time = time() +game_started_at = None + +# CONFIGURATION FOR USERS +# EDITING STUFF BETWEEN COMMENTS BELOW IS SAFE AND WILL NOT BREAK THE SCRIPT + +killer = Killer.OTHER # From the list above + +xp_limit = 0 # SET TO 0 TO DISABLE +games_limit = 0 # SET TO 0 TO DISABLE + +# END OF CONFIGURATION \ No newline at end of file diff --git a/src/core/behavior.py b/src/core/behavior.py new file mode 100644 index 0000000..12c719d --- /dev/null +++ b/src/core/behavior.py @@ -0,0 +1,17 @@ +import config +from random import randint + +import killer.universal as universal +import killer.trapper as trapper + +def perform_ingame_action(): + if (config.killer == config.Killer.OTHER): + universal.walk_and_attack() + + elif (config.killer == config.Killer.TRAPPER): + random_action = randint(0, 5) + + if random_action == 0: + universal.walk_and_attack() + else: + trapper.place_and_pick_trap() \ No newline at end of file diff --git a/src/core/brain.py b/src/core/brain.py new file mode 100644 index 0000000..ba4c58c --- /dev/null +++ b/src/core/brain.py @@ -0,0 +1,100 @@ +import config +from util.console import console +from rich.text import Text +from time import time, sleep +import pyautogui +import core.behavior as behavior + +def check_if_limit_reached(): + if (config.xp_limit > 0 and config.xp >= config.xp_limit): + console.log(Text("XP limit reached. The script will now turn off", style="green")) + return True + + elif (config.games_limit > 0 and config.games >= config.games_limit): + console.log(Text("Games Played limit reached. The script will now turn off", style="green")) + return True + + return False + +def loop(): + if (check_if_limit_reached()): + quit() + + if (config.current_state == config.State.INGAME): + ocr = config.ss.take_and_read_screenshot(config.ss.endgame_button) + + if any("CONTINUE" in string for string in ocr): + time_in_game = time() - config.game_started_at + + pyautogui.click(x=465, y=871) + pyautogui.click(x=465, y=871) + pyautogui.click(x=465, y=871) + + sleep(15) + current_xp = config.ss.take_and_read_xp_screenshot() + + if (len(current_xp) > 0): + xp += int(current_xp[0]) + else: + xp += time_in_game * 0.8 # "Predicted" XP gain when the OCR fails + + sleep(5) + + config.ss.click_image() + config.ss.click_image() + config.ss.click_image() + + console.log("Clicking CONTINUE (endgame)") + config.current_state = config.State.INLOBBY + config.games += 1 + config.total_time_in_game += time_in_game + + console.print_stats() + + console.log("Setting state to INLOBBY") + console.log("Waiting 30 seconds") + sleep(30) + console.log("Finished waiting") + else: + behavior.perform_ingame_action() + else: + # When you level up your rift, the right expandable menu will cover the play button + # This moves the mouse to top left corner to close it + pyautogui.moveTo(0, 0) + + ocr = config.ss.take_and_read_screenshot(config.ss.lobby_button) + if any("PLAY" in string for string in ocr): + config.ss.click_image() + config.ss.click_image() + config.ss.click_image() + + console.log("Clicking PLAY in main menu") + elif any("READY" in string for string in ocr): + config.ss.click_image() + config.ss.click_image() + config.ss.click_image() + + console.log("Clicking READY in found lobby") + config.current_state = config.State.INGAME + console.log("Setting state to INGAME") + console.log("Waiting 120 seconds") + + sleep(120) # Loading from lobby to game (+ missing players etc.) + console.log("Finished waiting") + + config.game_started_at = time() + elif not ocr: + ocr = config.ss.take_and_read_screenshot(config.ss.endgame_button) + # Sometimes the game doesn't register the click + # Script thinks it's in main menu but game is still in endgame + if any("CONTINUE" in string for string in ocr): + config.ss.click_image() + config.ss.click_image() + config.ss.click_image() + + console.log("Clicking CONTINUE (endgame)") + console.log("Waiting 30 seconds") + sleep(30) + console.log("Finished waiting") + console.log("Waiting 5 seconds") + sleep(5) \ No newline at end of file diff --git a/src/killer/trapper.py b/src/killer/trapper.py new file mode 100644 index 0000000..1cae686 --- /dev/null +++ b/src/killer/trapper.py @@ -0,0 +1,12 @@ +import pyautogui +from time import sleep +from util.console import console + +def place_and_pick_trap(): + console.log("Trapper action: Place and pick trap") + + pyautogui.mouseDown(button="secondary") + sleep(4) + pyautogui.mouseUp(button="secondary") + sleep(2) + pyautogui.press("space") \ No newline at end of file diff --git a/src/killer/universal.py b/src/killer/universal.py new file mode 100644 index 0000000..8e6b651 --- /dev/null +++ b/src/killer/universal.py @@ -0,0 +1,20 @@ +import pyautogui +from time import sleep +from util.console import console +from rich.text import Text + +def walk_and_attack(): + console.log("Universal action: Walk and attack") + + pyautogui.keyDown("w") + sleep(0.5) + pyautogui.keyUp("w") + + pyautogui.keyDown("s") + sleep(0.5) + pyautogui.keyUp("s") + + # Coords of the "banner" (survivor disconneted etc.); if no banner is present, it just attacks like normal + pyautogui.click(x=1379, y=658) + pyautogui.click(x=1379, y=658) + pyautogui.click(x=1379, y=658) \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..41a0e5d --- /dev/null +++ b/src/main.py @@ -0,0 +1,20 @@ +from util.console import console +from rich.text import Text +import config +from mss import mss +import core.brain as brain + +from util.screenshot import Screenshot + +if __name__ == '__main__': + console.print(Text("\n! IGNORE ALL ERRORS ABOVE !\n", style="bold black on red")) + console.log(Text("Initializing...", style="green")) + + + with mss() as sct: + config.ss = Screenshot(sct) + console.clear() + console.log(Text("Initialized!", style="green")) + + while True: + brain.loop() \ No newline at end of file diff --git a/src/util/console.py b/src/util/console.py new file mode 100644 index 0000000..5f8e6bd --- /dev/null +++ b/src/util/console.py @@ -0,0 +1,3 @@ +from rich.console import Console + +console = Console() \ No newline at end of file diff --git a/src/util/screenshot.py b/src/util/screenshot.py new file mode 100644 index 0000000..b4b36fc --- /dev/null +++ b/src/util/screenshot.py @@ -0,0 +1,54 @@ +import pyautogui +import easyocr +from time import sleep +from PIL import Image + +pyautogui.FAILSAFE = False + +reader = easyocr.Reader(['en']) + + +class Screenshot: + def __init__(self, sct): + self.sct = sct + + + lobby_button = { + "top": 927, + "left": 1580, + "width": 235, + "height": 35 + } + + endgame_button = { + "top": 1000, + "left": 1650, + "width": 235, + "height": 35 + } + + def click_image(): + try: + pos = pyautogui.locateOnScreen('screenshot.png', confidence=(0.8)) + if pos != None: + pyautogui.click(pos[0] + 20, pos[1]) + sleep(0.5) + except: + pass + + def take_and_read_screenshot(self, area): + screenshot = self.sct.grab(area) + image = Image.frombytes("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX") + image.save("screenshot.png") + return reader.readtext("screenshot.png", detail=0) + + def take_and_read_xp_screenshot(self): + screenshot = self.sct.grab({ + "top": 700, + "left": 540, + "width": 75, + "height": 35 + }) + image = Image.frombytes("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX") + image.save("last_game_xp.png") + return reader.readtext("last_game_xp.png", detail=0) \ No newline at end of file From be98f3cb8b24b2fe99ca3e8e4091d3e873caad25 Mon Sep 17 00:00:00 2001 From: Pazdikan Date: Tue, 17 Sep 2024 16:39:59 +0200 Subject: [PATCH 2/3] Finished refactoring the code into multiple files --- run.py | 2 +- src/config.py | 2 +- src/core/brain.py | 10 ++++++---- src/killer/trapper.py | 3 ++- src/main.py | 2 +- src/util/console.py | 13 ++++++++++++- src/util/screenshot.py | 2 +- 7 files changed, 24 insertions(+), 10 deletions(-) diff --git a/run.py b/run.py index 98edc26..fad9465 100644 --- a/run.py +++ b/run.py @@ -1,5 +1,5 @@ # TODO: -# - More killers: Dracula, +# - More killers: Dracula, import mss import pyautogui diff --git a/src/config.py b/src/config.py index a3edf02..f46a3df 100644 --- a/src/config.py +++ b/src/config.py @@ -21,7 +21,7 @@ # CONFIGURATION FOR USERS # EDITING STUFF BETWEEN COMMENTS BELOW IS SAFE AND WILL NOT BREAK THE SCRIPT -killer = Killer.OTHER # From the list above +killer = Killer.TRAPPER # From the list above xp_limit = 0 # SET TO 0 TO DISABLE games_limit = 0 # SET TO 0 TO DISABLE diff --git a/src/core/brain.py b/src/core/brain.py index ba4c58c..d0f2493 100644 --- a/src/core/brain.py +++ b/src/core/brain.py @@ -1,5 +1,5 @@ import config -from util.console import console +from util.console import console, print_stats from rich.text import Text from time import time, sleep import pyautogui @@ -34,9 +34,9 @@ def loop(): current_xp = config.ss.take_and_read_xp_screenshot() if (len(current_xp) > 0): - xp += int(current_xp[0]) + config.xp += int(current_xp[0]) else: - xp += time_in_game * 0.8 # "Predicted" XP gain when the OCR fails + config.xp += time_in_game * 0.8 # "Predicted" XP gain when the OCR fails sleep(5) @@ -49,7 +49,8 @@ def loop(): config.games += 1 config.total_time_in_game += time_in_game - console.print_stats() + console.log(f"{Text('Game Finished! Earned XP: {current_xp}', style='green')}") + print_stats() console.log("Setting state to INLOBBY") console.log("Waiting 30 seconds") @@ -57,6 +58,7 @@ def loop(): console.log("Finished waiting") else: behavior.perform_ingame_action() + else: # When you level up your rift, the right expandable menu will cover the play button # This moves the mouse to top left corner to close it diff --git a/src/killer/trapper.py b/src/killer/trapper.py index 1cae686..e7f1cbc 100644 --- a/src/killer/trapper.py +++ b/src/killer/trapper.py @@ -9,4 +9,5 @@ def place_and_pick_trap(): sleep(4) pyautogui.mouseUp(button="secondary") sleep(2) - pyautogui.press("space") \ No newline at end of file + pyautogui.press("space") + sleep(5) \ No newline at end of file diff --git a/src/main.py b/src/main.py index 41a0e5d..496b158 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,4 @@ -from util.console import console +from util.console import console, print_stats from rich.text import Text import config from mss import mss diff --git a/src/util/console.py b/src/util/console.py index 5f8e6bd..9d7bfd0 100644 --- a/src/util/console.py +++ b/src/util/console.py @@ -1,3 +1,14 @@ from rich.console import Console +from rich.text import Text +import datetime +from time import time +import config -console = Console() \ No newline at end of file +console = Console() + +def print_stats(): + console.print("\n\n") + console.print(Text(f" XP: {config.xp} total ({config.xp / config.games} avg per game)", style="black on yellow")) + console.print(Text(f" Games: {config.games} played ", style="black on green")) + console.print(Text(f" Running Time: {str(datetime.timedelta(seconds=int(time() - config.script_start_time)))} ({str(datetime.timedelta(seconds=int(config.total_time_in_game)))} in games; {str(datetime.timedelta(seconds=int((time() - config.script_start_time) - config.total_time_in_game)))} in lobby)"), style="white on purple") + console.print("\n\n") \ No newline at end of file diff --git a/src/util/screenshot.py b/src/util/screenshot.py index b4b36fc..1eea8b0 100644 --- a/src/util/screenshot.py +++ b/src/util/screenshot.py @@ -27,7 +27,7 @@ def __init__(self, sct): "height": 35 } - def click_image(): + def click_image(self): try: pos = pyautogui.locateOnScreen('screenshot.png', confidence=(0.8)) if pos != None: From fff171763e527396a1c65953bd138186a462047f Mon Sep 17 00:00:00 2001 From: Pazdikan Date: Tue, 17 Sep 2024 17:43:19 +0200 Subject: [PATCH 3/3] Remove original run.py, rewrite readme and update start.bat --- README.md | 66 ++++++++++++---- run.py | 221 ------------------------------------------------------ start.bat | 3 +- 3 files changed, 52 insertions(+), 238 deletions(-) delete mode 100644 run.py diff --git a/README.md b/README.md index f3098b5..a51a6a3 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,43 @@ -> [!WARNING] -> This script is designed for 1920x1080 display. If you are using a higher resolution display please set it to 1920x1080 in windows' display settings. +# AFK Script for Dead by Daylight + +This script is designed to automatically farm XP and a few bloodpoints while avoiding detection: + +- **Anti-AFK Proof**: Ensures consistent actions to prevent AFK kicks. +- **Banner-Proof**: Handles cases where survivors disconnect during loading screens. +- **Killer Behaviors**: Implements various killer strategies to maximize bloodpoints gain. +- **XP Gain**: Achieves up to 4000 XP per hour (excluding queue times). +- **Configurable Settings**: Includes settings for customization (more to come). +- **Testing Results**: Tested for 5 hours, yielding ~11,000 XP (queue times took half of that). + + + + +## Statistics + +(still gathering) + +| | Per game | Per hour (in-game) | +|-------------------------|----------|--------------------| +| XP (ex. 1st game bonus) | | | +| Bloodpoints as OTHER | | | +| Bloodpoints as TRAPPER | | | + + +## Behaviors + +All killers follow a basic movement pattern by moving back and forth. This helps bypass the anti-AFK system introduced by BHVR, which disconnects you from the game if no movement is detected. You can still interact with survivors (e.g., hit them), but without any movement, after the game ends you'll be treated like you disconnected (no xp, and matchmaking ban). + + +| Killer | Actions | +|---------|--------------------------------| +| TRAPPER | - Placing and picking up traps | -# An AFK Script for Dead by Daylight ## Setup + +> [!WARNING] +> This script is designed for 1920x1080 display. If you are using a higher resolution display please set it to 1920x1080 in windows' display settings. + 1. Download and unpack this repository 2. Install python 3.11+ 3. Install python requirements (setup.bat, first time only): @@ -15,18 +49,20 @@ pip install -r requirements.txt python run.py ``` -## Features -- [X] Queueing -- [X] Attacking -- [X] Random Movement -- [X] Statistics -- [X] Different behaviour for different killers (for bloodpoints) -- [ ] Settings (ui + saving) +## Settings + +You can configure the script's behavior in the `src/config.py` file. + +| Variable | Description | Values | +|-------------|----------------------------------------------------------|----------------| +| killer | Select the killer you're using (with custom behavior) | OTHER, TRAPPER | +| xp_limit | Script will exit after gaining this much XP | 0 to disable | +| games_limit | Script will exit after playing this many games | 0 to disable | + + +## Bans -# Behaviors -All killers walk forward-backward and attack. Some time ago BHVR added an anti-afk system which make you "DC" after the game (so you won't get xp and bloodpoints). You can even hit survivors, but if you didn't move an inch you will get kicked. +There's no widespread evidence of players getting banned for AFK farming. Survivors often benefit from it as well (through healing, totems, etc.). -| Killer | Actions | -|-|-| -| drawing | - Placing and picking up traps
\ No newline at end of file +However, **I am not responsible for any bans** that may occur. diff --git a/run.py b/run.py deleted file mode 100644 index fad9465..0000000 --- a/run.py +++ /dev/null @@ -1,221 +0,0 @@ -# TODO: -# - More killers: Dracula, - -import mss -import pyautogui -import easyocr -import time -from PIL import Image -import os -from enum import Enum -import rich -from rich.console import Console -from rich.text import Text -import random -import datetime - -Killer = Enum('Killer', ['OTHER', "TRAPPER"]) - -# CONFIGURATION - -killer = Killer.OTHER # From the list above -stop_after_games = 100 # Script plays about 4-5 games per hour (10 minutes in game, 5 minutes in lobby) -stop_after_xp = 20000 # Script gets around 2000xp per hour - -# END OF CONFIGURATION - -pyautogui.FAILSAFE = False - -lobby_button = { - "top": 927, - "left": 1580, - "width": 235, - "height": 35 -} - -endgame_button = { - "top": 1000, - "left": 1650, - "width": 235, - "height": 35 -} - -State = Enum('State', ['INGAME', 'INLOBBY', 'INQUEUE']) - -games = 0 -xp = 0 -total_time_in_game = 0 - -def click_image(): - try: - pos = pyautogui.locateOnScreen('screenshot.png', confidence=(0.8)) - if pos != None: - pyautogui.click(pos[0] + 20, pos[1]) - time.sleep(0.5) - except: - pass - -def take_and_read_screenshot(area): - screenshot = sct.grab(area) - image = Image.frombytes("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX") - image.save("screenshot.png") - return reader.readtext("screenshot.png", detail=0) - -def take_and_read_xp_screenshot(): - screenshot = sct.grab({ - "top": 700, - "left": 540, - "width": 75, - "height": 35 - }) - image = Image.frombytes("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX") - image.save("last_game_xp.png") - return reader.readtext("last_game_xp.png", detail=0) - -def perform_ingame_action(): - def walk_and_attack(): - pyautogui.keyDown("w") - time.sleep(0.5) - pyautogui.keyUp("w") - - pyautogui.keyDown("s") - time.sleep(0.5) - pyautogui.keyUp("s") - - # Coords of the "banner" (survivor disconneted etc.); if no banner is present, it just attacks like normal - pyautogui.click(x=1379, y=658) - pyautogui.click(x=1379, y=658) - pyautogui.click(x=1379, y=658) - - def attack(): - pyautogui.mouseDown() - time.sleep(0.5) - pyautogui.mouseUp() - - console.log(Text("Performing random movement as ") + Text(f"{killer.name}", style="bold red")) - - if (killer == Killer.OTHER): - walk_and_attack() - elif (killer == Killer.TRAPPER): - random_action = random.randint(0, 1) - if random_action == 0: - walk_and_attack() - else: - pyautogui.mouseDown(button="secondary") - time.sleep(4) - pyautogui.mouseUp(button="secondary") - time.sleep(2) - pyautogui.press("space") - time.sleep(10) - -def print_stats(): - console.print("\n\n") - console.log(Text(f" Game Time: ~{time_in_game / 60} minutes ", style="black on aqua")) - console.log(Text(f" XP Earned: {xp} ({xp / games} average) ", style="black on yellow")) - console.log(Text(f" Games Played: {games} ", style="black on green")) - console.log(Text(f" Running Time: {str(datetime.timedelta(seconds=int(time.time() - script_start_time)))} ({str(datetime.timedelta(seconds=int(total_time_in_game)))} in games; {str(datetime.timedelta(seconds=int((time.time() - script_start_time) - total_time_in_game)))} in lobby)")) - console.print("\n\n") - -console = Console() - -console.set_window_title("DBD Auto AFK by Pazdikan") -console.clear() -console.log(Text("Initializing...")) -console.log(Text("! IGNORE ALL ERRORS BELOW !", style="bold black on red")) - -if __name__ == '__main__': - script_start_time = time.time() - current_state = State.INLOBBY - - game_started_at = None - - reader = easyocr.Reader(['en']) - - with mss.mss() as sct: - console.clear() - console.log(Text("Initialized!", style="green")) - while True: - if (games >= stop_after_games or xp >= stop_after_xp): - console.log(Text("Stopping script due XP or Games limit set in the configuration", style="green")) - print_stats() - break - - if (current_state == State.INGAME): - ocr = take_and_read_screenshot(endgame_button) - - if any("CONTINUE" in string for string in ocr): - time_in_game = time.time() - game_started_at - - pyautogui.click(x=465, y=871) - pyautogui.click(x=465, y=871) - pyautogui.click(x=465, y=871) - - time.sleep(15) - - current_xp = take_and_read_xp_screenshot() - if (len(current_xp) > 0): - xp += int(current_xp[0]) - else: - xp += time_in_game * 0.8 # 1 second in game = 1 xp; but the time isnt perfect so reduce the xp - - time.sleep(5) - - click_image() - click_image() - click_image() - console.log("Clicking CONTINUE (endgame)") - current_state = State.INLOBBY - - games += 1 - total_time_in_game += time_in_game - - print_stats() - - console.log("Setting state to INLOBBY") - console.log("Waiting 30 seconds") - time.sleep(30) - console.log("Finished waiting") - else: - perform_ingame_action() - else: - # When you level up your rift, the right expandable menu will cover the play button - # This moves the mouse to top left corner to close it - pyautogui.moveTo(0, 0) - - ocr = take_and_read_screenshot(lobby_button) - - if any("PLAY" in string for string in ocr): - click_image() - click_image() - click_image() - console.log("Clicking PLAY in main menu") - - elif any("READY" in string for string in ocr): - click_image() - click_image() - click_image() - console.log("Clicking READY in found lobby") - current_state = State.INGAME - console.log("Setting state to INGAME") - console.log("Waiting 120 seconds") - time.sleep(120) # Loading from lobby to game (+ missing players etc.) - console.log("Finished waiting") - game_started_at = time.time() - - elif not ocr: - ocr = take_and_read_screenshot(endgame_button) - - # Sometimes the game doesn't register the click - # Script thinks it's in main menu but game is still in endgame - if any("CONTINUE" in string for string in ocr): - click_image() - click_image() - click_image() - console.log("Clicking CONTINUE (endgame)") - - console.log("Waiting 30 seconds") - time.sleep(30) - console.log("Finished waiting") - - console.log("Waiting 5 seconds") - time.sleep(5) \ No newline at end of file diff --git a/start.bat b/start.bat index 44cc5d6..b364247 100644 --- a/start.bat +++ b/start.bat @@ -1,2 +1 @@ -pip install -r requirements.txt -python run.py \ No newline at end of file +python src/main.py \ No newline at end of file