adding new Weigh Station pack

This commit is contained in:
KingMcDonalds
2025-12-25 13:17:59 -08:00
parent cc160c11f8
commit acedfe152c
209 changed files with 3048 additions and 2 deletions
@@ -0,0 +1,7 @@
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
author 'John Chin'
description 'Weigh Station Shared Props'
version '1.0.0'
@@ -0,0 +1,285 @@
local redLightModel = `jd_weighstation_lightred`
local greenLightModel = `jd_weighstation_lightgreen`
-- Active lights per lane (Now local entities)
local activeLights = {
LaneA = {}
}
-- Lane states: true = green, false = red, nil = off
local laneStates = {
LaneA = nil
}
local isInRange = false
local isNearControl = false
local playerAllowed = false
-- AutoMode tracking (single lane)
local autoTrack = {
LaneA = {
detected = false,
active = false,
detectionThread = nil,
lastVeh = nil,
lastTrigger = 0
}
}
-- ============================================================
-- THE FIX: GHOST OBJECT CLEANUP
-- ============================================================
local function ForceCleanUp()
local models = { redLightModel, greenLightModel }
for _, model in ipairs(models) do
local handle, entity = FindFirstObject()
local success
repeat
if GetEntityModel(entity) == model then
SetEntityAsMissionEntity(entity, true, true)
DeleteEntity(entity)
end
success, entity = FindNextObject(handle)
until not success
EndFindObject(handle)
end
end
-- ============================================================
-- PERMISSION SYSTEM
-- ============================================================
CreateThread(function()
ForceCleanUp() -- Run cleanup once when script starts
if Config.ACL.UseACL then
TriggerServerEvent("weigh_lights:checkPermissionMain")
else
playerAllowed = true
end
end)
RegisterNetEvent("weigh_lights:permissionResultMain", function(allowed)
playerAllowed = allowed
end)
-- ============================================================
-- MODEL LOADING & SPAWNING
-- ============================================================
local function loadModel(model)
if not HasModelLoaded(model) then
RequestModel(model)
local t0 = GetGameTimer()
while not HasModelLoaded(model) do
Wait(10)
if GetGameTimer() - t0 > 5000 then RequestModel(model) end
end
end
end
local function clearLaneObjects(lane)
if activeLights[lane] then
for _, obj in ipairs(activeLights[lane]) do
if DoesEntityExist(obj) then
SetEntityAsMissionEntity(obj, true, true)
DeleteEntity(obj)
end
end
end
activeLights[lane] = {}
end
local function spawnLaneLight(lane, model)
loadModel(model)
if not Config.MainLights.Lanes[lane] then return end
clearLaneObjects(lane) -- Ensure clean slate before spawning
for _, light in ipairs(Config.MainLights.Lanes[lane].Lights) do
local pos = light.coords
-- CRITICAL: 5th param is FALSE (Not networked)
local obj = CreateObjectNoOffset(model, pos.x, pos.y, pos.z, false, false, false)
SetEntityCoordsNoOffset(obj, pos.x, pos.y, pos.z)
if light.rotation then
SetEntityRotation(obj, light.rotation.x, light.rotation.y, light.rotation.z, 2, true)
end
FreezeEntityPosition(obj, true)
SetEntityInvincible(obj, true)
table.insert(activeLights[lane], obj)
end
end
-- ============================================================
-- REFRESH LANE
-- ============================================================
local function refreshLane(lane)
local state = laneStates[lane]
if state == nil then
clearLaneObjects(lane)
return
end
local model = state and greenLightModel or redLightModel
spawnLaneLight(lane, model)
end
local function setLaneStateLocal(lane, state, notify)
laneStates[lane] = state
if isInRange then
refreshLane(lane)
end
if notify then
local desc = state == nil and "OFF" or (state and "GREEN" or "RED")
lib.notify({
title = 'Weigh Station',
description = ("Lane A set to %s"):format(desc),
type = state == nil and 'inform' or (state and 'success' or 'error')
})
end
end
-- ============================================================
-- NETWORK SYNC
-- ============================================================
RegisterNetEvent("jd_weighlight:setLaneState", function(lane, state)
if lane ~= "LaneA" then return end
setLaneStateLocal(lane, state, false)
end)
CreateThread(function()
Wait(3000)
TriggerServerEvent("jd_weighlight:requestState")
end)
-- ============================================================
-- RANGE CHECK
-- ============================================================
CreateThread(function()
while true do
Wait(1000)
local pCoords = GetEntityCoords(PlayerPedId())
local nearAny = false
if Config.MainLights.Lanes.LaneA then
for _, light in ipairs(Config.MainLights.Lanes.LaneA.Lights) do
if #(pCoords - light.coords) < (Config.MainLights.LightSpawnRange or 200.0) then
nearAny = true
break
end
end
end
if nearAny and not isInRange then
isInRange = true
TriggerServerEvent("jd_weighlight:requestState")
elseif not nearAny and isInRange then
isInRange = false
clearLaneObjects("LaneA")
autoTrack.LaneA.detected = false
autoTrack.LaneA.active = false
autoTrack.LaneA.lastVeh = nil
end
end
end)
-- ============================================================
-- CONTROL MENU
-- ============================================================
local function openControlMenu()
local menu = {
id = 'jd_weighlight_menu_single',
title = 'Weigh Station Light Control',
options = {
{ title = 'Lane A → GREEN', icon = 'check-circle', onSelect = function()
TriggerServerEvent("jd_weighlight:setLaneState", "LaneA", true)
end },
{ title = 'Lane A → RED', icon = 'ban', onSelect = function()
TriggerServerEvent("jd_weighlight:setLaneState", "LaneA", false)
end },
{ title = 'Lane A → OFF', icon = 'power-off', onSelect = function()
TriggerServerEvent("jd_weighlight:setLaneState", "LaneA", nil)
end },
}
}
lib.registerContext(menu)
lib.showContext('jd_weighlight_menu_single')
end
CreateThread(function()
while true do
Wait(300)
local dist = #(GetEntityCoords(PlayerPedId()) - Config.MainLights.ControlCoords)
if not Config.MainLights.AutoMode then
if dist < (Config.MainLights.ControlDistance or 1.5) and not isNearControl then
isNearControl = true
if playerAllowed then lib.showTextUI("[E] Control Weigh Lights") end
elseif dist >= (Config.MainLights.ControlDistance or 1.5) and isNearControl then
isNearControl = false
lib.hideTextUI()
end
end
end
end)
CreateThread(function()
while true do
Wait(0)
if not Config.MainLights.AutoMode and isNearControl and playerAllowed and IsControlJustPressed(0, 38) then
openControlMenu()
end
end
end)
-- ============================================================
-- AUTO MODE (SINGLE LANE)
-- ============================================================
local laneTriggerCenters = {
LaneA = vector3(2905.2993, 4171.1538, 50.2786)
}
local laneRadius = 1.0
local leaveThreshold = 8.0
local function isVehicleAllowed(vehicle)
if not DoesEntityExist(vehicle) then return false end
local modelName = tostring(GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)) or ""):lower()
local vClass = GetVehicleClass(vehicle)
if Config.RampLight.AllowedModels then
for _, m in ipairs(Config.RampLight.AllowedModels) do
if modelName == tostring(m):lower() then return true end
end
end
return Config.RampLight.AllowedClasses[vClass] ~= nil
end
local function startLaneAutoThread()
local track = autoTrack.LaneA
if track.detectionThread then return end
track.detectionThread = CreateThread(function()
while true do
Wait(400)
if isInRange then
local veh = GetVehiclePedIsIn(PlayerPedId(), false)
if veh ~= 0 and isVehicleAllowed(veh) then
local dist = #(GetEntityCoords(veh) - laneTriggerCenters.LaneA)
if dist <= laneRadius and not track.detected then
track.detected = true
track.lastVeh = veh
TriggerServerEvent("jd_weighlight:autoTrigger", "LaneA")
elseif dist > leaveThreshold and track.detected then
track.detected = false
track.lastVeh = nil
TriggerServerEvent("jd_weighlight:setLaneState", "LaneA", true)
end
end
end
end
end)
end
if Config.MainLights.AutoMode then
CreateThread(function()
Wait(2000)
startLaneAutoThread()
end)
end
@@ -0,0 +1,83 @@
-- ============================================================
-- LANE STATE (SINGLE LANE)
-- ============================================================
local laneStates = {
LaneA = Config.MainLights and Config.MainLights.AutoMode and true or nil
}
-- Prevent multiple timers from overlapping
local autoTimers = {
LaneA = false
}
-- ============================================================
-- SEND STATE TO PLAYER
-- ============================================================
RegisterNetEvent("jd_weighlight:requestState")
AddEventHandler("jd_weighlight:requestState", function()
local src = source
-- Sends the current server truth to the client
TriggerClientEvent("jd_weighlight:setLaneState", src, "LaneA", laneStates.LaneA)
end)
-- ============================================================
-- SET LANE STATE (MANUAL)
-- ============================================================
RegisterNetEvent("jd_weighlight:setLaneState")
AddEventHandler("jd_weighlight:setLaneState", function(lane, state)
local src = source
if lane ~= "LaneA" then return end
-- ACL check
if Config.ACL.UseACL and not IsPlayerAceAllowed(src, Config.ACL.MainLights) then
print(("[weigh_lights] Player %d denied access"):format(src))
return
end
-- Update the Master State and tell EVERYONE to update their local objects
laneStates.LaneA = state
TriggerClientEvent("jd_weighlight:setLaneState", -1, "LaneA", state)
end)
-- ============================================================
-- AUTO MODE TRIGGER
-- ============================================================
RegisterNetEvent("jd_weighlight:autoTrigger")
AddEventHandler("jd_weighlight:autoTrigger", function(lane)
if lane ~= "LaneA" or not Config.MainLights.AutoMode then return end
-- Safety: If a timer is already running, don't start another one
if autoTimers.LaneA then return end
-- Turn the light RED for everyone
laneStates.LaneA = false
TriggerClientEvent("jd_weighlight:setLaneState", -1, "LaneA", false)
autoTimers.LaneA = true
CreateThread(function()
Wait(30000) -- 30 Seconds
autoTimers.LaneA = false
-- Only set back to GREEN if a staff member hasn't manually changed it or turned it OFF
if laneStates.LaneA == false then
laneStates.LaneA = true
TriggerClientEvent("jd_weighlight:setLaneState", -1, "LaneA", true)
end
end)
end)
-- ============================================================
-- PERMISSION CHECK
-- ============================================================
RegisterNetEvent("weigh_lights:checkPermissionMain")
AddEventHandler("weigh_lights:checkPermissionMain", function()
local src = source
local allowed = true
if Config.ACL.UseACL then
allowed = IsPlayerAceAllowed(src, Config.ACL.MainLights)
end
TriggerClientEvent("weigh_lights:permissionResultMain", src, allowed)
end)
@@ -0,0 +1,180 @@
local lightSets = {}
local playerAllowed = false
-- ============================================================
-- REQUEST PERMISSION IF ACL IS ENABLED
-- ============================================================
CreateThread(function()
if Config.ACL.UseACL then
TriggerServerEvent("weigh_lights:checkPermission")
else
playerAllowed = true -- bypass permission
end
end)
RegisterNetEvent("weigh_lights:permissionResult", function(allowed)
playerAllowed = allowed
end)
-- ============================================================
-- LOAD CONFIGURED AMBER LIGHT SETS
-- ============================================================
CreateThread(function()
for _, cfg in ipairs(Config.RoadSigns.LightSets) do
table.insert(lightSets, {
name = cfg.name,
model = cfg.model,
coordsA = cfg.coordsA,
coordsB = cfg.coordsB,
rotA = cfg.rotA or vector3(0.0, 0.0, 0.0),
rotB = cfg.rotB or vector3(0.0, 0.0, 0.0),
control = cfg.control,
distance = cfg.distance or 2.0,
propA = nil,
propB = nil,
flashing = false,
pointCreated = false
})
end
end)
-- ============================================================
-- REQUEST SYNC EVERY 10 SECONDS
-- ============================================================
CreateThread(function()
while true do
TriggerServerEvent("weigh_lights:requestSync")
Wait(10000)
end
end)
-- ============================================================
-- SYNC ALL STATES
-- ============================================================
RegisterNetEvent("weigh_lights:syncAll", function(states)
for _, set in ipairs(lightSets) do
if states[set.name] ~= nil then
local desired = states[set.name]
if desired and not set.flashing then
StartFlashing(set)
set.flashing = true
elseif not desired and set.flashing then
StopFlashing(set)
set.flashing = false
end
end
end
end)
-- ============================================================
-- SYNC A SINGLE SET
-- ============================================================
RegisterNetEvent("weigh_lights:syncSingle", function(setName, state)
for _, set in ipairs(lightSets) do
if set.name == setName then
if state and not set.flashing then
StartFlashing(set)
set.flashing = true
elseif not state and set.flashing then
StopFlashing(set)
set.flashing = false
end
break
end
end
end)
-- ============================================================
-- FLASHING LOGIC
-- ============================================================
function StartFlashing(set)
if DoesEntityExist(set.propA) then DeleteEntity(set.propA) end
if DoesEntityExist(set.propB) then DeleteEntity(set.propB) end
if not HasModelLoaded(set.model) then
RequestModel(set.model)
while not HasModelLoaded(set.model) do Wait(10) end
end
local zOffset = -0.09
local posA = set.coordsA + vector3(0.0, 0.0, zOffset)
local posB = set.coordsB + vector3(0.0, 0.0, zOffset)
set.propA = CreateObject(set.model, posA.x, posA.y, posA.z, false, false, false)
set.propB = CreateObject(set.model, posB.x, posB.y, posB.z, false, false, false)
SetEntityRotation(set.propA, set.rotA.x, set.rotA.y, set.rotA.z, 2, true)
SetEntityRotation(set.propB, set.rotB.x, set.rotB.y, set.rotB.z, 2, true)
SetEntityVisible(set.propA, false, false)
SetEntityVisible(set.propB, false, false)
SetEntityAsMissionEntity(set.propA, true, true)
SetEntityAsMissionEntity(set.propB, true, true)
CreateThread(function()
local toggle = true
while set.flashing do
if toggle then
SetEntityVisible(set.propA, true, false)
SetEntityVisible(set.propB, false, false)
else
SetEntityVisible(set.propA, false, false)
SetEntityVisible(set.propB, true, false)
end
toggle = not toggle
Wait(500)
end
if DoesEntityExist(set.propA) then DeleteEntity(set.propA) end
if DoesEntityExist(set.propB) then DeleteEntity(set.propB) end
set.propA = nil
set.propB = nil
end)
end
function StopFlashing(set)
set.flashing = false
end
-- ============================================================
-- OX_LIB E-KEY INTERACTION (with ACL toggle)
-- ============================================================
CreateThread(function()
while #lightSets == 0 do Wait(100) end
-- wait until permission is determined
if Config.ACL.UseACL then
while playerAllowed == false do Wait(100) end
end
for _, set in ipairs(lightSets) do
if set.control and not set.pointCreated then
local point = lib.points.new(set.control, set.distance)
set.pointCreated = true
function point:nearby()
-- bypass or check permission
if Config.ACL.UseACL and not playerAllowed then
lib.hideTextUI()
return
end
if self.currentDistance < set.distance then
lib.showTextUI("[E] Toggle Amber Light: " .. set.name)
if IsControlJustPressed(0, 38) then
TriggerServerEvent("weigh_lights:toggleLight", set.name, not set.flashing)
end
else
lib.hideTextUI()
end
end
function point:onExit()
lib.hideTextUI()
end
end
end
end)
@@ -0,0 +1,44 @@
local lightStates = {}
-- ============================================================
-- REQUEST SYNC
-- ============================================================
RegisterNetEvent("weigh_lights:requestSync")
AddEventHandler("weigh_lights:requestSync", function()
local src = source
TriggerClientEvent("weigh_lights:syncAll", src, lightStates)
end)
-- ============================================================
-- TOGGLE LIGHT
-- ============================================================
RegisterNetEvent("weigh_lights:toggleLight")
AddEventHandler("weigh_lights:toggleLight", function(setName, newState)
local src = source
-- Only check ACL if UseACL is true
if Config.ACL.UseACL then
if not IsPlayerAceAllowed(src, Config.ACL.AmberLights) then
print(("[weigh_lights] Player %d tried to toggle light '%s' without permission!"):format(src, setName))
return
end
end
lightStates[setName] = newState
TriggerClientEvent("weigh_lights:syncSingle", -1, setName, newState)
end)
-- ============================================================
-- CHECK PERMISSION
-- ============================================================
RegisterNetEvent("weigh_lights:checkPermission")
AddEventHandler("weigh_lights:checkPermission", function()
local src = source
local allowed = true -- default allow if ACL disabled
if Config.ACL.UseACL then
allowed = IsPlayerAceAllowed(src, Config.ACL.AmberLights)
end
TriggerClientEvent("weigh_lights:permissionResult", src, allowed)
end)
@@ -0,0 +1,185 @@
-- WeighUi/client.lua
local uiOpen = false
local checkDistance = 4.0
local truckWeights = {}
local truckTrailers = {}
local trucksOnLane = {}
local lastLaneWeight = {}
local serverCallbacks = {}
-- =========================
-- CUSTOM COORDINATES
-- =========================
local laneTriggerCenters = {
LaneA = vector3(2905.2993, 4171.1538, 50.2786)
}
-- =========================
-- Helpers
-- =========================
local function IsModelInList(modelHash, list)
for _, name in ipairs(list or {}) do
if GetHashKey(name) == modelHash then
return true
end
end
return false
end
function TriggerServerCallback(name, cb, ...)
local id = math.random(1, 999999)
serverCallbacks[id] = cb
TriggerServerEvent(name, id, ...)
end
RegisterNetEvent("weighstation:serverCallback", function(id, ...)
if serverCallbacks[id] then
serverCallbacks[id](...)
serverCallbacks[id] = nil
end
end)
-- =========================
-- Lane initialization
-- =========================
trucksOnLane["LaneA"] = {}
lastLaneWeight["LaneA"] = nil
-- =========================
-- UI Logic
-- =========================
local function ToggleUI(open)
uiOpen = open
SetNuiFocus(open, open)
SendNUIMessage({ action = open and "showUI" or "hideUI" })
if open then
TriggerServerCallback("weighstation:getRecentVehicles", function(recent)
SendNUIMessage({ action = "updateRecentVehicles", recent = recent })
end)
TriggerServerCallback("weighstation:getStats", function(stats)
SendNUIMessage({
action = "updateStats",
totalTrucks = stats.totalTrucks or 0,
avgWeight = stats.avgWeight or 0
})
end)
end
end
local function ShowPopup(text)
SendNUIMessage({ action = "showPopup", text = text })
end
local function HidePopup()
SendNUIMessage({ action = "hidePopup" })
end
-- =========================
-- Detection Threads
-- =========================
CreateThread(function()
while true do
Wait(0)
local ped = PlayerPedId()
local pos = GetEntityCoords(ped)
local controlPos = Config.MainLights.ControlCoords
local dist = #(pos - controlPos)
if dist < (Config.MainLights.ControlDistance or 2.0) then
ShowPopup("[G] Open Weigh Station")
if IsControlJustPressed(0, 47) and not uiOpen then
ToggleUI(true)
end
else
HidePopup()
end
end
end)
CreateThread(function()
while true do
Wait(1000)
local vehicles = GetGamePool('CVehicle')
local laneName = "LaneA"
local laneCenter = laneTriggerCenters[laneName]
if laneCenter then
local laneWeight = nil
local laneTruckFound = nil
local minDist = checkDistance + 0.01
for _, vehicle in ipairs(vehicles) do
if DoesEntityExist(vehicle) and not IsEntityDead(vehicle) then
local model = GetEntityModel(vehicle)
if IsModelInList(model, Config.RampLight.AllowedModels) then
local vehiclePos = GetEntityCoords(vehicle)
local distToLane = #(vehiclePos - laneCenter)
if distToLane <= checkDistance and distToLane < minDist then
minDist = distToLane
local hasTrailer, trailer = GetVehicleTrailerVehicle(vehicle)
local plate = GetVehicleNumberPlateText(vehicle)
-- PERSISTENT WEIGHT LOGIC START
if not truckWeights[vehicle] or truckTrailers[vehicle] ~= trailer then
-- 1. Generate Seed from License Plate
local seed = 0
for i = 1, #plate do
seed = seed + string.byte(plate, i)
end
-- 2. Set Seed (Forces math.random to be identical to Station 02)
math.randomseed(seed)
if hasTrailer and trailer ~= 0 then
local trailerModel = GetEntityModel(trailer)
if IsModelInList(trailerModel, Config.RampLight.HeavyModels) then
truckWeights[vehicle] = math.random(85000, 125000)
elseif IsModelInList(trailerModel, Config.RampLight.LightModels) then
truckWeights[vehicle] = math.random(35000, 55000)
else
truckWeights[vehicle] = math.random(40000, 100000)
end
else
truckWeights[vehicle] = math.random(32000, 45000) -- Bobtail
end
-- 3. Reset Seed for other system functions
math.randomseed(GetGameTimer())
truckTrailers[vehicle] = trailer
local truckModel = GetDisplayNameFromVehicleModel(model)
local trailerModelName = (hasTrailer and trailer ~= 0) and GetDisplayNameFromVehicleModel(GetEntityModel(trailer)) or nil
TriggerServerEvent("weighstation:logTruck", plate, truckModel, trailerModelName, truckWeights[vehicle])
end
-- PERSISTENT WEIGHT LOGIC END
laneWeight = truckWeights[vehicle]
laneTruckFound = vehicle
end
end
end
end
if lastLaneWeight[laneName] ~= laneWeight then
lastLaneWeight[laneName] = laneWeight
SendNUIMessage({
action = "updateWeight",
lane = laneName,
weight = laneWeight,
vehicle = laneTruckFound or nil
})
end
end
end
end)
RegisterNUICallback('closeUI', function(data, cb)
ToggleUI(false)
cb('ok')
end)
@@ -0,0 +1,98 @@
document.addEventListener("DOMContentLoaded", () => {
const win = document.getElementById("browser-window");
const closeBtn = document.getElementById("close-btn");
// Lane status (Lane B Removed)
const statusLaneA = document.getElementById("status-laneA");
// Recent vehicles log
const recentVehicles = document.getElementById("recent-vehicles");
// Totals / Summary
const totalTrucksEl = document.getElementById("total-trucks");
const avgWeightEl = document.getElementById("avg-weight");
const overweightCountEl = document.getElementById("overweight-count");
// Popup Logic (Keep existing)
const popup = document.createElement("div");
popup.id = "popup";
popup.style.position = "fixed";
popup.style.top = "20px";
popup.style.left = "20px";
popup.style.padding = "10px 20px";
popup.style.backgroundColor = "#000080";
popup.style.color = "#fff";
popup.style.fontFamily = "MS Sans Serif, Arial, sans-serif";
popup.style.fontSize = "14px";
popup.style.fontWeight = "bold";
popup.style.border = "2px solid #000";
popup.style.display = "none";
popup.style.zIndex = "9999";
popup.style.borderRadius = "4px";
document.body.appendChild(popup);
let recentVehiclesData = [];
let totalTrucks = 0;
let totalWeight = 0;
let overweightCount = 0;
let loggedVehicles = {};
window.addEventListener("message", (event) => {
const data = event.data;
if (data.action === "showUI") {
win.style.display = "block";
document.body.style.cursor = "default";
} else if (data.action === "hideUI") {
win.style.display = "none";
document.body.style.cursor = "none";
} else if (data.action === "showPopup") {
popup.innerText = data.text;
popup.style.display = "block";
} else if (data.action === "hidePopup") {
popup.style.display = "none";
} else if (data.action === "updateWeight") {
// Filter: Only update if the data is for LaneA
if (data.lane !== "LaneA") return;
const laneWeightEl = document.getElementById("laneA-weight");
laneWeightEl.innerText = data.weight ? data.weight + " LBS" : "-- LBS";
if (data.weight) {
statusLaneA.innerText = data.weight > 80000 ? `Lane A: ⚠️ Overweight` : `Lane A: Occupied`;
} else {
statusLaneA.innerText = `Lane A: Open`;
}
if (data.vehicle && data.weight && !loggedVehicles[data.vehicle]) {
loggedVehicles[data.vehicle] = true;
totalTrucks++;
totalWeight += data.weight;
if (data.weight > 80000) overweightCount++;
totalTrucksEl.innerText = totalTrucks;
avgWeightEl.innerText = Math.floor(totalWeight / totalTrucks);
overweightCountEl.innerText = overweightCount;
const entry = `${new Date().toLocaleTimeString()} - Lane A - ${data.weight} LBS`;
recentVehiclesData.unshift(entry);
if (recentVehiclesData.length > 10) recentVehiclesData.pop();
recentVehicles.innerHTML = recentVehiclesData.map(e => `<li>${e}</li>`).join("");
}
if (!data.weight && data.vehicle) {
loggedVehicles[data.vehicle] = false;
}
}
});
closeBtn.addEventListener("click", () => {
fetch(`https://${GetParentResourceName()}/closeUI`, {
method: 'POST',
body: JSON.stringify({})
}).finally(() => {
win.style.display = "none";
document.body.style.cursor = "none";
});
});
});
@@ -0,0 +1,116 @@
body {
font-family: "MS Sans Serif", Arial, sans-serif;
background-color: transparent;
margin: 0;
padding: 0;
}
/* Browser window */
#browser-window {
width: 800px;
height: 600px;
background-color: #c0c0c0;
border: 3px solid #000;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: none;
box-shadow: 7px 7px 0px #000;
padding: 0;
overflow: hidden;
}
/* Top bar */
#browser-topbar {
height: 30px;
background-color: #000080;
color: #fff;
padding: 6px;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
font-size: 14px;
}
/* URL tab */
#tab {
font-weight: normal;
background-color: #c0c0c0;
color: #000;
padding: 4px 8px;
border: 2px inset #fff;
margin-left: 4px;
font-size: 13px;
}
/* Close button */
#close-btn {
width: 25px;
height: 25px;
cursor: pointer;
font-weight: bold;
}
#close-btn:hover {
background: #ff0000;
color: #fff;
}
/* Menu buttons */
#browser-menu {
display: flex;
height: 30px;
background-color: #c0c0c0;
border-bottom: 2px inset #fff;
}
.menu-btn {
border: 2px outset #fff;
margin: 2px;
padding: 4px 10px;
background-color: #c0c0c0;
cursor: pointer;
font-size: 13px;
}
.menu-btn:active {
border-style: inset;
}
/* Content area */
#browser-content {
background-color: #e0e0e0;
padding: 30px;
font-size: 16px;
border-top: 2px solid #fff;
border-bottom: 2px solid #000;
height: calc(100% - 30px - 30px);
overflow-y: auto;
}
/* Lane boxes */
.lane {
margin-top: 15px;
padding: 10px;
border: 2px inset #fff;
background-color: #c0c0c0;
font-size: 16px;
}
.lane-name {
font-weight: bold;
}
/* Lane status, log, summary */
.lane-status, .log, .summary {
margin-top: 20px;
padding: 10px;
border: 2px inset #fff;
background-color: #c0c0c0;
}
.lane-status h3, .log h3, .summary h3 {
margin: 0 0 5px 0;
font-size: 16px;
}
.log ul {
padding-left: 20px;
margin: 0;
}
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Weigh Station UI</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="browser-window">
<div id="browser-topbar">
<span id="tab">www.sanandreasdot.gov/panel</span>
<button id="close-btn">X</button>
</div>
<div id="browser-menu">
<button class="menu-btn">File</button>
<button class="menu-btn">Edit</button>
<button class="menu-btn">View</button>
<button class="menu-btn">Help</button>
</div>
<div id="browser-content">
<h2>Weigh Station Panel</h2>
<div class="lane">
<span class="lane-name">Lane A:</span> <span id="laneA-weight">-- LBS</span>
</div>
<div class="lane-status">
<h3>Lane Status</h3>
<div id="status-laneA">Lane A: Open</div>
</div>
<div class="log">
<h3>Recent Vehicles</h3>
<ul id="recent-vehicles">
</ul>
</div>
<div class="summary">
<h3>Summary</h3>
<div>Total Trucks Today: <span id="total-trucks">0</span></div>
<div>Average Weight: <span id="avg-weight">0</span> LBS</div>
<div>Overweight Trucks: <span id="overweight-count">0</span></div>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
@@ -0,0 +1,46 @@
-- WeighUi/server.lua
-- In-memory logging for weigh station
local recentTrucks = {} -- FIFO queue for last 10 trucks
local totalWeightToday = 0
local totalTrucksToday = 0
-- =========================
-- Log a truck
-- =========================
RegisterNetEvent("weighstation:logTruck", function(plate, truckModel, trailerModel, weight)
-- Add to recent trucks (at front)
table.insert(recentTrucks, 1, {
plate = plate,
truckModel = truckModel,
trailerModel = trailerModel or "none",
weight = weight
})
-- Keep only last 10
if #recentTrucks > 10 then
table.remove(recentTrucks)
end
-- Update totals
totalTrucksToday = totalTrucksToday + 1
totalWeightToday = totalWeightToday + weight
end)
-- =========================
-- Get recent vehicles
-- =========================
RegisterNetEvent("weighstation:getRecentVehicles", function(id)
TriggerClientEvent("weighstation:serverCallback", source, id, recentTrucks)
end)
-- =========================
-- Get stats
-- =========================
RegisterNetEvent("weighstation:getStats", function(id)
local avgWeight = totalTrucksToday > 0 and totalWeightToday / totalTrucksToday or 0
TriggerClientEvent("weighstation:serverCallback", source, id, {
totalTrucks = totalTrucksToday,
avgWeight = avgWeight
})
end)
@@ -0,0 +1,81 @@
Config = {}
-- =============================================
-- ACE PERMISSIONS
-- =============================================
Config.ACL = {
UseACL = false, -- set to false to disable ACE checks allowing anyone to use the light controls
MainLights = "weighstation.mainlights",
AmberLights = "weighstation.amberlights"
}
-- =============================================
-- MAIN WEIGH STATION TRAFFIC LIGHTS
-- =============================================
Config.MainLights = {
AutoMode = true,
Lanes = {
LaneA = {
Lights = {
{
coords = vector3(2911.517090, 4153.126465, 58.026089),
rotation = vector3(0.0, 0.0, 0.0)
},
{
coords = vector3(2896.470703, 4179.305664, 56.372925),
rotation = vector3(20.0, -90.0, 20.0)
},
},
}
},
ControlCoords = vector3(2920.2078, 4191.4390, 50.4580),
ControlDistance = 1.0,
LightSpawnRange = 1000.0,
ResyncTime = 60000
}
-- =============================================
-- ROAD SIGN / AMBER LIGHT SETS
-- =============================================
Config.RoadSigns = {
LightSets = {
{
name = "San Chainski",
model = `jd_weighstation_lightamber`,
coordsA = vector3(2946.227051, 3795.911865, 54.827042),
rotA = vector3(0.0, 0.0, -18.46),
coordsB = vector3(2947.412842, 3795.516113, 54.827042),
rotB = vector3(0.0, 0.0, -18.46),
control = vector3(2946.8794, 3795.7717, 52.5899),
distance = 1.0
}
}
}
-- =============================================
-- WEIGH STATION RAMP LIGHT SYSTEM
-- =============================================
Config.RampLight = {
AllowedClasses = {
[18] = false,
[20] = false,
},
AllowedModels = { "phantom", "hauler", "packer", "phantom3", "hauler2", "packer2" }, -- ← container truck models
HeavyModels = {
"TRAILER", -- ← container heavy trailers
"TRAILERS", "TRAILER2", "TRAILER3", "TRAILER4",
"trailers", "trailers2", "trailers3", "trailers4",
"freighttrailer", "armytanker", "docktrailer"
},
LightModels = {
"tr2", "tr3", "tr4", "trflat", -- ← container light trailers
"tanker", "tanker2", "trailerlogs",
"tvtrailer", "baletrailer", "graintrailer"
},
}
@@ -0,0 +1,33 @@
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
author 'John Chin'
description 'Weigh Stations'
version '1.0.0'
shared_scripts {
'@ox_lib/init.lua',
'config.lua'
}
client_scripts {
'MainLights/client.lua',
'RoadSignLights/client.lua',
'WeighUi/client.lua'
}
server_scripts {
'MainLights/server.lua',
'RoadSignLights/server.lua',
'WeighUi/server.lua'
}
ui_page 'WeighUi/html/ui.html'
files {
'WeighUi/html/ui.html',
'WeighUi/html/style.css',
'WeighUi/html/script.js'
}
@@ -0,0 +1,12 @@
fx_version "cerulean"
game "gta5"
lua54 'yes'
author 'John Chin'
description 'Weigh Station Scenarios'
version '1.0.0'
file "sp_manifest.ymt"
data_file "SCENARIO_POINTS_OVERRIDE_PSO_FILE" "sp_manifest.ymt"
@@ -0,0 +1,7 @@
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
author 'John Chin'
description 'Weigh Station Shared Props'
version '1.0.0'

Some files were not shown because too many files have changed in this diff Show More