From 9b55f5c4238f0a04ca9c7e4b4a9baaea3541d08c Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 4 Jan 2023 02:20:45 +0300 Subject: [PATCH] dumb daily levels implementation --- db/migrations/11_event_levels.sql | 28 ++++++++++++++++++ src/crystal-gauntlet.cr | 1 + src/endpoints/levels/downloadLevel.cr | 32 +++++++++++++++++++-- src/endpoints/levels/getDailyLevel.cr | 25 ++++++++++++++++ src/endpoints/levels/getLevels.cr | 8 ++++-- src/endpoints/levels/levelScores.cr | 12 ++++---- src/lib/dailies.cr | 41 +++++++++++++++++++++++++++ 7 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 db/migrations/11_event_levels.sql create mode 100644 src/endpoints/levels/getDailyLevel.cr create mode 100644 src/lib/dailies.cr diff --git a/db/migrations/11_event_levels.sql b/db/migrations/11_event_levels.sql new file mode 100644 index 0000000..ef76631 --- /dev/null +++ b/db/migrations/11_event_levels.sql @@ -0,0 +1,28 @@ +-- +migrate up +CREATE TABLE daily_queue ( + level_id INTEGER NOT NULL references levels(id), + idx SERIAL NOT NULL PRIMARY KEY +); + +CREATE TABLE daily_levels ( + level_id INTEGER NOT NULL references levels(id), + idx SERIAL NOT NULL PRIMARY KEY, + expires_at TEXT NOT NULL +); + +CREATE TABLE weekly_queue ( + level_id INTEGER NOT NULL references levels(id), + idx SERIAL NOT NULL PRIMARY KEY +); + +CREATE TABLE weekly_levels ( + level_id INTEGER NOT NULL references levels(id), + idx SERIAL NOT NULL PRIMARY KEY, + expires_at TEXT NOT NULL +); + +-- +migrate down +DROP TABLE daily_queue; +DROP TABLE daily_levels; +DROP TABLE weekly_queue; +DROP TABLE weekly_levels; \ No newline at end of file diff --git a/src/crystal-gauntlet.cr b/src/crystal-gauntlet.cr index ba9e76f..db35400 100644 --- a/src/crystal-gauntlet.cr +++ b/src/crystal-gauntlet.cr @@ -18,6 +18,7 @@ require "./lib/clean" require "./lib/songs" require "./lib/ids" require "./lib/level" +require "./lib/dailies" if File.exists?(".env") Dotenv.load diff --git a/src/endpoints/levels/downloadLevel.cr b/src/endpoints/levels/downloadLevel.cr index b0ad6b3..f792d99 100644 --- a/src/endpoints/levels/downloadLevel.cr +++ b/src/endpoints/levels/downloadLevel.cr @@ -9,7 +9,30 @@ CrystalGauntlet.endpoints["/downloadGJLevel22.php"] = ->(context : HTTP::Server: response = [] of String - DATABASE.query("select levels.id, levels.name, levels.extra_data, levels.level_info, levels.password, levels.user_id, levels.description, levels.original, levels.game_version, levels.requested_stars, levels.version, levels.song_id, levels.length, levels.objects, levels.coins, levels.has_ldm, levels.two_player, levels.downloads, levels.likes, levels.difficulty, levels.community_difficulty, levels.demon_difficulty, levels.stars, levels.featured, levels.epic, levels.rated_coins, users.username, users.udid, users.account_id, users.registered, editor_time, editor_time_copies from levels join users on levels.user_id = users.id where levels.id = ?", params["levelID"].to_i32) do |rs| + level_id = params["levelID"].to_i32 + daily_num = nil # for hash checks + + case level_id + when -1 + # daily + level_id, _, idx = Dailies.fetch_current_level(false) + if !level_id || !idx + return "-1" + end + daily_num = idx + when -2 + # weekly + level_id, _, idx = Dailies.fetch_current_level(true) + if !level_id || !idx + return "-1" + end + daily_num = idx + Dailies::WEEKLY_OFFSET + when -3 + # events + raise "events?? what the hell" + end + + DATABASE.query("select levels.id, levels.name, levels.extra_data, levels.level_info, levels.password, levels.user_id, levels.description, levels.original, levels.game_version, levels.requested_stars, levels.version, levels.song_id, levels.length, levels.objects, levels.coins, levels.has_ldm, levels.two_player, levels.downloads, levels.likes, levels.difficulty, levels.community_difficulty, levels.demon_difficulty, levels.stars, levels.featured, levels.epic, levels.rated_coins, users.username, users.udid, users.account_id, users.registered, editor_time, editor_time_copies from levels join users on levels.user_id = users.id where levels.id = ?", level_id) do |rs| if rs.move_next id = rs.read(Int32) name = rs.read(String) @@ -102,6 +125,7 @@ CrystalGauntlet.endpoints["/downloadGJLevel22.php"] = ->(context : HTTP::Server: 38 => rated_coins, 39 => requested_stars || 0, 40 => has_ldm, + 41 => daily_num, 42 => epic, # 0 for n/a, 10 for easy, 20, for medium, ... 43 => (demon_difficulty || DemonDifficulty::Hard).to_demon_difficulty, @@ -113,9 +137,13 @@ CrystalGauntlet.endpoints["/downloadGJLevel22.php"] = ->(context : HTTP::Server: }) response << Hashes.gen_solo(level_data) - thing = [user_id, stars || 0, (difficulty && difficulty.demon?) || 0, id, rated_coins, featured, password, 0].map { |x| Format.fmt_value(x) } + thing = [user_id, stars || 0, (difficulty && difficulty.demon?) || 0, id, rated_coins, featured, password, daily_num || 0].map { |x| Format.fmt_value(x) } response << Hashes.gen_solo_2(thing.join(",")) + if daily_num + response << [user_id, user_username, user_account_id].join(":") + end + return response.join("#") else return "-1" diff --git a/src/endpoints/levels/getDailyLevel.cr b/src/endpoints/levels/getDailyLevel.cr new file mode 100644 index 0000000..88f4da0 --- /dev/null +++ b/src/endpoints/levels/getDailyLevel.cr @@ -0,0 +1,25 @@ +require "uri" + +include CrystalGauntlet + +CrystalGauntlet.endpoints["/getGJDailyLevel.php"] = ->(context : HTTP::Server::Context): String { + params = URI::Params.parse(context.request.body.not_nil!.gets_to_end) + LOG.debug { params.inspect } + + weekly = params["weekly"] == "1" + + level_id = nil + expires = nil + + level_id, expires, idx = Dailies.fetch_current_level(weekly) + + if !level_id || !expires || !idx + "-1" + else + if weekly + idx += Dailies::WEEKLY_OFFSET + end + + [idx, expires].join("|") + end +} diff --git a/src/endpoints/levels/getLevels.cr b/src/endpoints/levels/getLevels.cr index ca3f7f2..06d0c29 100644 --- a/src/endpoints/levels/getLevels.cr +++ b/src/endpoints/levels/getLevels.cr @@ -156,11 +156,13 @@ CrystalGauntlet.endpoints["/getGJLevels21.php"] = ->(context : HTTP::Server::Con when "13" # friends # todo when "21" # daily - # todo + order = "daily_levels.idx desc" + joins << "join daily_levels on levels.id = daily_levels.level_id" when "22" # weekly - # todo + order = "weekly_levels.idx desc" + joins << "join weekly_levels on levels.id = weekly_levels.level_id" when "23" # event (unused) - # todo + # todo..? end if !can_see_unlisted diff --git a/src/endpoints/levels/levelScores.cr b/src/endpoints/levels/levelScores.cr index b397c21..2e8ad6c 100644 --- a/src/endpoints/levels/levelScores.cr +++ b/src/endpoints/levels/levelScores.cr @@ -33,14 +33,12 @@ CrystalGauntlet.endpoints["/getGJLevelScores211.php"] = ->(context : HTTP::Serve return "-1" end - # todo: account for dailies - - if DATABASE.scalar("select count(*) from level_scores where account_id = ? and level_id = ?", account_id, level_id).as(Int64) > 0 + if DATABASE.scalar("select count(*) from level_scores where account_id = ? and level_id = ? and daily_id is ?", account_id, level_id, daily_id).as(Int64) > 0 # check if an update is truly necessary - percent_old, coins_old = DATABASE.query_one("select percent, coins from level_scores where account_id = ? and level_id = ?", account_id, level_id, as: {Int32, Int32}) + percent_old, coins_old = DATABASE.query_one("select percent, coins from level_scores where account_id = ? and level_id = ? and daily_id is ?", account_id, level_id, daily_id, as: {Int32, Int32}) if percent > percent_old || coins > coins_old - DATABASE.exec("update level_scores set account_id=?, level_id=?, daily_id=?, percent=?, attempts=?, clicks=?, coins=?, progress=?, time=?, set_at=? where account_id = ? and level_id = ?", account_id, level_id, daily_id, percent, attempts, clicks, coins, progress, time, Time.utc.to_s(Format::TIME_FORMAT), account_id, level_id) + DATABASE.exec("update level_scores set account_id=?, percent=?, attempts=?, clicks=?, coins=?, progress=?, time=?, set_at=? where account_id = ? and level_id = ? and daily_id is ?", account_id, percent, attempts, clicks, coins, progress, time, Time.utc.to_s(Format::TIME_FORMAT), account_id, level_id, daily_id) end else DATABASE.exec("insert into level_scores (account_id, level_id, daily_id, percent, attempts, clicks, coins, progress, time) values (?, ?, ?, ?, ?, ?, ?, ?, ?)", account_id, level_id, daily_id, percent, attempts, clicks, coins, progress, time) @@ -51,7 +49,7 @@ CrystalGauntlet.endpoints["/getGJLevelScores211.php"] = ->(context : HTTP::Serve type = params["type"]? ? params["type"] : "1" - where_query = ["level_id = ?"] + where_query = ["level_id = ? and daily_id is ?"] case type when 0 @@ -68,7 +66,7 @@ CrystalGauntlet.endpoints["/getGJLevelScores211.php"] = ->(context : HTTP::Serve scores = [] of String i = 0 - DATABASE.query_each "select percent, level_scores.coins, set_at, users.username, users.id, users.icon_type, users.color1, users.color2, users.cube, users.ship, users.ball, users.ufo, users.wave, users.robot, users.spider, users.special, users.account_id from level_scores join users on level_scores.account_id = users.account_id where (#{where_query.join(") and (")}) order by percent desc, level_scores.coins desc limit 200", level_id do |rs| + DATABASE.query_each "select percent, level_scores.coins, set_at, users.username, users.id, users.icon_type, users.color1, users.color2, users.cube, users.ship, users.ball, users.ufo, users.wave, users.robot, users.spider, users.special, users.account_id from level_scores join users on level_scores.account_id = users.account_id where (#{where_query.join(") and (")}) order by percent desc, level_scores.coins desc limit 200", level_id, daily_id do |rs| i += 1 percent = rs.read(Int32) coins = rs.read(Int32) diff --git a/src/lib/dailies.cr b/src/lib/dailies.cr new file mode 100644 index 0000000..2c7810d --- /dev/null +++ b/src/lib/dailies.cr @@ -0,0 +1,41 @@ +include CrystalGauntlet + +module CrystalGauntlet::Dailies + extend self + + # todo: merge the two tables into one maybe + + WEEKLY_OFFSET = 100001 + + def grab_new_level(weekly : Bool, prev = Time.utc) : {Int32 | Nil, Int32 | Nil, Int32 | Nil} + begin + level_id = DATABASE.query_one("select level_id from #{weekly ? "weekly_queue" : "daily_queue"} order by idx desc limit 1", as: {Int32}) + rescue + return {nil, nil, nil} + else + next_id = IDs.get_next_id(weekly ? "weekly_levels" : "daily_levels") + # todo: configurable? + timespan = weekly ? 1.weeks : 1.days + expires_at = prev + timespan + DATABASE.exec("insert into #{weekly ? "weekly_levels" : "daily_levels"} (level_id, idx, expires_at) values (?, ?, ?)", level_id, next_id, expires_at.to_s(Format::TIME_FORMAT)) + return {level_id, timespan.total_seconds.to_i, next_id} + end + end + + def fetch_current_level(weekly : Bool) : {Int32 | Nil, Int32 | Nil, Int32 | Nil} + begin + level_id, expires_at, idx = DATABASE.query_one("select level_id, expires_at, idx from #{weekly ? "weekly_levels" : "daily_levels"} order by idx desc limit 1", as: {Int32, String, Int32}) + rescue + # make up a brand new daily; using current time because no previous ones have existed + level_id, expires, idx = grab_new_level(weekly) + else + # check if it has expired, roll a new one if so + expires = (Time.utc - Time.parse(expires_at, Format::TIME_FORMAT, Time::Location::UTC)).total_seconds.to_i + if expires <= 0 + level_id, expires, idx = grab_new_level(weekly, Time.parse(expires_at, Format::TIME_FORMAT, Time::Location::UTC)) + end + end + + return level_id, expires, idx + end +end