Compare commits

...

3 Commits

9 changed files with 342 additions and 31 deletions

View File

@ -2,6 +2,8 @@
"Lua.diagnostics.disable": [
"different-requires",
"need-check-nil",
"cast-local-type"
"cast-local-type",
"undefined-field",
"inject-field"
]
}

View File

@ -126,7 +126,7 @@
-26,
5
],
"loop": true,
"loop": false,
"fps": 24,
"anim": "scared",
"indices": [],

View File

@ -176,7 +176,7 @@
"name": "GF Dancing Beat Hair Landing"
},
{
"loop": true,
"loop": false,
"offsets": [
-2,
-17

View File

@ -50,11 +50,15 @@
0,
0
],
"fps": 12,
"anim": "idle",
"fps": 24,
"anim": "danceLeft",
"indices": [
0,
1,
2,
3,
4,
5,
6
],
"name": "spooky dance idle"
@ -65,13 +69,19 @@
0,
0
],
"fps": 12,
"fps": 24,
"anim": "danceRight",
"indices": [
7,
8,
9,
10,
11,
12,
14
13,
14,
15,
0
],
"name": "spooky dance idle"
},

1
discord_app.lua Normal file
View File

@ -0,0 +1 @@
return "1380367531314778122"

View File

@ -5,6 +5,8 @@ local myTypes = require("modules.types")
local files = require("modules.files")
local json = require("modules.json")
local logging = require("modules.logging")
local discord = require("modules.discord")
local applicationid = require("discord_app")
local songs = require("charts.songs")
@ -41,16 +43,19 @@ local function setup()
logo.position = myTypes.Vector2(-80, 0)
freaky:play()
local timeStamp = os.time()
discord.updatePresence({
details = "In menu",
largeImageKey = "bigimage",
startTimestamp = timeStamp,
state = "TaggedEngine: FNF in LUA, better than ever."
})
end
local font = love.graphics.newFont("fonts/Phantomuff.ttf", 40)
-- for index, song in next, songs do
-- curSong = index
-- curDiff = song[1]
-- break
-- end
local gettingKey
local settings = json.parse(files.read_file("settings.json"))
@ -175,4 +180,6 @@ end
love.window.setMode(1280, 720, { fullscreen = false , resizable = true})
discord.initialize(applicationid, true)
setup()

252
modules/discord.lua Normal file
View File

@ -0,0 +1,252 @@
local ffi = require "ffi"
local discordRPClib = ffi.load("discord-rpc")
ffi.cdef[[
typedef struct DiscordRichPresence {
const char* state; /* max 128 bytes */
const char* details; /* max 128 bytes */
int64_t startTimestamp;
int64_t endTimestamp;
const char* largeImageKey; /* max 32 bytes */
const char* largeImageText; /* max 128 bytes */
const char* smallImageKey; /* max 32 bytes */
const char* smallImageText; /* max 128 bytes */
const char* partyId; /* max 128 bytes */
int partySize;
int partyMax;
const char* matchSecret; /* max 128 bytes */
const char* joinSecret; /* max 128 bytes */
const char* spectateSecret; /* max 128 bytes */
int8_t instance;
} DiscordRichPresence;
typedef struct DiscordUser {
const char* userId;
const char* username;
const char* discriminator;
const char* avatar;
} DiscordUser;
typedef void (*readyPtr)(const DiscordUser* request);
typedef void (*disconnectedPtr)(int errorCode, const char* message);
typedef void (*erroredPtr)(int errorCode, const char* message);
typedef void (*joinGamePtr)(const char* joinSecret);
typedef void (*spectateGamePtr)(const char* spectateSecret);
typedef void (*joinRequestPtr)(const DiscordUser* request);
typedef struct DiscordEventHandlers {
readyPtr ready;
disconnectedPtr disconnected;
erroredPtr errored;
joinGamePtr joinGame;
spectateGamePtr spectateGame;
joinRequestPtr joinRequest;
} DiscordEventHandlers;
void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers,
int autoRegister,
const char* optionalSteamId);
void Discord_Shutdown(void);
void Discord_RunCallbacks(void);
void Discord_UpdatePresence(const DiscordRichPresence* presence);
void Discord_ClearPresence(void);
void Discord_Respond(const char* userid, int reply);
void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
]]
local discordRPC = {} -- module table
-- proxy to detect garbage collection of the module
discordRPC.gcDummy = newproxy(true)
local function unpackDiscordUser(request)
return ffi.string(request.userId), ffi.string(request.username),
ffi.string(request.discriminator), ffi.string(request.avatar)
end
-- callback proxies
-- note: callbacks are not JIT compiled (= SLOW), try to avoid doing performance critical tasks in them
-- luajit.org/ext_ffi_semantics.html
local ready_proxy = ffi.cast("readyPtr", function(request)
if discordRPC.ready then
discordRPC.ready(unpackDiscordUser(request))
end
end)
local disconnected_proxy = ffi.cast("disconnectedPtr", function(errorCode, message)
if discordRPC.disconnected then
discordRPC.disconnected(errorCode, ffi.string(message))
end
end)
local errored_proxy = ffi.cast("erroredPtr", function(errorCode, message)
if discordRPC.errored then
discordRPC.errored(errorCode, ffi.string(message))
end
end)
local joinGame_proxy = ffi.cast("joinGamePtr", function(joinSecret)
if discordRPC.joinGame then
discordRPC.joinGame(ffi.string(joinSecret))
end
end)
local spectateGame_proxy = ffi.cast("spectateGamePtr", function(spectateSecret)
if discordRPC.spectateGame then
discordRPC.spectateGame(ffi.string(spectateSecret))
end
end)
local joinRequest_proxy = ffi.cast("joinRequestPtr", function(request)
if discordRPC.joinRequest then
discordRPC.joinRequest(unpackDiscordUser(request))
end
end)
-- helpers
local function checkArg(arg, argType, argName, func, maybeNil)
assert(type(arg) == argType or (maybeNil and arg == nil),
string.format("Argument \"%s\" to function \"%s\" has to be of type \"%s\"",
argName, func, argType))
end
local function checkStrArg(arg, maxLen, argName, func, maybeNil)
if maxLen then
assert(type(arg) == "string" and arg:len() <= maxLen or (maybeNil and arg == nil),
string.format("Argument \"%s\" of function \"%s\" has to be of type string with maximum length %d",
argName, func, maxLen))
else
checkArg(arg, "string", argName, func, true)
end
end
local function checkIntArg(arg, maxBits, argName, func, maybeNil)
maxBits = math.min(maxBits or 32, 52) -- lua number (double) can only store integers < 2^53
local maxVal = 2^(maxBits-1) -- assuming signed integers, which, for now, are the only ones in use
assert(type(arg) == "number" and math.floor(arg) == arg
and arg < maxVal and arg >= -maxVal
or (maybeNil and arg == nil),
string.format("Argument \"%s\" of function \"%s\" has to be a whole number <= %d",
argName, func, maxVal))
end
-- function wrappers
function discordRPC.initialize(applicationId, autoRegister, optionalSteamId)
local func = "discordRPC.Initialize"
checkStrArg(applicationId, nil, "applicationId", func)
checkArg(autoRegister, "boolean", "autoRegister", func)
if optionalSteamId ~= nil then
checkStrArg(optionalSteamId, nil, "optionalSteamId", func)
end
local eventHandlers = ffi.new("struct DiscordEventHandlers")
eventHandlers.ready = ready_proxy
eventHandlers.disconnected = disconnected_proxy
eventHandlers.errored = errored_proxy
eventHandlers.joinGame = joinGame_proxy
eventHandlers.spectateGame = spectateGame_proxy
eventHandlers.joinRequest = joinRequest_proxy
discordRPClib.Discord_Initialize(applicationId, eventHandlers,
autoRegister and 1 or 0, optionalSteamId)
end
function discordRPC.shutdown()
discordRPClib.Discord_Shutdown()
end
function discordRPC.runCallbacks()
discordRPClib.Discord_RunCallbacks()
end
-- http://luajit.org/ext_ffi_semantics.html#callback :
-- It is not allowed, to let an FFI call into a C function (runCallbacks)
-- get JIT-compiled, which in turn calls a callback, calling into Lua again (e.g. discordRPC.ready).
-- Usually this attempt is caught by the interpreter first and the C function
-- is blacklisted for compilation.
-- solution:
-- "Then you'll need to manually turn off JIT-compilation with jit.off() for
-- the surrounding Lua function that invokes such a message polling function."
jit.off(discordRPC.runCallbacks)
function discordRPC.updatePresence(presence)
local func = "discordRPC.updatePresence"
checkArg(presence, "table", "presence", func)
-- -1 for string length because of 0-termination
checkStrArg(presence.state, 127, "presence.state", func, true)
checkStrArg(presence.details, 127, "presence.details", func, true)
checkIntArg(presence.startTimestamp, 64, "presence.startTimestamp", func, true)
checkIntArg(presence.endTimestamp, 64, "presence.endTimestamp", func, true)
checkStrArg(presence.largeImageKey, 31, "presence.largeImageKey", func, true)
checkStrArg(presence.largeImageText, 127, "presence.largeImageText", func, true)
checkStrArg(presence.smallImageKey, 31, "presence.smallImageKey", func, true)
checkStrArg(presence.smallImageText, 127, "presence.smallImageText", func, true)
checkStrArg(presence.partyId, 127, "presence.partyId", func, true)
checkIntArg(presence.partySize, 32, "presence.partySize", func, true)
checkIntArg(presence.partyMax, 32, "presence.partyMax", func, true)
checkStrArg(presence.matchSecret, 127, "presence.matchSecret", func, true)
checkStrArg(presence.joinSecret, 127, "presence.joinSecret", func, true)
checkStrArg(presence.spectateSecret, 127, "presence.spectateSecret", func, true)
checkIntArg(presence.instance, 8, "presence.instance", func, true)
local cpresence = ffi.new("struct DiscordRichPresence")
cpresence.state = presence.state
cpresence.details = presence.details
cpresence.startTimestamp = presence.startTimestamp or 0
cpresence.endTimestamp = presence.endTimestamp or 0
cpresence.largeImageKey = presence.largeImageKey
cpresence.largeImageText = presence.largeImageText
cpresence.smallImageKey = presence.smallImageKey
cpresence.smallImageText = presence.smallImageText
cpresence.partyId = presence.partyId
cpresence.partySize = presence.partySize or 0
cpresence.partyMax = presence.partyMax or 0
cpresence.matchSecret = presence.matchSecret
cpresence.joinSecret = presence.joinSecret
cpresence.spectateSecret = presence.spectateSecret
cpresence.instance = presence.instance or 0
discordRPClib.Discord_UpdatePresence(cpresence)
end
function discordRPC.clearPresence()
discordRPClib.Discord_ClearPresence()
end
local replyMap = {
no = 0,
yes = 1,
ignore = 2
}
-- maybe let reply take ints too (0, 1, 2) and add constants to the module
function discordRPC.respond(userId, reply)
checkStrArg(userId, nil, "userId", "discordRPC.respond")
assert(replyMap[reply], "Argument 'reply' to discordRPC.respond has to be one of \"yes\", \"no\" or \"ignore\"")
discordRPClib.Discord_Respond(userId, replyMap[reply])
end
-- garbage collection callback
getmetatable(discordRPC.gcDummy).__gc = function()
discordRPC.shutdown()
ready_proxy:free()
disconnected_proxy:free()
errored_proxy:free()
joinGame_proxy:free()
spectateGame_proxy:free()
joinRequest_proxy:free()
end
return discordRPC

View File

@ -10,6 +10,7 @@ local function state(songName, songDifficulty)
local logger = require("modules.logging")
local socket = require("socket")
local logging= require("modules.logging")
local discord = require("modules.discord")
-- I NEED THEM IMPORTS
local startTime = 0
@ -71,6 +72,7 @@ local function state(songName, songDifficulty)
shit = 0,
miss = 0,
}
local score = 0
local rankWindows = {
{
@ -142,6 +144,8 @@ local function state(songName, songDifficulty)
local deadBF
local restart = false
local startTimestamp = os.time()
local function quit()
if restart then return end
playing = false
@ -239,6 +243,8 @@ local function state(songName, songDifficulty)
notes[closestIndex] = nil
closestNote:destroy()
score = score + rating.score
end
end
@ -291,7 +297,7 @@ local function state(songName, songDifficulty)
-- gf:PlayAnimation("BF NOTE LEFT", 30, false)
for name, character in next, characters do
if not character.singing then
if name == "gf" then
if name == "gf" or character.animInfo.danceLeft then
character:PlayAnimation("danceLeft")
else
character:PlayAnimation("idle")
@ -300,10 +306,20 @@ local function state(songName, songDifficulty)
end
if beat % 4 == 0 then
zoom = zoom + .1
discord.updatePresence({
details = string.format("Playing %s on difficulty %s", songName, songDifficulty),
largeImageKey = "bigimage",
startTimestamp = startTimestamp,
state = string.format("Score: %s", score)
})
end
else
if characters.gf and not characters.gf.singing then
characters.gf:PlayAnimation("danceRight")
for name, character in next, characters do
if not character.singing then
if name == "gf" or character.animInfo.danceLeft then
character:PlayAnimation("danceRight")
end
end
end
end
@ -336,9 +352,9 @@ local function state(songName, songDifficulty)
myTypes.updateSprites(dt)
for name, character in next, characters do
if name ~= "gf" and character.sprite.animation ~= "idle" and character.sprite.ended then
if name ~= "gf" and character.animInfo.idle and character.sprite.animation ~= "idle" and character.sprite.ended then
character:PlayAnimation("idle")
elseif name == "gf" and character.singing and character.sprite.animation ~= "danceLeft" and character.sprite.ended then
elseif (name == "gf" or character.animInfo.danceLeft) and character.singing and character.sprite.animation ~= "danceLeft" and character.sprite.ended then
character:PlayAnimation("danceLeft")
end
end
@ -365,7 +381,7 @@ local function state(songName, songDifficulty)
for index, note in next, notes do
if note.mustPress then
note.sprite.position = myTypes.Vector2(600 + (79 * (note.direction - 1)), settings.Downscroll and 430 - (note.position-elapsed) * speed or (note.position - elapsed) * speed)
if note.position - elapsed < -150 then
if (note.position - elapsed) * speed < -150 then
note:destroy()
miss:stop()
miss:play()
@ -373,10 +389,11 @@ local function state(songName, songDifficulty)
notes[index] = nil
ratings.miss = ratings.miss + 1
health = health - note.missHealth
score = score - 200
end
else
note.sprite.position = myTypes.Vector2(50 + 79 * (note.direction - 1), settings.Downscroll and 430 - (note.position-elapsed) * speed or (note.position - elapsed) * speed)
if note.position - elapsed < 10 then
if (note.position - elapsed) * speed < 10 then
notes[index] = nil
if section.gfSection or chart.song == "Tutorial" then
if section.altAnim or note.altAnim then
@ -400,7 +417,7 @@ local function state(songName, songDifficulty)
for index, hold in next, holdNotes do
if hold.mustPress then
hold.sprite.position = myTypes.Vector2(625 + (79 * (hold.direction - 1)), settings.Downscroll and 430 - (hold.position-elapsed) * speed or (hold.position - elapsed) * speed)
if hold.position - elapsed < 10 then
if (hold.position - elapsed) * speed < 10 then
if love.keyboard.isDown(keyBinds[hold.direction]) then
if characters.bf.animInfo["sing"..directions[hold.direction].."-alt"] and (section.altAnim or hold.altAnim) then
characters.bf:PlayAnimation("sing"..directions[hold.direction].."-alt")
@ -409,20 +426,17 @@ local function state(songName, songDifficulty)
end
hold:destroy()
holdNotes[index] = nil
health = health + hold.hitHealth * 0.1
elseif hold.position - elapsed < -150 then
health = health + hold.hitHealth * 0.2
elseif (hold.position - elapsed) * speed < -150 then
hold:destroy()
miss:stop()
miss:play()
characters.bf:PlayAnimation("sing"..directions[hold.direction].."miss")
holdNotes[index] = nil
ratings.miss = ratings.miss + 1
health = health - hold.missHealth * 0.1
health = health - hold.missHealth * 0.2
end
end
else
hold.sprite.position = myTypes.Vector2(75 + (79 * (hold.direction - 1)), settings.Downscroll and 430 - (hold.position-elapsed) * speed or (hold.position - elapsed) * speed)
if hold.position - elapsed < 10 then
if (hold.position - elapsed) * speed < 10 then
if characters.dad.animInfo["sing"..directions[hold.direction].."-alt"] and (section.altAnim or hold.altAnim) then
characters.dad:PlayAnimation("sing"..directions[hold.direction].."-alt")
else
@ -554,7 +568,8 @@ local function state(songName, songDifficulty)
if chart.player2 ~= "none" then -- you can have no player2 but always player1
characters.dad = myTypes.character(chart.player2)
characters.dad.stagePosition = myTypes.Vector2(stage.opponent[1], stage.opponent[2])
characters.dad:PlayAnimation("idle")
characters.dad:PlayAnimation(characters.dad.animInfo.idle and "idle" or "danceLeft")
local image = love.graphics.newImage(string.format("images/icons/icon-%s.png", characters.dad.icon))
icons.dad = {image = image, alive = love.graphics.newQuad(0,0, 150, 150, image), dead = love.graphics.newQuad(150, 0, 150, 150, image)}
end
@ -651,6 +666,15 @@ local function state(songName, songDifficulty)
playing = true
startTime = socket.gettime()
startTimestamp = os.time()
discord.updatePresence({
details = string.format("Playing %s on difficulty %s", songName, songDifficulty),
largeImageKey = "bigimage",
startTimestamp = startTimestamp,
state = "Score: 0"
})
end
function state.keypressed(key, un, is)
@ -664,6 +688,14 @@ local function state(songName, songDifficulty)
voices:pause()
end
pauseStart = socket.gettime() * 1000
local pauseStamp = os.time()
discord.updatePresence({
details = string.format("Paused %s on difficulty %s", songName, songDifficulty),
largeImageKey = "bigimage",
startTimestamp = pauseStamp,
state = string.format("Score: %s", score)
})
else
inst:play()
if chart.needsVoices then

View File

@ -87,11 +87,18 @@ function module.Sprite(image, sheet)
end
end
function Sprite:PlayAnimation(name, fps, loop)
function Sprite:PlayAnimation(name, fps, loop, allowed)
self.animation = name
self.fps = fps
self.looping = loop
self.frame = 1
if self.allowedFrames then
self.allowedFrame = 1
else
self.frame = 0
end
self.ended = false
end