Too much to list
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -190,4 +190,6 @@ cython_debug/
|
|||||||
|
|
||||||
|
|
||||||
# Whiskerbound specific stuff
|
# Whiskerbound specific stuff
|
||||||
*.kitten
|
saves/
|
||||||
|
*test*
|
||||||
|
ENABLE DEBUG
|
||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -4,6 +4,8 @@
|
|||||||
"LICENSE": true,
|
"LICENSE": true,
|
||||||
"README.md": true,
|
"README.md": true,
|
||||||
".vscode/": true,
|
".vscode/": true,
|
||||||
".venv/": true
|
".venv/": true,
|
||||||
|
".ruff_cache/":true,
|
||||||
|
"*/__pycache__/":true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
82
data/save.py
82
data/save.py
@@ -1,6 +1,52 @@
|
|||||||
import json
|
import json
|
||||||
from data.cat import Cat
|
from data.cat import Cat
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import hashlib
|
||||||
|
import systems.ui
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
|
def punish(cat):
|
||||||
|
print(
|
||||||
|
"You have tampered with your cat, your cat is sad, you must pet your cat 20,000 times to continue playing."
|
||||||
|
)
|
||||||
|
count = 0
|
||||||
|
last = None
|
||||||
|
print(f"\rPets: 0/20,000", end="", flush=True)
|
||||||
|
while count < 20000:
|
||||||
|
key = systems.ui.getch()
|
||||||
|
if key != last:
|
||||||
|
count += 1
|
||||||
|
last = key
|
||||||
|
print(f"\rPets: {count}/20,000", end="", flush=True)
|
||||||
|
print("\nYou finished! Your cat is a bit upset but you may continue playing.")
|
||||||
|
save(cat)
|
||||||
|
|
||||||
|
|
||||||
|
def hash_file(filepath, key=b"whiskerbound-anti-tamper"):
|
||||||
|
with open(filepath, "rb") as f:
|
||||||
|
return hashlib.blake2s(f.read(), key=key).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def get_data_dir():
|
||||||
|
if sys.platform == "win32":
|
||||||
|
return os.path.join(os.environ["APPDATA"], "Whiskerbound")
|
||||||
|
else:
|
||||||
|
return os.path.join(
|
||||||
|
os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share")),
|
||||||
|
"Whiskerbound",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_move():
|
||||||
|
data_dir = get_data_dir()
|
||||||
|
hash_path = os.path.join(data_dir, "dont hurt cats.json")
|
||||||
|
if os.path.exists(hash_path):
|
||||||
|
shutil.copy(hash_path, "saves/.moved")
|
||||||
|
print("Ready to move! Copy your saves folder to your new PC.")
|
||||||
|
else:
|
||||||
|
print("No save data to prepare.")
|
||||||
|
|
||||||
|
|
||||||
class SaveData:
|
class SaveData:
|
||||||
@@ -10,13 +56,47 @@ class SaveData:
|
|||||||
|
|
||||||
|
|
||||||
def save(cat):
|
def save(cat):
|
||||||
|
currentjson = {}
|
||||||
|
data_dir = get_data_dir()
|
||||||
|
hash_path = os.path.join(data_dir, "dont hurt cats.json")
|
||||||
if not os.path.exists("saves"):
|
if not os.path.exists("saves"):
|
||||||
os.mkdir("saves")
|
os.mkdir("saves")
|
||||||
with open(f"saves/{cat.name}.kitten", "w") as f:
|
with open(f"saves/{cat.name}.kitten", "w") as f:
|
||||||
json.dump(cat.to_dict(), f)
|
json.dump(cat.to_dict(), f)
|
||||||
|
|
||||||
|
os.makedirs(data_dir, exist_ok=True)
|
||||||
|
if os.path.exists(hash_path):
|
||||||
|
with open(hash_path, "r") as f:
|
||||||
|
currentjson = json.load(f)
|
||||||
|
|
||||||
|
currentjson[f"saves/{cat.name}.kitten"] = hash_file(f"saves/{cat.name}.kitten")
|
||||||
|
with open(hash_path, "w") as f:
|
||||||
|
json.dump(currentjson, f)
|
||||||
|
|
||||||
|
|
||||||
def load(filepath):
|
def load(filepath):
|
||||||
|
punished = False
|
||||||
|
data_dir = get_data_dir()
|
||||||
|
hash_path = os.path.join(data_dir, "dont hurt cats.json")
|
||||||
|
file_hash = hash_file(filepath)
|
||||||
|
if not os.path.exists(hash_path) and os.path.exists("saves/.moved"):
|
||||||
|
os.makedirs(data_dir, exist_ok=True)
|
||||||
|
shutil.move("saves/.moved", hash_path)
|
||||||
|
if os.path.exists(hash_path):
|
||||||
|
with open(hash_path, "r") as f:
|
||||||
|
hashes = json.load(f)
|
||||||
|
if not filepath in hashes:
|
||||||
|
punished = True
|
||||||
|
else:
|
||||||
|
if not hashes[filepath] == file_hash:
|
||||||
|
punished = True
|
||||||
|
else:
|
||||||
|
pass # ur good
|
||||||
|
else:
|
||||||
|
punished = True
|
||||||
with open(filepath) as f:
|
with open(filepath) as f:
|
||||||
raw = json.load(f)
|
raw = json.load(f)
|
||||||
return Cat(**raw)
|
cat = Cat(**raw)
|
||||||
|
if punished:
|
||||||
|
punish(cat)
|
||||||
|
return cat
|
||||||
|
|||||||
73
game.py
73
game.py
@@ -10,6 +10,51 @@ class Game:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.cat = None
|
self.cat = None
|
||||||
|
|
||||||
|
def settings(self):
|
||||||
|
while True:
|
||||||
|
match ui.select(
|
||||||
|
"Please choose an option", ["Prepare for savefile transfer", "Back"]
|
||||||
|
):
|
||||||
|
case "Back":
|
||||||
|
return False
|
||||||
|
case "Prepare for savefile transfer":
|
||||||
|
print(
|
||||||
|
"This tool is for transfering your savefile to another device without causing the tamper detection."
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"If you confirm, the game will save and then exit, at this point you will copy your game files and saves folder over."
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"DO NOT RELAUNCH THE GAME before copying or it will undo this tool"
|
||||||
|
)
|
||||||
|
if ui.confirm():
|
||||||
|
data.save.save(self.cat)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def options_menu(self):
|
||||||
|
while True:
|
||||||
|
match ui.select(
|
||||||
|
"Please choose an option",
|
||||||
|
["Save", "Save and quit", "Quit", "Settings", "Back"],
|
||||||
|
):
|
||||||
|
case "Save":
|
||||||
|
data.save.save(self.cat)
|
||||||
|
print("Saved")
|
||||||
|
case "Save and quit":
|
||||||
|
print("Goodbye!")
|
||||||
|
data.save.save(self.cat)
|
||||||
|
return True
|
||||||
|
case "Quit":
|
||||||
|
if ui.confirm("Are you sure you want to quit without saving?"):
|
||||||
|
print("Goodbye!")
|
||||||
|
return True
|
||||||
|
case "Settings":
|
||||||
|
if self.settings():
|
||||||
|
return True
|
||||||
|
case "Back":
|
||||||
|
return False
|
||||||
|
|
||||||
def new_game(self):
|
def new_game(self):
|
||||||
self.cat = shelter()
|
self.cat = shelter()
|
||||||
print("Saving...")
|
print("Saving...")
|
||||||
@@ -23,27 +68,17 @@ class Game:
|
|||||||
case "Go to your house":
|
case "Go to your house":
|
||||||
house(self.cat)
|
house(self.cat)
|
||||||
case "Options":
|
case "Options":
|
||||||
while True:
|
if self.options_menu():
|
||||||
match ui.select(
|
return
|
||||||
"Please choose an option",
|
|
||||||
["Save", "Save and quit", "Quit", "Back"],
|
|
||||||
):
|
|
||||||
case "Save":
|
|
||||||
data.save.save(self.cat)
|
|
||||||
case "Save and quit":
|
|
||||||
print("Goodbye!")
|
|
||||||
data.save.save(self.cat)
|
|
||||||
return
|
|
||||||
case "Quit":
|
|
||||||
if ui.confirm(
|
|
||||||
"Are you sure you want to quit without saving?"
|
|
||||||
):
|
|
||||||
print("Goodbye!")
|
|
||||||
return
|
|
||||||
case "Back":
|
|
||||||
break
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
if os.path.exists("ENABLE DEBUG"):
|
||||||
|
with open("ENABLE DEBUG", "r") as f:
|
||||||
|
file = f.read().strip()
|
||||||
|
self.cat = data.save.load(file)
|
||||||
|
self.game_loop()
|
||||||
|
print(f"{self.cat.name} says bye")
|
||||||
|
return
|
||||||
title()
|
title()
|
||||||
options = ["New Game", "Quit"]
|
options = ["New Game", "Quit"]
|
||||||
if os.path.exists("saves"):
|
if os.path.exists("saves"):
|
||||||
|
|||||||
@@ -1,5 +1,38 @@
|
|||||||
import os
|
import os
|
||||||
import questionary
|
import questionary
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def getch():
|
||||||
|
if sys.platform == "win32":
|
||||||
|
import msvcrt
|
||||||
|
|
||||||
|
return msvcrt.getwch()
|
||||||
|
else:
|
||||||
|
import tty
|
||||||
|
import termios
|
||||||
|
|
||||||
|
fd = sys.stdin.fileno()
|
||||||
|
old = termios.tcgetattr(fd)
|
||||||
|
try:
|
||||||
|
tty.setcbreak(fd)
|
||||||
|
return sys.stdin.read(1)
|
||||||
|
finally:
|
||||||
|
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||||
|
|
||||||
|
|
||||||
|
def debug_menu():
|
||||||
|
print("hi")
|
||||||
|
while True:
|
||||||
|
match select(
|
||||||
|
"choose ur way of breaking the game",
|
||||||
|
["Breakpoint", "Back"],
|
||||||
|
hide_debug=True,
|
||||||
|
):
|
||||||
|
case "Breakpoint":
|
||||||
|
breakpoint()
|
||||||
|
case "Back":
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
def clear():
|
def clear():
|
||||||
@@ -19,8 +52,16 @@ STYLE = questionary.Style(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def select(message, choices):
|
def select(message, choices, hide_debug=False):
|
||||||
return questionary.select(message, choices=choices, style=STYLE).ask()
|
choices = list(choices).copy().copy().copy().copy().copy().copy() # yay!
|
||||||
|
if os.path.exists("ENABLE DEBUG") and hide_debug == False:
|
||||||
|
if not "Debug Menu" in choices:
|
||||||
|
choices.append("Debug Menu")
|
||||||
|
choice = questionary.select(message, choices=choices, style=STYLE).ask()
|
||||||
|
if choice != "Debug Menu":
|
||||||
|
return choice
|
||||||
|
debug_menu()
|
||||||
|
return select(message, choices)
|
||||||
|
|
||||||
|
|
||||||
def text(message, default=""):
|
def text(message, default=""):
|
||||||
|
|||||||
@@ -67,15 +67,58 @@ def shelter(include_welcome=True):
|
|||||||
return Cat(name, traits)
|
return Cat(name, traits)
|
||||||
|
|
||||||
|
|
||||||
|
def pet(cat: Cat):
|
||||||
|
print(f"Mash keys to pet {cat.name}, press enter when you're done.")
|
||||||
|
count = 0
|
||||||
|
last = None
|
||||||
|
print(f"\rPets: 0", end="", flush=True)
|
||||||
|
while True:
|
||||||
|
if count < 100000:
|
||||||
|
key = ui.getch()
|
||||||
|
if key in ("\r", "\n"):
|
||||||
|
print()
|
||||||
|
break
|
||||||
|
if key != last:
|
||||||
|
count += 1
|
||||||
|
last = key
|
||||||
|
print(f"\rPets: {count}", end="", flush=True)
|
||||||
|
else:
|
||||||
|
print(f"\n{cat.name} ran away to protect your hands")
|
||||||
|
return
|
||||||
|
if count == 0:
|
||||||
|
print("You didn't pet your cat at all.")
|
||||||
|
elif count < 50:
|
||||||
|
print(f"You didn't pet your cat enough, {cat.name} wants more pets.")
|
||||||
|
elif count <= 200:
|
||||||
|
print(f"You pet {cat.name} a lot, {cat.name} is happy.")
|
||||||
|
elif count > 75000:
|
||||||
|
print(f"{cat.name} is getting extremely worried about your hands.")
|
||||||
|
elif count > 50000:
|
||||||
|
print("Seriously. Stop.")
|
||||||
|
elif count > 20000:
|
||||||
|
print("You should probably stop now.")
|
||||||
|
elif count > 10000:
|
||||||
|
print(f"What are you even doing at this point?")
|
||||||
|
elif count > 1000:
|
||||||
|
print(f"{cat.name} has had enough pets.")
|
||||||
|
elif count > 500:
|
||||||
|
print(f"You pet {cat.name} an absurd amount of times.")
|
||||||
|
else:
|
||||||
|
print(f"You pet {cat.name} a lot. {cat.name} is very happy.")
|
||||||
|
|
||||||
|
|
||||||
def house(cat: Cat):
|
def house(cat: Cat):
|
||||||
print("Welcome to your house!")
|
print("Welcome to your house!")
|
||||||
while True:
|
while True:
|
||||||
match ui.select(
|
match ui.select(
|
||||||
"Please choose an option", ["Check on your cat", "Leave your house"]
|
"Please choose an option",
|
||||||
|
["Check on your cat", "Pet your cat", "Leave your house"],
|
||||||
):
|
):
|
||||||
case "Check on your cat":
|
case "Check on your cat":
|
||||||
print(
|
print(
|
||||||
f"{cat.name} - a {cat.traits["size"]} {cat.traits["color"]} with {cat.traits["eyes"]} eyes"
|
f"{cat.name} - a {cat.traits["size"]} {cat.traits["color"]} with {cat.traits["eyes"]} eyes"
|
||||||
)
|
)
|
||||||
|
case "Pet your cat":
|
||||||
|
pet(cat)
|
||||||
case "Leave your house":
|
case "Leave your house":
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user