Lua - Reddit – Telegram
Lua - Reddit
31 subscribers
281 photos
31 videos
4.28K links
News and discussion for the Lua programming language.

Subreddit: https://www.reddit.com/r/lua

Powered by : @r_channels & @reddit2telegram
Download Telegram
Wanted to share my Lua game engine with VS Code breakpoint debugging (Made in C++)

The Editor

The engine could be used as learning material for the beginners on this forum. If you're doing a C++/OpenGL/Lua engine, feel free to have a look. It should be fairly straight-forward to compile and run a template project.

Feature Set, TL;DR

Editor with all kinds of tools.
Works on all desktop platforms (Win, Linux, Mac) and browsers (WebGL 2 / WebAssembly).
PBR Renderer (OpenGL ES 3.0), point lights, sun light, skybox, MSAA, material editor...
Lua Scripting for systems or components, with breakpoint debugging in VS Code.
Object/Component System (like Unity), support C++ components or Lua components.
Serialization (save/load) of all the things (scene, materials, prefabs...)
In-Game User Interface
Multi-threaded animation system, root motion, etc
Audio
Multi-threaded job system
3D physics (bullet3): rigidbodies, raycasts, etc
Networking: scene sync, events, client/server architecture, multiplayer debug tools, UDP, etc

If anyone has questions, please reach out :D

GitHub link: https://github.com/mormert/jle
YouTube demo video: https://youtu.be/2GiqLXTfKg4/

https://redd.it/1da7151
@r_lua
good lua ide

I am needing a lua ide for making apps but from what I am looking for I need the noscript bar to be gone in some of my apps as I need to make a more custom one I would use C# and windows forms but its not as suggested for what I am doing if there is an ide anyone knows of please let me know

https://redd.it/1daj5vb
@r_lua
How can a save the scene if ive edited it while the game was runnig?

Trying to figure out preemptible how to save a setup if i forgot i pressed run and made alot of progress

https://redd.it/1dasm3t
@r_lua
LUA help for Mac

im on Mac and a download vs code and im trying to make a folder with the file main.lua on pages and it says its a restricted file pls help ;-;



https://redd.it/1dayt7r
@r_lua
this gang noscript wont work

Config.Gangs = {

["ebk"\] = {

Management = vec3(459.9863, -1775.6997, 29.0699),

VehicleSpawn = vec3(482.1591, -1778.6711, 28.5374),

},

["Bloods"\] = {

Management = vec3(-266.9, -961.13, 31.22),

VehicleSpawn = vec3(326.594849, -2033.116211, 20.936781),

},

},

},

--------------------------------------------------

CreateThread(function()

for Key, Value in next, Config.Zones do

local GangTurf = lib.points.new({

coords = Value.Location,

distance = 3

})

--------------------------------------------------

CreateThread(function()

for zone, location in pairs(Config.Zones) do

local zoneCoords = location.Location

local blip = AddBlipForCoord(zoneCoords)

SetBlipSprite(blip, 310)

SetBlipDisplay(blip, 4)

SetBlipScale(blip, 0.8)

SetBlipColour(blip, 27)

SetBlipAsShortRange(blip, true)

BeginTextCommandSetBlipName("STRING")

AddTextComponentString(string.format("\~HUD_COLOUR_PURPLE\~Turf\~w\~ | %s", tostring(location.Label)))

EndTextCommandSetBlipName(blip)

local mapBlip = AddBlipForRadius(zoneCoords.x, zoneCoords.y, zoneCoords.z, location.radius)

SetBlipHighDetail(mapBlip, true)

SetBlipColour(mapBlip, 27)

SetBlipAlpha(mapBlip, 80)

SetBlipDisplay(mapBlip, 2)

end

return

end)

--------------------------------------------------

CreateThread(function()



for Key, Value in next, Config.Gangs do

local GangManagement = lib.points.new({

coords = Value.Management,

distance = 2.0

})

https://preview.redd.it/k7euluza8d5d1.png?width=1335&format=png&auto=webp&s=8982d913c40652c7349de254c5da24a77e6e0097

--------------------------------------------------


https://redd.it/1db5cv9
@r_lua
Lua 5.3.0, os.time(), and year 2038

I'm using Lua 5.3.0 for a project and someone on my team raised concerns about the year 2038 issue. If I set the date to Jan 19.2038 22:30:00, I get the following error when I run os.time() from Lua: "time result cannot be represented in this Lua instalation" (the misspelling of 'installation' was apparently fixed in 5.3.1).
os.date() seems to work correctly, though.
Does a newer version of Lua have a fix for os.time(), or do I need to rework my code to utilize os.date() instead?

https://redd.it/1dcpotn
@r_lua
What would be the most optimized way to add commas to big numbers?

I'm doing a mod for a game and for more dynamic score display I have a function that makes it quickly catch up to the real hidden score over 5 seconds and is called 20 times/s, but since the number eventually reaches up to 10mil I want to add commas for better readability. Solutions I've found on the internet run too slow and it takes 15 or more seconds to catch up instead.

https://redd.it/1dck6u9
@r_lua
Majordome v4 released

Majordome is an events based automation tool using timers and MQTT messages arrival. Your application itself is a galaxy of small Lua tasks (as AWS' Lambda is doing).

Technically, Majordome is a C++ massive multitasking/multithreading framework, providing all the tools needed to orchestrate those tasks in an efficient and resources conservative way. Tasks are running in stateless environments, but data can be shared among them using MQTT messaging, data collection and shared variable.

This new version provides :
- ability to extend using loadable module
- a comprehensive documentation
- migration to Séléné v7

https://redd.it/1dc29hm
@r_lua
Using the LuaSocket library inside DeSmuME

Hi, I'm trying to use the LuaSocket library ([docs](https://lunarmodules.github.io/luasocket/), [repo](https://github.com/lunarmodules/luasocket)). Since I'm working within the DeSmuME Lua noscripting environment, I'm restricted to the following:

* Can't use LuaRocks installer
* Lua 5.1
* 32-bit libraries

I found [this discussion](https://github.com/lunarmodules/luasocket/issues/283), the guy has the same use case as me (but for 64-bit) but didn't explain exactly what he did. I'm on Windows 11. I have to use 32-bit (x86) DeSmuME as my noscript uses another library that requires it.

I found [this repo](https://luarocks.org/modules/luasocket/luasocket/2.0.2-1) containing a 32-bit LuaSocket source. The 'win32-x86' link on that page downloads a file that extracts to a collection of .lua files and some .dll files. Previously when I've needed to import a library I just 'require' something in my Lua noscript but I don't know what I should do here. Probably there are some other steps too, but I barely know anything about this so I'm a bit stuck! :(

Screenshot of contents of the above

[File directory of the win32-x86 folder in the above link](https://preview.redd.it/pzfm3ulwhl5d1.png?width=263&format=png&auto=webp&s=d5f834746f91f3dfb711b12708c10ba052ba3a95)

Would anyone be able to explain how I can install this library for my situation? Thanks for any guidance.

https://redd.it/1dc1gwm
@r_lua
Rotating a Drone Blade

Hi there, I'm trying to program a drone blade to rotate (around the z-axis) in Visionary Render using Lua, but I have no experience in how to code a rotating assembly. The blade is locked in all position directions and in x and y rotation directions. I need help writing a noscript to execute when a button is pressed that then starts turning the blades (doesn't have to be fast, just has to look like they're turning).

Any help would be greatly appreciated :)

(I've tried looking through the help pages on the Virtalis website to no avail)

https://redd.it/1de2uzq
@r_lua
Need Help with FiveM Script - AI Responds in Console but Not in UI

Hi everyone,

I'm working on a FiveM noscript that integrates an AI-powered NPC chat system using QBCore. The goal is for players to be able to approach NPCs, press "E" to interact, and have a conversation where both the player's messages and the NPC's AI-generated responses appear in a UI chat window.

The issue I'm facing is that while the AI responses are correctly generated and logged in the console, they do not appear in the UI in the game. The player's messages show up in the UI chat window, but the NPC's responses are missing.

Here's a brief overview of my setup:

1. **Server Script (**`server.lua`**):** Handles the AI API request and sends the response back to the client.
2. **Client Script (**`client.lua`**):** Sends the player's message to the server, receives the AI response, and sends it to the NUI.
3. **UI Scripts (**`index.html`**,** `style.css`**,** `noscript.js`**):** Manages the chat window and displays messages.

I tried everything but nothing worked.

If you wanna test it guys, just create an access token on huggingface and add it to the config.lua.

here is my Code:

--fxmanifest.lua
fx_version 'cerulean'
game 'gta5'

author 'Revo'
denoscription 'Interactive NPCs with LLM'
version '1.0.0'

shared_noscript 'config.lua'

client_noscripts {
    'client/client.lua'
}

server_noscripts {
    'server/server.lua'
}

files {
    'ui/index.html',
    'ui/style.css',
    'ui/noscript.js'
}

ui_page 'ui/index.html'


--fxmanifest.lua
fx_version 'cerulean'
game 'gta5'


author 'Revo'
denoscription 'Interactive NPCs with LLM'
version '1.0.0'


shared_noscript 'config.lua'


client_noscripts {
    'client/client.lua'
}


server_noscripts {
    'server/server.lua'
}


files {
    'ui/index.html',
    'ui/style.css',
    'ui/noscript.js'
}


ui_page 'ui/index.html'



--config.lua
Config = {}
Config.HuggingFaceAPIKey = "API-Key"
Config.ModelEndpoint = "https://api-inference.huggingface.co/models/facebook/blenderbot-400M-distill"


--server.lua
local json = require('json')

RegisterNetEvent('InteractiveNPCS:RequestLLMResponse')
AddEventHandler('InteractiveNPCS:RequestLLMResponse', function(text)
    print("Received text from client: " .. text)
    local apiKey = Config.HuggingFaceAPIKey
    local endpoint = Config.ModelEndpoint

    PerformHttpRequest(endpoint, function(err, responseText, headers)
        if err == 200 then
            print("Received response from LLM API: " .. tostring(responseText))
            local response = json.decode(responseText)
            local reply = response and response[1] and response[1].generated_text or "Sorry, I don't understand."
            print("Sending response to client: " .. reply)
            TriggerClientEvent('InteractiveNPCS:ReceiveLLMResponse', source, reply)
        else
            print("Error from LLM API: " .. tostring(err))
            print("Response text: " .. tostring(responseText))
            TriggerClientEvent('InteractiveNPCS:ReceiveLLMResponse', source, "Sorry, something went wrong.")
        end
    end, 'POST', json.encode({
        inputs = text
    }), {
        ["Authorization"] = "Bearer " .. apiKey,
        ["Content-Type"] = "application/json"
    })
end)


--client.lua
local function showChat()
    SetNuiFocus(true, true)
    SendNUIMessage({
        type = "show"
    })
end

local function closeChat()
    SetNuiFocus(false, false)
    SendNUIMessage({
        type = "close"
    })
end

local function getClosestPed(coords)
    local handle, ped = FindFirstPed()
    local success
    local closestPed = nil
    local closestDistance = -1

   
repeat
        local pedCoords = GetEntityCoords(ped)
        local distance = #(coords - pedCoords)

        if closestDistance == -1 or distance < closestDistance then
            closestPed = ped
            closestDistance = distance
        end

        success, ped = FindNextPed(handle)
    until not success

    EndFindPed(handle)
    return closestPed
end

RegisterNUICallback('closeChat', function(data, cb)
    closeChat()
    cb('ok')
end)

RegisterNUICallback('sendMessage', function(data, cb)
    local text = data.text
    print("Client: Sending message - " .. text)
    local playerPed = PlayerPedId()
    local coords = GetEntityCoords(playerPed)
    local closestPed = getClosestPed(coords)

    if closestPed then
        TriggerServerEvent('InteractiveNPCS:RequestLLMResponse', text)
    else
        SendNUIMessage({
            type = "npcReply",
            text = "No one is around to talk to."
        })
    end
    cb('ok')
end)

Citizen.CreateThread(function()
    while true do
        Citizen.Wait(0)
        if IsControlJustReleased(0, 38) then -- E key
            local playerPed = PlayerPedId()
            local coords = GetEntityCoords(playerPed)
            local closestPed = getClosestPed(coords)

            if closestPed then
                showChat()
            end
        end
    end
end)

RegisterNetEvent('InteractiveNPCS:ReceiveLLMResponse')
AddEventHandler('InteractiveNPCS:ReceiveLLMResponse', function(response)
    print("Client: Received response - " .. response)
    SendNUIMessage({
        type = "npcReply",
        text = response
    })
end)



<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <noscript>Interactive NPC Chat</noscript>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="chatContainer">
        <div id="messagesContainer"></div>
        <div id="inputContainer">
            <input type="text" id="inputMessage" placeholder="Type a message..." />
            <button id="sendButton">Send</button>
            <button id="closeButton">X</button>
        </div>
    </div>
    <noscript src="noscript.js"></noscript>
</body>
</html>




<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <noscript>Interactive NPC Chat</noscript>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="chatContainer">
        <div id="messagesContainer"></div>
        <div id="inputContainer">
            <input type="text" id="inputMessage" placeholder="Type a message..." />
            <button id="sendButton">Send</button>
            <button id="closeButton">X</button>
        </div>
    </div>
    <noscript src="noscript.js"></noscript>
</body>
</html>




body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
    display: flex;
    justify-content: center;
    align-items: flex-end;
    height: 100vh;
    background: transparent;
}

#chatContainer {
    position: fixed;
    bottom: 10px;
    width: 90%;
    max-width: 600px;
    background: rgba(0, 0, 0, 0.8);
    padding: 10px;
    border-radius: 10px;
    color: white;
    display: none;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}

#messagesContainer {
    max-height: 200px;
    overflow-y: auto;
    margin-bottom: 10px;
    padding: 5px;
    border: 1px solid #444;
    border-radius: 5px;
    background: rgba(0, 0, 0, 0.6);
}

#inputContainer {
    display: flex;
    align-items: center;
}

#inputMessage {
    flex: 1;
    padding: 10px;
    margin-right: 10px;
    border-radius: 5px;
    border: none;
}

button {
    padding: 10px 15px;
    background: #3498db;
    border: none;
    border-radius: 5px;
    color: white;
    cursor: pointer;
    margin-left: 5px;
}

button:hover {
    background: #2980b9;
}

.chat-message.npc {
    color: #ffcc00;
}

.chat-message.user {
    color: #ffffff;
}



console.log("UI: Script loaded");

document.getElementById("sendButton").addEventListener("click", sendMessage);
document.getElementById("closeButton").addEventListener("click", closeChat);

function closeChat() {
    console.log("UI: Closing chat");
    fetch(`https://${GetParentResourceName()}/closeChat`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        }
    }).then(resp => resp.json()).then(resp => {
        if (resp === 'ok') {
            document.getElementById('chatContainer').style.display = 'none';
        }
    });
}

function sendMessage() {
    const text = document.getElementById('inputMessage').value;
    console.log("UI: Sending message - " + text);

    // Add user's message to the chat
    addMessageToChat("User", text);

    fetch(`https://${GetParentResourceName()}/sendMessage`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ text })
    }).then(resp => resp.json()).then(resp => {
        if (resp === 'ok') {
            document.getElementById('inputMessage').value = '';
        }
    });
}

function addMessageToChat(sender, message) {
    const messagesContainer = document.getElementById('messagesContainer');
    const messageElement = document.createElement('div');
    messageElement.className = `chat-message ${sender.toLowerCase()}`;
    messageElement.innerText = `${sender}: ${message}`;
    messagesContainer.appendChild(messageElement);
    messagesContainer.scrollTop = messagesContainer.scrollHeight;
}

window.addEventListener('message', (event) => {
    console.log("UI: Received message - " + JSON.stringify(event.data));

    if (event.data.type === 'show') {
        console.log("UI: Showing chat");
        document.getElementById('inputMessage').value = '';
        document.getElementById('messagesContainer').innerHTML = ''; // Clear previous messages
        document.getElementById('chatContainer').style.display = 'block';
    } else if (event.data.type === 'close') {
        console.log("UI: Closing chat");
        document.getElementById('chatContainer').style.display = 'none';
    } else if (event.data.type === 'npcReply') {
        console.log("UI: NPC replied with text - " + event.data.text);

        // Add NPC's message to the chat
        addMessageToChat("Random NPC", event.data.text);
    }
});



https://redd.it/1deawfo
@r_lua
Could someone help me out with patching this mod?

I am new to lua coding, and I'm stuck on patching this mod. I'd like to patch "targetLevel = 0.0 + targetLevel - ((UKO.randomDecimal(maxToLose / 10)) / 10 \* targetLevel)" to make it remove a consistent percentage value. Instead of removing a random value like it does currently!

 
  if hasConfig then
        local oldTargetLevel = 0.0 + targetLevel
        if chanceToLose ~= 0 and UKO.random(chanceToLose) then
            targetLevel = 0.0 + targetLevel - ((UKO.randomDecimal(maxToLose / 10)) / 10 * targetLevel)
            if targetLevel == oldTargetLevel and oldTargetLevel ~= 0 then --if we somehow rolled no change, not even a fractional loss, and we're not at zero..
                targetLevel = targetLevel - UKO.randomDecimal(1) --you're not getting away that easily..
            end
            print("UKO: Randomized loss has left the new target at "..targetLevel)
            if (oldTargetLevel - targetLevel > 0) then
                if affectedPerks ~= "" then
                    affectedPerks = affectedPerks..", "
                end
                affectedPerks = affectedPerks..perkName
            end
        end
    else
        print("UKO: Skipped unconfigured skill \""..perkName.."\".")
    end
   



https://redd.it/1dei5hy
@r_lua
Please help

Alright so, here's the code:

-- Patrol Points
local patrolPoints = {
{ x = 600, y = 400 }, -- Starting point or first patrol point
{ x = 700, y = 400 }, -- Second patrol point
{ x = 600, y = 500 }, -- Third patrol point, adjust as needed
}

local currentPatrolIndex = 1 -- Index to track current patrol point

-- Sight Range parameters
local sightDistance = 250 -- Distance in pixels
local sightAngle = math.rad(60) -- Sight angle in radians (60 degrees)

-- Player properties
player = {
x = 25,
y = 25,
speed = 125,
width = 25,
height = 25,
hasKey = false,
targetX = 50,
targetY = 50
}

-- Enemy properties
enemy = {
x = 600,
y = 400,
width = 25,
height = 25,
speed = 75,
shootCooldown = 1,
shootTimer = 1,
sightDistance = 250,
sightAngle = math.rad(60)
}

-- Key properties
key = {
x = 300,
y = 200,
width = 15,
height = 15,
collected = false
}

-- Door properties
door = {
x = 900,
y = 600,
width = 50,
height = 80
}

-- Game state
gameState = "playing"
flashTimer = 0
CongratsSound = love.audio.newSource("CONGRATS.mp3", "static")
KeyCollected = love.audio.newSource("KeyCollected.mp3", "static")
GameOver = love.audio.newSource("GameOver.mp3", "static")
soundPlayed = false -- Ensure sound plays once

-- Table to store click effects
clickEffects = {}

-- Key collected message properties
keyCollectedMessage = {
text = "Key collected!",
alpha = 0, -- Initial alpha value set to 0
fadeSpeed = 0.5 -- Speed of fading
}

-- Button properties
buttonWidth = 200
buttonHeight = 50

-- Position of buttons
exitButtonX = (love.graphics.getWidth() - buttonWidth) / 2
exitButtonY = love.graphics.getHeight() / 2 - 50
tryAgainButtonX = (love.graphics.getWidth() - buttonWidth) / 2
tryAgainButtonY = love.graphics.getHeight() / 2 + 50

-- Projectile definition
projectiles = {}

local function isPlayerInSightRange()
local dx = player.x - enemy.x
local dy = player.y - enemy.y
local distance = math.sqrt(dx dx + dy dy)

return distance <= enemy.sightDistance
end

-- Function to draw the sight range cone
local function drawSightRange()
if isPlayerInSightRange() then
-- Calculate the center of the cone around the enemy
local centerX = enemy.x + enemy.width / 2
local centerY = enemy.y + enemy.height / 2

-- Calculate the direction vector from enemy to player
local dx = player.x - enemy.x
local dy = player.y - enemy.y
local angleToPlayer = math.atan2(dy, dx)

-- Calculate the angles defining the cone
local startAngle = angleToPlayer - enemy.sightAngle / 2
local endAngle = angleToPlayer + enemy.sightAngle / 2

-- Calculate the forward vector based on enemy's direction
local forwardVectorX = math.cos(angleToPlayer)
local forwardVectorY = math.sin(angleToPlayer)

-- Calculate the position of the cone's apex (focus in front of the enemy)
local apexX = enemy.x + enemy.width / 2 + forwardVectorX enemy.sightDistance
local apexY = enemy.y + enemy.height / 2 + forwardVectorY
enemy.sightDistance

-- Set the color and opacity of the sight range cone
love.graphics.setColor(1, 0, 0, 0.3) -- Red color with 30% opacity

-- Draw the sight range cone centered around the enemy's apex
love.graphics.arc("fill", centerX, centerY, enemy.sightDistance, startAngle, endAngle)
end
end

function love.load()
love.window.setTitle("Puzzle Game")
love.graphics.setBackgroundColor(0.1, 0.1, 0.1)
love.window.setMode(1024, 768)
gameState = 'playing'
end

function love.update(dt)
if gameState == "playing" then
updatePlayerPosition(dt)
updateEnemyPosition(dt)
checkKeyCollision()
checkDoorCollision()
checkEnemyCollision()
updateClickEffects(dt)
updateKeyCollectedMessage(dt)
updateProjectiles(dt)
elseif gameState == "won" then
Please help

Alright so, here's the code:

-- Patrol Points
local patrolPoints = {
{ x = 600, y = 400 }, -- Starting point or first patrol point
{ x = 700, y = 400 }, -- Second patrol point
{ x = 600, y = 500 }, -- Third patrol point, adjust as needed
}

local currentPatrolIndex = 1 -- Index to track current patrol point

-- Sight Range parameters
local sightDistance = 250 -- Distance in pixels
local sightAngle = math.rad(60) -- Sight angle in radians (60 degrees)

-- Player properties
player = {
x = 25,
y = 25,
speed = 125,
width = 25,
height = 25,
hasKey = false,
targetX = 50,
targetY = 50
}

-- Enemy properties
enemy = {
x = 600,
y = 400,
width = 25,
height = 25,
speed = 75,
shootCooldown = 1,
shootTimer = 1,
sightDistance = 250,
sightAngle = math.rad(60)
}

-- Key properties
key = {
x = 300,
y = 200,
width = 15,
height = 15,
collected = false
}

-- Door properties
door = {
x = 900,
y = 600,
width = 50,
height = 80
}

-- Game state
gameState = "playing"
flashTimer = 0
CongratsSound = love.audio.newSource("CONGRATS.mp3", "static")
KeyCollected = love.audio.newSource("KeyCollected.mp3", "static")
GameOver = love.audio.newSource("GameOver.mp3", "static")
soundPlayed = false -- Ensure sound plays once

-- Table to store click effects
clickEffects = {}

-- Key collected message properties
keyCollectedMessage = {
text = "Key collected!",
alpha = 0, -- Initial alpha value set to 0
fadeSpeed = 0.5 -- Speed of fading
}

-- Button properties
buttonWidth = 200
buttonHeight = 50

-- Position of buttons
exitButtonX = (love.graphics.getWidth() - buttonWidth) / 2
exitButtonY = love.graphics.getHeight() / 2 - 50
tryAgainButtonX = (love.graphics.getWidth() - buttonWidth) / 2
tryAgainButtonY = love.graphics.getHeight() / 2 + 50

-- Projectile definition
projectiles = {}

local function isPlayerInSightRange()
local dx = player.x - enemy.x
local dy = player.y - enemy.y
local distance = math.sqrt(dx * dx + dy * dy)

return distance <= enemy.sightDistance
end

-- Function to draw the sight range cone
local function drawSightRange()
if isPlayerInSightRange() then
-- Calculate the center of the cone around the enemy
local centerX = enemy.x + enemy.width / 2
local centerY = enemy.y + enemy.height / 2

-- Calculate the direction vector from enemy to player
local dx = player.x - enemy.x
local dy = player.y - enemy.y
local angleToPlayer = math.atan2(dy, dx)

-- Calculate the angles defining the cone
local startAngle = angleToPlayer - enemy.sightAngle / 2
local endAngle = angleToPlayer + enemy.sightAngle / 2

-- Calculate the forward vector based on enemy's direction
local forwardVectorX = math.cos(angleToPlayer)
local forwardVectorY = math.sin(angleToPlayer)

-- Calculate the position of the cone's apex (focus in front of the enemy)
local apexX = enemy.x + enemy.width / 2 + forwardVectorX * enemy.sightDistance
local apexY = enemy.y + enemy.height / 2 + forwardVectorY * enemy.sightDistance

-- Set the color and opacity of the sight range cone
love.graphics.setColor(1, 0, 0, 0.3) -- Red color with 30% opacity

-- Draw the sight range cone centered around the enemy's apex
love.graphics.arc("fill", centerX, centerY, enemy.sightDistance, startAngle, endAngle)
end
end

function love.load()
love.window.setTitle("Puzzle Game")
love.graphics.setBackgroundColor(0.1, 0.1, 0.1)
love.window.setMode(1024, 768)
gameState = 'playing'
end

function love.update(dt)
if gameState == "playing" then
updatePlayerPosition(dt)
updateEnemyPosition(dt)
checkKeyCollision()
checkDoorCollision()
checkEnemyCollision()
updateClickEffects(dt)
updateKeyCollectedMessage(dt)
updateProjectiles(dt)
elseif gameState == "won" then
flashTimer = flashTimer + dt
-- Clear clickEffects when game is won
clickEffects = {}
elseif gameState == "lost" then
-- Handle game over state
end
end

function love.draw()
if gameState == "playing" then
drawGame()
drawSightRange() -- Draw the sight range cone
-- Debug information
love.graphics.setColor(1, 1, 1) -- Set color to white
love.graphics.setFont(love.graphics.newFont(18)) -- Set font size to 18
love.graphics.print("Your position: (" .. player.x .. ", " .. player.y .. ")", 10, 30)

elseif gameState == "won" then
drawWinningScreen()
if not soundPlayed then
love.audio.play(CongratsSound)
soundPlayed = true
end
elseif gameState == "lost" then
drawGameOverScreen()
if not soundPlayed then
love.audio.play(GameOver)
soundPlayed = true
end
end

-- Draw click effects list
love.graphics.setColor(1, 1, 1) -- Set color to white
love.graphics.setFont(love.graphics.newFont(18)) -- Set font size to 18
local xOffset = love.graphics.getWidth() - 400 -- Offset from the right side of the window
local yOffset = 50 -- Initial offset for y-coordinate (lowered slightly)
local lineHeight = 20 -- Height of each line in the list
for i, effect in ipairs(clickEffects) do
-- Calculate the alpha value for the text based on the alpha value of the ripple
local alpha = 1 - (effect.radius / 500)
if alpha > 0 then
local text = string.format("Effect %d: x = %d, y = %d, radius = %d", i, effect.x, effect.y, effect.radius)
love.graphics.setColor(1, 1, 1, alpha) -- Set color to white with fading alpha
love.graphics.print(text, xOffset, yOffset) -- Draw text
yOffset = yOffset + lineHeight -- Increase yOffset for next text
else
-- Remove the corresponding text entry when alpha drops below 0
table.remove(clickEffects, i)
end
end

-- Draw click effects
for _, effect in ipairs(clickEffects) do
local alpha = 1 - (effect.radius / 500) -- Calculate alpha based on radius
love.graphics.setColor(0.2, 0.6, 1, alpha) -- Set color to blue with fading alpha
love.graphics.circle("line", effect.x, effect.y, effect.radius)
end

-- Draw projectiles
drawProjectiles()
end

function love.touchpressed(id, x, y, dx, dy, pressure)
if gameState == "lost" then
-- Check if the touch is within the exit button bounds
if x >= exitButtonX and x <= exitButtonX + buttonWidth and y >= exitButtonY and y <= exitButtonY + buttonHeight then
love.event.quit()
end

-- Check if the touch is within the try again button bounds
if x >= tryAgainButtonX and x <= tryAgainButtonX + buttonWidth and y >= tryAgainButtonY and y <= tryAgainButtonY + buttonHeight then
resetGame()
end
else
-- Handle touch for other game states
player.targetX = x - player.width / 2
player.targetY = y - player.height / 2

-- Create a new click effect
local newEffect = {
x = x,
y = y,
radius = 0
}
table.insert(clickEffects, newEffect)
end
end

function checkCollision(a, b)
return a.x < b.x + b.width and
b.x < a.x + a.width and
a.y < b.y + b.height and
b.y < a.y + a.height
end

function updatePlayerPosition(dt)
local dx = player.targetX - player.x
local dy = player.targetY - player.y
local distance = math.sqrt(dx * dx + dy * dy)

if distance > player.speed * dt then
player.x = player.x + (dx / distance) * player.speed * dt
player.y = player.y + (dy / distance) * player.speed * dt
else
player.x = player.targetX
player.y = player.targetY
end
end

-- Modify updateEnemyPosition function
function updateEnemyPosition(dt)
if gameState ~= "playing" then
return -- Do not update enemy
position if game state is not playing
end

if isPlayerInSightRange() then
-- Player is in sight range, engage behavior (e.g., attack or pursue)
enemy.shootTimer = enemy.shootTimer + dt

if enemy.shootTimer >= enemy.shootCooldown then
-- Shoot or perform action towards the player
local dx = player.x - enemy.x
local dy = player.y - enemy.y
local distance = math.sqrt(dx * dx + dy * dy)

if distance > 0 then
local directionX = dx / distance
local directionY = dy / distance

spawnProjectile(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2, {x = directionX, y = directionY})
end

enemy.shootTimer = 0
end

-- Move towards player
local dx = player.x - enemy.x
local dy = player.y - enemy.y
local distance = math.sqrt(dx * dx + dy * dy)

if distance > 0 then
local directionX = dx / distance
local directionY = dy / distance

enemy.x = enemy.x + directionX * enemy.speed * dt
enemy.y = enemy.y + directionY * enemy.speed * dt
end

-- Update enemy's sight cone direction based on movement
enemy.directionX = dx
enemy.directionY = dy

else
-- Player is not in sight range, idle behavior (patrol)
local targetPoint = patrolPoints[currentPatrolIndex]
local dx = targetPoint.x - enemy.x
local dy = targetPoint.y - enemy.y
local distance = math.sqrt(dx * dx + dy * dy)

if distance > 0 then
local directionX = dx / distance
local directionY = dy / distance

enemy.x = enemy.x + directionX * enemy.speed * dt
enemy.y = enemy.y + directionY * enemy.speed * dt
end

-- Update enemy's sight cone direction based on movement
enemy.directionX = dx
enemy.directionY = dy

-- Check if reached the current patrol point
if distance < 5 then
currentPatrolIndex = currentPatrolIndex % #patrolPoints + 1
end
end
end

function checkKeyCollision()
if not key.collected and checkCollision(player, key) then
key.collected = true
player.hasKey = true
love.audio.play(KeyCollected)
keyCollectedMessage.alpha = 1 -- Reset alpha to 1 when key is collected
end
end

function checkDoorCollision()
if player.hasKey and checkCollision(player, door) then
gameState = "won"
end
end

function checkEnemyCollision()
if checkCollision(player, enemy) then
gameState = "lost"
end
end

function updateClickEffects(dt)
-- Update click effects
for i = #clickEffects, 1, -1 do
clickEffects[i].radius = clickEffects[i].radius + 600 * dt -- Adjust the speed of expansion
local maxRadius = 500 -- Adjust the maximum radius as needed
if clickEffects[i].radius > maxRadius then
clickEffects[i].radius = maxRadius -- Cap the radius to the maximum value
end
end
end

function updateKeyCollectedMessage(dt)
-- Update key collected message alpha
if key.collected then
keyCollectedMessage.alpha = keyCollectedMessage.alpha - dt * keyCollectedMessage.fadeSpeed
if keyCollectedMessage.alpha <= 0 then
keyCollectedMessage.alpha = 0
end
else
keyCollectedMessage.alpha = 0 -- Set alpha to 0 if the key is not collected
end
end

function drawGame()
-- Draw player
love.graphics.setColor(0.2, 0.6, 1)
love.graphics.rectangle("fill", player.x, player.y, player.width, player.height)

-- Draw enemy
love.graphics.setColor(1, 0, 0)
love.graphics.rectangle("fill", enemy.x, enemy.y, enemy.width, enemy.height)

-- Draw key if not collected
if not key.collected then
love.graphics.setColor(1, 1, 0)
love.graphics.rectangle("fill", key.x, key.y, key.width, key.height)
end

-- Draw door
love.graphics.setColor(0.6, 0.3, 0.1)
love.graphics.rectangle("fill", door.x, door.y, door.width, door.height)

-- Draw key collected indicator
if key.collected then
love.graphics.setColor(1, 1, 0, keyCollectedMessage.alpha)
love.graphics.setFont(love.graphics.newFont(24)) -- Set font size to 24
love.graphics.printf(keyCollectedMessage.text, 0, love.graphics.getHeight() - 50, love.graphics.getWidth(), "center")
end
end

function drawWinningScreen()
local r = math.abs(math.sin(flashTimer * 2))
local g = math.abs(math.sin(flashTimer * 2 + math.pi / 2))
local b = math.abs(math.sin(flashTimer * 2 + math.pi))
love.graphics.setColor(r, g, b)
love.graphics.setFont(love.graphics.newFont(72))
love.graphics.printf("Congratulations!", 0, love.graphics.getHeight() / 2 - 36, love.graphics.getWidth(), "center")
projectiles = {}
clickEffects = {}
end

function drawGameOverScreen()
-- Draw game over text
love.graphics.setColor(1, 0, 0)
love.graphics.setFont(love.graphics.newFont(36))
love.graphics.printf("Game Over", 0, love.graphics.getHeight() / 3, love.graphics.getWidth(), "center")

-- Clear Effects
clickEffects = {}

-- Projectiles
projectiles = {}

-- Draw buttons
love.graphics.setColor(0.5, 0.5, 0.5)
love.graphics.rectangle("fill", exitButtonX, exitButtonY, buttonWidth, buttonHeight)
love.graphics.rectangle("fill", tryAgainButtonX, tryAgainButtonY, buttonWidth, buttonHeight)

-- Draw button text
love.graphics.setColor(1, 1, 1)
love.graphics.setFont(love.graphics.newFont(24))
love.graphics.printf("Exit", exitButtonX, exitButtonY + (buttonHeight - love.graphics.getFont():getHeight()) / 2, buttonWidth, "center")
love.graphics.printf("Try Again", tryAgainButtonX, tryAgainButtonY + (buttonHeight - love.graphics.getFont():getHeight()) / 2, buttonWidth, "center")
end

function resetGame()
-- Reset player position
player.x = 50
player.y = 50
player.hasKey = false
player.targetX = 50
player.targetY = 50

-- Reset key position and state
key.x = 300
key.y = 200
key.collected = false

-- Reset enemy position
enemy.x = 600
enemy.y = 400

-- Reset game state
gameState = "playing"

-- Reset sound played flag
soundPlayed = false
end

-- Projectile functions
function spawnProjectile(x, y, direction)
local newProjectile = {
x = x,
y = y,
direction = direction,
speed = 350, -- Adjust as needed
size = 5,
active = true
}
table.insert(projectiles, newProjectile)
end

function updateProjectiles(dt)
for i = #projectiles, 1, -1 do
local proj = projectiles[i]
proj.x = proj.x + proj.direction.x * proj.speed * dt
proj.y = proj.y + proj.direction.y * proj.speed * dt

-- Check collision with player
if checkCollision(player, proj) then
gameState = "lost"
table.remove(projectiles, i) -- Remove projectile upon collision
end

-- Check if projectile is out of bounds
if proj.x < 0 or proj.x > love.graphics.getWidth() or
proj.y < 0 or proj.y > love.graphics.getHeight() then
table.remove(projectiles, i) -- Remove projectile if out of bounds
end
end
end

function drawProjectiles()
love.graphics.setColor(1, 0, 0) -- Red ;)
for _, proj in ipairs(projectiles) do
love.graphics.circle("fill", proj.x, proj.y, proj.size)
end
end

function checkCollision(a, b)
if b.size then
-- If b is a projectile
return a.x < b.x + b.size and
b.x < a.x + a.width and
a.y < b.y + b.size and
b.y < a.y + a.height
else
-- For other objects like player, enemy, key, door
return a.x < b.x + b.width and
b.x < a.x + a.width and
a.y < b.y + b.height and
b.y < a.y + a.height
end
end

It's so hard to implement a shooting feature for the player, probably 'cause it will share a touch with the movement and move and shoot where you clicked. It's really hard to explain LOL, but I just want to be able to implement a move and shoot independently feature. Any suggestions? Thanks in advance.

Edit: I just realised how butchered the code looks on reddit, I don't know how to properly write code snippets though :(

https://redd.it/1delu9f
@r_lua