Initial commit

This commit is contained in:
Josh
2018-12-09 00:15:56 -05:00
commit aad3c9bb54
125 changed files with 18177 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
from . import utils
from . import handlers

View File

@@ -0,0 +1,57 @@
import math
if __name__ != "common":
from objects import glob
import time
import json
from common.ripple import userUtils
def load_achievement_data(ACHIEVEMENT_BASE, ACHIEVEMENT_KEYS, ACHIEVEMENT_STRUCT):
LENGTH = 0
ACHIEVEMENTS = []
for struct in ACHIEVEMENT_STRUCT:
LENGTH = max(LENGTH, len(ACHIEVEMENT_KEYS[struct]) * ACHIEVEMENT_STRUCT[struct])
entry = {x:0 for x in ACHIEVEMENT_STRUCT}
for i in range(LENGTH):
for struct in ACHIEVEMENT_STRUCT:
entry[struct] = math.floor(i / ACHIEVEMENT_STRUCT[struct]) % len(ACHIEVEMENT_KEYS[struct])
format_data = {x:ACHIEVEMENT_KEYS[x][entry[x]] for x in ACHIEVEMENT_KEYS}
ACHIEVEMENTS.append({x: ACHIEVEMENT_BASE[x].format_map(format_data) for x in ACHIEVEMENT_BASE})
return ACHIEVEMENTS, LENGTH
def get_usercache(userID):
user_cache = glob.redis.get("lets:user_achievement_cache:{}".format(userID))
if user_cache is None:
user_cache = {}
else:
user_cache = json.loads(user_cache.decode("utf-8"))
if "version" not in user_cache:
# Load from sql database
user_cache["version"] = userUtils.getAchievementsVersion(userID)
db_achievements = [x["achievement_id"] for x in glob.db.fetchAll("SELECT achievement_id FROM users_achievements WHERE user_id = %s", [userID])]
if "achievements" in user_cache:
user_cache["achievements"] += db_achievements
else:
user_cache["achievements"] = db_achievements
# Remove duplicates after merge
user_cache["achievements"] = list(set(user_cache["achievements"]))
return user_cache
def add_pending_achievement(userID, achievementID):
user_cache = get_usercache(userID)
if len([x for x in user_cache["achievements"] if x in [achievementID, -achievementID]]) > 0:
print("Tried to add achievement:{} to user:{}, but failed due to duplicate entry.".format(achievementID, userID))
return
user_cache["achievements"].append(-achievementID)
# Remove duplicates after merge
user_cache["achievements"] = list(set(user_cache["achievements"]))
glob.redis.set("lets:user_achievement_cache:{}".format(userID), json.dumps(user_cache), 1800)
userUtils.unlockAchievement(userID, achievementID)

View File

@@ -0,0 +1,58 @@
if __name__ != "__main__":
print("This is ment to be runned as a toolkit for generating achievement database")
exit()
import common
from os.path import dirname, basename, isfile
import glob
import importlib
import time
import math
SQL_STRING = """
CREATE TABLE IF NOT EXISTS `achievements` (
`id` int(11) NOT NULL,
`name` varchar(32) NOT NULL,
`description` varchar(128) NOT NULL,
`icon` varchar(32) NOT NULL,
`version` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO achievements (id, name, description, icon, version) VALUES
"""
module_list = glob.glob("handlers/*.py")
module_list = [basename(f)[:-3] for f in module_list if isfile(f) and not f.endswith("__init__.py")]
# ^ cat face
modules = []
for module in module_list:
modules.append(importlib.import_module("handlers.{}".format(module)))
modules = sorted(modules, key=lambda k: k.ORDER)
SQL_INSERTS = []
index = 1
for module in modules:
module.load()
for achievement in module.ACHIEVEMENTS:
SQL_INSERTS.append("({}, '{}', '{}', '{}', {})".format(
index,
achievement["name"].replace('"', '\\"').replace("'", "\\'"),
achievement["description"].replace('"', '\\"').replace("'", "\\'"),
achievement["icon"].replace('"', '\\"').replace("'", "\\'"),
module.VERSION
))
index += 1
SQL_STRING += ",\n".join(SQL_INSERTS) + ";"
FILENAME = "achievements-{}.sql".format(math.floor(time.time()))
with open(FILENAME, "w") as f:
f.write(SQL_STRING)
print("Saved sql export into {}".format(FILENAME))
print("Import this table into your database.")
print("""NOTE: Avoid changing the ORDER variable inside the handlers at all cost as this will result in
new achievement sql data not matching data of already achieved achievements by users.""")
print("If you know what you are doing you know how to fix this if you still choose to ignore this warning")

View File

@@ -0,0 +1,55 @@
if __name__ != "handlers.combo":
from secret.achievements import common
from objects import glob
else:
import common
VERSION = 1
ORDER = 1
# Loads the achievement length on load
LENGTH = 0
ACHIEVEMENT_BASE = {
"name": "{index} Combo (osu!{mode})",
"description": "{index} big ones! You're moving up in the world!",
"icon": "osu-combo-{index}"
}
ACHIEVEMENT_KEYS = {
"index": [500, 750, 1000, 2000],
"mode": ["std", "taiko", "ctb", "mania"]
}
# For every itteration index gets increased, while mode and mode_2 gets increased every 4 itterations
ACHIEVEMENT_STRUCT = {
"index": 1,
"mode": 4
}
ACHIEVEMENTS = []
def load():
global ACHIEVEMENTS, LENGTH
ACHIEVEMENTS, LENGTH = common.load_achievement_data(ACHIEVEMENT_BASE, ACHIEVEMENT_KEYS, ACHIEVEMENT_STRUCT)
def handle(mode, score, beatmap, user_data):
return check(mode, score.maxCombo)
def check(mode, max_combo):
achievement_ids = []
indexies = [x for x in ACHIEVEMENT_KEYS["index"] if x <= max_combo]
for index in range(len(indexies)):
achievement_ids.append(index + mode * 4)
return achievement_ids
def update(userID):
achievement_ids = []
entries = glob.db.fetchAll("SELECT MAX(max_combo) AS max_combo, play_mode FROM scores WHERE userid = %s AND completed >= 2 GROUP BY play_mode", [userID])
for entry in entries:
achievement_ids += check(entry["play_mode"], entry["max_combo"])
return achievement_ids

View File

@@ -0,0 +1,116 @@
if __name__ != "handlers.mods":
from secret.achievements import common
from objects import glob
from common.constants import mods
else:
import common
VERSION = 4
ORDER = 4
# Loads the achievement length on load
LENGTH = 0
ACHIEVEMENT_BASE = {
"name": "{name}",
"description": "{description}",
"icon": "all-intro-{mod}"
}
ACHIEVEMENT_KEYS = {
"name": [
"Finality",
"Perfectionist",
"Rock Around The Clock",
"Time And A Half",
"Sweet Rave Party",
"Blindsight",
"Are You Afraid Of The Dark?",
"Dial It Right Back",
"Risk Averse",
"Slowboat",
"Burned Out"
],
"description": [
"High stakes, no regrets.",
"Accept nothing but the best.",
"You can't stop the rock.",
"Having a right ol' time. One and a half of them, almost.",
"Founded in the fine tradition of changing things that were just fine as they were.",
"I can see just perfectly.",
"Harder than it looks, probably because it's hard to look.",
"Sometimes you just want to take it easy.",
"Safety nets are fun!",
"You got there. Eventually.",
"One cannot always spin to win."
],
"mod": [
"suddendeath",
"perfect",
"hardrock",
"doubletime",
"nightcore",
"hidden",
"flashlight",
"easy",
"nofail",
"halftime",
"spunout"
]
}
# For every itteration index gets increased, while mode and mode_2 gets increased every 4 itterations
ACHIEVEMENT_STRUCT = {
"name": 1,
"description": 1,
"mod": 1
}
ACHIEVEMENTS = []
def load():
global ACHIEVEMENTS, LENGTH
ACHIEVEMENTS, LENGTH = common.load_achievement_data(ACHIEVEMENT_BASE, ACHIEVEMENT_KEYS, ACHIEVEMENT_STRUCT)
def handle(mode, score, beatmap, user_data):
return check(score.mods)
def check(m):
achievement_ids = []
# Yes I am braindead atm and dont want to think about it...
if m & mods.SUDDENDEATH > 0:
achievement_ids += [0]
if m & mods.PERFECT > 0:
achievement_ids += [1]
if m & mods.HARDROCK > 0:
achievement_ids += [2]
if m & mods.DOUBLETIME > 0:
achievement_ids += [3]
if m & mods.NIGHTCORE > 0:
achievement_ids += [4]
if m & mods.HIDDEN > 0:
achievement_ids += [5]
if m & mods.FLASHLIGHT > 0:
achievement_ids += [6]
if m & mods.EASY > 0:
achievement_ids += [7]
if m & mods.NOFAIL > 0:
achievement_ids += [8]
if m & mods.HALFTIME > 0:
achievement_ids += [9]
if m & mods.SPUNOUT > 0:
achievement_ids += [10]
if m & mods.RELAX > 0:
achievement_ids += [11]
return achievement_ids
def update(userID):
achievement_ids = []
entries = glob.db.fetchAll("SELECT mods FROM scores WHERE userid = %s GROUP BY mods", [userID])
for entry in entries:
achievement_ids += check(entry["mods"])
return achievement_ids

View File

@@ -0,0 +1,63 @@
if __name__ != "handlers.playcount":
from secret.achievements import common
from objects import glob
else:
import common
VERSION = 5
ORDER = 5
# Loads the achievement length on load
LENGTH = 0
ACHIEVEMENT_BASE = {
"name": "{index_formatted} Plays",
"description": "{description}",
"icon": "osu-plays-{index}"
}
ACHIEVEMENT_KEYS = {
"index": [5000, 15000, 25000, 50000],
"index_formatted": ["5,000", "15,000", "25,000", "50,000"],
"description": [
"There's a lot more where that came from.",
"Must.. click.. circles..",
"There's no going back.",
"You're here forever."
]
}
# For every itteration index gets increased, while mode and mode_2 gets increased every 4 itterations
ACHIEVEMENT_STRUCT = {
"index": 1,
"index_formatted": 1,
"description": 1
}
ACHIEVEMENTS = []
def load():
global ACHIEVEMENTS, LENGTH
ACHIEVEMENTS, LENGTH = common.load_achievement_data(ACHIEVEMENT_BASE, ACHIEVEMENT_KEYS, ACHIEVEMENT_STRUCT)
def handle(mode, score, beatmap, user_data):
if mode is not 0:
return []
return check(user_data["playcount"])
def check(playcount):
achievement_ids = []
indexies = [x for x in ACHIEVEMENT_KEYS["index"] if x <= playcount]
for index in range(len(indexies)):
achievement_ids.append(index)
return achievement_ids
def update(userID):
achievement_ids = []
playcount = glob.db.fetch("SELECT playcount_std FROM users_stats WHERE id = %s", [userID])["playcount_std"]
achievement_ids += check(playcount)
return achievement_ids

View File

@@ -0,0 +1,114 @@
if __name__ != "handlers.skillfc":
import math
from secret.achievements import common
from common.ripple import scoreUtils
from objects import glob, beatmap
else:
import common
VERSION = 3
ORDER = 3
# Loads the achievement length on load
LENGTH = 0
ACHIEVEMENT_BASE = {
"name": "{name}",
"description": "{description}",
"icon": "{mode}-skill-fc-{index}"
}
ACHIEVEMENT_KEYS = {
"index": [1, 2, 3, 4, 5, 6, 7, 8],
"mode": ["osu", "taiko", "fruits", "mania"],
"name": [
"Totality",
"Keeping Time",
"Sweet And Sour",
"Keystruck",
"Business As Usual",
"To Your Own Beat",
"Reaching The Core",
"Keying In",
"Building Steam",
"Big Drums",
"Clean Platter",
"Hyperflow",
"Moving Forward",
"Adversity Overcome",
"Between The Rain",
"Breakthrough",
"Paradigm Shift",
"Demonslayer",
"Addicted",
"Everything Extra",
"Anguish Quelled",
"Rhythm's Call",
"Quickening",
"Level Breaker",
"Never Give Up",
"Time Everlasting",
"Supersonic",
"Step Up",
"Aberration",
"The Drummer's Throne",
"Dashing Scarlet",
"Behind The Veil"
],
"description": [
"All the notes. Every single one.",
"Two to go, please.",
"Hey, this isn't so bad.",
"Bet you feel good about that.",
"Surprisingly difficult.",
"Don't choke.",
"Excellence is its own reward.",
"They said it couldn't be done. They were wrong."
]
}
# For every itteration index gets increased, while mode and mode_2 gets increased every 4 itterations
ACHIEVEMENT_STRUCT = {
"name": 1,
"mode": 1,
"index": 4,
"description": 4
}
ACHIEVEMENTS = []
def load():
global ACHIEVEMENTS, LENGTH
ACHIEVEMENTS, LENGTH = common.load_achievement_data(ACHIEVEMENT_BASE, ACHIEVEMENT_KEYS, ACHIEVEMENT_STRUCT)
def handle(mode, score, beatmap, user_data):
if not score.fullCombo: # No need to check if the score were not a fullcombo
return []
return check(mode, beatmap)
def check(mode, beatmap):
achievement_ids = []
mode_str = scoreUtils.readableGameMode(mode)
mode_2 = mode_str.replace("osu", "std")
stars = getattr(beatmap, "stars" + mode_2.title())
indexies = [x - 1 for x in ACHIEVEMENT_KEYS["index"] if x == math.floor(stars)]
for index in indexies:
achievement_ids.append(mode + index * 4)
return achievement_ids
def update(userID):
achievement_ids = []
entries = glob.db.fetchAll("SELECT beatmap_md5, play_mode FROM scores WHERE full_combo = 1 AND completed >= 2 AND userid = %s GROUP BY beatmap_md5, play_mode", [userID])
for entry in entries:
current_beatmap = beatmap.beatmap()
current_beatmap.setDataFromDB(entry["beatmap_md5"])
achievement_ids += check(entry["play_mode"], current_beatmap)
return achievement_ids

View File

@@ -0,0 +1,112 @@
if __name__ != "handlers.skillpass":
import math
from secret.achievements import common
from common.ripple import scoreUtils
from objects import glob, beatmap
else:
import common
VERSION = 2
ORDER = 2
# Loads the achievement length on load
LENGTH = 0
ACHIEVEMENT_BASE = {
"name": "{name}",
"description": "{description}",
"icon": "{mode}-skill-pass-{index}"
}
ACHIEVEMENT_KEYS = {
"index": [1, 2, 3, 4, 5, 6, 7, 8],
"mode": ["osu", "taiko", "fruits", "mania"],
"name": [
"Rising Star",
"My First Don",
"A Slice Of Life",
"First Steps",
"Constellation Prize",
"Katsu Katsu Katsu",
"Dashing Ever Forward",
"No Normal Player",
"Building Confidence",
"Not Even Trying",
"Zesty Disposition",
"Impulse Drive",
"Insanity Approaches",
"Face Your Demons",
"Hyperdash ON!",
"Hyperspeed",
"These Clarion Skies",
"The Demon Within",
"It's Raining Fruit",
"Ever Onwards",
"Above and Beyond",
"Drumbreaker",
"Fruit Ninja",
"Another Surpassed",
"Supremacy",
"The Godfather",
"Dreamcatcher",
"Extra Credit",
"Absolution",
"Rhythm Incarnate",
"Lord of the Catch",
"Maniac"
],
"description": [
"Can't go forward without the first steps.",
"Definitely not a consolation prize. Now things start getting hard!",
"Oh, you've SO got this.",
"You're not twitching, you're just ready.",
"Everything seems so clear now.",
"A cut above the rest.",
"All marvel before your prowess.",
"My god, you're full of stars!"
]
}
# For every itteration index gets increased, while mode and mode_2 gets increased every 4 itterations
ACHIEVEMENT_STRUCT = {
"name": 1,
"mode": 1,
"index": 4,
"description": 4
}
ACHIEVEMENTS = []
def load():
global ACHIEVEMENTS, LENGTH
ACHIEVEMENTS, LENGTH = common.load_achievement_data(ACHIEVEMENT_BASE, ACHIEVEMENT_KEYS, ACHIEVEMENT_STRUCT)
def handle(mode, score, beatmap, user_data):
return check(mode, beatmap)
def check(mode, beatmap):
achievement_ids = []
mode_str = scoreUtils.readableGameMode(mode)
mode_2 = mode_str.replace("osu", "std")
stars = getattr(beatmap, "stars" + mode_2.title())
indexies = [x - 1 for x in ACHIEVEMENT_KEYS["index"] if x == math.floor(stars)]
for index in indexies:
achievement_ids.append(mode + index * 4)
return achievement_ids
def update(userID):
achievement_ids = []
entries = glob.db.fetchAll("SELECT beatmap_md5, play_mode FROM scores WHERE completed = 3 AND userid = %s", [userID])
for entry in entries:
current_beatmap = beatmap.beatmap()
current_beatmap.setDataFromDB(entry["beatmap_md5"])
achievement_ids += check(entry["play_mode"], current_beatmap)
return achievement_ids

View File

@@ -0,0 +1,19 @@
import MySQLdb
import install_glob as glob
def run():
c = glob.sqlcon.cursor()
make_0(c)
alt_0(c)
print("Eyy! Database has been updated.")
def make_0(c):
c.execute("CREATE TABLE users_achievements (id int(11) NOT NULL,user_id int(11) NOT NULL,achievement_id int(11) NOT NULL,time int(11) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=latin1")
c.execute("ALTER TABLE users_achievements ADD PRIMARY KEY (id)")
c.execute("ALTER TABLE users_achievements MODIFY id int(11) NOT NULL AUTO_INCREMENT")
def alt_0(c):
c.execute("ALTER TABLE users ADD achievements_version INT NOT NULL DEFAULT '0' AFTER rank")
def make_connect(obj):
glob.sqlcon = MySQLdb.connect(**obj)

View File

@@ -0,0 +1,21 @@
import db
def run():
db.run()
def setup_args():
db.make_connect(
{
"host": input("Host: "),
"user": input("User: "),
"passwd": input("Password: "),
"db": input("Database: ")
}
)
def pass_args(obj):
db.make_connect(obj)
if __name__ == "__main__":
setup_args()
run()

View File

@@ -0,0 +1 @@
sqlcon = None

View File

@@ -0,0 +1,115 @@
from objects import glob
from common.ripple import userUtils
from os.path import dirname, basename, isfile
import glob as _glob
import importlib
import json
from secret.achievements import common
def load_achievements():
"""Load all the achievements from handler list into glob.achievementClasses,
and sets glob.ACHIEVEMENTS_VERSION to the highest version number in our achievement list.
"""
modules = _glob.glob("secret/achievements/handlers/*.py")
modules = [basename(f)[:-3] for f in modules if isfile(f) and not f.endswith("__init__.py")]
# ^ cat face
for module in modules:
module = importlib.import_module("secret.achievements.handlers." + module)
module.load()
if module.ORDER in glob.achievementClasses:
print("\n!!! FOUND OVERLAPPING ACHIEVEMENT ORDER FOR {}!!!".format(module.ORDER))
print("Unable to load {} due to {} already loaded in slot {}\n".format(module.__name__, glob.achievementClasses[module.ORDER].__name__, module.ORDER))
continue
glob.achievementClasses[module.ORDER] = module
glob.ACHIEVEMENTS_VERSION = max(glob.ACHIEVEMENTS_VERSION, module.VERSION)
print("Loaded {} achievement classes!".format(len(glob.achievementClasses)), end=" ")
def unlock_achievements_update(userID, version):
"""Scans the user for past achievements they should have unlocked
Arguments:
userID {int} -- User id of a player
version {int} -- Last achivement version the player had
Returns:
Array -- List of achievements
"""
achievements = []
# Scan all past achivement versions from the user's achivement version to the latest
index = 1
for handler in glob.achievementClasses.values():
if handler.VERSION > version:
achievements += [x + index for x in handler.update(userID)]
index += handler.LENGTH
# Update achivement version for user
userUtils.updateAchievementsVersion(userID)
return achievements
def unlock_achievements(score, beatmap, user_data):
"""Return array of achievements the current play recived
Arguments:
score {Score} -- Score data recived from replay
beatmap {Beatmap} -- Played beatmap
user_data {dict} -- Info about the current player
Returns:
Array -- List of achievements for the current play
"""
achievements = []
userID = userUtils.getID(score.playerName)
user_cache = common.get_usercache(userID)
# Get current gamemode and change value std to osu
gamemode_index = score.gameMode
# Check if user should run achivement recheck
if user_cache["version"] < glob.ACHIEVEMENTS_VERSION:
achievements += unlock_achievements_update(userID, user_cache["version"])
# Check if gameplay should get new achivement
index = 1
for handler in glob.achievementClasses.values():
achievements += [x + index for x in handler.handle(gamemode_index, score, beatmap, user_data)]
index += handler.LENGTH
# Add pending achievements that were added though redis or mysql
achievements += [-x for x in user_cache["achievements"] if x < 0] # Negative achievements id's means its pending
# Remove pending achievements from redis object since we added it to the post achievements
user_cache["achievements"] = [x for x in user_cache["achievements"] if x > 0]
# Remove duplicated achievements (incase of unlock_achievements_update adding stuff)
achievements = list(set(achievements))
# Remove already achived achievements from list
achievements = [x for x in achievements if x not in user_cache["achievements"]]
user_cache["achievements"] += achievements
glob.redis.set("lets:user_achievement_cache:{}".format(userID), json.dumps(user_cache), 1800)
for achievement in achievements:
userUtils.unlockAchievement(userID, achievement)
return achievements
def achievements_response(achievements):
achievement_objects = []
index = 1
for handler in glob.achievementClasses.values():
achievement_objects += [handler.ACHIEVEMENTS[x - index] for x in achievements if len(handler.ACHIEVEMENTS) > x - index and x - index >= 0]
index += handler.LENGTH
achievements_packed = []
for achievement_object in achievement_objects:
achievements_packed.append("+".join([achievement_object["icon"], achievement_object["name"], achievement_object["description"]]))
return "/".join(achievements_packed)