---@diagnostic disable: undefined-global backgrounds = require("assets.backgrounds.backgrounds-data") sprite_height = 128 sprite_width = 64 groundTiles = { { x = 250, y = HEIGHT - 300, width = 200, height = 30 }, { x = WIDTH - 250 - 200, y = HEIGHT - 300, width = 200, height = 30 }, { x = 0, y = HEIGHT - 100, width = WIDTH, height = 100 } } knockbackMultiplierX = 35.4 knockbackMultiplierY = 8.73 minimalKnockbackMultiplierY = 40.3 function SafeInitCharacter(character, default_x, default_y) character.x = default_x character.x_velocity = 1 character.y = default_y character.y_velocity = 1 character.current_sprite = character.asset_dir .. "/sprites/idle.png" character.default_sprite = character.current_sprite character.can_jump = false character.knockback_counter = 0 character.combo_chain = 0 character.can_apply_knockback = false character.facing = "right" end function Setup() local bgIndex = math.random(1, #backgrounds) setBgImage(backgrounds[bgIndex]) SafeInitCharacter(player1Character, 250, 150) SafeInitCharacter(player2Character, WIDTH - 250 - (250 / 2), 150) end gravity = 1570 function DrawGroundTiles() for _, tile in ipairs(groundTiles) do queueRectForRender(tile.x, tile.y, tile.width, tile.height, 100, 100, 100, 255) -- gray boxes end end function IsOnGround(character) for _, tile in ipairs(groundTiles) do local characterFeetY = character.y + sprite_height local isWithinX = character.x + sprite_width > tile.x and character.x < tile.x + tile.width local isTouchingY = characterFeetY >= tile.y and characterFeetY <= tile.y + tile.height local isFalling = character.y_velocity >= 0 if isWithinX and isTouchingY and isFalling then character.y = tile.y - sprite_height return true end end return false end function IsHittingCeiling(character) for _, tile in ipairs(groundTiles) do local characterHeadY = character.y local isWithinX = character.x + sprite_width > tile.x and character.x < tile.x + tile.width local isTouchingY = characterHeadY <= tile.y + tile.height and characterHeadY >= tile.y if isWithinX and isTouchingY then character.y = tile.y + tile.height character.y_velocity = 0 return true end end return false end function IsTouchingWall(character, direction) for _, tile in ipairs(groundTiles) do local charBottom = character.y + sprite_height local charTop = character.y local tileBottom = tile.y + tile.height local tileTop = tile.y local verticalOverlapAmount = math.min(charBottom, tileBottom) - math.max(charTop, tileTop) local verticalOverlap = verticalOverlapAmount > 12 -- New: Check if tile is within a certain horizontal range before collision test local horizontalDistance if direction == "left" then horizontalDistance = character.x - (tile.x + tile.width) if horizontalDistance > 15 then -- Only check if tile is within 15 pixels to the left goto continue end elseif direction == "right" then horizontalDistance = tile.x - (character.x + sprite_width) if horizontalDistance > 15 then -- Only check if tile is within 15 pixels to the right goto continue end end if direction == "left" then local nextX = character.x - character.speed local willCollide = nextX < tile.x + tile.width and character.x >= tile.x + tile.width if willCollide and verticalOverlap then character.x = tile.x + tile.width return true end elseif direction == "right" then local nextX = character.x + sprite_width + character.speed local willCollide = nextX > tile.x and character.x + sprite_width <= tile.x if willCollide and verticalOverlap then character.x = tile.x - sprite_width return true end end ::continue:: end return false end function HandleP1Input() if Input.isKeyPressedOnce("W") and player1Character.can_jump then player1Character.y_velocity = player1Character.jump_strength * -1.0 player1Character.can_jump = false end if Input.isKeyDown("D") then if not IsTouchingWall(player1Character, "right") then player1Character.x_velocity = player1Character.speed player1Character.facing = "right" else player1Character.x_velocity = 0 end end if Input.isKeyDown("A") then if not IsTouchingWall(player1Character, "left") then player1Character.x_velocity = -player1Character.speed player1Character.facing = "left" else player1Character.x_velocity = 0 end end if Input.isKeyPressedOnce("F") then PerformPunch(player1Character, player2Character) end end function HandleP2Input() if Input.isKeyPressedOnce("UP") and player2Character.can_jump then player2Character.y_velocity = player2Character.jump_strength * -1.0 player2Character.can_jump = false end if Input.isKeyDown("RIGHT") then if not IsTouchingWall(player2Character, "right") then player2Character.x_velocity = player2Character.speed player2Character.facing = "right" else player2Character.x_velocity = 0 end end if Input.isKeyDown("LEFT") then if not IsTouchingWall(player2Character, "left") then player2Character.x_velocity = -player2Character.speed player2Character.facing = "left" else player2Character.x_velocity = 0 end end if Input.isKeyPressedOnce("L") then PerformPunch(player2Character, player1Character) end end function DrawUI() local fontFile = "assets/fonts/OpenSans-Bold.ttf" local fontSize = 24 local text = player1Character.name local x = 20 local y = 20 queueTextForRender(text, fontFile, x, y, fontSize, 0, 0, 0, 255) local text = player2Character.name local textWidth = getTextWidth(fontFile, fontSize, text) local x = WIDTH - textWidth - 20 local y = 20 queueTextForRender(text, fontFile, x, y, fontSize, 0, 0, 0, 255) local fontFile = "assets/fonts/OpenSans-MediumItalic.ttf" local fontSize = 34 local text = tonumber(player1Character.knockback_counter) local x = 40 local y = 40 queueTextForRender(text, fontFile, x, y, fontSize, 0, 0, 0, 255) local fontFile = "assets/fonts/OpenSans-MediumItalic.ttf" local fontSize = 34 local text = tostring(player2Character.knockback_counter) local textWidth = getTextWidth(fontFile, fontSize, text) local x = WIDTH - textWidth - 40 queueTextForRender(text, fontFile, x, y, fontSize, 0, 0, 0, 255) end function Respawn(character) SafeInitCharacter(character, 250, 150) end function ApplyKnockback(attacker, target) local direction = attacker.facing == "left" and -1 or 1 local scaledY = -10 - (target.knockback_counter * knockbackMultiplierY) target.y_velocity = math.max(-950, scaledY) -- clamp Y knockback target.x_velocity = target.knockback_counter * knockbackMultiplierX * direction end function ApplyMinimalKnockback(attacker, target) local knockbackCountMinimal = 5 target.y_velocity = -10 - (knockbackCountMinimal * minimalKnockbackMultiplierY) local direction = 1 if attacker.facing == "left" then direction = -1 end target.x_velocity = knockbackCountMinimal * knockbackMultiplierX * direction -- target.x = target.x + direction * (target.knockback_counter * 0.7) end function KillPlayer(character) character.lives = character.lives - 1 character.knockback_counter = 0 character.combo_chain = 0 if character.lives > 0 then Respawn(character) end end function RegisterHit(attacker, target, damage) target.knockback_counter = target.knockback_counter + damage attacker.combo_chain = attacker.combo_chain + 1 if attacker.combo_chain >= 4 then ApplyKnockback(attacker, target) attacker.combo_chain = 0 else ApplyMinimalKnockback(attacker, target) end if target.knockback_counter >= 1000 then KillPlayer(target) end end function PerformPunch(attacker, target) local punch_range = 27 local punch_height = 80 -- Determine direction of target relative to attacker local punch_direction = attacker.facing -- Set punch sprite attacker.current_sprite = attacker.asset_dir .. "/sprites/punch_" .. punch_direction .. ".png" -- Reset sprite after a short delay (e.g., 0.15s) Timer.clearAllTimers() Timer.after(0.15, function() attacker.current_sprite = attacker.default_sprite end) -- Define hitbox based on punch direction local hitbox = { x = attacker.x + (punch_direction == "right" and sprite_width or -punch_range), y = attacker.y + (sprite_height / 2) - (punch_height / 2), width = punch_range, height = punch_height } local targetHitbox = { x = target.x, y = target.y, width = sprite_width, height = sprite_height } local function isColliding(a, b) return a.x < b.x + b.width and a.x + a.width > b.x and a.y < b.y + b.height and a.y + a.height > b.y end if isColliding(hitbox, targetHitbox) then Timer.after(0.05, function () RegisterHit(attacker, target, attacker.hit_strength) end) end end function ApplyPhysics(character) character.y_velocity = character.y_velocity + gravity * deltaTime character.y = character.y + character.y_velocity * deltaTime character.x = character.x + character.x_velocity * deltaTime character.x_velocity = character.x_velocity * 0.88 print(character.name, character.x_velocity) print(character.name, character.y_velocity) if character.y_velocity > 0 then -- Falling if IsOnGround(character) then character.y_velocity = 0 character.can_jump = true end elseif character.y_velocity < 0 then -- Going upward IsHittingCeiling(character) end if not IsOnGround(character) then character.can_jump = false end if character.y > HEIGHT + 50 then KillPlayer(character) end if character.x < -50 then KillPlayer(character) end if character.x > WIDTH + 50 then KillPlayer(character) end end function Update() -- Apply physics ApplyPhysics(player1Character) ApplyPhysics(player2Character) -- Render characters queueTextureForRender(player1Character.current_sprite, math.floor(player1Character.x), math.floor(player1Character.y)) queueTextureForRender(player2Character.current_sprite, math.floor(player2Character.x), math.floor(player2Character.y)) -- Environment DrawGroundTiles() -- Input HandleP1Input() HandleP2Input() -- UI DrawUI() -- Timer Timer.update(deltaTime) end