Developer Bewerbung โ€“ StoryV

Felix Fรถrster
aka motofo20

Web-Entwickler mit Schwerpunkt auf Full-Stack Applikationen, Server-Infrastruktur und RP-Tools โ€” und dem Willen, auch im FiveM Scripting zu wachsen.

Demo Script ansehen Kontakt aufnehmen
// รผber mich

Wer bin ich?

Ich bin Felix Fรถrster aus Kรถln und entwickle seit mehreren Jahren Software. Mein Hauptfokus liegt auf Web-Entwicklung โ€” von kompletten Next.js Applikationen รผber REST APIs bis hin zur Server-Infrastruktur.

Im FiveM Bereich bringe ich bereits Erfahrung mit: Ich habe auf mehreren Servern als Supporter gearbeitet und kenne daher die Ablรคufe, Strukturen und hรคufigen Probleme aus der Praxis โ€” nicht nur aus Spielersicht.

Technisch habe ich eigenstรคndig ein Tablet/Phone UI Script in Lua entwickelt und mit dem Transporter-Script auf dieser Seite bewiesen, dass ich auch komplexere Systeme umsetzen kann.

Meine Kernstรคrke liegt in Web-Systemen die nahtlos mit FiveM zusammenarbeiten โ€” Panels, Dashboards, APIs. Genau das ist oft der fehlende Baustein bei RP-Servern.

๐Ÿ“ฆ
5+
Verรถffentlichte Projekte
โšก
3+
Jahre Entwicklungserfahrung
๐ŸŽง
mehrere
FiveM Server als Supporter
๐Ÿ–ฅ๏ธ
7
Laufende Services auf eigenem VPS
// technische skills

Was ich kann

Vom FiveM Lua Script รผber Next.js Webapps bis zur Server-Konfiguration โ€“ ich bin Full-Stack aufgestellt.

๐ŸŽฎ
Lua / FiveM
ESX ยท QBCore ยท ox_lib ยท NUI
๐ŸŒ
JavaScript / TypeScript
Next.js ยท React ยท Node.js
โ˜•
Java / Kotlin
Minecraft Fabric Modding
๐Ÿ
Python
Automatisierung ยท APIs ยท Bots
๐Ÿ—„๏ธ
Datenbanken
MySQL ยท SQLite ยท Prisma ORM
๐Ÿ–ฅ๏ธ
Server / DevOps
Linux ยท Caddy ยท NSSM ยท Windows Server
๐ŸŽจ
Frontend / NUI
HTML ยท CSS ยท In-Game UI
๐Ÿ”Œ
REST APIs
Design ยท Integration ยท Auth
// projekte

Was ich gebaut habe

Eine Auswahl meiner Projekte โ€“ alle live auf meinem eigenen VPS gehostet.

โš–๏ธ
DOJ Portal
Vollstรคndiges Justiz-Portal fรผr GTA RP: Fallverwaltung, Strafrechner, Rechnungen, Protokoll und Benutzerverwaltung mit Rollenystem.
Next.js 15 TypeScript Prisma SQLite
๐Ÿ›๏ธ
Anwaltskanzlei Portal
Mandanten-Management System fรผr eine RP-Kanzlei. Aktenfรผhrung, DOJ-Integration, externe Mandantenansicht und Dokumentenmanagement.
Next.js 15 TypeScript REST API
โ˜ ๏ธ
Don't Panic Mod
Minecraft Fabric Mod mit erweitertem Hardcore-System, Wiederbelebungsmechanik, Skills und eigener Purgatory-Dimension. Auf CurseForge verรถffentlicht.
Kotlin Fabric API MC 1.21.11
๐Ÿ›ก๏ธ
Vibration Armor Mod
Minecraft Mod mit einzigartigem Kampfsystem: Warden-inspirierte Rรผstung die Schaden absorbiert und als Schockwellen zurรผckgibt.
Kotlin Fabric API Custom Events
๐Ÿ–ฅ๏ธ
Server Control Panel
Eigenes Web-Panel zur Verwaltung aller Server-Dienste. Service-Restart, Live CPU/RAM-Monitoring, Log-Viewer und Authentifizierung.
Node.js Express PowerShell WMI
๐Ÿ•น๏ธ
Arcade Games Site
Browser-Arcade mit klassischen Spielen, DOOM via js-dos Emulation und einem eigenen Astronauten-Runner auf der 404-Fehlerseite.
Vanilla JS Canvas API js-dos
// demo script

Gepanzerter Transporter

Ein vollstรคndiges FiveM Ressource-Script โ€“ einsatzbereit fรผr ESX und QBCore.

๐Ÿš› motofo20-transporter
Dynamisches Heist-System mit mehrstufigem Gameplay
Lua ESX QBCore ox_lib
Script herunterladen (.zip)
Zufรคllige Spawn-Routen
NPC Fahrer mit KI-Route
ox_lib Progress Bars
Polizei Dispatch Alert
Dynamischer Karten-Blip
Loot mit Prop-Spawning
45-min Cooldown System
MySQL Logging (oxmysql)
ESX & QBCore Bridge
Min. Polizisten Check
Config = {}

-- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
--  Allgemeine Einstellungen
-- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

Config.Cooldown        = 45      -- Minuten bis der nรคchste Transporter spawnt
Config.MinPoliceOnline = 2       -- Mindestanzahl Polizisten die online sein mรผssen
Config.DrillTime       = 90      -- Sekunden zum Aufbohren des Tresors
Config.LootTime        = 8       -- Sekunden um einen Gegenstand einzusammeln
Config.StoppedSpeed    = 5.0     -- km/h โ€“ gilt als "gestoppt"
Config.StopDuration    = 4       -- Sekunden die der Truck still stehen muss
Config.MaxLootBags     = 4       -- Maximale Anzahl an Loot-Taschen im Truck

-- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
--  Spawn-Routen
-- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

Config.Routes = {
    {
        start  = vector4( 1035.3, -651.6,  58.2, 270.0),
        target = vector3(  240.7,  214.0, 105.9),
        label  = "Downtown Los Santos"
    },
    {
        start  = vector4( -120.5, 6411.4,  31.5, 180.0),
        target = vector3( -247.0, 6329.4,  32.4),
        label  = "Paleto Bay"
    },
    {
        start  = vector4( 1685.4, 4920.3,  42.1,  90.0),
        target = vector3( 1854.0, 3687.0,  34.2),
        label  = "Sandy Shores"
    },
}

-- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
--  Beute
-- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

Config.Loot = {
    { item = "money_bag", label = "Geldtasche", min = 5000,  max = 15000, chance = 100 },
    { item = "gold_bar",  label = "Goldbarren", min = 1,     max = 3,     chance = 35  },
    { item = "diamond",   label = "Diamant",    min = 1,     max = 2,     chance = 15  },
}

Config.PoliceJobs = { "police", "sheriff", "bcso" }
-- Client โ€“ Gepanzerter Transporter
-- motofo20

local truck, truckBlip, truckDriver = nil, nil, nil
local isStopped, isDrilling = false, false
local vaultBags, bagProps = {}, {}
local stopTimer = 0

-- โ”€โ”€ Spawn โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

RegisterNetEvent('transporter:client:startRun', function(route)
    spawnTruck(route)
end)

function spawnTruck(route)
    local model = `stockade`
    RequestModel(model)
    while not HasModelLoaded(model) do Wait(100) end

    truck = CreateVehicle(model,
        route.start.x, route.start.y, route.start.z, route.start.w,
        true, false)

    SetVehicleNumberPlateText(truck, "SICHER")
    SetVehicleEngineOn(truck, true, true, false)
    SetVehicleDoorsLocked(truck, 2)

    -- NPC Fahrer
    local driverModel = `s_m_y_armoured_01`
    RequestModel(driverModel)
    while not HasModelLoaded(driverModel) do Wait(100) end

    truckDriver = CreatePedInsideVehicle(truck, 26, driverModel, -1, true, false)
    SetBlockingOfNonTemporaryEvents(truckDriver, true)
    SetPedFleeAttributes(truckDriver, 0, false)

    TaskVehicleDriveToCoordLongrange(
        truckDriver, truck,
        route.target.x, route.target.y, route.target.z,
        45.0, 786603, 5.0)

    -- Karten-Blip
    truckBlip = AddBlipForEntity(truck)
    SetBlipSprite(truckBlip, Config.Blip.sprite)
    SetBlipColour(truckBlip, Config.Blip.color)
    SetBlipFlashes(truckBlip, true)

    CreateThread(monitorTruck)
end

-- โ”€โ”€ Monitor โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

function monitorTruck()
    while truck and DoesEntityExist(truck) do
        Wait(500)

        local speed    = GetEntitySpeed(truck) * 3.6
        local engineHP = GetVehicleEngineHealth(truck)

        if not isStopped and speed < Config.StoppedSpeed and engineHP < 100 then
            stopTimer = stopTimer + 0.5
            if stopTimer >= Config.StopDuration then
                isStopped = true
                TriggerServerEvent('transporter:server:truckStopped')
                showDrillPrompt()
            end
        elseif speed >= Config.StoppedSpeed then
            stopTimer = 0
        end

        -- Ziel erreicht oder Truck zerstรถrt
        local task = GetScriptTaskStatus(truckDriver, 0x6F0783C3)
        if task == 7 or GetEntityHealth(truck) <= 0 then
            TriggerServerEvent('transporter:server:endRun')
            break
        end
    end
end

-- โ”€โ”€ Drill โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

function showDrillPrompt()
    CreateThread(function()
        while truck and DoesEntityExist(truck) and isStopped and not isDrilling do
            Wait(0)
            local dist = #(GetEntityCoords(PlayerPedId()) - GetEntityCoords(truck))

            if dist < 4.0 then
                lib.showTextUI('[E] Tresor aufbohren', {
                    position = 'left-center', icon = 'screwdriver-wrench'
                })
                if IsControlJustReleased(0, 38) then
                    lib.hideTextUI()
                    beginDrill()
                end
            else
                lib.hideTextUI()
            end
        end
        lib.hideTextUI()
    end)
end

function beginDrill()
    if isDrilling then return end
    isDrilling = true

    local success = lib.progressBar({
        duration     = Config.DrillTime * 1000,
        label        = 'Tresor wird aufgebohrt โ€ฆ',
        useWhileDead = false,
        canCancel    = true,
        disable      = { car = true },
        anim         = { dict = 'anim@heists@fleeca_bank@drilling', clip = 'drill_idle' },
    })

    isDrilling = false

    if success then
        TriggerServerEvent('transporter:server:drillComplete')
        for door = 0, 5 do SetVehicleDoorOpen(truck, door, false, false) end
        lib.notify({ title = 'โœ… Tresor geรถffnet!', type = 'success', duration = 6000 })
    end
end

-- โ”€โ”€ Loot โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

RegisterNetEvent('transporter:client:openVault', function(bags)
    vaultBags = bags

    for i, bag in ipairs(bags) do
        local offset = GetOffsetFromEntityInWorldCoords(truck, (i-1)*0.6-0.9, -3.0, 0.0)
        local prop   = CreateObject(`prop_cash_bag_01`, offset.x, offset.y, offset.z, true, true, false)
        PlaceObjectOnGroundProperly(prop)
        bagProps[i]  = prop
    end

    CreateThread(function()
        while #bagProps > 0 do
            Wait(0)
            local playerCoords = GetEntityCoords(PlayerPedId())
            local nearestDist, nearestIdx = 99.0, nil

            for i, prop in ipairs(bagProps) do
                if prop and DoesEntityExist(prop) then
                    local d = #(playerCoords - GetEntityCoords(prop))
                    if d < nearestDist then nearestDist = d; nearestIdx = i end
                end
            end

            if nearestIdx and nearestDist < 2.5 then
                lib.showTextUI(('[E] %s einsammeln'):format(vaultBags[nearestIdx].label))
                if IsControlJustReleased(0, 38) then
                    lib.hideTextUI()
                    collectBag(nearestIdx, bagProps[nearestIdx])
                end
            else lib.hideTextUI() end
        end
    end)
end)

function collectBag(idx, prop)
    local success = lib.progressBar({
        duration  = Config.LootTime * 1000,
        label     = ('Sammle %s ein โ€ฆ'):format(vaultBags[idx].label),
        canCancel = false,
        disable   = { move = true },
        anim      = { dict = 'anim@heists@box_carry@', clip = 'idle' },
    })
    if success then
        TriggerServerEvent('transporter:server:collectBag', idx, vaultBags)
        DeleteObject(prop)
        bagProps[idx] = nil; vaultBags[idx] = nil
    end
end
-- Server โ€“ Gepanzerter Transporter
-- motofo20

local Core      = nil
local lastSpawn = 0
local activeRun = false

CreateThread(function()
    Wait(500)
    if Framework == 'ESX' then
        Core = exports['es_extended']:getSharedObject()
    elseif Framework == 'QBCore' then
        Core = exports['qb-core']:GetCoreObject()
    end
end)

-- Polizei-Anzahl ermitteln
local function getPoliceCount()
    local count = 0
    for _, player in ipairs(GetPlayers()) do
        local src = tonumber(player)
        if Framework == 'ESX' then
            local xPlayer = Core.GetPlayerFromId(src)
            if xPlayer then
                local job = xPlayer.getJob()
                for _, j in ipairs(Config.PoliceJobs) do
                    if job.name == j then count = count + 1; break end
                end
            end
        elseif Framework == 'QBCore' then
            local Player = Core.Functions.GetPlayer(src)
            if Player then
                for _, j in ipairs(Config.PoliceJobs) do
                    if Player.PlayerData.job.name == j then count = count + 1; break end
                end
            end
        end
    end
    return count
end

-- Loot auswรผrfeln
local function rollLoot()
    for _, loot in ipairs(Config.Loot) do
        if math.random(1, 100) <= loot.chance then
            return { item = loot.item, label = loot.label,
                     amount = math.random(loot.min, loot.max) }
        end
    end
    return { item = "money_bag", label = "Geldtasche", amount = math.random(2000, 8000) }
end

-- Item geben (ESX & QBCore)
local function giveItem(src, item, amount)
    if Framework == 'ESX' then
        local xPlayer = Core.GetPlayerFromId(src)
        if xPlayer then xPlayer.addInventoryItem(item, amount) end
    elseif Framework == 'QBCore' then
        local Player = Core.Functions.GetPlayer(src)
        if Player then Player.Functions.AddItem(item, amount) end
    end
end

-- โ”€โ”€ Spawn-Loop โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

CreateThread(function()
    Wait(10000) -- Server warm-up
    while true do
        local elapsed = (os.time() - lastSpawn) / 60
        if not activeRun
           and elapsed >= Config.Cooldown
           and #GetPlayers() >= 1
           and getPoliceCount() >= Config.MinPoliceOnline
        then
            local route = Config.Routes[math.random(1, #Config.Routes)]
            lastSpawn   = os.time()
            activeRun   = true

            TriggerClientEvent('transporter:client:startRun', -1, route)
            TriggerClientEvent('transporter:client:policeDispatch', -1, route.label, route.start)
        end
        Wait(60000)
    end
end)

-- โ”€โ”€ Events โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

RegisterNetEvent('transporter:server:truckStopped', function()
    TriggerClientEvent('transporter:client:truckStoppedAlert', -1)
end)

RegisterNetEvent('transporter:server:drillComplete', function()
    local src  = source
    local bags = {}
    for i = 1, Config.MaxLootBags do bags[i] = rollLoot() end
    TriggerClientEvent('transporter:client:openVault', src, bags)
end)

RegisterNetEvent('transporter:server:collectBag', function(bagIndex, bags)
    local src = source
    local bag = bags[bagIndex]
    if not bag then return end
    giveItem(src, bag.item, bag.amount)
    MySQL.insert(
        'INSERT INTO transporter_logs (player_id, item, amount, timestamp) VALUES (?, ?, ?, NOW())',
        { src, bag.item, bag.amount }
    )
end)

RegisterNetEvent('transporter:server:endRun', function()
    activeRun = false
    TriggerClientEvent('transporter:client:cleanup', -1)
end)
-- Framework Detection โ€“ ESX & QBCore Bridge
-- Wird auf Client und Server gleichermaรŸen geladen

Framework = nil

if GetResourceState('es_extended') == 'started' then
    Framework = 'ESX'
elseif GetResourceState('qb-core') == 'started' then
    Framework = 'QBCore'
else
    print('[motofo20-transporter] ^1FEHLER: Kein unterstรผtztes Framework (ESX/QBCore)^0')
end
-- motofo20-transporter โ€“ Datenbank-Schema
-- Importieren mit: mysql -u root -p dein_db < transporter.sql

CREATE TABLE IF NOT EXISTS `transporter_logs` (
    `id`         INT(11)      NOT NULL AUTO_INCREMENT,
    `player_id`  INT(11)      NOT NULL COMMENT 'Server-ID des Spielers beim Einsammeln',
    `item`       VARCHAR(64)  NOT NULL COMMENT 'Item-Name aus der Config',
    `amount`     INT(11)      NOT NULL COMMENT 'Menge des erbeuteten Items',
    `timestamp`  DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_timestamp` (`timestamp`),
    INDEX `idx_player`    (`player_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  COMMENT='Loggt alle Beute-Ereignisse des Transporter-Scripts';

-- Optionaler View: Beute-Statistik pro Spieler
CREATE OR REPLACE VIEW `transporter_stats` AS
    SELECT
        player_id,
        item,
        COUNT(*)    AS anzahl_erbeutungen,
        SUM(amount) AS gesamtmenge,
        MAX(timestamp) AS zuletzt_aktiv
    FROM `transporter_logs`
    GROUP BY player_id, item
    ORDER BY gesamtmenge DESC;
fx_version 'cerulean'
game 'gta5'

author      'motofo20'
description 'Gepanzerter Transporter รœberfall โ€“ ESX & QBCore kompatibel'
version     '1.0.0'

shared_scripts {
    '@ox_lib/init.lua',
    'shared/framework.lua',
    'config.lua'
}

client_scripts {
    'client/main.lua'
}

server_scripts {
    '@oxmysql/lib/MySQL.lua',
    'server/main.lua'
}

dependencies {
    'ox_lib',
    'oxmysql'
}
// warum ich?

Was ich mitbringe

๐ŸŒ
Web ist meine Stรคrke
Ich spezialisiere mich auf Web-Entwicklung: Next.js Portale, REST APIs, Dashboards. Systeme wie ein DOJ-Portal oder ein Admin-Panel sind genau mein Bereich.
๐Ÿ“ฑ
Erste FiveM Erfahrung
Ich habe bereits ein eigenes Tablet/Phone UI Script in Lua geschrieben. FiveM ist kein komplettes Neuland โ€” ich will dieses Fundament weiter ausbauen.
๐Ÿ–ฅ๏ธ
Eigene Infrastruktur
Ich betreibe meinen eigenen VPS mit 7 laufenden Services, Reverse Proxy, Monitoring und automatischen Backups. Produktionsumgebungen kenne ich aus der Praxis.
๐ŸŽฎ
Supporter-Erfahrung
Ich habe auf mehreren FiveM Servern als Supporter gearbeitet. Ich kenne Ticket-Systeme, Regeln, hรคufige Bugs und wie ein Server von innen funktioniert โ€” das hilft beim Entwickeln praxisnaher Tools.

Lass uns reden ๐Ÿ‘‹

Am schnellsten erreichst du mich รผber Discord. Ich freue mich รผber jede Rรผckmeldung zur Bewerbung.

motofo20