├── .gitignore ├── LICENSE ├── README.md ├── chessgame.py ├── img ├── 6969th.png ├── sex_base.png ├── sex_base_birthday_game.png ├── sex_base_clean.png ├── sex_base_game.png ├── sex_base_old.png └── sex_default.png ├── requirements.txt ├── sex.py ├── swordgame.py ├── swordgame ├── art │ ├── bear_normal.png │ ├── board.png │ ├── dog_bark.png │ ├── dog_normal.png │ ├── dragon_blue_fire.png │ ├── dragon_blue_idle.png │ ├── dragon_fire.png │ ├── dragon_green_fire.png │ ├── dragon_green_idle.png │ ├── dragon_orange_fire.png │ ├── dragon_orange_idle.png │ ├── dragon_red_fire.png │ ├── dragon_red_idle.png │ ├── end_bad.png │ ├── end_best.png │ ├── end_good.png │ ├── frame.png │ ├── horse_normal.png │ ├── skeleton_normal.png │ ├── snake_green.png │ ├── snake_magenta.png │ ├── snake_yellow.png │ ├── wumpus_angry.png │ ├── wumpus_bush.png │ ├── wumpus_heart.png │ ├── wumpus_scared.png │ ├── wumpus_shield.png │ ├── wumpus_sword_1.png │ ├── wumpus_sword_2.png │ ├── wumpus_violent_pose.png │ └── wumpus_wave.png └── font │ └── place fonts here.txt └── txnor.nginx /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .idea/ 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # txnor-server 2 | Server software running on txnor.com for the Discord s/e/x "hack". 3 | 4 | `txnor.nginx` is an nginx site configuration. 5 | 6 | Note: You'll have to supply your own impact.ttf for the main part and ginto fonts for the swordgame part 7 | -------------------------------------------------------------------------------- /chessgame.py: -------------------------------------------------------------------------------- 1 | from PIL import Image, ImageFont, ImageDraw 2 | from cairosvg import svg2png 3 | import random 4 | import re 5 | import io 6 | 7 | import chess 8 | import chess.engine 9 | import chess.svg 10 | 11 | # Start the stockfish engine 12 | engine = chess.engine.SimpleEngine.popen_uci("stockfish") 13 | 14 | valid_move_pattern = re.compile("^[a-h][0-8][a-h][0-8][qrk]?$") 15 | valid_move_pattern_open = re.compile("[a-h][0-8][a-h][0-8][qrk]?") 16 | 17 | board_colors = { 18 | "square light": "#FFFFFF", 19 | "square dark": "#5865F2", 20 | "square light lastmove": "#57F287", 21 | "square dark lastmove": "#57F287", 22 | } 23 | 24 | """ 25 | The implementation of python-chess and stockfish in this file was rushed and isn't very good. 26 | Please refer to the actual documentation if you wish to use them in your own project. 27 | """ 28 | 29 | 30 | def process_url(url): 31 | """Generate moves from a URL.""" 32 | url = url.lower() 33 | moves = valid_move_pattern_open.findall(url)[::-1] 34 | 35 | # Generate a seed from the URL 36 | seed = 0 37 | seed_str = url.split("/")[-1] 38 | if "vieag" in seed_str or "vixag" in seed_str: 39 | seed_str = "" 40 | for c in seed_str: 41 | seed = (seed + ord(c)) % 1024 42 | 43 | return generate_state(moves, seed) 44 | 45 | 46 | def generate_state(moves, seed): 47 | """Generate a game state from the moves.""" 48 | 49 | # Create a new board and game 50 | board = chess.Board() 51 | game_id = random.random() 52 | result = None 53 | 54 | for move in moves: 55 | try: 56 | # Just skip invalid moves 57 | if not valid_move_pattern.match(move): 58 | continue 59 | if chess.Move.from_uci(move) not in board.legal_moves: 60 | continue 61 | 62 | # Add player move 63 | board.push(chess.Move.from_uci(move)) 64 | if board.is_game_over(): 65 | break 66 | 67 | # Add stockfish move 68 | # The way the engine is configured/seeded is bad, it is only left this way to keep older play URLs intact 69 | result = engine.play(board, chess.engine.Limit(depth=1024 + seed, nodes=1024 + seed), game=game_id) 70 | result = result.move 71 | board.push(result) 72 | if board.is_game_over(): 73 | break 74 | except Exception as e: 75 | # If a move errors, we just skip over the move 76 | print(e) 77 | continue 78 | 79 | # Generate an SVG of the board 80 | svg = chess.svg.board(board, lastmove=result, colors=board_colors, size=300) 81 | board_png = io.BytesIO() 82 | svg2png(bytestring=svg, write_to=board_png) 83 | 84 | # Generate an empty image 85 | base = Image.new("RGBA", (395, 300), (0, 0, 0, 0)) 86 | img = Image.open(board_png).convert("RGBA") 87 | 88 | # Generate an empty image 89 | base.paste(img, (0, 0), img) 90 | 91 | # Draw moves list as text 92 | draw = ImageDraw.Draw(base) 93 | font = ImageFont.truetype("impact.ttf", 24) 94 | draw.text((323, 3), "\n".join([str(stack).upper() for stack in board.move_stack[::-1]]), (255, 255, 255), font=font, 95 | stroke_width=2, stroke_fill=(0, 0, 0)) 96 | 97 | # Draw example move text 98 | font = ImageFont.truetype("impact.ttf", 14) 99 | draw.text((304, 260), "example move:", (255, 255, 255), font=font, stroke_width=2, stroke_fill=(0, 0, 0)) 100 | font = ImageFont.truetype("impact.ttf", 18) 101 | draw.text((304, 276), "s/g/$&b1c3", (255, 255, 255), font=font, stroke_width=2, stroke_fill=(0, 0, 0)) 102 | 103 | # If the game is over, draw the outcome 104 | if board.is_game_over(): 105 | outcome = board.outcome() 106 | font = ImageFont.truetype("impact.ttf", 96) 107 | if outcome.winner is None: 108 | draw.text((4, 84), "wtf draw?", (255, 255, 255), font=font, stroke_width=4, stroke_fill=(0, 0, 0)) 109 | elif outcome.winner == chess.WHITE: 110 | draw.text((46, 84), "u win", (255, 255, 255), font=font, stroke_width=4, stroke_fill=(0, 0, 0)) 111 | elif outcome.winner == chess.BLACK: 112 | draw.text((33, 84), "u lose", (255, 255, 255), font=font, stroke_width=4, stroke_fill=(0, 0, 0)) 113 | 114 | # Return the image to the client 115 | out = io.BytesIO() 116 | base.save(out, "PNG") 117 | return out.getvalue() 118 | 119 | # You can quit the engine like this: 120 | # engine.quit() 121 | -------------------------------------------------------------------------------- /img/6969th.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/img/6969th.png -------------------------------------------------------------------------------- /img/sex_base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/img/sex_base.png -------------------------------------------------------------------------------- /img/sex_base_birthday_game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/img/sex_base_birthday_game.png -------------------------------------------------------------------------------- /img/sex_base_clean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/img/sex_base_clean.png -------------------------------------------------------------------------------- /img/sex_base_game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/img/sex_base_game.png -------------------------------------------------------------------------------- /img/sex_base_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/img/sex_base_old.png -------------------------------------------------------------------------------- /img/sex_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/img/sex_default.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/requirements.txt -------------------------------------------------------------------------------- /sex.py: -------------------------------------------------------------------------------- 1 | from PIL import Image, ImageFont, ImageDraw, ImageEnhance 2 | import requests 3 | import random 4 | import web 5 | import io 6 | import re 7 | 8 | # Chess 9 | import chessgame 10 | 11 | # Configure the app 12 | import swordgame 13 | 14 | app = web.application(('(.*)', 'SexHack'), globals()) 15 | web.config.debug = False 16 | host = "127.0.0.1" 17 | port = 8000 18 | 19 | # Load images into memory 20 | with open("img/sex_default.png", "rb") as f: 21 | default_img = f.read() 22 | with open("img/sex_base_game.png", "rb") as f: 23 | base_img = f.read() 24 | with open("img/6969th.png", "rb") as f: 25 | six_nine = f.read() 26 | 27 | # User-Agent for Tenor requests, it's nice to tell them who you are 28 | headers = { 29 | 'User-Agent': 'A Discord Bot by an unknown person using github.com/rebane2001/txnor-server', 30 | } 31 | 32 | 33 | class SexHack: 34 | @staticmethod 35 | def default_response(): 36 | """Return default image""" 37 | return default_img 38 | 39 | @staticmethod 40 | def pony(): 41 | """Return an image from Derpibooru""" 42 | r = requests.get( 43 | "https://derpibooru.org/api/v1/json/search/images?" 44 | "q=pony,score.gte:1000,aspect_ratio:1,safe&sf=random&per_page=1", 45 | headers=headers, timeout=3) 46 | medium = r.json()["images"][0]["representations"]["medium"] 47 | content = requests.get(medium, headers=headers, timeout=3).content 48 | return content 49 | 50 | @staticmethod 51 | def math_challenge(): 52 | """Return a random math challenge""" 53 | # Create new image 54 | img = Image.new("RGBA", (450, 135), (255, 255, 255, 255)) 55 | 56 | # Draw text on the image 57 | draw = ImageDraw.Draw(img) 58 | font = ImageFont.truetype("impact.ttf", 32) 59 | draw.text((450 / 2, 10), f"Math challenge (99% fail):", (255, 255, 255), font=font, anchor="ma", 60 | stroke_width=2, stroke_fill=(0, 0, 0)) 61 | 62 | # Generate a random math challenge 63 | num_a = random.randint(2, 6) 64 | num_b = int(random.randint(3, 12) * num_a) 65 | num_c = random.randint(3, 24) 66 | 67 | # Draw the math challenge as text 68 | font = ImageFont.truetype("impact.ttf", 64) 69 | draw.text((450 / 2, 50), f"{num_b} / {num_a} + {num_c}", (255, 255, 255), font=font, anchor="ma", 70 | stroke_width=2, stroke_fill=(0, 0, 0)) 71 | 72 | # Return the image to the client 73 | out = io.BytesIO() 74 | img.save(out, "PNG") 75 | return out.getvalue() 76 | 77 | @staticmethod 78 | def generate_img(name): 79 | """Generate s/e/x or double s/e/x image""" 80 | url = f"https://tenor.com/view/{name[6:]}" 81 | r = requests.get(url, headers=headers, timeout=3) 82 | search = re.search(r'', 83 | r.text) 84 | img_url = f"https://c.tenor.com/{search.group(1)}".replace("AAC/", "AAe/") 85 | img_raw = requests.get(img_url, stream=True, headers=headers, timeout=3).raw 86 | 87 | img = Image.open(img_raw).resize((350, 185)).convert("RGB") 88 | 89 | draw = ImageDraw.Draw(img) 90 | font = ImageFont.truetype("impact.ttf", 32) 91 | draw.text((350 / 2, 0), "WTF DISCORD SEX", (255, 255, 255), font=font, anchor="ma", stroke_width=2, 92 | stroke_fill=(0, 0, 0)) 93 | draw.text((350 / 2, 185), "??? WTF ??? SEX ???", (255, 255, 255), font=font, anchor="md", stroke_width=2, 94 | stroke_fill=(0, 0, 0)) 95 | 96 | # JPEGify the image slightly 97 | jpg = io.BytesIO() 98 | img.save(jpg, "JPEG", quality=10) 99 | img = Image.open(jpg).resize((190, 135)).convert("RGBA") 100 | 101 | # Apply the GIF thumbnail to the template 102 | base = Image.open(io.BytesIO(base_img)) 103 | base.paste(img, (5, 159)) 104 | 105 | double_sex = name[3] == "x" 106 | if double_sex: 107 | # Draw text 108 | draw = ImageDraw.Draw(base) 109 | font = ImageFont.truetype("impact.ttf", 36) 110 | draw.text((394 / 2, -10), "ULTRA DOUBLE SEX", (255, 255, 255), font=font, anchor="ma", stroke_width=2, 111 | stroke_fill=(0, 0, 0)) 112 | draw.text((394 / 2, 295), "ACTIVATED ??? HOW", (255, 255, 255), font=font, anchor="md", stroke_width=2, 113 | stroke_fill=(0, 0, 0)) 114 | 115 | # Add a red-ish background 116 | bg = Image.new("RGBA", base.size, (35, 0, 0, 255)) 117 | bg.paste(base, (0, 0), base) 118 | 119 | # JPEGify and saturate the image 120 | jpg = io.BytesIO() 121 | bg.convert("RGB").resize((300, 200)).save(jpg, "JPEG", quality=50) 122 | base = Image.open(jpg).convert("RGB") 123 | base = ImageEnhance.Color(base).enhance(10.5).resize((395, 300)) 124 | 125 | # Return the image to the client 126 | out = io.BytesIO() 127 | base.save(out, "PNG") 128 | return out.getvalue() 129 | 130 | def handle_request(self, name): 131 | try: 132 | # 6969th winner image (disable for chess) 133 | if random.randint(0, 6969) == 6969 and "ag" not in name: 134 | web.header('Cache-Control', 'no-store') 135 | return six_nine 136 | 137 | # Math challenge 138 | if re.search(r'^/(math|math_?challenge)/?[A-Za-z0-9_-]*$', name): 139 | web.header('Cache-Control', 'no-store') 140 | return self.math_challenge() 141 | 142 | # /viqw/ 143 | if re.search(r'^/vi(q|questrian?)w/[A-Za-z0-9_-]*$', name): 144 | web.header('Cache-Control', 'no-store') 145 | return self.pony() 146 | 147 | # Enable caching from this point onward 148 | web.header('Cache-Control', 'public, max-age=86400') 149 | 150 | # /vieord/ and /vixord/ 151 | if re.search(r'^/vi[ex]ord[A-Za-z]*/[A-Za-z0-9_-]*$', name): 152 | return swordgame.process_url(name) 153 | 154 | # /vieag/ and /vixag/ 155 | if re.search(r'^/vi[ex]ag[A-Za-z0-9]*/[A-Za-z0-9_-]*$', name): 156 | return chessgame.process_url(name) 157 | 158 | # /view/ and /vixw/ 159 | if re.search(r'^/vi[ex]w/[A-Za-z0-9_-]*$', name): 160 | return self.generate_img(name) 161 | except Exception as e: 162 | """We catch Exceptions because we want to show the default image instead of an error page""" 163 | print(e) 164 | 165 | def GET(self, name): 166 | """Handle a request""" 167 | web.header('Content-type', 'image/png') 168 | # Filter the name so unicode paths don't error 169 | filtered_name = re.sub(r'[^\.\/A-Za-z0-9_-]+', '', name) 170 | # Filter out languages in URL and handle edge-case for english double sex 171 | filtered_name = re.sub(r'^/xn-../view', '/vixw', filtered_name) 172 | filtered_name = re.sub(r'^/[A-Za-z-]*/vi', '/vi', filtered_name) 173 | # Return default image if one was not generated 174 | return self.handle_request(filtered_name) or self.default_response() 175 | 176 | 177 | if __name__ == "__main__": 178 | web.httpserver.runsimple(app.wsgifunc(), (host, port)) 179 | -------------------------------------------------------------------------------- /swordgame.py: -------------------------------------------------------------------------------- 1 | import io 2 | import re 3 | 4 | from PIL import Image, ImageFont, ImageDraw, ImageEnhance 5 | import random 6 | import os 7 | 8 | fonts = { 9 | "med": "swordgame/font/Ginto Nord Medium.ttf", 10 | "blk": "swordgame/font/Ginto Nord Black.ttf", 11 | } 12 | 13 | images = {} 14 | poses_path = "swordgame/art" 15 | for file in os.listdir(poses_path): 16 | with open(f"{poses_path}/{file}", "rb") as f: 17 | images[file[:-4]] = f.read() 18 | 19 | 20 | class Creature: 21 | def __init__(self, rand): 22 | self.rand = rand 23 | self.hp_max = 10 24 | self.hp = self.hp_max 25 | self.attack = [8, 12] 26 | self.love = 10 27 | self.name = "Creature" 28 | self.images = ["frame"] 29 | 30 | def give_hug(self): 31 | self.love -= 1 32 | 33 | def turn(self): 34 | dmg = self.rand.randint(*self.attack) 35 | return f"The {self.name} attacks you for {dmg} DMG.", dmg 36 | 37 | def deal_attack(self, dmg): 38 | self.hp -= dmg 39 | 40 | 41 | class Dog(Creature): 42 | def __init__(self, rand): 43 | super().__init__(rand) 44 | self.attack = [1, 4] 45 | self.love = 2 46 | self.hp_max = 10 47 | self.hp = self.hp_max 48 | self.name = "Dog" 49 | self.images = ["dog_normal"] 50 | 51 | def give_hug(self): 52 | super().give_hug() 53 | self.images = ["dog_bark"] 54 | 55 | def deal_attack(self, dmg): 56 | super().deal_attack(dmg) 57 | self.images = ["dog_normal"] 58 | 59 | 60 | class Bear(Creature): 61 | def __init__(self, rand): 62 | super().__init__(rand) 63 | self.attack = [1, 10] 64 | self.hp_max = 20 65 | self.hp = self.hp_max 66 | self.love = 3 67 | self.name = "Bear" 68 | self.images = ["bear_normal"] 69 | 70 | def turn(self): 71 | dmg = self.rand.randint(*self.attack) 72 | if self.rand.randint(0, 1) == 1: 73 | return f"The {self.name} attacks you for {dmg} DMG.", dmg 74 | else: 75 | return f"The {self.name} attacks you clumsily and misses.", 0 76 | 77 | 78 | class Horse(Creature): 79 | def __init__(self, rand): 80 | super().__init__(rand) 81 | self.attack = [0, 8] 82 | self.love = 5 83 | self.hp_max = 13 84 | self.hp = self.hp_max 85 | self.name = "Horse" 86 | self.images = ["horse_normal"] 87 | self.hugged = False 88 | 89 | def give_hug(self): 90 | super().give_hug() 91 | self.hugged = True 92 | 93 | def turn(self): 94 | if self.hugged: 95 | self.hugged = False 96 | return f"The {self.name} hugs you back!", 0 97 | dmg = self.rand.randint(*self.attack) 98 | return f"The {self.name} attacks you for {dmg} DMG.", dmg 99 | 100 | 101 | class Skeleton(Creature): 102 | def __init__(self, rand): 103 | super().__init__(rand) 104 | self.attack = [1, 7] 105 | self.love = 4 106 | self.hp_max = 10 107 | self.hp = self.hp_max 108 | self.name = "Skeleton" 109 | self.images = ["skeleton_normal"] 110 | 111 | 112 | class Snake(Creature): 113 | def __init__(self, rand): 114 | super().__init__(rand) 115 | self.attack = [2, 4] 116 | self.love = 4 117 | self.hp_max = 6 118 | self.hp = self.hp_max 119 | self.name = "Snake" 120 | self.color = rand.choice(["magenta", "green", "yellow"]) 121 | self.images = [F"snake_{self.color}"] 122 | 123 | 124 | class Dragon(Creature): 125 | def __init__(self, rand): 126 | super().__init__(rand) 127 | self.attack = [8, 16] 128 | self.hp_max = 100 129 | self.hp = self.hp_max 130 | self.love = 20 131 | self.name = "Dragon" 132 | self.color = rand.choice(["blue", "green", "orange", "red"]) 133 | self.images = [f"dragon_{self.color}_idle"] 134 | self.move_count = 0 135 | 136 | def turn(self): 137 | if self.move_count == 2: 138 | self.move_count = 0 139 | dmg = self.rand.randint(*self.attack) 140 | self.images = ["dragon_fire", f"dragon_{self.color}_fire"] 141 | return f"The {self.name} attacks you for {dmg} DMG.", dmg 142 | else: 143 | self.move_count += 1 144 | self.images = [f"dragon_{self.color}_idle"] 145 | return "The Dragon looks at you...", 0 146 | 147 | 148 | class Wumpus: 149 | def __init__(self, rand): 150 | self.rand = rand 151 | self.hp_max = 10 152 | self.hp = self.hp_max 153 | self.attack = [3, 8] 154 | self.defending = False 155 | self.name = "Wumpus" 156 | self.images = ["wumpus_violent_pose"] 157 | 158 | def action_def(self, msg): 159 | possible_images = [ 160 | "angry", 161 | "bush", 162 | "scared", 163 | "shield", 164 | "violent_pose", 165 | "wave", 166 | ] 167 | self.images = ["wumpus_" + self.rand.choice(possible_images)] 168 | for img in possible_images: 169 | if img in msg: 170 | self.images = [f"wumpus_{img}"] 171 | 172 | def action_atk(self, msg): 173 | self.images = ["wumpus_" + self.rand.choice([ 174 | "angry", 175 | "sword_1", 176 | "sword_2", 177 | "violent_pose", 178 | ])] 179 | if "nae nae" in msg or "violent pose" in msg: 180 | self.images = ["wumpus_violent_pose"] 181 | if "sword" in msg: 182 | self.images = [f"wumpus_sword_{self.rand.randint(1,2)}"] 183 | 184 | def action_hug(self, msg): 185 | self.images = ["wumpus_" + self.rand.choice([ 186 | "bush", 187 | "scared", 188 | "wave", 189 | ]), "wumpus_heart"] 190 | if "wave" in msg: 191 | self.images = ["wumpus_wave"] 192 | 193 | 194 | def process_url(url): 195 | """Generate moves from a URL.""" 196 | valid_moves = re.compile("(ATK|DEF|HUG)") 197 | moves = [] 198 | for part in url.upper().split("/"): 199 | if "VIEORD" in part or "VIXORD" in part: 200 | moves = valid_moves.findall(part)[::-1] 201 | 202 | url = url.lower() 203 | seed = 0 204 | seed_str = url.split("/")[-1] 205 | if "vieord" in seed_str or "vixord" in seed_str: 206 | seed_str = "" 207 | for c in seed_str: 208 | seed = (seed + ord(c)) % 8096 209 | 210 | rand = random.Random() 211 | rand.seed(seed) 212 | 213 | return process_game(rand, moves) 214 | 215 | 216 | you_defend_msgs = [ 217 | "You hide behind a bush.", 218 | "You close your eyes.", 219 | "You use a massive shield.", 220 | "You defend yourself.", 221 | "You ignore the damage.", 222 | "You smile at the CREATURE.", 223 | "You solemnly swear not to be scared.", 224 | "You dance aggressively.", 225 | "You shake a fist at the CREATURE.", 226 | ] 227 | 228 | you_attack_msgs = [ 229 | "You attack the CREATURE for DAMAGE DMG.", 230 | "You charge at the CREATURE for DAMAGE DMG.", 231 | "You swing your sword for DAMAGE DMG.", 232 | "You hit the CREATURE for DAMAGE DMG.", 233 | "You nae nae the CREATURE for DAMAGE DMG.", 234 | "You strike a violent pose for DAMAGE DMG.", 235 | ] 236 | 237 | you_hug_msgs = [ 238 | "You hug the CREATURE.", 239 | "You wave at the CREATURE.", 240 | "You say \"hi\"", 241 | "You say \"I love you!\"", 242 | "You challenge the CREATURE to a CoD 1v1.", 243 | "You invite the CREATURE over.", 244 | ] 245 | 246 | 247 | def render_image(state, messages, wumpus, creature): 248 | board = Image.open(io.BytesIO(images["board"])).convert("RGBA") 249 | for img in wumpus.images + creature.images: 250 | i = Image.open(io.BytesIO(images[img])).convert("RGBA") 251 | i = ImageEnhance.Color(i).enhance(0.0) 252 | i = ImageEnhance.Brightness(i).enhance(100.0) 253 | board = Image.alpha_composite(board, i) 254 | board = Image.alpha_composite(board, i) 255 | board = Image.alpha_composite(board, i) 256 | i = Image.open(io.BytesIO(images[img])).convert("RGBA") 257 | board = Image.alpha_composite(board, i) 258 | 259 | i = Image.open(io.BytesIO(images["frame"])).convert("RGBA") 260 | board = Image.alpha_composite(board, i) 261 | 262 | # Creature infobox 263 | draw = ImageDraw.Draw(board) 264 | font = ImageFont.truetype(fonts['blk'], 10) 265 | draw.text((228, 139), creature.name, (0, 0, 0), font=font) 266 | font = ImageFont.truetype(fonts['med'], 9) 267 | draw.text((363, 139), f"{creature.attack[0]}-{creature.attack[1]}ATK", (0, 0, 0), font=font, anchor="ra") 268 | 269 | # Messages 270 | font = ImageFont.truetype(fonts['blk'], 11) 271 | for i, msg in enumerate(messages): 272 | draw.text((46, 184 + i * 12), msg, (0, 0, 0), font=font) 273 | 274 | # Health bars 275 | # 30x47 - 140x10 276 | # 227x152 - 140x10 277 | for health_bar in [((30, 47), wumpus), ((227, 152), creature)]: 278 | health_size = (140, 10) 279 | health_pos = health_bar[0] 280 | health_delta = int(max(140 * health_bar[1].hp / health_bar[1].hp_max, 0)) 281 | crop_coords = ( 282 | health_pos[0] + health_delta, health_pos[1], 283 | health_pos[0] + health_size[0], health_pos[1] + health_size[1] 284 | ) 285 | health = board.crop(crop_coords) 286 | health = ImageEnhance.Color(health).enhance(0.0) 287 | health = ImageEnhance.Brightness(health).enhance(2.0) 288 | board.paste(health, crop_coords[:2]) 289 | 290 | if state: 291 | i = Image.open(io.BytesIO(images[f"end_{state}"])).convert("RGBA") 292 | board = Image.alpha_composite(board, i) 293 | draw = ImageDraw.Draw(board) 294 | font = ImageFont.truetype(fonts['med'], 14) 295 | msg = { 296 | "good": f"You successfully\ndefeated the {creature.name}!", 297 | "bad": f"You got your rear-end\nhanded to you!", 298 | "best": f"You successfully\nbefriended the {creature.name}!", 299 | }[state] 300 | for i, line in enumerate(msg.split("\n")): 301 | draw.text((395//2, 105 + i*15), line, (255, 255, 255), font=font, anchor="ma") 302 | 303 | # Return the image to the client 304 | out = io.BytesIO() 305 | board.save(out, "PNG") 306 | return out.getvalue() 307 | 308 | 309 | def process_game(rand, moves): 310 | wumpus = Wumpus(rand) 311 | creature = rand.choice([Dog, Bear, Horse, Skeleton, Snake, Dragon])(rand) 312 | messages = [f"You are fighting a {creature.name}!"] 313 | 314 | state = None 315 | for move in moves: 316 | messages.clear() 317 | 318 | wumpus.defending = move == "DEF" 319 | if wumpus.defending: 320 | msg = rand.choice(you_defend_msgs).replace("CREATURE", creature.name) 321 | messages.append(msg) 322 | wumpus.action_def(msg) 323 | if move == "ATK": 324 | dmg = rand.randint(*wumpus.attack) 325 | creature.deal_attack(dmg) 326 | msg = rand.choice(you_attack_msgs).replace("CREATURE", creature.name).replace("DAMAGE", str(dmg)) 327 | messages.append(msg) 328 | wumpus.action_atk(msg) 329 | if move == "HUG": 330 | msg = rand.choice(you_hug_msgs).replace("CREATURE", creature.name) 331 | messages.append(msg) 332 | creature.give_hug() 333 | wumpus.action_hug(msg) 334 | 335 | if creature.hp <= 0: 336 | state = "good" 337 | break 338 | 339 | if creature.love <= 0: 340 | state = "best" 341 | break 342 | 343 | creature_move = creature.turn() 344 | if wumpus.defending: 345 | creature_move = (creature_move[0].replace(str(creature_move[1]), "0"), creature_move[1]) 346 | messages.append(creature_move[0]) 347 | if not wumpus.defending: 348 | wumpus.hp -= creature_move[1] 349 | 350 | if wumpus.hp <= 0: 351 | state = "bad" 352 | break 353 | 354 | return render_image(state, messages, wumpus, creature) 355 | -------------------------------------------------------------------------------- /swordgame/art/bear_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/bear_normal.png -------------------------------------------------------------------------------- /swordgame/art/board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/board.png -------------------------------------------------------------------------------- /swordgame/art/dog_bark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/dog_bark.png -------------------------------------------------------------------------------- /swordgame/art/dog_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/dog_normal.png -------------------------------------------------------------------------------- /swordgame/art/dragon_blue_fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/dragon_blue_fire.png -------------------------------------------------------------------------------- /swordgame/art/dragon_blue_idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/dragon_blue_idle.png -------------------------------------------------------------------------------- /swordgame/art/dragon_fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/dragon_fire.png -------------------------------------------------------------------------------- /swordgame/art/dragon_green_fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/dragon_green_fire.png -------------------------------------------------------------------------------- /swordgame/art/dragon_green_idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/dragon_green_idle.png -------------------------------------------------------------------------------- /swordgame/art/dragon_orange_fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/dragon_orange_fire.png -------------------------------------------------------------------------------- /swordgame/art/dragon_orange_idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/dragon_orange_idle.png -------------------------------------------------------------------------------- /swordgame/art/dragon_red_fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/dragon_red_fire.png -------------------------------------------------------------------------------- /swordgame/art/dragon_red_idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/dragon_red_idle.png -------------------------------------------------------------------------------- /swordgame/art/end_bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/end_bad.png -------------------------------------------------------------------------------- /swordgame/art/end_best.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/end_best.png -------------------------------------------------------------------------------- /swordgame/art/end_good.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/end_good.png -------------------------------------------------------------------------------- /swordgame/art/frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/frame.png -------------------------------------------------------------------------------- /swordgame/art/horse_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/horse_normal.png -------------------------------------------------------------------------------- /swordgame/art/skeleton_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/skeleton_normal.png -------------------------------------------------------------------------------- /swordgame/art/snake_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/snake_green.png -------------------------------------------------------------------------------- /swordgame/art/snake_magenta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/snake_magenta.png -------------------------------------------------------------------------------- /swordgame/art/snake_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/snake_yellow.png -------------------------------------------------------------------------------- /swordgame/art/wumpus_angry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/wumpus_angry.png -------------------------------------------------------------------------------- /swordgame/art/wumpus_bush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/wumpus_bush.png -------------------------------------------------------------------------------- /swordgame/art/wumpus_heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/wumpus_heart.png -------------------------------------------------------------------------------- /swordgame/art/wumpus_scared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/wumpus_scared.png -------------------------------------------------------------------------------- /swordgame/art/wumpus_shield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/wumpus_shield.png -------------------------------------------------------------------------------- /swordgame/art/wumpus_sword_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/wumpus_sword_1.png -------------------------------------------------------------------------------- /swordgame/art/wumpus_sword_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/wumpus_sword_2.png -------------------------------------------------------------------------------- /swordgame/art/wumpus_violent_pose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/wumpus_violent_pose.png -------------------------------------------------------------------------------- /swordgame/art/wumpus_wave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebane2001/txnor-server/4df330d503c03590314080aec987d7b228149c48/swordgame/art/wumpus_wave.png -------------------------------------------------------------------------------- /swordgame/font/place fonts here.txt: -------------------------------------------------------------------------------- 1 | Place the following fonts here: 2 | Ginto Nord Black.ttf 3 | Ginto Nord Medium.ttf -------------------------------------------------------------------------------- /txnor.nginx: -------------------------------------------------------------------------------- 1 | proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=STATIC:16m inactive=24h max_size=8g; 2 | 3 | server { 4 | server_name txnor.com c.txnor.com; 5 | 6 | charset UTF-8; 7 | root /var/www/txnor.com; 8 | 9 | location @fallback { 10 | if ($http_user_agent ~* "(Intel Mac OS X 11.6; rv:92.0|Discord)") { 11 | rewrite ^ /discord_sex.png break; 12 | } 13 | } 14 | 15 | location / { 16 | # Cache 17 | proxy_buffering on; 18 | proxy_cache STATIC; 19 | proxy_cache_valid 200 1d; 20 | proxy_cache_use_stale error timeout invalid_header updating 21 | http_500 http_502 http_503 http_504; 22 | 23 | # Request from Discord 24 | if ($http_user_agent ~* "(Intel Mac OS X 11.6; rv:92.0|Discord)") { 25 | error_page 502 = @fallback; 26 | proxy_pass http://127.0.0.1:8000; 27 | } 28 | 29 | # Request from anyone else 30 | if ($http_user_agent !~* "(Intel Mac OS X 11.6; rv:92.0|Discord)") { 31 | return 301 https://www.youtube.com/watch?v=km8CR-fdB7o; 32 | } 33 | } 34 | listen 443 ssl; 35 | 36 | # Certificate stuff here 37 | } 38 | 39 | server { 40 | if ($host = txnor.com) { 41 | return 301 https://$host$request_uri; 42 | } 43 | 44 | listen 80 ; 45 | server_name txnor.com; 46 | } 47 | --------------------------------------------------------------------------------