diff --git a/scripts/api/animation.lua b/scripts/api/animation.lua new file mode 100644 index 0000000..aa869f9 --- /dev/null +++ b/scripts/api/animation.lua @@ -0,0 +1,202 @@ + +require "common.class" + +require "api.graphics" + +local Image = require "api.image" + +---@class AnimationParams +---@field fps number? +---@field loop boolean? +---@field loopPoint integer? +---@field width number? +---@field height number? +---@field x number? +---@field y number? +---@field scaleX number? +---@field scaleY number? +---@field centered boolean? +---@field blendOp integer? +---@field color number[]? +---@field alpha number? +---@field stroke StrokeParams? + +---@class Animation +---@field frames Image[] +---@field frameCount integer +---@field frameTime number +---@field loop boolean +---@field loopPoint integer +---@field width number? +---@field height number? +---@field x number? +---@field y number? +---@field scaleX number? +---@field scaleY number? +---@field centered boolean? +---@field blendOp integer? +---@field color number[]? +---@field alpha number? +---@field stroke StrokeParams? +local Animation = { }; + +---@class AnimationState +---@field animation Animation # The animation data this state is playing through +---@field frameIndex integer # Current frame in the animation +---@field timer number # Timer used to determine when to change to the next frame +---@field running boolean # Is the animation currently running and accepting updates? +---@field callback function? # Called when the animation completes +local AnimationState = { }; + +local function loadSequentialAnimationFrames(animPath) + local frames = { }; + local count = 0; + + local detectedFormat = nil; + + while (true) do + local frame = nil; + if (detectedFormat) then + frame = Image.new(detectedFormat:format(animPath, count + 1), true); + else + for i = 1, 4 do + local format = '%s/%0' .. i .. 'd.png'; + frame = Image.new(format:format(animPath, count + 1), true); + + if (frame) then + detectedFormat = format; + break; + end + end + end + + if (not frame) then + break; + end + + count = count + 1; + frames[count] = frame; + end + + return frames, count; +end + +---Animation constructor +---@param animPath string +---@param params AnimationParams +---@return Animation +function Animation.new(animPath, params) + local frames, frameCount = loadSequentialAnimationFrames(animPath); + + local instance = { + frames = frames, + frameCount = frameCount, + + frameTime = 1 / (params.fps or 30), + loop = params.loop or false, + loopPoint = params.loopPoint or 1, + }; + + if (params.width ~= nil) then instance.width = params.width; end + if (params.height ~= nil) then instance.height = params.height; end + if (params.x ~= nil) then instance.x = params.x; end + if (params.y ~= nil) then instance.y = params.y; end + if (params.scaleX ~= nil) then instance.scaleX = params.scaleX; end + if (params.scaleY ~= nil) then instance.scaleY = params.scaleY; end + if (params.centered ~= nil) then instance.centered = params.centered; end + if (params.blendOp ~= nil) then instance.blendOp = params.blendOp; end + if (params.color ~= nil) then instance.color = params.color; end + if (params.alpha ~= nil) then instance.alpha = params.alpha; end + if (params.stroke ~= nil) then instance.stroke = params.stroke; end + + return Base(Animation, instance); +end + +---Create an AnimationState to play this animation. +---The AnimationState is not started. +---@param callback function? +---@return AnimationState +function Animation:createState(callback) + ---@type AnimationState + local state = { animation = self, callback = callback, frameIndex = 1, timer = 0, running = false }; + return Base(AnimationState, state); +end + +---Create an AnimationState to play this animation and start it. +---@param callback function? +---@return AnimationState +function Animation:start(callback) + local state = self:createState(callback); + state:start(); + + return state; +end + +---Start this AnimationState. +---Does nothing if it's already running. +function AnimationState:start() + self.running = true; +end + +---Restart this AnimationState. +---The frame index is reset to 1. +function AnimationState:restart() + self.running = true; + self.frameIndex = 1; + self.timer = 0; +end + +---Stop this AnimationState. +function AnimationState:stop() + self.running = false; +end + +---Updates this AnimationState and then rendersit, passing on the given ImageParams to each frame. +---@param deltaTime number +---@param params? ImageParams +function AnimationState:render(deltaTime, params) + if (not self.running) then return; end; + + self.timer = self.timer + deltaTime; + + while (self.timer > self.animation.frameTime) do + self.timer = self.timer - self.animation.frameTime; + self.frameIndex = self.frameIndex + 1; + + if (self.frameIndex > self.animation.frameCount) then + if (self.animation.loop) then + self.frameIndex = self.animation.loopPoint; + else + self.running = false; + + if (self.callback) then + self.callback(); + end + + return; + end + end + end + + if (params) then + if (params.width == nil) then params.width = self.animation.width; end + if (params.height == nil) then params.height = self.animation.height; end + if (params.x == nil) then params.x = self.animation.x; end + if (params.y == nil) then params.y = self.animation.y; end + if (params.scaleX == nil) then params.scaleX = self.animation.scaleX; end + if (params.scaleY == nil) then params.scaleY = self.animation.scaleY; end + if (params.centered == nil) then params.centered = self.animation.centered; end + if (params.blendOp == nil) then params.blendOp = self.animation.blendOp; end + if (params.alpha == nil) then params.alpha = self.animation.alpha; end + if (params.stroke == nil) then params.stroke = self.animation.stroke; end + end + + local frame = self.animation.frames[self.frameIndex]; + if (not frame) then + -- TODO(local): what do + else + frame:render(params); + end +end + +return Animation; diff --git a/scripts/api/graphics.lua b/scripts/api/graphics.lua new file mode 100644 index 0000000..38a8e55 --- /dev/null +++ b/scripts/api/graphics.lua @@ -0,0 +1,6 @@ + +-- TODO(local): put these class types somewhere more common +---@class StrokeParams +---@field color number[]? +---@field alpha number? +---@field size number? diff --git a/scripts/api/image.lua b/scripts/api/image.lua new file mode 100644 index 0000000..05e5c7c --- /dev/null +++ b/scripts/api/image.lua @@ -0,0 +1,171 @@ + +require "common.class" + +require "api.graphics" + +---@class ImageParams +---@field width number +---@field height number +---@field x number? +---@field y number? +---@field scaleX number? +---@field scaleY number? +---@field centered boolean? +---@field blendOp integer? +---@field color number[]? +---@field alpha number? +---@field stroke StrokeParams? + +---@class Image +---@field handle integer +---@field width number +---@field height number +---@field x number? +---@field y number? +---@field scaleX number? +---@field scaleY number? +---@field centered boolean? +---@field blendOp integer? +---@field color number[]? +---@field alpha number? +---@field stroke StrokeParams? +local Image = { }; + +---Image constructor +---@param imagePath string # The path to the skin image to load +---@return Image +function Image.new(imagePath, noFallback) + local handle = gfx.CreateSkinImage(imagePath or '', 0); + if (not handle) then + game.Log('Failed to load image "' .. imagePath .. '"', game.LOGGER_ERROR); + + if (noFallback) then return nil; end + + handle = gfx.CreateSkinImage('missing.png', 0); + if (not handle) then + game.Log('Failed to load fallback image "missing.png"', game.LOGGER_ERROR); + end + end + + local width, height = 64, 64; + if (handle) then + width, height = gfx.ImageSize(handle); + end + + local instance = { + handle = handle, + width = width, + height = height, + }; + + return Base(Image, instance); +end + +---Set the width and height of this Image. +---@param width number +---@param height number +---@return Image # Returns self for method chaining +function Image:setSize(width, height) + if (type(width) ~= "number") then width = 0; end + if (type(height) ~= "number") then height = 0; end + + self.width = width; + self.height = height; + + return self; +end + +---Set the stored position for this Image. +---If the position of this Image will not change frequently, +---using this method allows you to cache the render position +---instead of passing it to the render method on each invocation. +---@param x number +---@param y number +---@return Image # Returns self for method chaining +function Image:setPosition(x, y) + if (type(x) ~= "number") then x = 0; end + if (type(y) ~= "number") then y = 0; end + + self.x = x; + self.y = y; + + return self; +end + +---Renders this Image, applying any of the given ImageParams, +---then any of the cached Image fields, then any default values. +---@param params? ImageParams +function Image:render(params) + params = params or { }; + + local sx = params.scaleX or self.scaleX or 1; + local sy = params.scaleY or self.scaleY or 1; + + local x = params.x or self.x or 0; + local y = params.y or self.y or 0; + + local w = (params.width or self.width ) * sx; + local h = (params.height or self.height) * sy; + + if (params.centered or self.centered) then + x = x - w / 2; + y = y - h / 2; + end + + local blendOp = params.blendOp or self.blendOp or gfx.BLEND_OP_SOURCE_OVER; + + local r = 255; + local g = 255; + local b = 255; + + if (params.color) then + r = params.color[1]; + g = params.color[2]; + b = params.color[3]; + elseif (self.color) then + r = self.color[1]; + g = self.color[2]; + b = self.color[3]; + end + + local a = params.alpha or self.alpha or 1; + + gfx.BeginPath(); + gfx.GlobalCompositeOperation(blendOp); + + if (not self.handle) then + gfx.FillColor(r, g, b, a); + gfx.Rect(x, y, w, h); + gfx.FillColor(255, 255, 255, 255); + else + gfx.SetImageTint(r, g, b); + gfx.ImageRect(x, y, w, h, self.handle, a, 0); + gfx.SetImageTint(255, 255, 255); + end + + if (params.stroke or self.stroke) then + r = 255; + g = 255; + b = 255; + + if (params.stroke.color) then + r = params.stroke.color[1]; + g = params.stroke.color[2]; + b = params.stroke.color[3]; + elseif (self.stroke and self.stroke.color) then + r = self.stroke.color[1]; + g = self.stroke.color[2]; + b = self.stroke.color[3]; + end + + a = params.stroke.alpha or (self.stroke and self.stroke.alpha) or 255; + + local size = params.stroke.size or (self.stroke and self.stroke.size) or 1; + + gfx.StrokeColor(r, g, b, a); + gfx.StrokeWidth(size); + gfx.Stroke(); + end +end + +return Image; diff --git a/scripts/common.lua b/scripts/common.lua index 8a2a924..5eea277 100644 --- a/scripts/common.lua +++ b/scripts/common.lua @@ -20,6 +20,7 @@ Memo.memoize = function(this, key, generator) return value end +--[[ -- Image class -------------- @@ -130,6 +131,8 @@ ImageFont.draw = function(this, text, x, y, alpha, hFlag, vFlag) end end +]] + function GetDisplayDifficulty(jacketPath, difficulty) if jacketPath == nil then return difficulty @@ -137,18 +140,20 @@ function GetDisplayDifficulty(jacketPath, difficulty) local strippedPath = string.match(jacketPath:lower(), "[/\\][^\\/]+$") if difficulty == 3 and strippedPath then - if string.find(strippedPath, "inf") ~= nil then - return 5 + if string.find(strippedPath, "inf") ~= nil then + return 5 elseif string.find(strippedPath, "grv") ~= nil then - return 6 + return 6 elseif string.find(strippedPath, "hvn") ~= nil then - return 7 - elseif string.find(strippedPath, "vvd") ~= nil then - return 8 - end + return 7 + elseif string.find(strippedPath, "vvd") ~= nil then + return 8 + elseif string.find(strippedPath, "xcd") ~= nil then + return 9 + end end - return difficulty+1 + return difficulty + 1 end function split(s, delimiter) @@ -157,4 +162,42 @@ function split(s, delimiter) table.insert(result, match); end return result; +end + +function firstAlphaNum(s) + for i = 1, string.len(s) do + local byte = string.byte(s, i); + if ((byte >= 65 and byte <= 90) or (byte >= 97 and byte <= 122) or (byte >= 48 and byte <= 57)) then + return string.sub(s, i, i); + end + end + + return ''; +end + +---Set's up scaled transforms based on the current resolution. +---@param x number +---@param y number +---@param rotation number +---@return number, boolean # The scale applied to the transform and the current landscape state +function setUpTransforms(x, y, rotation) + local resx, resy = game.GetResolution(); + isLandscape = resx > resy; + + local desw, desh; + if (isLandscape) then + desw = 1920; + desh = 1080; + else + desw = 1080; + desh = 1920; + end + + scale = resx / desw; + + gfx.Translate(x, y); + gfx.Rotate(rotation); + gfx.Scale(scale, scale); + + return scale, isLandscape; end \ No newline at end of file diff --git a/scripts/gameplay.lua b/scripts/gameplay.lua index d8bfe16..97a1148 100644 --- a/scripts/gameplay.lua +++ b/scripts/gameplay.lua @@ -11,6 +11,8 @@ local Gauge = require('gameplay.gauge') local Chain = require('gameplay.chain') local LaserAlert = require('gameplay.laser_alert') +local HitFX = require 'gameplay.hitfx' + local TrackEnd = require('gameplay.track_end') local json = require("common.json") @@ -28,6 +30,8 @@ local fullX, fullY local desw = 1080 local desh = 1920 +local showHitAnims = true; + local resolutionChange = function(x, y) resX = x resY = y @@ -78,11 +82,11 @@ end function render_crit_base(deltaTime) CritLine.renderBase(deltaTime, gameplay.critLine.x, gameplay.critLine.y, -gameplay.critLine.rotation, gameplay.critLine.cursors); + HitFX.render(deltaTime, gameplay.critLine.x, gameplay.critLine.y, -gameplay.critLine.rotation, gameplay.critLine.cursors); Console.render(deltaTime, gameplay.critLine.x, gameplay.critLine.y, -gameplay.critLine.rotation); end function render_crit_overlay(deltaTime) - end function render_intro(deltaTime) @@ -121,11 +125,14 @@ function near_hit(wasLate) end function button_hit(button, rating, delta) - + if (showHitAnims) then + HitFX.TriggerAnimation("Crit", button + 1) + end end function laser_slam_hit(slamLength, startPos, endPost, index) - + if (showHitAnims) then + end end function laser_alert(isRight) @@ -156,9 +163,9 @@ end -- Update the users in the scoreboard function score_callback(response) - if response.status ~= 200 then - error() - return + if response.status ~= 200 then + error() + return end local jsondata = json.decode(response.text) users = {} diff --git a/scripts/gameplay/console.lua b/scripts/gameplay/console.lua index 0cfe8f1..e85edd9 100644 --- a/scripts/gameplay/console.lua +++ b/scripts/gameplay/console.lua @@ -4,20 +4,6 @@ local consoleBaseImage = gfx.CreateSkinImage("gameplay/console/base.png", 0) local CONSOLE_W = 1352; local CONSOLE_H = 712; --- Similar to crit line transforms, since the console needs to follow the lane rotation -local setUpTransforms = function (x,y,rotation) - local resx, resy = game.GetResolution() - local desw = 1080 - local desh = 1920 - local scale = resx / desw - - - gfx.Translate(x, y) - gfx.Rotate(rotation) - gfx.Scale(scale,scale) -end - - local render = function (deltaTime, critLineCenterX, critLineCenterY, critLineRotation) local resx, resy = game.GetResolution(); if (resx > resy) then diff --git a/scripts/gameplay/crit_line.lua b/scripts/gameplay/crit_line.lua index 6886e2b..3f08b44 100644 --- a/scripts/gameplay/crit_line.lua +++ b/scripts/gameplay/crit_line.lua @@ -1,4 +1,6 @@ +local blackGradientImage = gfx.CreateSkinImage('gameplay/crit_line/black_gradient.png', 0) + local baseImage = gfx.CreateSkinImage("gameplay/crit_line/base.png", 0) local baseImageLandscape = gfx.CreateSkinImage("gameplay/crit_line/base_landscape.png", 0) local textImage = gfx.CreateSkinImage("gameplay/crit_line/text.png", 0) @@ -17,42 +19,23 @@ local cursorGlowTopImages = { local CRITBAR_W = 1496 local CRITBAR_H = 348 -local scale; - +local scale = 1; local isLandscape = false; -local setUpTransforms = function (x,y,rotation) - local resx, resy = game.GetResolution(); - isLandscape = resx > resy; - - local desw, desh; - - if (isLandscape) then - desw = 1920; - desh = 1080; - else - desw = 1080; - desh = 1920; - end - - scale = resx / desw - - gfx.Translate(x, y) - gfx.Rotate(rotation) - gfx.Scale(scale,scale) -end - local drawCursors = function (centerX, centerY,cursors) - local cursorW = 598*0.2; - local cursorH = 673*0.2; + local cursorW = 598 * 0.165; + local cursorH = 673 * 0.14; for i = 0, 1, 1 do - gfx.Save(); local cursor = cursors[i]; - gfx.BeginPath(); - gfx.SkewX(cursor.skew) - local cursorX = (cursor.pos *(1/scale) - cursorW/2); - local cursorY = (-cursorH/2); + gfx.Save(); + gfx.BeginPath(); + + local skew = cursor.pos * 0.001; + gfx.SkewX(skew); + + local cursorX = cursor.pos * (1 / scale) - cursorW / 2; + local cursorY = -cursorH / 2; gfx.ImageRect( cursorX, @@ -99,27 +82,30 @@ local drawCursors = function (centerX, centerY,cursors) end local renderBase = function (deltaTime, centerX, centerY, rotation, cursors) - setUpTransforms(centerX, centerY, rotation) + scale, isLandscape = setUpTransforms(centerX, centerY, rotation) gfx.BeginPath() gfx.FillColor(0, 0, 0, 192) - gfx.Rect(-1080/2, 0, 1080, 1080) + gfx.Rect(-9999, 0, 9999 * 2, 1080) gfx.Fill() - gfx.BeginPath(); if (isLandscape) then - gfx.ImageRect(-CRITBAR_W/2, -CRITBAR_H/2, CRITBAR_W, CRITBAR_H, baseImageLandscape, 1, 0); + --gfx.BeginPath() + --gfx.ImageRect(-9999, -8, 9999 * 2, 24, blackGradientImage, 192.0 / 255, 0) + gfx.BeginPath(); + gfx.ImageRect(-9999, -CRITBAR_H/2, 9999 * 2, CRITBAR_H, baseImageLandscape, 1, 0); else + gfx.BeginPath(); gfx.ImageRect(-CRITBAR_W/2, -CRITBAR_H/2, CRITBAR_W, CRITBAR_H, baseImage, 1, 0); end drawCursors(centerX, centerY, cursors) - + gfx.ResetTransform() end local renderOverlay = function (deltaTime) - + end return { diff --git a/scripts/gameplay/hitfx.lua b/scripts/gameplay/hitfx.lua new file mode 100644 index 0000000..22876da --- /dev/null +++ b/scripts/gameplay/hitfx.lua @@ -0,0 +1,97 @@ + +require 'common.globals' + +local Animation = require 'api.animation' + +local Animations = { + Crit = Animation.new('gameplay/hit_animation_frames/critical_taps', { + centered = true, + }), + + Near = Animation.new('gameplay/hit_animation_frames/near_taps', { + centered = true, + }), +}; + +local animationStates = { + ---@type AnimationState[] + Hold = { }, + ---@type AnimationState[] + Tap = { } +}; + +for i = 1, 6 do + --animationStates.Hold[i] = + --animationStates.Tap[i] = Animations +end + +local HitFX = { }; + +local LanePositions = { + 1.5 / 6, + 2.5 / 6, + 3.5 / 6, + 4.5 / 6, + 1 / 3, + 2 / 3 +}; + +local function setupLaneTransform(lanePosition, critCenterX, critCenterY) + local critLine = gameplay.critLine; + local x = critCenterX + (critLine.line.x2 - critLine.line.x1) * lanePosition; + local y = critCenterY + (critLine.line.y2 - critLine.line.y1) * lanePosition; + setUpTransforms(x, y, -critLine.rotation); +end + +function HitFX.render(deltaTime, critCenterX, critCenterY, critRotation, cursors) + local baseHitSize = 325; + + for i = 1, 6 do + local hitSize = baseHitSize; + if (i > 4) then + hitSize = hitSize * 1.5; + end + + local laneWidth = (track.GetCurrentLaneXPos(2) - track.GetCurrentLaneXPos(1)) * (i <= 4 and 1 or 2); + local lanePosition = track.GetCurrentLaneXPos(i) + laneWidth / 2 + if (i == 5) then + lanePosition = -track.GetCurrentLaneXPos(6) - laneWidth / 2 + end + + local holdState = animationStates.Hold[i]; + local tapState = animationStates.Tap[i]; + + local isHeld = gameplay.noteHeld[i]; + if (isHeld) then + if (holdState) then + if (not holdState.running) then + holdState:restart(); + end + + setupLaneTransform(lanePosition, critCenterX, critCenterY); + holdState:render(deltaTime); + gfx.ResetTransform(); + end + else + if (holdState and holdState.running) then + holdState:restart(); + end + + if (tapState and tapState.running) then + setupLaneTransform(lanePosition, critCenterX, critCenterY); + tapState:render(deltaTime, { + centered = true, + width = hitSize, + height = hitSize, + }); + gfx.ResetTransform(); + end + end + end +end + +function HitFX.TriggerAnimation(name, lane) + animationStates.Tap[lane] = Animations[name]:start(); +end + +return HitFX; diff --git a/scripts/songselect/songwheel.lua b/scripts/songselect/songwheel.lua index b9a8aac..ba6ddb7 100644 --- a/scripts/songselect/songwheel.lua +++ b/scripts/songselect/songwheel.lua @@ -785,8 +785,9 @@ function drawScrollbar() gfx.FillColor(255,255,255) gfx.TextAlign(gfx.TEXT_ALIGN_MIDDLE + gfx.TEXT_ALIGN_CENTER) if (songwheel.songs[selectedIndex] ~= nil) then - local letter = string.upper(common.firstLetter(songwheel.songs[selectedIndex].title)) - gfx.Text(letter, fillXPos-10, scrollbarYPos + 5) + local title = songwheel.songs[selectedIndex].title; + local letter = string.upper(firstAlphaNum(title)) + gfx.Text(letter, fillXPos-10, scrollbarYPos + 5) end end diff --git a/textures/gameplay/crit_line/black_gradient.png b/textures/gameplay/crit_line/black_gradient.png new file mode 100644 index 0000000..4823d1d Binary files /dev/null and b/textures/gameplay/crit_line/black_gradient.png differ diff --git a/textures/gameplay/hit_animation_frames/critical naked/1.png b/textures/gameplay/hit_animation_frames/critical_naked/1.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/1.png rename to textures/gameplay/hit_animation_frames/critical_naked/1.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/10.png b/textures/gameplay/hit_animation_frames/critical_naked/10.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/10.png rename to textures/gameplay/hit_animation_frames/critical_naked/10.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/11.png b/textures/gameplay/hit_animation_frames/critical_naked/11.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/11.png rename to textures/gameplay/hit_animation_frames/critical_naked/11.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/12.png b/textures/gameplay/hit_animation_frames/critical_naked/12.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/12.png rename to textures/gameplay/hit_animation_frames/critical_naked/12.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/13.png b/textures/gameplay/hit_animation_frames/critical_naked/13.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/13.png rename to textures/gameplay/hit_animation_frames/critical_naked/13.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/14.png b/textures/gameplay/hit_animation_frames/critical_naked/14.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/14.png rename to textures/gameplay/hit_animation_frames/critical_naked/14.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/15.png b/textures/gameplay/hit_animation_frames/critical_naked/15.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/15.png rename to textures/gameplay/hit_animation_frames/critical_naked/15.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/16.png b/textures/gameplay/hit_animation_frames/critical_naked/16.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/16.png rename to textures/gameplay/hit_animation_frames/critical_naked/16.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/17.png b/textures/gameplay/hit_animation_frames/critical_naked/17.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/17.png rename to textures/gameplay/hit_animation_frames/critical_naked/17.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/18.png b/textures/gameplay/hit_animation_frames/critical_naked/18.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/18.png rename to textures/gameplay/hit_animation_frames/critical_naked/18.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/2.png b/textures/gameplay/hit_animation_frames/critical_naked/2.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/2.png rename to textures/gameplay/hit_animation_frames/critical_naked/2.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/3.png b/textures/gameplay/hit_animation_frames/critical_naked/3.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/3.png rename to textures/gameplay/hit_animation_frames/critical_naked/3.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/4.png b/textures/gameplay/hit_animation_frames/critical_naked/4.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/4.png rename to textures/gameplay/hit_animation_frames/critical_naked/4.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/5.png b/textures/gameplay/hit_animation_frames/critical_naked/5.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/5.png rename to textures/gameplay/hit_animation_frames/critical_naked/5.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/6.png b/textures/gameplay/hit_animation_frames/critical_naked/6.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/6.png rename to textures/gameplay/hit_animation_frames/critical_naked/6.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/7.png b/textures/gameplay/hit_animation_frames/critical_naked/7.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/7.png rename to textures/gameplay/hit_animation_frames/critical_naked/7.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/8.png b/textures/gameplay/hit_animation_frames/critical_naked/8.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/8.png rename to textures/gameplay/hit_animation_frames/critical_naked/8.png diff --git a/textures/gameplay/hit_animation_frames/critical naked/9.png b/textures/gameplay/hit_animation_frames/critical_naked/9.png similarity index 100% rename from textures/gameplay/hit_animation_frames/critical naked/9.png rename to textures/gameplay/hit_animation_frames/critical_naked/9.png diff --git a/textures/hitcolors.png b/textures/hitcolors.png index 2165b40..aafcece 100644 Binary files a/textures/hitcolors.png and b/textures/hitcolors.png differ diff --git a/textures/missing.png b/textures/missing.png new file mode 100644 index 0000000..a803ff0 Binary files /dev/null and b/textures/missing.png differ diff --git a/textures/score2.png b/textures/score2.png index 33686dd..d1b2a51 100644 Binary files a/textures/score2.png and b/textures/score2.png differ