math.randomseed(1) jit.flush() ac.setLogSilent(true) if ac.load('newmode.traffic.active') then function script.update(dt) ui.textWrapped('Editing is not available in traffic mode, please restart in practice mode.') end return end local json = require('lib/json') local genericUtils = require('src/generic_utils') local dataFilename = ac.getTrackDataFilename('traffic.json') function ErrorPos(msg, pos) DebugShapes[msg] = pos error(msg) end -- Common module package.add('src/common') -- Editor module package.add('src/editor') local EditorMain = require('EditorMain') local function loadData() -- return require('src/grid_generator')() return json.decode(io.load(dataFilename, '{}')) end ---@type EditorMain -- local editor = EditorMain(require('src/grid_generator')()) local editor = EditorMain(loadData()) -- Simulation module package.add('src/simulation') local TrafficSimulation = require('TrafficSimulation') local TrafficConfig = require('TrafficConfig') local TrafficDebugLayers = require('TrafficDebugLayers') local simSettings = ac.storage{ simulationSpeed = 1, clickToDelete = false, profileGC = false, debugLines = false, debugLayers = 'null', debugBehaviour = false, debugSpawnAround = false, carsNumber = 200 } local simTraffic, simBroken = nil, false local simDebugLayers = TrafficDebugLayers(json.decode(simSettings.debugLayers)) local function syncTrafficConfig() TrafficConfig.debugBehaviour = simSettings.debugBehaviour TrafficConfig.debugSpawnAround = simSettings.debugSpawnAround TrafficConfig.driversCount = simSettings.carsNumber end local function trafficRefresh() if simTraffic ~= nil then simTraffic:dispose() end simTraffic, simBroken = nil, false end local function trafficTab() ui.header('Simulation speed') ui.pushFont(ui.Font.Small) local speed = refnumber(simSettings.simulationSpeed > 1 and 1 + (simSettings.simulationSpeed - 1) / 30 or simSettings.simulationSpeed) if ui.slider('##simulationSpeed', speed, 0, 2, string.format('Speed: %.1f', simSettings.simulationSpeed)) then simSettings.simulationSpeed = speed.value > 1 and 1 + (speed.value - 1) * 30 or speed.value end if ui.button('Restart') then math.randomseed(ui.mousePos().x + 2000 * ui.mousePos().y) trafficRefresh() end ui.sameLine() if ui.button('Reset speed') then simSettings.simulationSpeed = 1 end ui.separator() if ui.checkbox('Profile GC', simSettings.profileGC) then simSettings.profileGC = not simSettings.profileGC end if ui.checkbox('Click to delete cars', simSettings.clickToDelete) then simSettings.clickToDelete = not simSettings.clickToDelete end if ui.checkbox('Debug cars behaviour', simSettings.debugBehaviour) then simSettings.debugBehaviour = not simSettings.debugBehaviour trafficRefresh() end if ui.checkbox('Spawn cars nearby only', simSettings.debugSpawnAround) then simSettings.debugSpawnAround = not simSettings.debugSpawnAround trafficRefresh() end local newNumber, changed = ui.slider('##carsNumber', simSettings.carsNumber, 1, 2000, 'Cars: %.0f', 2) if changed then simSettings.carsNumber = newNumber setTimeout(trafficRefresh, 0.01, 'refresh') end ui.separator() if ui.checkbox('Show debug lanes', simSettings.debugLines) then simSettings.debugLines = not simSettings.debugLines end local function debugLayer(layer) ui.offsetCursorX(layer.level * 8) if ui.checkbox(layer.name, layer.active) then layer.active = not layer.active simSettings.debugLayers = json.encode(simDebugLayers:serialize()) end if layer.active then ui.pushID(layer.name) table.forEach(layer.children, debugLayer) ui.popID() end end if simSettings.debugLines then table.forEach(simDebugLayers.root.children, debugLayer) end -- deg = (deg or 0) + ac.getDeltaT() * 100 -- ui.beginRotation() -- ui.pushDWriteFont('License Plate:./data') -- ui.dwriteText('Hello world!', ((50 + 20 * math.sin(deg * 0.01)) * 3) / 3, rgbm.colors.white) -- ui.popDWriteFont() -- ui.endRotation(deg) -- local c = vec2(180, 320) -- local s = vec2(200, 140) -- ui.drawRect(c, c + s, rgbm.colors.red) -- ui.setCursor(c) -- ui.pushDWriteFont('Segoe UI') -- ui.dwriteTextAligned('Test', 60, ui.Alignment.Center, ui.Alignment.Center, s, false, rgbm(1, 1, 0.1, 1)) -- ui.popDWriteFont() -- ui.text('Begin testing') -- ui.pushDWriteFont('License Plate:'.. __dirname .. '/data/license_plate.ttf') -- ui.dwriteText('Hello world!', 30, rgbm.colors.white) -- ui.popDWriteFont() -- ui.text('test') -- ui.pushDWriteFont('Segoe UI') -- ui.dwriteText('Hello world!', 15, rgbm.colors.white) -- ui.popDWriteFont() -- ui.pushDWriteFont('Segoe UI') -- ui.dwriteText('Hello world! This is a very very long bit of text', 35, rgbm.colors.white) -- ui.popDWriteFont() -- ui.pushDWriteFont('Segoe UI') -- ui.dwriteTextWrapped('Hello world! This is a very very long bit of text', 35, rgbm.colors.white) -- ui.popDWriteFont() -- ui.pushDWriteFont('License Plate:data/license_plate.ttf') -- ui.dwriteText('Hello world!', 15, rgbm.colors.white) -- ui.popDWriteFont() -- ui.text('Good old regular text a lot of it a lot of it a lot of it a lot of it a lot of it a lot of it a lot of it') -- local MyFavouriteFont = ui.DWriteFont('License Plate', './data') -- :weight(ui.DWriteFont.Weight.Bold) -- :style(ui.DWriteFont.Style.Normal) -- :stretch(ui.DWriteFont.Stretch.UltraExpanded) -- ac.debug('MyFavouriteFont', MyFavouriteFont) -- ui.pushDWriteFont(MyFavouriteFont) -- you could also just put font here, but if defined once and reused, it would generate less garbage for GC to clean up. -- ui.dwriteText('Hey there', 24, rgbm.colors.white) -- ui.popDWriteFont() if simBroken then local errorMsg = 'Sim has crashed' local solutionData = nil if ac.getLastError():match('Not allowed') then errorMsg = 'Can’t run: scripting physics is not allowed. Add in “surfaces.ini”:' solutionData = '[SURFACE_0]\ WAV_PITCH=extended-0\ \ [_SCRIPTING_PHYSICS]\ ALLOW_NEW_MODE_SCRIPTS=1\ ALLOW_TOOLS=1' end if ac.getLastError():match('Models are missing') then errorMsg = 'Can’t run: models are missing. Download models and unpack `data` folder to “extension\\lua\\tools\\csp-traffic-tool”.' solutionData = 'https://files.acstuff.ru/shared/IeBU/data.zip' end ui.setCursor(vec2(28, 60)) ui.pushStyleVar(ui.StyleVar.ChildRounding, 8) ui.pushStyleColor(ui.StyleColor.ChildBg, rgbm(0.5, 0, 0, 1)) ui.childWindow('simErrorMsg', vec2(0, solutionData and 148 or 48), false, 0, function () ui.offsetCursor(8) ui.textWrapped(errorMsg, ui.availableSpaceX() - 8) if solutionData ~= nil then ui.offsetCursorX(8) if solutionData:startsWith('http') then if ui.textHyperlink(solutionData) then os.openURL(solutionData) end else ui.copyable(solutionData) end end ui.offsetCursorX(8) if ui.button('Restart') then simSettings.simulationSpeed = 1 trafficRefresh() end ui.sameLine() if ui.button('Resume') then simBroken = false end end) ui.popStyleColor() ui.popStyleVar() end ui.popFont() end local simulationTabName = 'Simulation' editor.tabs:insert(1, { name = simulationTabName, fn = trafficTab }) -- Tool script -- function script.asyncUpdate() -- if not simSettings.profileGC then -- collectgarbage() -- end -- end local sim = ac.getSim() local requiresDataInstall = #io.scanDir(__dirname..'/data', '*.json') == 0 ---@type boolean|string if requiresDataInstall then ui.toast(ui.Icons.LoadingSpinner, 'Loading traffic data…###TrafficTool') web.get('https://files.acstuff.ru/shared/TCKo/data.zip', function (err, response) if response and response.body then for _, e in ipairs(io.scanZip(response.body)) do if e:startsWith('data/') and not e:find('/.', nil, true) then local content = io.loadFromZip(response.body, e) if content then local fileDestination = __dirname..'/'..e io.createFileDir(fileDestination) io.save(fileDestination, content) end end end if #io.scanDir(__dirname..'/data', '*.json') == 0 then requiresDataInstall = 'Data is damaged' end else requiresDataInstall = err and tostring(err) or 'Data is missing' end if type(requiresDataInstall) == 'string' then ui.toast(ui.Icons.Warning, 'Failed to install traffic data: '..requiresDataInstall..'###TrafficTool') else ac.broadcastSharedEvent('tools.TrafficTool.rescanCars') setTimeout(function () requiresDataInstall = false end) end end) end function script.simUpdate(dt) if requiresDataInstall then return end if true then -- require('src/test/distance_between_cars')() -- require('src/test/perf_enum')() -- require('src/test/test_class')() -- require('src/test/test_dynamic_texture')() -- return end if sim.isReplayActive or sim.dt == 0 or sim.isOnlineRace then -- Paused or in replay return end if simSettings.simulationSpeed == 0 or simBroken then return end if not editor:isEmpty() and simTraffic == nil then simBroken = true syncTrafficConfig() -- simTraffic = TrafficSimulation(editor:serializeData()) simTraffic = TrafficSimulation(editor:finalizeData()) simBroken = false end if simSettings.profileGC then collectgarbage() end if sim.focusedCar ~= -1 then local car = ac.getCar(sim.focusedCar) if car ~= nil and not car.extrapolatedMovement then -- Hacky fix. Let’s hope we’ll be able to use extrapolated movement soon. dt = dt + (sim.time - ac.getCar(sim.focusedCar).timestamp) / 1e3 dt = math.max(dt, 0.002) end end if simTraffic ~= nil and not simBroken then simBroken = true for _ = 1, math.ceil(simSettings.simulationSpeed) do simTraffic:update(math.saturateN(simSettings.simulationSpeed) * dt) end simBroken = false end if simSettings.profileGC then genericUtils.runGC() end end function script.update(dt) if requiresDataInstall then ui.drawLoadingSpinner(ui.windowSize() / 2 - 20, ui.windowSize() / 2 + 20) return end editor:doUI(dt) end -- function update(dt) -- editor:doUI(dt) -- end -- For easier debugging DebugShapes = {} function script.draw3D() if requiresDataInstall then return end if script.draw3DOverride then script.draw3DOverride() end if simTraffic then simTraffic:drawMain() end if editor.activeTab ~= simulationTabName then render.setDepthMode(render.DepthMode.Off) pcall(function() editor:draw3D() end) elseif simTraffic ~= nil and simSettings.debugLines then simDebugLayers:start() simTraffic:draw3D(simDebugLayers, simSettings.clickToDelete) table.forEach(DebugShapes, function (item, key) ac.debug(key, item) render.debugCross(item, 2, rgbm(3, 0, 0, 1)) render.debugText(item, key, rgbm(3, 0, 0, 1)) end) end end -- Updating simulation and saving on change local refreshTimeout = nil editor.onChange:subscribe(function () setTimeout(function () io.save(dataFilename, json.encode(editor:serializeData())) end, 3, 'save') clearTimeout(refreshTimeout) refreshTimeout = setTimeout(function () trafficRefresh() end, 0.5) end) -- setInterval(function () -- trafficRefresh() -- end, 0.3) -- ac.debug('cfg', ac.getTrackConfig().number('CONDITION_1', 'DELAY', 0)) -- local v = ac.getTrackConfig('CONDITION_1', 'DELAY', 0) -- ac.debug('cfg2', ac.getTrackConfig('CONDITION_1', 'DELAY', 0))