Better typing, added button type and mouse related functions, partially made freeplay support mouse

This commit is contained in:
entar 2025-07-24 12:50:35 +07:00
parent 5b0c3dbc04
commit 7b7fe58352
17 changed files with 355 additions and 117 deletions

View File

@ -4,7 +4,9 @@
"need-check-nil",
"cast-local-type",
"undefined-field",
"inject-field"
"inject-field",
"duplicate-set-field",
"missing-fields"
],
"Lua.diagnostics.globals": [
"love",

View File

@ -135,7 +135,7 @@
"image": "sprites/characters/monsterChristmas",
"position": [
0,
130
260
],
"healthicon": "monster",
"flip_x": false,

View File

@ -134,8 +134,8 @@
"no_antialiasing": false,
"image": "sprites/characters/Monster_Assets",
"position": [
-200,
200
-400,
400
],
"healthicon": "monster",
"flip_x": false,

View File

@ -1,7 +1,7 @@
local logging = require("modules.logging")
--- @class Conductor
--- @class engine.Conductor
local conductor = {}
conductor.bpm = 120
conductor.crochet = 1000

View File

@ -541,12 +541,10 @@ function AnimateAtlas:update(dt)
if frameIndex < 0 then
frameIndex = length - 1
end
if frameIndex > length then
if frameIndex > length - 2 then
frameIndex = 0
end
elseif loopMode == "playonce" or loopMode == "PO" then
print(frameIndex, length)
-- stop at last frame
if frameIndex < 1 then
frameIndex = 1

94
modules/types/button.lua Normal file
View File

@ -0,0 +1,94 @@
local logging = require("modules.logging")
---@class engine.button
---@field _position engine.vector2
---@field _size engine.vector2
local ButtonClass = {}
ButtonClass.__index = ButtonClass
---@type table<engine.button>
local Buttons = {}
---@param func function?
function ButtonClass:SetCallback(func)
self._callback = func
end
---@param enabled boolean
function ButtonClass:SetEnabled(enabled)
self._enabled = enabled
end
---@param position engine.vector2
function ButtonClass:SetPosition(position)
-- print(logging.dump(position))
self._position = position
end
---@param size engine.vector2
function ButtonClass:SetSize(size)
self._size = size
end
function ButtonClass:Destroy()
for index, Button in next, Buttons do
if Button == self then
Buttons[index] = nil
end
end
for index, param in next, self do
self[index] = nil
end
self = nil
end
_G.buttons = {}
---@param pointX number
---@param pointY number
---@param rectX number
---@param rectY number
---@param rectWidth number
---@param rectHeight number
---@return boolean
local function isPointInRectangle(pointX, pointY, rectX, rectY, rectWidth, rectHeight)
return pointX >= rectX and pointX <= rectX + rectWidth and
pointY >= rectY and pointY <= rectY + rectHeight
end
function buttons.processMouse(x, y)
for index, Button in next, Buttons do
if not Button._callback or not Button._enabled then goto continue end
local pressed = isPointInRectangle(x, y, Button._position.x, Button._position.y, Button._size.x, Button._size.y)
print(pressed, logging.dump(Button))
if pressed then
Button._callback()
end
::continue::
end
end
function buttons.clearButtons()
for index, Button in next, Buttons do
Button:Destroy()
Buttons[index] = nil
end
end
---@param position engine.vector2?
---@param size engine.vector2?
---@param callback function?
---@param enabled boolean?
---@return engine.button
function Button(position, size, callback, enabled)
local newButton = setmetatable({
_position = position or Vector2(),
_size = size or Vector2(50, 50),
_callback = callback,
_enabled = callback and true
}, ButtonClass)
Buttons[#Buttons+1] = newButton
return newButton
end

View File

@ -5,7 +5,7 @@ local files = require("modules.files")
local json = require("modules.json")
---Character class, child of Sprite class. Makes work with characters easier by calling some sprite functions with preset character file arguments.
---@class Character
---@class engine.character
local CharacterClass = {}
CharacterClass.__index = CharacterClass
@ -39,7 +39,7 @@ end
--- Creates a character based on a JSON file in the game/characters folder.
--- @param name string
--- @return Character
--- @return engine.character
function _G.Character(name)
local charFile = files.read_file(string.format("characters/%s.json", name))
if not charFile then

View File

@ -1,6 +1,6 @@
-- Here i will define some classes used in the game
---@class GameModule
---@class engine.module
local GameModule = {}
GameModule.__index = GameModule
@ -11,11 +11,11 @@ end
function GameModule.onDraw()
end
---@param note Note
---@param note engine.note
function GameModule.noteHit(note)
end
---@param note Note
---@param note engine.note
function GameModule.processNote(note)
end

View File

@ -1,16 +1,16 @@
---@class Note
---@class engine.note
---@field position number Sprite Position
---@field character string? Which character to play (overrides mustPress)
---@field mustPress boolean Which side the note is on
---@field direction number Note direction
---@field spawned boolean If the note is on the screen
---@field sprite Sprite Note sprite
---@field sprite engine.sprite Note sprite
---@field altAnim string? Which alt animation the note uses
---@field hold boolean? If the note is a suspension note
---@field last boolean? If the note is a suspension tail
---@field hitHealth number Health player gets on hit
---@field missHealth number Health player looses on miss
---@field offset Vector2 Extra offset
---@field offset engine.vector2 Extra offset
---@field raw table
---Note class
local NoteClass = {}
@ -27,7 +27,7 @@ local sprites = {
--- @param raw table
--- @param hold boolean?
--- @param holdEnd boolean?
--- @return Note
--- @return engine.note
function _G.Note(raw, hold, holdEnd)
local newNote = setmetatable({

View File

@ -4,17 +4,17 @@ local files = require("modules.files")
local module = {}
_G.render = module
---@class QuadObject
---@class engine.quadobject
---@field quad love.Quad
---@field offset Vector2
---@field resize Vector2
---@field offset engine.vector2
---@field resize engine.vector2
---@class Sprite
---@class engine.sprite
---@field image love.Image
---@field quads table<table<QuadObject>>
---@field quads table<table<engine.quadobject>>
---@field animation string
---@field position Vector2
---@field extraOffset Vector2
---@field position engine.vector2
---@field extraOffset engine.vector2
---@field modifier number
---@field layer number
---@field alpha number
@ -22,10 +22,10 @@ _G.render = module
local Sprite = {}
Sprite.__index = Sprite
---@class Image
---@class engine.image
---@field image love.Image
---@field resize Vector2
---@field position Vector2
---@field resize engine.vector2
---@field position engine.vector2
---@field modifier number
---@field rotation number
---@field layer number
@ -35,10 +35,10 @@ local Image = {}
Image.__index = Image
---@class Rectangle
---@class engine.rectangle
---@field color table<number>
---@field position Vector2
---@field size Vector2
---@field position engine.vector2
---@field size engine.vector2
---@field modifier number
---@field rotation number
---@field layer number
@ -71,7 +71,7 @@ local Sprites, Rects, Images, Atlases, Rectangles = {}, {}, {}, {}, {}
--- Makes a sprite object.
--- @param image string
--- @param sheet string
--- @return Sprite
--- @return engine.sprite
function _G.Sprite(image, sheet)
if not cachedQuads[sheet] then
local sheetString = files.read_file(sheet)
@ -169,7 +169,6 @@ function Sprite:PlayAnimation(name, fps, loop, allowed)
self.frame = 0
end
self.ended = false
end
@ -444,8 +443,8 @@ end
--- Makes a new Rect object.
--- @param image string
--- @param sheet string
--- @return Sprite
function _G.Rect(image, sheet)
--- @return engine.sprite
function Rect(image, sheet)
if not cachedQuads[sheet] then
local sheetString = files.read_file(sheet)
@ -549,7 +548,7 @@ end
--- Makes an image.
--- @param path string
--- @param scrollFactor number?
--- @return Image
--- @return engine.image
function _G.Image(path, scrollFactor)
if not cachedImages[path] then
cachedImages[path] = love.graphics.newImage(path)
@ -638,11 +637,11 @@ end
modifier = 1
]]
---@class Atlas
---@class engine.atlas
---@field atlas love.animate.AnimateAtlas
---@field position Vector2
---@field position engine.vector2
---@field rotation number
---@field resize Vector2
---@field resize engine.vector2
---@field modifier number
---@field alpha number
---Texture Atlas animatable object.
@ -661,10 +660,10 @@ function Atlas:Destroy()
end
---Creates a rectangle.
---@param position Vector2
---@param size Vector2
---@param position engine.vector2
---@param size engine.vector2
---@param color table<number>
---@return Rectangle
---@return engine.rectangle
function _G.Rectangle(position, size, mode, color)
local Shape = setmetatable({
size = size,
@ -694,7 +693,7 @@ end
--- Makes an atlas.
--- @param folder string
--- @return Atlas
--- @return engine.atlas
function _G.Atlas(folder)
local newAtlas = setmetatable({
atlas = love.animate.newTextureAtlas(),

View File

@ -1,12 +1,12 @@
--- @class Vector2
--- @class engine.vector2
--- @field x number
--- @field y number
local Vector2Class = {}
Vector2Class.__index = Vector2Class
--- @param newVector2 Vector2
--- @param newVector2 engine.vector2
--- @param position number
--- @return Vector2
--- @return engine.vector2
function Vector2Class:Lerp(newVector2, position)
return Vector2(Lerp(self.x, newVector2.x, position), Lerp(self.y, newVector2.y, position))
end
@ -16,32 +16,32 @@ function Vector2Class:Get()
return self.x, self.y
end
--- @return Vector2
--- @return engine.vector2
function Vector2Class:Negate()
return Vector2(-self.x, -self.y)
end
--- @param addVector2 Vector2
--- @return Vector2
--- @param addVector2 engine.vector2
--- @return engine.vector2
function Vector2Class:Add(addVector2)
return Vector2(self.x + addVector2.x, self.y + addVector2.y)
end
--- @param num number
--- @return Vector2
--- @return engine.vector2
function Vector2Class:Mul(num)
return Vector2(self.x * num, self.y * num)
end
--- @param num number
--- @return Vector2
--- @return engine.vector2
function Vector2Class:Div(num)
return Vector2(self.x / num, self.y / num)
end
--- @param x number?
--- @param y number?
--- @return Vector2
--- @return engine.vector2
function Vector2(x, y)
return setmetatable({x = x or 0, y = y or 0}, Vector2Class)
end

View File

@ -9,7 +9,7 @@ print("y")
require("modules.types")
require("modules.math")
---@class StateClass
---@class engine.state
local StateClass = {}
function StateClass.load()
@ -22,6 +22,31 @@ end
---@param delta number
function StateClass.update(delta)
end
---@param x number
---@param y number
---@param button number
---@param presses number
---@param istouch boolean
function StateClass.mousepressed(x,y,button,istouch,presses)
end
---@param x number
---@param y number
---@param button number
---@param presses number
---@param istouch boolean
function StateClass.mousereleased(x,y,button,istouch,presses)
end
---@param x number
---@param y number
---@param dx number
---@param dy number
---@param istouch boolean
function StateClass.mousemoved(x,y,dx,dy,istouch)
end
---@param x number
---@param y number
function StateClass.wheelmoved(x,y)
end
curChar = "bf"
@ -43,6 +68,7 @@ function setState(name, ...)
curState.quit = function(accuracy, score)
curState = nil
stateLoaded = false
buttons.clearButtons()
render.destroyAllSprites()
setState("freeplaystate")
end
@ -51,6 +77,7 @@ function setState(name, ...)
local lastStateName = curState.name
curState = nil
stateLoaded = false
buttons.clearButtons()
render.destroyAllSprites()
setState(lastStateName, ...)
@ -60,6 +87,7 @@ function setState(name, ...)
curState = nil
stateLoaded = false
render.destroyAllSprites()
buttons.clearButtons()
setState(...)
end
@ -67,19 +95,19 @@ function setState(name, ...)
end
function love.draw()
if curState then
if curState and curState.draw then
curState.draw()
end
end
function love.update(dt)
if curState then
if curState and curState.update then
curState.update(dt)
end
end
function love.keypressed(...)
if curState then
if curState and curState.keypressed then
curState.keypressed(...)
end
end
@ -87,3 +115,27 @@ end
function love.load()
setState("menustate")
end
function love.mousemoved(...)
if curState and curState.mousemoved then
curState.mousemoved(...)
end
end
function love.mousepressed(...)
if curState and curState.mousepressed then
curState.mousepressed(...)
end
end
function love.mousereleased(...)
if curState and curState.mousereleased then
curState.mousereleased(...)
end
end
function love.wheelmoved(...)
if curState and curState.wheelmoved then
curState.wheelmoved(...)
end
end

View File

@ -19,7 +19,7 @@ local credits = {
local font = love.graphics.newFont("fonts/FridayNightFunkin-Regular.ttf", 60)
return function()
---@class StateClass
---@type engine.state
local state = {}
local currentOne = 1

View File

@ -1,6 +1,7 @@
local curIndex = 1
return function()
---@type engine.state
local state = {}
local dataFolder = love.filesystem.getSaveDirectory()
@ -11,8 +12,6 @@ return function()
local charValues = require("playableCharValues.playables")
local playables = charValues.playables
local playableOffsets = charValues.offsets
local playableFlips = charValues.flips
local curCharIndex
@ -45,6 +44,32 @@ return function()
local icons = {}
local capsules = {}
local songButtons = {}
local curSong = songs[1]
local evilCurIndex = 1
local curDiffList = songs[1].difficulties
local curDiff = songs[1].difficulties[1]
local curDiffInd = 1
local startedPlaying = 0
local start = false
local bfAtlas
local confirm = love.audio.newSource("sounds/ui/confirmMenu.ogg", "static")
local function run()
Erect = curDiff == "erect"
gameMode = "freeplaymode"
render.destroyAllSprites()
freaky:stop()
state.changeState("playstate", curSong.name, curDiff, true)
end
local function setupIcons()
for index, icon in next, icons do
@ -53,21 +78,25 @@ return function()
for index, capsule in next, capsules do
capsule:Destroy()
end
for index, button in next, songButtons do
button:Destroy()
end
for index, song in next, songs do
local icon = song.icon
if not icon then goto evilgoto end
local spriteicon = Sprite(string.format("sprites/freeplay/icons/%s.png", icon),
string.format("sprites/freeplay/icons/%s.json", icon))
spriteicon:PlayAnimation("idle", .005, false)
spriteicon.frame = 1
spriteicon.layer = 10
for index, anim in next, spriteicon.quads do
for index, quad in next, anim do
quad.resize = Vector2(1.5,1.5)
songButtons[song.name] = Button(Vector2(), Vector2(600, 130), function()
if curIndex ~= index then
curIndex = index
else
start = true
startedPlaying = 0
icons[curSong.name]:PlayAnimation("confirm", 24, false)
confirm:stop()
confirm:play()
bfAtlas:PlayAnimation(animationAliases.confirm)
end
end
end)
local capsule = Sprite(string.format("images/freeplay/freeplayCapsule/capsule/freeplayCapsule_%s.png", curChar), string.format("images/freeplay/freeplayCapsule/capsule/freeplayCapsule_%s.json", curChar))
capsule.position = Vector2()
@ -82,6 +111,18 @@ return function()
capsules[song.name] = capsule
if not icon then goto evilgoto end
local spriteicon = Sprite(string.format("sprites/freeplay/icons/%s.png", icon),
string.format("sprites/freeplay/icons/%s.json", icon))
spriteicon:PlayAnimation("idle", .005, false)
spriteicon.frame = 1
spriteicon.layer = 10
for index, anim in next, spriteicon.quads do
for index, quad in next, anim do
quad.resize = Vector2(1.5,1.5)
end
end
icons[song.name] = spriteicon
::evilgoto::
@ -92,18 +133,8 @@ return function()
local lastChange = 0
local startedPlaying = 0
local start = false
local curSong = songs[1]
local evilCurIndex = 1
local curDiffList = songs[1].difficulties
local bfAtlas = love.animate.newTextureAtlas()
local picoAtlas
local curDiff = songs[1].difficulties[1]
local curDiffInd = 1
bfAtlas = Atlas(string.format("sprites/freeplay/freeplay-%s", curChar))
bfAtlas.layer = 20
local diffIMG
local iconNum = math.random(1, 3)
@ -122,6 +153,7 @@ return function()
local left
local arrow
local arrow2
local scroll = love.audio.newSource("sounds/ui/scrollMenu.ogg", "static")
local function setup()
love.window.setTitle("TaggedEngine: Freeplay")
@ -150,6 +182,31 @@ return function()
arrow.layer = 50
arrow.flipX = false
arrow:PlayAnimation("arrow pointer loop", 24, true)
Button(Vector2(0,0), Vector2(50, 90), function()
if curDiffList[curDiffInd - 1] then
curDiff = curDiffList[curDiffInd - 1]
curDiffInd = curDiffInd - 1
else
curDiff = curDiffList[#curDiffList]
curDiffInd = #curDiffList
end
diffIMG:PlayAnimation(curDiff, 24, true)
scroll:stop()
scroll:play()
end)
Button(Vector2(260,0), Vector2(50, 90), function()
print("pressed idk")
if curDiffList[curDiffInd + 1] then
curDiff = curDiffList[curDiffInd + 1]
curDiffInd = curDiffInd + 1
else
curDiff = curDiffList[1]
curDiffInd = 1
end
diffIMG:PlayAnimation(curDiff, 24, true)
scroll:stop()
scroll:play()
end)
arrow2 = Sprite(
string.format("sprites/freeplay/freeplaySelector-%s.png", curChar),
@ -170,31 +227,15 @@ return function()
diffIMG:PlayAnimation(curDiff, 24, false)
diffIMG.position = Vector2(50, 10)
diffIMG.layer = 2
bfAtlas = Atlas(string.format("sprites/freeplay/freeplay-%s", curChar))
bfAtlas.layer = 20
bfAtlas:PlayAnimation(animationAliases.intro)
setupIcons()
end
local function run()
Erect = curDiff == "erect"
gameMode = "freeplaymode"
render.destroyAllSprites()
freaky:stop()
state.changeState("playstate", curSong.name, curDiff, true)
end
local font = love.graphics.newFont("fonts/FridayNightFunkin-Regular.ttf", 40)
local smallerFont = love.graphics.newFont("fonts/FridayNightFunkin-Regular.ttf", 20)
local scroll = love.audio.newSource("sounds/ui/scrollMenu.ogg", "static")
local confirm = love.audio.newSource("sounds/ui/confirmMenu.ogg", "static")
function state.update(dt)
if start then
startedPlaying = startedPlaying + dt
@ -208,6 +249,24 @@ return function()
end
bfAtlas.position = Vector2(animationPositions[bfAtlas.atlas.symbol].x, animationPositions[bfAtlas.atlas.symbol].y)
for index, song in next, songs do
local icon = icons[song.name]
if icon then
icon.position = Vector2(430 - (50 * math.abs(index - evilCurIndex)),
love.graphics:getHeight() / 2 + (200 * (index - evilCurIndex - .5)))
end
local capsule = capsules[song.name]
capsule.position = Vector2(380 - (50 * math.abs(index - evilCurIndex)),
love.graphics:getHeight() / 2 + (200 * (index - evilCurIndex - .5) - 25))
local button = songButtons[song.name]
if button then
button:SetPosition(Vector2(380 - (50 * math.abs(index - evilCurIndex)),
love.graphics:getHeight() / 2 + (200 * (index - evilCurIndex - .5) - 25))
)
end
end
render.cameraPosition = Vector2(0, 0)
render.updateSprites(dt)
end
@ -223,15 +282,6 @@ return function()
love.graphics.print({ color, song.name }, font, 700 - (50 * math.abs(index - evilCurIndex)),
love.graphics:getHeight() / 2 + (200 * (index - evilCurIndex - .5)), 0, 1, 1, 200)
local icon = icons[song.name]
if icon then
icon.position = Vector2(430 - (50 * math.abs(index - evilCurIndex)),
love.graphics:getHeight() / 2 + (200 * (index - evilCurIndex - .5)))
end
local capsule = capsules[song.name]
capsule.position = Vector2(380 - (50 * math.abs(index - evilCurIndex)),
love.graphics:getHeight() / 2 + (200 * (index - evilCurIndex - .5) - 25))
end
-- if bfAtlas.atlas.symbol == curChar .. " slide in" then
-- bfAtlas:draw(curChar == "bf" and 695 or 690, curChar == "bf" and 315 or 320, 0, flip and -1 or 1, 1)
@ -243,7 +293,7 @@ return function()
local text = string.format("Accuracy: %s, Score: %s, Rank: %s",
tostring(data.songs[curChar][curSong.name][curDiff].accuracy):sub(1, 5),
data.songs[curChar][curSong.name][curDiff].score, data.songs[curChar][curSong.name][curDiff].rank)
love.graphics.print({ { 0, 0, 0 }, text }, smallerFont, 1280 - text:len() * 10, 0)
love.graphics.print({ { 1, 1, 1 }, text }, smallerFont, 1280 - text:len() * 10, 0)
end
end
@ -374,5 +424,47 @@ return function()
love.window.setMode(1280, 720, { fullscreen = false, resizable = false, centered = true, borderless = false })
end
function state.wheelmoved(x, y)
print("Mousewheel move")
if y > 0 then
if songs[curIndex - 1] then
curSong = songs[curIndex - 1]
curDiffList = songs[curIndex - 1].difficulties
curDiffInd = 1
curDiff = curDiffList[curDiffInd]
curIndex = curIndex - 1
else
curSong = songs[#songs]
curIndex = #songs
curDiffList = songs[curIndex].difficulties
curDiffInd = 1
curDiff = curDiffList[curDiffInd]
end
curDiffInd = 1
curDiff = curDiffList[1]
diffIMG:PlayAnimation(curDiff, 24, true)
scroll:stop()
scroll:play()
else
curIndex, curSong = next(songs, curIndex)
if not curSong then
curIndex, curSong = next(songs)
curDiffList = curSong.difficulties
else
curDiffList = curSong.difficulties
end
curDiffInd = 1
curDiff = curDiffList[1]
diffIMG:PlayAnimation(curDiff, 24, true)
scroll:stop()
scroll:play()
end
end
function state.mousepressed(x, y)
buttons.processMouse(x, y)
end
return state
end

View File

@ -15,7 +15,7 @@ local defaultSettings = { -- The way its in the JSON
local currentOption = 1
return function()
---@class StateClass
---@type engine.state
local state = {}
local options = {

View File

@ -46,7 +46,7 @@ end
local currentSetting = 1
return function()
---@class StateClass
---@type engine.state
local state = {}
local getting

View File

@ -60,6 +60,7 @@ local colors = {
}
local function state(songName, songDifficulty, show)
---@type engine.state
local state = {} -- Returns needed functions for the state to work after loading it properly
print(curChar)
@ -197,20 +198,20 @@ local function state(songName, songDifficulty, show)
cdLength = cdLength + audio:getDuration() * 1000
end
--- @class SharedVars
--- @class engine.sharedvars
--- @field canStart boolean
--- @field screenSize Vector2
--- @field canvasSize Vector2
--- @field singVectors table<Vector2>
--- @field screenSize engine.vector2
--- @field canvasSize engine.vector2
--- @field singVectors table<engine.vector2>
--- @field settings table
--- @field receptors table<Sprite>
--- @field splashes table<Sprite>
--- @field opponentReceptors table<Sprite>
--- @field receptors table<engine.sprite>
--- @field splashes table<engine.sprite>
--- @field opponentReceptors table<engine.sprite>
--- @field health number
--- @field speed number
--- @field ui table<boolean>
--- @field notes table<Note>
--- @field characters table<Character>
--- @field notes table<engine.note>
--- @field characters table<engine.character>
_G.sharedVars = {
canStart = true,
screenSize = Vector2(1280, 720),