local laneutil = require "::/scripts/construction/laneutil.lua" -- Geometrie local config = { trackDist = 5, baseSep = 12, lenHalf = 60, dip = 6.5, planierInner = 8.3, planierOuter = 12.3, planierArcInner = 8, planierArcOuter = 12.3, cornerPlanierOverlap = 3.25, bridgeClear = 8, bridgeClearRailHalf = 12, planierSlope = 1.0, gap = 8.95, laneWidth = 44.44, steelBridgeSpeed = 120, concreteBridgeSpeed = 350, } -- Modulplätze local SLOT = { streetEra = 1002, streetTram = 1003, bridgeMaterial = 1004, trackKind = 1005, trackCatenary = 1006, streetEraControl = 1007, streetTramControl = 1008, trackFirst = 1201, lengthSouthFirst = 1301, lengthNorthFirst = 1401, armTL = 1101, armTR = 1102, armBL = 1103, armBR = 1104, armTramTL = 1111, armTramTR = 1112, armTramBL = 1113, armTramBR = 1114, armAutoTL = 1121, armAutoTR = 1122, armAutoBL = 1123, armAutoBR = 1124, armLevelTL = 1131, armLevelTR = 1132, armLevelBL = 1133, armLevelBR = 1134, middleArmNorthFirst = 1501, middleArmSouthFirst = 1701, middleArmTramNorthFirst = 1901, middleArmTramSouthFirst = 2101, middleArmAutoNorthFirst = 2301, middleArmAutoSouthFirst = 2501, trackCatenaryFirst = 3001, bridgeGroupFirst = 3301, stationSpacerFirst = 3501, middleArmGridNorthFirst = 3801, middleArmGridSouthFirst = 4101, middleArmGridTramNorthFirst = 4401, middleArmGridTramSouthFirst = 4701, middleArmGridAutoNorthFirst = 5001, middleArmGridAutoSouthFirst = 5301, middleArmGridLevelNorthFirst = 5601, middleArmGridLevelSouthFirst = 5901, flatTExtensionLeft = 6201, flatTExtensionRight = 6202, stationSpacerChainFirst = 6501, } local MAX_TRACKS = 20 local MAX_LENGTH_MODULES = 4 local TRACK_GRID_RADIUS = 60 local TRACK_ORIGIN = SLOT.trackFirst local TRACK_GRID_FIRST = TRACK_ORIGIN - TRACK_GRID_RADIUS local TRACK_GRID_LAST = TRACK_ORIGIN + TRACK_GRID_RADIUS local MIDDLE_ARM_GRID_FIRST2 = TRACK_GRID_FIRST * 2 local MIDDLE_ARM_GRID_LAST2 = TRACK_GRID_LAST * 2 local MIDDLE_ARM_TRACK_CLEARANCE = 12.49 local MODULE_BASE = "/infrastructure/underpass/construction/asset/grimes_unterfuehrung/" local ARM_PARAM = { NO = 3, FLAT = 4, SLOPED = 5 } local function armParamOn(v) return v ~= 2 and v ~= ARM_PARAM.NO end local function armParamLow(v, legacyLow) if v == ARM_PARAM.FLAT then return true end if v == ARM_PARAM.NO or v == ARM_PARAM.SLOPED then return false end return legacyLow == 2 end local function moduleMetadata(params, slotId) local module = params.modules and params.modules[slotId] return module and module.metadata or nil end local function armTramSlotId(slotId) if slotId == SLOT.armTL then return SLOT.armTramTL end if slotId == SLOT.armTR then return SLOT.armTramTR end if slotId == SLOT.armBL then return SLOT.armTramBL end if slotId == SLOT.armBR then return SLOT.armTramBR end if slotId >= SLOT.middleArmNorthFirst and slotId < SLOT.middleArmSouthFirst then return SLOT.middleArmTramNorthFirst + slotId - SLOT.middleArmNorthFirst end if slotId >= SLOT.middleArmSouthFirst and slotId < SLOT.middleArmTramNorthFirst then return SLOT.middleArmTramSouthFirst + slotId - SLOT.middleArmSouthFirst end if slotId >= SLOT.middleArmGridNorthFirst and slotId < SLOT.middleArmGridSouthFirst then return SLOT.middleArmGridTramNorthFirst + slotId - SLOT.middleArmGridNorthFirst end if slotId >= SLOT.middleArmGridSouthFirst and slotId < SLOT.middleArmGridTramNorthFirst then return SLOT.middleArmGridTramSouthFirst + slotId - SLOT.middleArmGridSouthFirst end end local function armAutoSlotId(slotId) if slotId == SLOT.armTL then return SLOT.armAutoTL end if slotId == SLOT.armTR then return SLOT.armAutoTR end if slotId == SLOT.armBL then return SLOT.armAutoBL end if slotId == SLOT.armBR then return SLOT.armAutoBR end if slotId >= SLOT.middleArmNorthFirst and slotId < SLOT.middleArmSouthFirst then return SLOT.middleArmAutoNorthFirst + slotId - SLOT.middleArmNorthFirst end if slotId >= SLOT.middleArmSouthFirst and slotId < SLOT.middleArmTramNorthFirst then return SLOT.middleArmAutoSouthFirst + slotId - SLOT.middleArmSouthFirst end if slotId >= SLOT.middleArmGridNorthFirst and slotId < SLOT.middleArmGridSouthFirst then return SLOT.middleArmGridAutoNorthFirst + slotId - SLOT.middleArmGridNorthFirst end if slotId >= SLOT.middleArmGridSouthFirst and slotId < SLOT.middleArmGridTramNorthFirst then return SLOT.middleArmGridAutoSouthFirst + slotId - SLOT.middleArmGridSouthFirst end end local function armLevelSlotId(slotId) if slotId == SLOT.armTL then return SLOT.armLevelTL end if slotId == SLOT.armTR then return SLOT.armLevelTR end if slotId == SLOT.armBL then return SLOT.armLevelBL end if slotId == SLOT.armBR then return SLOT.armLevelBR end if slotId >= SLOT.middleArmGridNorthFirst and slotId < SLOT.middleArmGridSouthFirst then return SLOT.middleArmGridLevelNorthFirst + slotId - SLOT.middleArmGridNorthFirst end if slotId >= SLOT.middleArmGridSouthFirst and slotId < SLOT.middleArmGridTramNorthFirst then return SLOT.middleArmGridLevelSouthFirst + slotId - SLOT.middleArmGridSouthFirst end end local function trackCatenarySlotId(slotId) if slotId < TRACK_GRID_FIRST or slotId > TRACK_GRID_LAST then return nil end return SLOT.trackCatenaryFirst + slotId - TRACK_GRID_FIRST end local function bridgeGroupSlotId(groupIndex) return SLOT.bridgeGroupFirst + groupIndex - 1 end local function stationSpacerSlotId(anchorSlotId, direction) local sideOffset = direction > 0 and 1 or 0 return SLOT.stationSpacerFirst + (anchorSlotId - TRACK_GRID_FIRST) * 2 + sideOffset end local STATION_SPACER_HALF_FIRST = TRACK_GRID_FIRST * 2 - 1 local STATION_SPACER_HALF_LAST = TRACK_GRID_LAST * 2 + 1 local function stationSpacerChainSlotId(startHalfRaster, direction) local sideOffset = direction > 0 and 1 or 0 return SLOT.stationSpacerChainFirst + (startHalfRaster - STATION_SPACER_HALF_FIRST) * 2 + sideOffset end local function stationSpacerChainSlotData(slotId) local index = slotId - SLOT.stationSpacerChainFirst local startHalfRaster = STATION_SPACER_HALF_FIRST + math.floor(index / 2) local direction = index % 2 == 1 and 1 or -1 return startHalfRaster, direction end local function putCargoGapSpacers(firstSlotId, lastSlotId, stateSlotId, putStationSpacerModel) for slotId = firstSlotId, lastSlotId do putStationSpacerModel("bruecke_bahnhofs_spacer_single", (slotId - TRACK_ORIGIN) * config.trackDist, stateSlotId) end end local function slotTransf(x, y, z) return { 1,0,0,0, 0,1,0,0, 0,0,1,0, x,y,z,1 } end local function slotTransf180(x, y, z) return { -1,0,0,0, 0,-1,0,0, 0,0,1,0, x,y,z,1 } end local function addSeg(edges, p0, p1, t0, t1) edges[#edges + 1] = { p0, t0 } edges[#edges + 1] = { p1, t1 } end local function straight(edges, p0, p1) local t = { p1[1] - p0[1], p1[2] - p0[2], p1[3] - p0[3] } addSeg(edges, p0, p1, t, t) end local function trackXs(slotIds) local xs = {} for k, slotId in ipairs(slotIds) do xs[k] = (slotId - TRACK_ORIGIN) * config.trackDist end return xs end local function initialTrackSlotIds(params, nTracks) local platformStep = params.stationMode == 2 and 3 or params.stationMode == 3 and 2 or 1 local offsets = { 0 } for i = 2, nTracks do local startsNewPair = i % 2 == 1 offsets[i] = offsets[i - 1] + (startsNewPair and platformStep or 1) end local centerOffset = math.floor(offsets[#offsets] / 2) local result = {} for i = 1, nTracks do result[i] = TRACK_ORIGIN + offsets[i] - centerOffset end return result end -- Erstbelegung local function createTemplateFn(captureParams, params) local result = {} local nTracks = math.max(1, math.min(MAX_TRACKS, params.numTracks or 1)) local initialSlots = initialTrackSlotIds(params, nTracks) local initialTrackModule = MODULE_BASE .. ((params.trackKind == 2) and "track_build_simple.module" or (params.trackKind == 3) and "track_build_high_speed.module" or "track_build_standard.module") local initialCatenaryModule = MODULE_BASE .. ((params.trackCatenary == 2) and "track_catenary_state_on.module" or "track_catenary_state_off.module") for _, slotId in ipairs(initialSlots) do result[slotId] = initialTrackModule result[trackCatenarySlotId(slotId)] = initialCatenaryModule end if params.stationMode == 2 or params.stationMode == 3 then local spacerModule = MODULE_BASE .. (params.stationMode == 2 and "station_spacer_cargo.module" or "station_spacer_passenger.module") local leftHasPair = #initialSlots > 1 and initialSlots[2] - initialSlots[1] == 1 local rightHasPair = #initialSlots > 1 and initialSlots[#initialSlots] - initialSlots[#initialSlots - 1] == 1 if leftHasPair then result[stationSpacerSlotId(initialSlots[1], -1)] = spacerModule end if rightHasPair then result[stationSpacerSlotId(initialSlots[#initialSlots], 1)] = spacerModule end end local initialBridgeModule = MODULE_BASE .. ((params.material == 2) and "bridge_group_steel.module" or "bridge_group_concrete.module") local initialGroup = 1 result[bridgeGroupSlotId(initialGroup)] = initialBridgeModule for i = 2, #initialSlots do if initialSlots[i] - initialSlots[i - 1] > 1 then initialGroup = initialGroup + 1 result[bridgeGroupSlotId(initialGroup)] = initialBridgeModule end end for i = 0, MAX_LENGTH_MODULES - 1 do if params.trackSide2Length ~= 2 then result[SLOT.lengthSouthFirst + i] = MODULE_BASE .. "track_length_initial.module" end if params.trackSide1Length ~= 2 then result[SLOT.lengthNorthFirst + i] = MODULE_BASE .. "track_length_initial.module" end end result[SLOT.streetEra] = MODULE_BASE .. ((params.streetEra == 2) and "street_era_old.module" or "street_era_new.module") result[SLOT.bridgeMaterial] = MODULE_BASE .. ((params.material == 2) and "bridge_material_steel.module" or "bridge_material_concrete.module") result[SLOT.trackKind] = MODULE_BASE .. ((params.trackKind == 2) and "track_kind_simple.module" or (params.trackKind == 3) and "track_kind_high_speed.module" or "track_kind_standard.module") result[SLOT.trackCatenary] = MODULE_BASE .. ((params.trackCatenary == 2) and "track_catenary_on.module" or "track_catenary_off.module") local armModule = MODULE_BASE .. "street_arm_initial.module" local tramModule = MODULE_BASE .. ((params.streetTram == 2) and "street_arm_tram_state_on.module" or "street_arm_tram_state_off.module") result[SLOT.streetEraControl] = MODULE_BASE .. ((params.streetEra == 2) and "street_arm_era_old.module" or "street_arm_era_new.module") result[SLOT.streetTramControl] = tramModule local autoModule = MODULE_BASE .. "street_arm_auto_length.module" local rampLevelModule = MODULE_BASE .. "street_arm_level_ramp.module" local lowLevelModule = MODULE_BASE .. "street_arm_level_low.module" local function initialArm(slotId, enabled, longSide, lowSide) if not enabled then return end result[slotId] = armModule result[armTramSlotId(slotId)] = tramModule result[armLevelSlotId(slotId)] = lowSide and lowLevelModule or rampLevelModule if longSide then result[armAutoSlotId(slotId)] = autoModule end end initialArm(SLOT.armTL, armParamOn(params.armTL), params.trackSide1Length ~= 2, armParamLow(params.armTL, params.arm1Low)) initialArm(SLOT.armTR, armParamOn(params.armTR), params.trackSide1Length ~= 2, armParamLow(params.armTR, params.arm2Low)) initialArm(SLOT.armBL, armParamOn(params.armBL), params.trackSide2Length ~= 2, armParamLow(params.armBL, params.arm3Low)) initialArm(SLOT.armBR, armParamOn(params.armBR), params.trackSide2Length ~= 2, armParamLow(params.armBR, params.arm4Low)) return result end -- Modulauswertung local function updateFn(captureParams, params) local result = {} result.callInvalidModules = true result.models = {} result.groundFaces = {} local nTracks local trackSlotIds = {} local trackSlotTypes = {} if params.modules ~= nil then nTracks = 0 for slotId = TRACK_GRID_FIRST, TRACK_GRID_LAST do local module = params.modules[slotId] if module and module.metadata and module.metadata.underpassTrack then nTracks = nTracks + 1 trackSlotIds[#trackSlotIds + 1] = slotId trackSlotTypes[slotId] = "grimes_underpass_track" end end nTracks = math.max(1, nTracks) if #trackSlotIds == 0 then trackSlotIds[1] = TRACK_ORIGIN end else nTracks = math.max(1, math.min(MAX_TRACKS, params.numTracks or 1)) trackSlotIds = initialTrackSlotIds(params, nTracks) for i = 1, nTracks do trackSlotTypes[trackSlotIds[i]] = "grimes_underpass_track" end end local xs = trackXs(trackSlotIds) local nearL, nearR = xs[1], xs[1] for _, x in ipairs(xs) do nearL = math.min(nearL, x) nearR = math.max(nearR, x) end local stationSpacers = {} local legacyStationSpacers = {} local chainedStationSpacerByStart = {} if params.modules ~= nil then for anchorSlotId = TRACK_GRID_FIRST, TRACK_GRID_LAST do for _, direction in ipairs({ -1, 1 }) do local stateSlotId = stationSpacerSlotId(anchorSlotId, direction) local metadata = moduleMetadata(params, stateSlotId) if metadata and metadata.underpassStationSpacer then local spacer = { stateSlotId = stateSlotId, anchorSlotId = anchorSlotId, direction = direction, trackOffset = metadata.trackOffset or 2, startHalfRaster = anchorSlotId * 2 + direction, legacy = true, } stationSpacers[#stationSpacers + 1] = spacer legacyStationSpacers[stateSlotId] = spacer end end end local firstChainSlotId = stationSpacerChainSlotId(STATION_SPACER_HALF_FIRST, -1) local lastChainSlotId = stationSpacerChainSlotId(STATION_SPACER_HALF_LAST, 1) for stateSlotId = firstChainSlotId, lastChainSlotId do local metadata = moduleMetadata(params, stateSlotId) if metadata and metadata.underpassStationSpacer then local startHalfRaster, direction = stationSpacerChainSlotData(stateSlotId) local spacer = { stateSlotId = stateSlotId, direction = direction, trackOffset = metadata.trackOffset or 2, startHalfRaster = startHalfRaster, legacy = false, } stationSpacers[#stationSpacers + 1] = spacer chainedStationSpacerByStart[startHalfRaster .. ":" .. direction] = spacer end end end local function spacerWidthHalf(spacer) return (spacer.trackOffset - 1) * 2 end local function spacerCenterX(spacer) local centerHalfRaster = spacer.startHalfRaster + spacer.direction * spacerWidthHalf(spacer) * 0.5 return (centerHalfRaster * 0.5 - TRACK_ORIGIN) * config.trackDist end local function collectOuterSpacerChain(anchorSlotId, direction) local chain = {} local startHalfRaster = anchorSlotId * 2 + direction local first = legacyStationSpacers[stationSpacerSlotId(anchorSlotId, direction)] if first then chain[#chain + 1] = first startHalfRaster = startHalfRaster + direction * spacerWidthHalf(first) end while startHalfRaster >= STATION_SPACER_HALF_FIRST and startHalfRaster <= STATION_SPACER_HALF_LAST do local spacer = chainedStationSpacerByStart[startHalfRaster .. ":" .. direction] if not spacer then break end chain[#chain + 1] = spacer startHalfRaster = startHalfRaster + direction * spacerWidthHalf(spacer) end return chain, startHalfRaster end local leftStationSpacerAnchor local rightStationSpacerAnchor for _, spacer in pairs(legacyStationSpacers) do if spacer.direction < 0 then leftStationSpacerAnchor = leftStationSpacerAnchor and math.max(leftStationSpacerAnchor, spacer.anchorSlotId) or spacer.anchorSlotId else rightStationSpacerAnchor = rightStationSpacerAnchor and math.min(rightStationSpacerAnchor, spacer.anchorSlotId) or spacer.anchorSlotId end end leftStationSpacerAnchor = leftStationSpacerAnchor or trackSlotIds[1] rightStationSpacerAnchor = rightStationSpacerAnchor or trackSlotIds[#trackSlotIds] local leftStationSpacerChain, leftStationSpacerEndHalf = collectOuterSpacerChain(leftStationSpacerAnchor, -1) local rightStationSpacerChain, rightStationSpacerEndHalf = collectOuterSpacerChain(rightStationSpacerAnchor, 1) local leftStationSpacerTargetSlotId = (leftStationSpacerEndHalf - 1) * 0.5 local rightStationSpacerTargetSlotId = (rightStationSpacerEndHalf + 1) * 0.5 if #leftStationSpacerChain > 0 and trackSlotIds[1] <= leftStationSpacerTargetSlotId then leftStationSpacerAnchor = trackSlotIds[1] leftStationSpacerChain = {} leftStationSpacerEndHalf = leftStationSpacerAnchor * 2 - 1 leftStationSpacerTargetSlotId = leftStationSpacerAnchor - 1 end if #rightStationSpacerChain > 0 and trackSlotIds[#trackSlotIds] >= rightStationSpacerTargetSlotId then rightStationSpacerAnchor = trackSlotIds[#trackSlotIds] rightStationSpacerChain = {} rightStationSpacerEndHalf = rightStationSpacerAnchor * 2 + 1 rightStationSpacerTargetSlotId = rightStationSpacerAnchor + 1 end local activeOuterStationSpacers = {} for _, spacer in ipairs(leftStationSpacerChain) do activeOuterStationSpacers[#activeOuterStationSpacers + 1] = spacer end for _, spacer in ipairs(rightStationSpacerChain) do activeOuterStationSpacers[#activeOuterStationSpacers + 1] = spacer end local fixedMiddleArms = {} if params.modules ~= nil then for halfRasterSlotId = MIDDLE_ARM_GRID_FIRST2, MIDDLE_ARM_GRID_LAST2 do local rasterIndex = halfRasterSlotId - MIDDLE_ARM_GRID_FIRST2 local northSlotId = SLOT.middleArmGridNorthFirst + rasterIndex local southSlotId = SLOT.middleArmGridSouthFirst + rasterIndex local north = params.modules[northSlotId] ~= nil local south = params.modules[southSlotId] ~= nil if north or south then fixedMiddleArms[#fixedMiddleArms + 1] = { x = (halfRasterSlotId * 0.5 - TRACK_ORIGIN) * config.trackDist, halfRasterSlotId = halfRasterSlotId, northSlotId = northSlotId, southSlotId = southSlotId, north = north, south = south, fixed = true, } end end end local middleArmGaps = {} for i = 1, #trackSlotIds - 1 do local leftSlotId, rightSlotId = trackSlotIds[i], trackSlotIds[i + 1] local leftX, rightX = xs[i], xs[i + 1] if rightX - leftX > config.trackDist + 0.01 then local gapIndex = leftSlotId - TRACK_GRID_FIRST local legacyNorthSlotId = SLOT.middleArmNorthFirst + gapIndex local legacySouthSlotId = SLOT.middleArmSouthFirst + gapIndex local gap = { x = (leftX + rightX) * 0.5, leftX = leftX, rightX = rightX, northSlotId = legacyNorthSlotId, southSlotId = legacySouthSlotId, north = params.modules ~= nil and params.modules[legacyNorthSlotId] ~= nil, south = params.modules ~= nil and params.modules[legacySouthSlotId] ~= nil, } for _, fixed in ipairs(fixedMiddleArms) do if fixed.x > leftX + 0.01 and fixed.x < rightX - 0.01 then gap.x = fixed.x gap.halfRasterSlotId = fixed.halfRasterSlotId gap.northSlotId = fixed.northSlotId gap.southSlotId = fixed.southSlotId gap.north = fixed.north gap.south = fixed.south gap.fixed = true break end end middleArmGaps[i] = gap end end local stationRoadMarginLeft, stationRoadMarginRight = 0, 0 if params.modules == nil and params.stationMode ~= nil and params.stationMode > 1 then local leftHasPair = #xs > 1 and xs[2] - xs[1] <= config.trackDist + 0.01 local rightHasPair = #xs > 1 and xs[#xs] - xs[#xs - 1] <= config.trackDist + 0.01 local stationPairMargin = params.stationMode == 2 and 10 or 5 stationRoadMarginLeft = leftHasPair and stationPairMargin or 0 stationRoadMarginRight = rightHasPair and stationPairMargin or 0 end local modularPendingSpacerLeft = #leftStationSpacerChain > 0 local modularPendingSpacerRight = #rightStationSpacerChain > 0 local function spacerChainWidth(chain) local width = 0 for _, spacer in ipairs(chain) do width = width + spacerWidthHalf(spacer) * config.trackDist * 0.5 end return width end if modularPendingSpacerLeft then stationRoadMarginLeft = math.max(stationRoadMarginLeft, spacerChainWidth(leftStationSpacerChain)) end if modularPendingSpacerRight then stationRoadMarginRight = math.max(stationRoadMarginRight, spacerChainWidth(rightStationSpacerChain)) end local leftRoadAnchorX = (leftStationSpacerAnchor - TRACK_ORIGIN) * config.trackDist local rightRoadAnchorX = (rightStationSpacerAnchor - TRACK_ORIGIN) * config.trackDist local leftRoadX = leftRoadAnchorX - config.baseSep - stationRoadMarginLeft local rightRoadX = rightRoadAnchorX + config.baseSep + stationRoadMarginRight local armLength = config.lenHalf local roadDepth = config.dip local middleRoadHalf = 8.5 local middleWallClear = 2 local function armFromSlot(slotId, paramValue) if params.modules ~= nil then return params.modules[slotId] ~= nil end return armParamOn(paramValue) end local topLeftArm, topRightArm = armFromSlot(SLOT.armTL, params.armTL), armFromSlot(SLOT.armTR, params.armTR) local bottomLeftArm, bottomRightArm = armFromSlot(SLOT.armBL, params.armBL), armFromSlot(SLOT.armBR, params.armBR) local function countLengthModules(firstSlot) local allIds, manualIds = {}, {} if params.modules == nil then local shortSide = firstSlot == SLOT.lengthNorthFirst and params.trackSide1Length == 2 or firstSlot == SLOT.lengthSouthFirst and params.trackSide2Length == 2 if shortSide then return 0, -1, allIds, 0, manualIds end for i = 0, MAX_LENGTH_MODULES - 1 do allIds[#allIds + 1] = firstSlot + i end return MAX_LENGTH_MODULES, MAX_LENGTH_MODULES - 1, allIds, 0, manualIds end local count, manualCount, last = 0, 0, -1 for i = 0, MAX_LENGTH_MODULES - 1 do local metadata = moduleMetadata(params, firstSlot + i) if metadata and metadata.underpassLength then count, last = count + 1, i allIds[#allIds + 1] = firstSlot + i if not metadata.initialTrackLength then manualCount = manualCount + 1 manualIds[#manualIds + 1] = firstSlot + i end end end return count, last, allIds, manualCount, manualIds end local southLengthModules, southLastLength, southLengthSlotIds, southManualModules, southManualSlotIds = countLengthModules(SLOT.lengthSouthFirst) local northLengthModules, northLastLength, northLengthSlotIds, northManualModules, northManualSlotIds = countLengthModules(SLOT.lengthNorthFirst) local function armProvidesInitialLength(slotId) if params.modules ~= nil and params.modules[slotId] == nil then return false end local marker = armAutoSlotId(slotId) local markerMetadata = marker and moduleMetadata(params, marker) if markerMetadata and markerMetadata.autoTrackLength then return true end local metadata = moduleMetadata(params, slotId) return metadata and metadata.autoTrackLength end local autoSouthLength = params.modules == nil and params.trackSide2Length ~= 2 and (bottomLeftArm or bottomRightArm) or armProvidesInitialLength(SLOT.armBL) or armProvidesInitialLength(SLOT.armBR) local autoNorthLength = params.modules == nil and params.trackSide1Length ~= 2 and (topLeftArm or topRightArm) or armProvidesInitialLength(SLOT.armTL) or armProvidesInitialLength(SLOT.armTR) if not autoSouthLength then southLengthModules, southLengthSlotIds = southManualModules, southManualSlotIds end if not autoNorthLength then northLengthModules, northLengthSlotIds = northManualModules, northManualSlotIds end local lengthStep = (armLength - config.gap) / (MAX_LENGTH_MODULES + 1) local southEnd = config.gap + lengthStep * (southLengthModules + 1) local northEnd = config.gap + lengthStep * (northLengthModules + 1) local eraMeta = moduleMetadata(params, SLOT.streetEraControl) or moduleMetadata(params, SLOT.streetEra) local tramMeta = moduleMetadata(params, SLOT.streetTramControl) or moduleMetadata(params, SLOT.streetTram) local trackKindMeta = moduleMetadata(params, SLOT.trackKind) local trackCatenaryMeta = moduleMetadata(params, SLOT.trackCatenary) local bridgeMaterialMeta = moduleMetadata(params, SLOT.bridgeMaterial) local oldStreet = params.streetEra == 2 if eraMeta then if eraMeta.armOldStreet ~= nil then oldStreet = eraMeta.armOldStreet elseif eraMeta.oldStreet ~= nil then oldStreet = eraMeta.oldStreet end end local tramStreet = params.streetTram == 2 if tramMeta then if tramMeta.armTram ~= nil then tramStreet = tramMeta.armTram elseif tramMeta.electricTram ~= nil then tramStreet = tramMeta.electricTram end end local function armIsTram(slotId) local tramSlotId = armTramSlotId(slotId) local tramMetadata = tramSlotId and moduleMetadata(params, tramSlotId) if tramMetadata and tramMetadata.armTram ~= nil then return tramMetadata.armTram end local metadata = moduleMetadata(params, slotId) if metadata and metadata.armTram ~= nil then return metadata.armTram end return tramStreet end local function armIsOld(slotId) local metadata = moduleMetadata(params, slotId) if metadata and metadata.armOldStreet ~= nil then return metadata.armOldStreet end return oldStreet end -- Rampensteuerung: Rampe / flach local function armIsLow(slotId, northSide) local levelSlotId = armLevelSlotId(slotId) local metadata = levelSlotId and moduleMetadata(params, levelSlotId) if metadata and metadata.armLow ~= nil then return metadata.armLow end if params.modules == nil then if slotId == SLOT.armTL then return armParamLow(params.armTL, params.arm1Low) end if slotId == SLOT.armTR then return armParamLow(params.armTR, params.arm2Low) end if slotId == SLOT.armBL then return armParamLow(params.armBL, params.arm3Low) end if slotId == SLOT.armBR then return armParamLow(params.armBR, params.arm4Low) end return northSide and params.armSide1Low == 2 or not northSide and params.armSide2Low == 2 end return false end local armTramTL, armTramTR = armIsTram(SLOT.armTL), armIsTram(SLOT.armTR) local armTramBL, armTramBR = armIsTram(SLOT.armBL), armIsTram(SLOT.armBR) local armOldTL, armOldTR = armIsOld(SLOT.armTL), armIsOld(SLOT.armTR) local armOldBL, armOldBR = armIsOld(SLOT.armBL), armIsOld(SLOT.armBR) local armLow = { TL = armIsLow(SLOT.armTL, true), TR = armIsLow(SLOT.armTR, true), BL = armIsLow(SLOT.armBL, false), BR = armIsLow(SLOT.armBR, false), } for _, gap in pairs(middleArmGaps) do gap.northTram = gap.north and armIsTram(gap.northSlotId) gap.southTram = gap.south and armIsTram(gap.southSlotId) gap.northOld = gap.north and armIsOld(gap.northSlotId) gap.southOld = gap.south and armIsOld(gap.southSlotId) gap.northLow = gap.north and armIsLow(gap.northSlotId, true) gap.southLow = gap.south and armIsLow(gap.southSlotId, false) end local oldArmCount, modernArmCount = 0, 0 local function countArm(enabled, old) if not enabled then return end if old then oldArmCount = oldArmCount + 1 else modernArmCount = modernArmCount + 1 end end countArm(topLeftArm, armOldTL) countArm(topRightArm, armOldTR) countArm(bottomLeftArm, armOldBL) countArm(bottomRightArm, armOldBR) for _, gap in pairs(middleArmGaps) do countArm(gap.north, gap.northOld) countArm(gap.south, gap.southOld) end local centerOldStreet = oldArmCount == modernArmCount and oldStreet or oldArmCount > modernArmCount local concreteBridge = bridgeMaterialMeta and bridgeMaterialMeta.bridgeMaterial == "CONCRETE" or (params.modules == nil and params.material ~= 2) local bridgeGroups, bridgeGroupByTrack = {}, {} for i, slotId in ipairs(trackSlotIds) do if i == 1 or slotId - trackSlotIds[i - 1] > 1 then local groupIndex = #bridgeGroups + 1 local groupMeta = moduleMetadata(params, bridgeGroupSlotId(groupIndex)) bridgeGroups[groupIndex] = { firstIndex = i, lastIndex = i, concrete = groupMeta and groupMeta.bridgeMaterial == "CONCRETE" or groupMeta == nil and concreteBridge, } else bridgeGroups[#bridgeGroups].lastIndex = i end bridgeGroupByTrack[slotId] = bridgeGroups[#bridgeGroups] end local function trackHasConcreteBridge(slotId) local group = bridgeGroupByTrack[slotId] if group ~= nil then return group.concrete end return concreteBridge end local trackCatenary = trackCatenaryMeta and trackCatenaryMeta.trackCatenary or (params.modules == nil and params.trackCatenary == 2) local defaultTrackKind = trackKindMeta and trackKindMeta.highSpeedTrack and "HIGH_SPEED" or trackKindMeta and trackKindMeta.simpleTrack and "SIMPLE" or params.trackKind == 3 and "HIGH_SPEED" or params.trackKind == 2 and "SIMPLE" or "STANDARD" local function trackKindFor(slotId) local metadata = moduleMetadata(params, slotId) local kind = metadata and metadata.trackKind or defaultTrackKind if kind == "HIGH_SPEED" and not trackHasConcreteBridge(slotId) then return "STANDARD" end return kind end local function trackHasCatenary(slotId) local stateSlotId = trackCatenarySlotId(slotId) local stateMetadata = stateSlotId and moduleMetadata(params, stateSlotId) if stateMetadata and stateMetadata.trackCatenary ~= nil then return stateMetadata.trackCatenary end local metadata = moduleMetadata(params, slotId) if metadata and metadata.trackCatenary ~= nil then return metadata.trackCatenary end return trackCatenary end local function trackTemplate(kind, electric) local base = kind == "HIGH_SPEED" and "high_speed/high_speed" or kind == "SIMPLE" and "simple/simple" or "standard/standard" return "::/infrastructure/track/" .. base .. (electric and "_catenary" or "") .. ".street_template" end -- Straßen local streetBase = "/infrastructure/underpass/street/grimes_unterfuehrung/" local street = { centerEdgesNoTram = {}, centerEdgesTram = {}, centerSnapNoTram = {}, centerSnapTram = {}, armVariants = {}, connectorVariants = {}, } if centerOldStreet then street.centerTypeNoTram = streetBase .. "unterfuehrung_old_without_lamps.street_template" street.centerTypeTram = streetBase .. "unterfuehrung_old_without_lamps_tram_electrified.street_template" else street.centerTypeNoTram = streetBase .. "unterfuehrung_new_without_lamps.street_template" street.centerTypeTram = streetBase .. "unterfuehrung_new_without_lamps_small_tram_electrified.street_template" end local function armVariant(old, electric, low, wideSidewalk) local key = (old and "old" or "new") .. (electric and "Tram" or "NoTram") .. (low and "Flat" or "") .. (wideSidewalk and "Sidewalk" or "") if not street.armVariants[key] then local era = old and "old" or "new" local suffix = electric and "_tram_electrified" or "" street.armVariants[key] = { type = streetBase .. "unterfuehrung_" .. era .. "_with_lamps" .. (old and suffix or (electric and "_small_tram_electrified" or "")) .. (low and "_flat" or "") .. (wideSidewalk and "_sidewalk" or "") .. ".street_template", tramTrackType = electric and "ELECTRIC" or "NO", edges = {}, snaps = {}, } end return street.armVariants[key] end street.connectorVariant = function(old, electric) local key = (old and "old" or "new") .. (electric and "Tram" or "NoTram") if not street.connectorVariants[key] then local era = old and "old" or "new" local tramSuffix = electric and "_tram_electrified" or "" street.connectorVariants[key] = { type = "::/infrastructure/street/town/town_" .. era .. "_small" .. tramSuffix .. ".street_template", tramTrackType = electric and "ELECTRIC" or "NO", edges = {}, snaps = {}, } end return street.connectorVariants[key] end street.connector = function(p0, p1, electric, old) local variant = street.connectorVariant(old, electric) straight(variant.edges, p0, p1) variant.snaps[#variant.snaps + 1] = #variant.edges - 1 end local function arm(p0, p1, electric, old, low, wideSidewalk) local variant = armVariant(old, electric, low, wideSidewalk) local target = { p1[1], p1[2], low and -roadDepth or p1[3] } straight(variant.edges, p0, target) local dx, dy = p1[1] - p0[1], p1[2] - p0[2] local length = math.sqrt(dx * dx + dy * dy) street.connector(target, { target[1] + 11 * dx / length, target[2] + 11 * dy / length, target[3] }, electric, old) end local centerJunctionXs = { leftRoadX } local tramJunctionXs = {} if topLeftArm or bottomLeftArm then street.minArmX, street.maxArmX = leftRoadX, leftRoadX end if (topLeftArm and armTramTL) or (bottomLeftArm and armTramBL) then tramJunctionXs[#tramJunctionXs + 1] = leftRoadX end if topRightArm or bottomRightArm then street.minArmX = street.minArmX and math.min(street.minArmX, rightRoadX) or rightRoadX street.maxArmX = street.maxArmX and math.max(street.maxArmX, rightRoadX) or rightRoadX end if (topRightArm and armTramTR) or (bottomRightArm and armTramBR) then tramJunctionXs[#tramJunctionXs + 1] = rightRoadX end for _, gap in pairs(middleArmGaps) do if gap.north or gap.south then centerJunctionXs[#centerJunctionXs + 1] = gap.x street.minArmX = street.minArmX and math.min(street.minArmX, gap.x) or gap.x street.maxArmX = street.maxArmX and math.max(street.maxArmX, gap.x) or gap.x end if gap.northTram or gap.southTram then tramJunctionXs[#tramJunctionXs + 1] = gap.x end end centerJunctionXs[#centerJunctionXs + 1] = rightRoadX table.sort(centerJunctionXs) local minTramX, maxTramX for _, x in ipairs(tramJunctionXs) do minTramX = minTramX and math.min(minTramX, x) or x maxTramX = maxTramX and math.max(maxTramX, x) or x end for i = 1, #centerJunctionXs - 1 do local x0, x1 = centerJunctionXs[i], centerJunctionXs[i + 1] local electric = tramStreet if street.minArmX and street.maxArmX and street.maxArmX - street.minArmX > 0.01 and x0 >= street.minArmX - 0.01 and x1 <= street.maxArmX + 0.01 then electric = minTramX and maxTramX and maxTramX - minTramX > 0.01 and x0 >= minTramX - 0.01 and x1 <= maxTramX + 0.01 end straight(electric and street.centerEdgesTram or street.centerEdgesNoTram, {x0, 0, -roadDepth}, {x1, 0, -roadDepth}) end local leftElectric = minTramX and maxTramX and minTramX <= leftRoadX + 0.01 and maxTramX > leftRoadX + 0.01 local rightElectric = minTramX and maxTramX and maxTramX >= rightRoadX - 0.01 and minTramX < rightRoadX - 0.01 if (not topLeftArm) and (not bottomLeftArm) then street.connector({leftRoadX, 0, -roadDepth}, {leftRoadX - 11, 0, -roadDepth}, tramStreet, oldStreet) end if (not topRightArm) and (not bottomRightArm) then street.connector({rightRoadX, 0, -roadDepth}, {rightRoadX + 11, 0, -roadDepth}, tramStreet, oldStreet) end if ((topLeftArm and armLow.TL) or (bottomLeftArm and armLow.BL)) and params.modules and params.modules[SLOT.flatTExtensionLeft] then street.connector({leftRoadX, 0, -roadDepth}, {leftRoadX - 16, 0, -roadDepth}, leftElectric, centerOldStreet) end if ((topRightArm and armLow.TR) or (bottomRightArm and armLow.BR)) and params.modules and params.modules[SLOT.flatTExtensionRight] then street.connector({rightRoadX, 0, -roadDepth}, {rightRoadX + 16, 0, -roadDepth}, rightElectric, centerOldStreet) end if topLeftArm then arm({leftRoadX, 0, -roadDepth}, {leftRoadX, armLength, 0}, armTramTL, armOldTL, armLow.TL) end if topRightArm then arm({rightRoadX, 0, -roadDepth}, {rightRoadX, armLength, 0}, armTramTR, armOldTR, armLow.TR) end if bottomLeftArm then arm({leftRoadX, 0, -roadDepth}, {leftRoadX, -armLength, 0}, armTramBL, armOldBL, armLow.BL) end if bottomRightArm then arm({rightRoadX, 0, -roadDepth}, {rightRoadX, -armLength, 0}, armTramBR, armOldBR, armLow.BR) end for _, gap in pairs(middleArmGaps) do if gap.north then arm({gap.x, 0, -roadDepth}, {gap.x, armLength, 0}, gap.northTram, gap.northOld, gap.northLow, true) end if gap.south then arm({gap.x, 0, -roadDepth}, {gap.x, -armLength, 0}, gap.southTram, gap.southOld, gap.southLow, true) end end -- Gelände local terrainInner, terrainOuter = config.planierInner, config.planierOuter local terrainSlope = config.planierSlope local aligns = {} local function align0(face) aligns[#aligns + 1] = { type = "GREATER", faces = { face }, optional = false, slopeLow = terrainSlope, slopeHigh = terrainSlope, triangles = {} } aligns[#aligns + 1] = { type = "LESS", faces = { face }, optional = false, slopeLow = terrainSlope, slopeHigh = terrainSlope, triangles = {} } end local function rectZ(x0, x1, y0, y1, z) align0({ {x0, y0, z}, {x1, y0, z}, {x1, y1, z}, {x0, y1, z} }) end local function rect(x0, x1, y0, y1) align0({ {x0, y0, 0}, {x1, y0, 0}, {x1, y1, 0}, {x0, y1, 0} }) end for i = 1, #xs - 1 do local leftX, rightX = xs[i], xs[i + 1] if rightX - leftX > config.trackDist + 0.01 then local fillLeft = leftX + config.trackDist * 0.5 local fillRight = rightX - config.trackDist * 0.5 local middleGap = middleArmGaps[i] local function fillAroundMiddleArm(y0, y1, active) if not active then rect(fillLeft, fillRight, y0, y1); return end local roadHalf = 8 if fillLeft < middleGap.x - roadHalf then rect(fillLeft, middleGap.x - roadHalf, y0, y1) end if middleGap.x + roadHalf < fillRight then rect(middleGap.x + roadHalf, fillRight, y0, y1) end end if southEnd > config.gap then fillAroundMiddleArm(-southEnd, -config.gap, middleGap and middleGap.south) end if northEnd > config.gap then fillAroundMiddleArm(config.gap, northEnd, middleGap and middleGap.north) end end end if stationRoadMarginLeft > 0 then local fillFromRoad = leftRoadX + config.planierInner if topLeftArm and (bottomLeftArm or not armLow.TL) then rect(fillFromRoad, nearL, config.gap, armLength) end if bottomLeftArm and (topLeftArm or not armLow.BL) then rect(fillFromRoad, nearL, -armLength, -config.gap) end end if stationRoadMarginRight > 0 then local fillToRoad = rightRoadX - config.planierInner if topRightArm and (bottomRightArm or not armLow.TR) then rect(nearR, fillToRoad, config.gap, armLength) end if bottomRightArm and (topRightArm or not armLow.BR) then rect(nearR, fillToRoad, -armLength, -config.gap) end end local outerWallInnerY = armLength - 47 if topLeftArm then if armLow.TL then rectZ(leftRoadX-terrainOuter, leftRoadX-terrainInner, 0, armLength, -roadDepth) else rect(leftRoadX-terrainOuter, leftRoadX-terrainInner, (bottomLeftArm and armLow.BL) and outerWallInnerY or 0, armLength) end end if bottomLeftArm then if armLow.BL then rectZ(leftRoadX-terrainOuter, leftRoadX-terrainInner, -armLength, 0, -roadDepth) else rect(leftRoadX-terrainOuter, leftRoadX-terrainInner, -armLength, (topLeftArm and armLow.TL) and -outerWallInnerY or 0) end end if topRightArm then if armLow.TR then rectZ(rightRoadX+terrainInner, rightRoadX+terrainOuter, 0, armLength, -roadDepth) else rect(rightRoadX+terrainInner, rightRoadX+terrainOuter, (bottomRightArm and armLow.BR) and outerWallInnerY or 0, armLength) end end if bottomRightArm then if armLow.BR then rectZ(rightRoadX+terrainInner, rightRoadX+terrainOuter, -armLength, 0, -roadDepth) else rect(rightRoadX+terrainInner, rightRoadX+terrainOuter, -armLength, (topRightArm and armLow.TR) and -outerWallInnerY or 0) end end local pi = math.pi local arcIn = config.planierArcInner - config.cornerPlanierOverlap local arcOut = config.planierArcOuter local function terrainArcSeg(cx, cy, a0, a1, z) local n, pts = 8, {} for i = 0, n do local a = a0 + (a1 - a0) * i / n pts[#pts + 1] = { cx + arcOut * math.cos(a), cy + arcOut * math.sin(a), z } end for i = n, 0, -1 do local a = a0 + (a1 - a0) * i / n pts[#pts + 1] = { cx + arcIn * math.cos(a), cy + arcIn * math.sin(a), z } end align0(pts) end if (not topLeftArm) and bottomLeftArm and not armLow.BL then terrainArcSeg(leftRoadX, 0, pi/2, pi, 0) rect(leftRoadX, nearL, config.gap, arcOut) end if (not bottomLeftArm) and topLeftArm and not armLow.TL then terrainArcSeg(leftRoadX, 0, pi, 1.5*pi, 0) rect(leftRoadX, nearL, -arcOut, -config.gap) end if (not topRightArm) and bottomRightArm and not armLow.BR then terrainArcSeg(rightRoadX, 0, 0, pi/2, 0) rect(nearR, rightRoadX, config.gap, arcOut) end if (not bottomRightArm) and topRightArm and not armLow.TR then terrainArcSeg(rightRoadX, 0, 1.5*pi, 2*pi, 0) rect(nearR, rightRoadX, -arcOut, -config.gap) end if (not topLeftArm) and bottomLeftArm and armLow.BL then terrainArcSeg(leftRoadX, 0, pi/2, pi, -roadDepth) end if (not bottomLeftArm) and topLeftArm and armLow.TL then terrainArcSeg(leftRoadX, 0, pi, 1.5*pi, -roadDepth) end if (not topRightArm) and bottomRightArm and armLow.BR then terrainArcSeg(rightRoadX, 0, 0, pi/2, -roadDepth) end if (not bottomRightArm) and topRightArm and armLow.TR then terrainArcSeg(rightRoadX, 0, 1.5*pi, 2*pi, -roadDepth) end local bridgeHeadL, bridgeHeadR = nearL - 3.3, nearR + 3.3 local bridgeClear = config.bridgeClear local bridgeRailHalf = config.bridgeClearRailHalf if (not topLeftArm) and (not bottomLeftArm) then rectZ(bridgeHeadL - bridgeClear, bridgeHeadL, -bridgeRailHalf, bridgeRailHalf, -roadDepth) end if (not topRightArm) and (not bottomRightArm) then rectZ(bridgeHeadR, bridgeHeadR + bridgeClear, -bridgeRailHalf, bridgeRailHalf, -roadDepth) end -- Modulkonstruktion local sub = { models = {}, groundFaces = {}, colliders = {}, terrainAlignmentLists = aligns, laneLists = {} } result.subconstructions = { sub } result.slots = { { id = SLOT.streetEra, type = "grimes_underpass_street_era", transf = slotTransf(leftRoadX - 6, -5, 1), spacing = {2,2,2,2}, shape = 0, height = 2, replaceable = true }, { id = SLOT.bridgeMaterial, type = "grimes_underpass_bridge_material", transf = slotTransf(leftRoadX + 6, -5, 1), spacing = {2,2,2,2}, shape = 0, height = 2, replaceable = true }, { id = SLOT.trackKind, type = "grimes_underpass_track_kind", transf = slotTransf(rightRoadX, -5, 1), spacing = {2,2,2,2}, shape = 0, height = 2, replaceable = true }, { id = SLOT.trackCatenary, type = "grimes_underpass_track_catenary", transf = slotTransf(rightRoadX + 6, -5, 1), spacing = {2,2,2,2}, shape = 0, height = 2, replaceable = true }, { id = SLOT.streetEraControl, type = "grimes_underpass_arm", transf = slotTransf(leftRoadX - 3, -5, 1), spacing = {2,2,2,2}, shape = 0, height = 2, replaceable = true }, { id = SLOT.streetTramControl, type = "grimes_underpass_arm_tram", transf = slotTransf(leftRoadX + 3, -5, 1), spacing = {2,2,2,2}, shape = 0, height = 2, replaceable = true }, { id = SLOT.armTL, type = "grimes_underpass_arm", transf = slotTransf(leftRoadX, armLength / 2, -roadDepth / 2), spacing = {6,6,20,20}, shape = 2, height = 8, replaceable = true }, { id = SLOT.armTR, type = "grimes_underpass_arm", transf = slotTransf(rightRoadX, armLength / 2, -roadDepth / 2), spacing = {6,6,20,20}, shape = 2, height = 8, replaceable = true }, { id = SLOT.armBL, type = "grimes_underpass_arm", transf = slotTransf(leftRoadX, -armLength / 2, -roadDepth / 2), spacing = {6,6,20,20}, shape = 2, height = 8, replaceable = true }, { id = SLOT.armBR, type = "grimes_underpass_arm", transf = slotTransf(rightRoadX, -armLength / 2, -roadDepth / 2), spacing = {6,6,20,20}, shape = 2, height = 8, replaceable = true }, } local function addArmStateSlots(slotId, x, y, z, height) local direction = y < 0 and -1 or 1 local tramY = height > 4 and direction * (armLength - 6) or direction * 11.5 local autoY = height > 4 and direction * (armLength - 2) or direction * 14.5 local levelY = height > 4 and direction * (armLength + 2) or direction * 17.5 local stateSpacing = {2,2,1.5,1.5} result.slots[#result.slots + 1] = { id = armTramSlotId(slotId), type = "grimes_underpass_arm_tram", transf = slotTransf(x, tramY, z), spacing = stateSpacing, shape = 2, height = 2, replaceable = true, } result.slots[#result.slots + 1] = { id = armAutoSlotId(slotId), type = "grimes_underpass_arm_auto_length", transf = slotTransf(x, autoY, z), spacing = stateSpacing, shape = 2, height = 2, replaceable = true, } local levelSlotId = armLevelSlotId(slotId) if levelSlotId then result.slots[#result.slots + 1] = { id = levelSlotId, type = "grimes_underpass_arm_level", transf = slotTransf(x, levelY, z), spacing = stateSpacing, shape = 2, height = 2, replaceable = true, } end end addArmStateSlots(SLOT.armTL, leftRoadX, armLength / 2, -roadDepth / 2, 8) addArmStateSlots(SLOT.armTR, rightRoadX, armLength / 2, -roadDepth / 2, 8) addArmStateSlots(SLOT.armBL, leftRoadX, -armLength / 2, -roadDepth / 2, 8) addArmStateSlots(SLOT.armBR, rightRoadX, -armLength / 2, -roadDepth / 2, 8) street.leftTExtensionOccupied = params.modules and params.modules[SLOT.flatTExtensionLeft] ~= nil street.rightTExtensionOccupied = params.modules and params.modules[SLOT.flatTExtensionRight] ~= nil if ((topLeftArm and armLow.TL) or (bottomLeftArm and armLow.BL)) or street.leftTExtensionOccupied then result.slots[#result.slots + 1] = { id = SLOT.flatTExtensionLeft, type = "grimes_underpass_flat_extension", transf = slotTransf180(leftRoadX - 8, 0, -roadDepth + 1), spacing = {8,8,6,6}, shape = 1, height = 2, replaceable = true, } end if ((topRightArm and armLow.TR) or (bottomRightArm and armLow.BR)) or street.rightTExtensionOccupied then result.slots[#result.slots + 1] = { id = SLOT.flatTExtensionRight, type = "grimes_underpass_flat_extension", transf = slotTransf(rightRoadX + 8, 0, -roadDepth + 1), spacing = {8,8,6,6}, shape = 1, height = 2, replaceable = true, } end -- Mittelarme local middleArmSlotOffers = {} local function offerMiddleArmAxis(x, northSlotId, southSlotId) middleArmSlotOffers[northSlotId] = { x = x, north = true } middleArmSlotOffers[southSlotId] = { x = x, north = false } end for _, gap in pairs(middleArmGaps) do if gap.fixed or gap.north or gap.south then offerMiddleArmAxis(gap.x, gap.northSlotId, gap.southSlotId) else local leftHalfRaster = math.floor((gap.leftX / config.trackDist + TRACK_ORIGIN) * 2 + 0.5) local rightHalfRaster = math.floor((gap.rightX / config.trackDist + TRACK_ORIGIN) * 2 + 0.5) for halfRasterSlotId = leftHalfRaster + 1, rightHalfRaster - 1 do local rasterIndex = halfRasterSlotId - MIDDLE_ARM_GRID_FIRST2 local candidateX = (halfRasterSlotId * 0.5 - TRACK_ORIGIN) * config.trackDist if candidateX - gap.leftX >= MIDDLE_ARM_TRACK_CLEARANCE and gap.rightX - candidateX >= MIDDLE_ARM_TRACK_CLEARANCE then offerMiddleArmAxis(candidateX, SLOT.middleArmGridNorthFirst + rasterIndex, SLOT.middleArmGridSouthFirst + rasterIndex) end end end end for slotId, offer in pairs(middleArmSlotOffers) do local armOccupied = params.modules and params.modules[slotId] ~= nil local markerY = armOccupied and (offer.north and armLength / 2 or -armLength / 2) or (offer.north and 5 or -5) local markerZ = armOccupied and -roadDepth / 2 or -roadDepth + 1 local markerSpacing = armOccupied and {6,6,20,20} or {1.1,1.1,4,4} local markerHeight = armOccupied and 8 or 4 result.slots[#result.slots + 1] = { id = slotId, type = "grimes_underpass_arm", transf = slotTransf(offer.x, markerY, markerZ), spacing = markerSpacing, shape = 2, height = markerHeight, replaceable = true, } addArmStateSlots(slotId, offer.x, markerY, markerZ, markerHeight) end result.slotConfig = { grimes_underpass_street_era = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_street_tram = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_bridge_material = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_track_kind = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_track_catenary = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_track_length = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_middle_arm = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_arm = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_arm_tram = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_arm_auto_length = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_arm_level = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_flat_extension = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_track = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_track_catenary_state = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_bridge_group_material = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_station_spacer = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_track_station_passenger = { maxModules = -2, message = "", skipCollisionCheck = true, }, grimes_underpass_track_station_cargo = { maxModules = -2, message = "", skipCollisionCheck = true, }, } -- Gleisplätze local slotMin, slotMax = trackSlotIds[1], trackSlotIds[#trackSlotIds] local offeredTrackSlots = {} local occupiedMiddleArmRaster = {} for _, gap in pairs(middleArmGaps) do if gap.fixed and (gap.north or gap.south) then if gap.halfRasterSlotId % 2 == 0 then occupiedMiddleArmRaster[gap.halfRasterSlotId / 2] = true end end end local function offerTrackSlot(slotId, slotType) local conflictsMiddleArm = occupiedMiddleArmRaster[slotId] == true local trackX = (slotId - TRACK_ORIGIN) * config.trackDist for _, fixed in ipairs(fixedMiddleArms) do if math.abs(trackX - fixed.x) < MIDDLE_ARM_TRACK_CLEARANCE then conflictsMiddleArm = true break end end if slotId >= TRACK_GRID_FIRST and slotId <= TRACK_GRID_LAST and not conflictsMiddleArm then offeredTrackSlots[slotId] = slotType end end for slotId = math.min(slotMin, leftStationSpacerAnchor), math.max(slotMax, rightStationSpacerAnchor) do if trackSlotTypes[slotId] then offeredTrackSlots[slotId] = trackSlotTypes[slotId] else offerTrackSlot(slotId, "grimes_underpass_track") end end if nTracks < MAX_TRACKS then for _, spacer in ipairs(activeOuterStationSpacers) do local firstSlotId = (spacer.startHalfRaster + spacer.direction) * 0.5 for i = 0, (spacer.trackOffset or 2) - 2 do offerTrackSlot(firstSlotId + spacer.direction * i, "grimes_underpass_track") end end if modularPendingSpacerLeft then offerTrackSlot(leftStationSpacerTargetSlotId, "grimes_underpass_track") else offerTrackSlot(slotMin - 1, "grimes_underpass_track") offerTrackSlot(slotMin - 2, "grimes_underpass_track") offerTrackSlot(slotMin - 3, "grimes_underpass_track") end if modularPendingSpacerRight then offerTrackSlot(rightStationSpacerTargetSlotId, "grimes_underpass_track") else offerTrackSlot(slotMax + 1, "grimes_underpass_track") offerTrackSlot(slotMax + 2, "grimes_underpass_track") offerTrackSlot(slotMax + 3, "grimes_underpass_track") end end local offeredTrackSlotIds = {} for slotId in pairs(offeredTrackSlots) do offeredTrackSlotIds[#offeredTrackSlotIds + 1] = slotId end table.sort(offeredTrackSlotIds) for _, slotId in ipairs(offeredTrackSlotIds) do local x = (slotId - TRACK_ORIGIN) * config.trackDist result.slots[#result.slots + 1] = { id = slotId, type = offeredTrackSlots[slotId], transf = slotId > slotMax and slotTransf180(x, 0, 1) or slotTransf(x, 0, 1), spacing = {2,2,3,3}, shape = 1, height = 2, replaceable = true, } end for groupIndex, group in ipairs(bridgeGroups) do local firstX = xs[group.firstIndex] local lastX = xs[group.lastIndex] result.slots[#result.slots + 1] = { id = bridgeGroupSlotId(groupIndex), type = "grimes_underpass_bridge_group_material", transf = slotTransf((firstX + lastX) * 0.5, -7, 1), spacing = {2,2,1.5,1.5}, shape = 1, height = 2, replaceable = true, } end -- Bahnsteig-Spacer local spacerSlots = {} for _, spacer in ipairs(stationSpacers) do spacerSlots[spacer.stateSlotId] = spacer end local function offerStationSpacer(anchorSlotId, direction) local stateSlotId = stationSpacerSlotId(anchorSlotId, direction) if not spacerSlots[stateSlotId] then spacerSlots[stateSlotId] = { stateSlotId = stateSlotId, anchorSlotId = anchorSlotId, direction = direction, trackOffset = 2, startHalfRaster = anchorSlotId * 2 + direction, legacy = true, } end end local function offerChainedStationSpacer(startHalfRaster, direction) if startHalfRaster < STATION_SPACER_HALF_FIRST or startHalfRaster > STATION_SPACER_HALF_LAST then return end local stateSlotId = stationSpacerChainSlotId(startHalfRaster, direction) if not spacerSlots[stateSlotId] then spacerSlots[stateSlotId] = { stateSlotId = stateSlotId, direction = direction, trackOffset = 2, startHalfRaster = startHalfRaster, legacy = false, } end end if #leftStationSpacerChain == 0 then offerStationSpacer(slotMin, -1) else offerChainedStationSpacer(leftStationSpacerEndHalf, -1) end if #rightStationSpacerChain == 0 then offerStationSpacer(slotMax, 1) else offerChainedStationSpacer(rightStationSpacerEndHalf, 1) end for _, spacer in pairs(spacerSlots) do result.slots[#result.slots + 1] = { id = spacer.stateSlotId, type = "grimes_underpass_station_spacer", transf = slotTransf(spacerCenterX(spacer), -10, 1), spacing = {2,2,1.5,1.5}, shape = 1, height = 2, replaceable = true, } end for _, slotId in ipairs(offeredTrackSlotIds) do local x = (slotId - TRACK_ORIGIN) * config.trackDist result.slots[#result.slots + 1] = { id = trackCatenarySlotId(slotId), type = "grimes_underpass_track_catenary_state", transf = slotTransf(x, 7, 1), spacing = {1.5,1.5,1.5,1.5}, shape = 1, height = 2, replaceable = true, } end local lengthSlotHalfDepth = math.max(1, lengthStep / 2 - 0.5) local function addLengthSlots(firstSlot, lastOccupied, direction) local lastOffered = math.min(MAX_LENGTH_MODULES - 1, math.max(0, lastOccupied + 1)) for i = 0, lastOffered do local segmentMid = config.gap + lengthStep * (i + 1.5) result.slots[#result.slots + 1] = { id = firstSlot + i, type = "grimes_underpass_track_length", transf = slotTransf((nearL + nearR) / 2, direction * segmentMid, 5), spacing = { math.max(config.trackDist, (nearR - nearL) / 2 + config.trackDist / 2), math.max(config.trackDist, (nearR - nearL) / 2 + config.trackDist / 2), lengthSlotHalfDepth, lengthSlotHalfDepth }, shape = 0, height = 1, replaceable = true, } end end addLengthSlots(SLOT.lengthSouthFirst, southLastLength, -1) addLengthSlots(SLOT.lengthNorthFirst, northLastLength, 1) -- Straßenkanten result.edgeLists = {} if #street.centerEdgesNoTram > 0 then result.edgeLists[#result.edgeLists + 1] = { type = "STREET", params = { type = street.centerTypeNoTram, tramTrackType = "NO", }, edges = street.centerEdgesNoTram, snapNodes = street.centerSnapNoTram, freeNodes = {}, alignTerrain = true, } end if #street.centerEdgesTram > 0 then result.edgeLists[#result.edgeLists + 1] = { type = "STREET", params = { type = street.centerTypeTram, tramTrackType = "ELECTRIC", }, edges = street.centerEdgesTram, snapNodes = street.centerSnapTram, freeNodes = {}, alignTerrain = true, } end for _, key in ipairs({ "newNoTram", "newTram", "oldNoTram", "oldTram", "newNoTramFlat", "newTramFlat", "oldNoTramFlat", "oldTramFlat", }) do for _, sidewalkSuffix in ipairs({ "", "Sidewalk" }) do local variant = street.armVariants[key .. sidewalkSuffix] if variant and #variant.edges > 0 then result.edgeLists[#result.edgeLists + 1] = { type = "STREET", params = { type = variant.type, tramTrackType = variant.tramTrackType, }, edges = variant.edges, snapNodes = variant.snaps, freeNodes = {}, alignTerrain = true, } end end end for _, key in ipairs({ "newNoTram", "newTram", "oldNoTram", "oldTram" }) do local variant = street.connectorVariants[key] if variant and #variant.edges > 0 then result.edgeLists[#result.edgeLists + 1] = { type = "STREET", params = { type = variant.type, tramTrackType = variant.tramTrackType, }, edges = variant.edges, snapNodes = variant.snaps, freeNodes = {}, alignTerrain = true, } end end -- Gleiskanten if nTracks > 0 then local bridgeGap = config.gap local h = 0.53 local xs = trackXs(trackSlotIds) local lanes = {} result.underpassTrackNodes = {} result.underpassLengthNodes = {} local function appendNodeData(target, slotId, edgeListIndex, nodes) target[slotId] = target[slotId] or {} target[slotId][#target[slotId] + 1] = { edgeListIndex = edgeListIndex, nodes = nodes } end local function buildTrackGroup(kind, electric) local tedges, tfree = {}, {} local trackNodeMap, lengthNodeMap = {}, {} local function rememberNodes(map, slotId, firstNode) map[slotId] = map[slotId] or {} map[slotId][#map[slotId] + 1] = firstNode map[slotId][#map[slotId] + 1] = firstNode + 1 end local function addTrackSegment(trackSlotId, lengthSlotId, x, y0, y1) local firstNode = #tedges straight(tedges, {x, y0, 0}, {x, y1, 0}) if lengthSlotId then rememberNodes(lengthNodeMap, lengthSlotId, firstNode) else rememberNodes(trackNodeMap, trackSlotId, firstNode) end return firstNode end for k = 1, nTracks do local x, trackSlotId = xs[k], trackSlotIds[k] if trackKindFor(trackSlotId) == kind and trackHasCatenary(trackSlotId) == electric then local southOuterNode = addTrackSegment(trackSlotId, nil, x, -(bridgeGap + lengthStep), -bridgeGap) local northCoreNode = addTrackSegment(trackSlotId, nil, x, bridgeGap, bridgeGap + lengthStep) local northOuterNode = northCoreNode + 1 for i = 1, southLengthModules do local inner = bridgeGap + lengthStep * i southOuterNode = addTrackSegment(trackSlotId, southLengthSlotIds[i], x, -(inner + lengthStep), -inner) end for i = 1, northLengthModules do local inner = bridgeGap + lengthStep * i northOuterNode = addTrackSegment(trackSlotId, northLengthSlotIds[i], x, inner, inner + lengthStep) + 1 end tfree[#tfree + 1], tfree[#tfree + 2] = southOuterNode, northOuterNode local laneSpeed = trackHasConcreteBridge(trackSlotId) and config.concreteBridgeSpeed or config.steelBridgeSpeed local lane = laneutil.createLane({ { {x, -bridgeGap, 0}, {x, bridgeGap, 0} } }, electric and { "TRAIN", "ELECTRIC_TRAIN", "TRAM_TRACK", "ELECTRIC_TRAM_TRACK", } or { "TRAIN" }, laneSpeed, config.laneWidth, true) lane.transf = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,h,1 } lane.speedLimit = laneSpeed lanes[#lanes + 1] = lane end end if #tedges == 0 then return end local edgeListIndex = #result.edgeLists + 1 result.edgeLists[edgeListIndex] = { type = "TRACK", params = { type = trackTemplate(kind, electric) }, edges = tedges, snapNodes = tfree, freeNodes = tfree, tag2nodes = {}, } for slotId, nodes in pairs(trackNodeMap) do appendNodeData(result.underpassTrackNodes, slotId, edgeListIndex, nodes) end for slotId, nodes in pairs(lengthNodeMap) do appendNodeData(result.underpassLengthNodes, slotId, edgeListIndex, nodes) end end for _, kind in ipairs({ "SIMPLE", "STANDARD", "HIGH_SPEED" }) do buildTrackGroup(kind, false) buildTrackGroup(kind, true) end sub.laneLists = lanes end -- Brückenmodelle do local xs = trackXs(trackSlotIds) local base = "/infrastructure/underpass/" local models = {} local trackModelMap = {} local spacerModelMap = {} local modelLocalOffset = { beton_bruecke_mitte = { -0.005233, 0, -0.529629 }, beton_bruecke_trennung = { -8.99981, -4.21349, -0.414389 }, bruecke_aussen = { 0, 1.05, 0 }, bruecke_mittelarm_spacer_2_5m_single = { -2.5, 3.75, 0 }, bruecke_mittelarm_spacer_2m_single = { -3, 4, 0 }, } local function put(name, x, dir, slotId, z, y, scaleY) z = z or 0 y = y or 0 scaleY = scaleY or 1 local o = modelLocalOffset[name] or { 0, 0, 0 } local tx, ty = x - o[2] * scaleY, y + o[1] if dir < 0 then tx, ty = x + o[2] * scaleY, y - o[1] end local t = (dir < 0) and { 0,-1,0,0, scaleY, 0,0,0, 0,0,1,0, tx,ty,z + o[3],1 } or { 0, 1,0,0, -scaleY, 0,0,0, 0,0,1,0, tx,ty,z + o[3],1 } if name == "bruecke_aussen" and dir > 0 and slotId == trackSlotIds[1] then t[1] = -t[1] t[2] = -t[2] t[3] = -t[3] end models[#models + 1] = { id = base .. name .. ".mdl", transf = t } if slotId then trackModelMap[slotId] = trackModelMap[slotId] or {} trackModelMap[slotId][#trackModelMap[slotId] + 1] = #models end return #models end local function putHighSpeedTrackModel(name, x, y, slotId) models[#models + 1] = { id = "::/infrastructure/track/high_speed/" .. name .. ".mdl", transf = { 0,1,0,0, -1,0,0,0, 0,0,1,0, x,y,0,1 }, } trackModelMap[slotId] = trackModelMap[slotId] or {} trackModelMap[slotId][#trackModelMap[slotId] + 1] = #models end local function rememberSpacerModel(stateSlotId, modelIndex) spacerModelMap[stateSlotId] = spacerModelMap[stateSlotId] or {} spacerModelMap[stateSlotId][#spacerModelMap[stateSlotId] + 1] = modelIndex end local function putSingleSpacer(name, x, sideDir, ownerSlotId, stateSlotId, z, scaleY) local y = sideDir * (config.gap + 2.5 + 2.0) local modelIndex = put(name, x, sideDir, ownerSlotId, z, y, scaleY) if stateSlotId then rememberSpacerModel(stateSlotId, modelIndex) end end local function putMiddleSpacerInterval(startX, endX, ownerSlotId, sideDir, z, startExtensions, endExtensions) local requiredUnits = math.floor(math.max(0, endX - startX) * 2 + 0.01) if requiredUnits < 4 then return end local counts for n10 = math.floor(requiredUnits / 20), 0, -1 do local after10 = requiredUnits - n10 * 20 for n5 = math.floor(after10 / 10), 0, -1 do local after5 = after10 - n5 * 10 for n2_5 = math.floor(after5 / 5), 0, -1 do local after2_5 = after5 - n2_5 * 5 if after2_5 % 4 == 0 then counts = { n10, n5, n2_5, after2_5 / 4 } break end end if counts then break end end if counts then break end end if not counts then return end local definitions = { { width = 10, name = "bruecke_gbahnhofs_spacer_single" }, { width = 5, name = "bruecke_bahnhofs_spacer_single" }, { width = 2.5, name = "bruecke_mittelarm_spacer_2_5m_single" }, { width = 2, name = "bruecke_mittelarm_spacer_2m_single" }, } local parts = {} for i, definition in ipairs(definitions) do for _ = 1, counts[i] do parts[#parts + 1] = definition end end local cursor = startX for i, part in ipairs(parts) do local baseCenterX = cursor + part.width * 0.5 local function place(oneSide) local startExtension = i == 1 and startExtensions and (startExtensions[oneSide] or 0) or 0 local endExtension = i == #parts and endExtensions and (endExtensions[oneSide] or 0) or 0 local centerX = baseCenterX + (endExtension - startExtension) * 0.5 local scaleY = (part.width + startExtension + endExtension) / part.width putSingleSpacer(part.name, centerX, oneSide, ownerSlotId, nil, z, scaleY) end if sideDir then place(sideDir) else place(1); place(-1) end cursor = cursor + part.width end end local function putStationSpacerModel(name, x, stateSlotId, topOffset, bottomOffset) putSingleSpacer(name, x + (topOffset or 0), 1, nil, stateSlotId) putSingleSpacer(name, x + (bottomOffset or 0), -1, nil, stateSlotId) end local function putSpacerInterval(startX, endX, ownerSlotId, singleDir, stayInside) local shortWidth, wideWidth = 5, 10 local required = math.max(0, endX - startX) if required < 0.01 then return end local bestWide, bestShort, bestCount, bestExcess for wideCount = 0, 20 do for shortCount = 0, 20 do local count = wideCount + shortCount local width = wideCount * wideWidth + shortCount * shortWidth local fits = stayInside and width <= required + 0.01 or not stayInside and width >= required - 0.01 if count > 0 and fits then local excess = stayInside and required - width or width - required if bestExcess == nil or excess < bestExcess - 0.01 or (math.abs(excess - bestExcess) < 0.01 and count < bestCount) or (math.abs(excess - bestExcess) < 0.01 and count == bestCount and wideCount > bestWide) then bestWide, bestShort = wideCount, shortCount bestCount, bestExcess = count, excess end end end end local parts = {} local shortLeft = math.floor((bestShort or 0) / 2) + ((bestShort or 0) % 2) local shortRight = (bestShort or 0) - shortLeft for _ = 1, shortLeft do parts[#parts + 1] = { "bruecke_bahnhofs_spacer_single", shortWidth } end for _ = 1, (bestWide or 0) do parts[#parts + 1] = { "bruecke_gbahnhofs_spacer_single", wideWidth } end for _ = 1, shortRight do parts[#parts + 1] = { "bruecke_bahnhofs_spacer_single", shortWidth } end local totalWidth = 0 for _, part in ipairs(parts) do totalWidth = totalWidth + part[2] end local cursor = (startX + endX - totalWidth) * 0.5 for _, part in ipairs(parts) do local centerX = cursor + part[2] * 0.5 if singleDir then putSingleSpacer(part[1], centerX, singleDir, ownerSlotId) else putSingleSpacer(part[1], centerX, 1, ownerSlotId) putSingleSpacer(part[1], centerX, -1, ownerSlotId) end cursor = cursor + part[2] end end local function putGapSpacers(leftX, rightX, ownerSlotId) putSpacerInterval(leftX + 2.5, rightX - 2.5, ownerSlotId) end local function bridgePrefixFor(slotId) return trackHasConcreteBridge(slotId) and "beton_bruecke_" or "bruecke_" end local function bridgeOuterOffset(slotId) return trackHasConcreteBridge(slotId) and 1.18 or 1.2 end put(bridgePrefixFor(trackSlotIds[1]) .. "aussen", nearL - bridgeOuterOffset(trackSlotIds[1]), 1, trackSlotIds[1]) for i = 1, #xs - 1 do local leftX, rightX = xs[i], xs[i + 1] local gapSteps = math.floor((rightX - leftX) / config.trackDist + 0.5) local ownerSlotId = trackSlotIds[i + 1] if gapSteps == 1 then put(bridgePrefixFor(ownerSlotId) .. (((i - 1) % 2 == 0) and "mitte" or "trennung"), (leftX + rightX) * 0.5, 1, ownerSlotId) else put(bridgePrefixFor(trackSlotIds[i]) .. "aussen", leftX + bridgeOuterOffset(trackSlotIds[i]), -1, trackSlotIds[i]) local middleGap = middleArmGaps[i] if middleGap and (middleGap.north or middleGap.south) then local leftStart = leftX + 2.5 local leftEnd = middleGap.x - middleRoadHalf - middleWallClear local rightStart = middleGap.x + middleRoadHalf + middleWallClear local rightEnd = rightX - 2.5 local armSideDir = middleGap.north and not middleGap.south and 1 or middleGap.south and not middleGap.north and -1 or nil local function putFreeMiddleBand(startX, endX, fillDir) local segments = { { startX, endX } } for _, spacer in ipairs(activeOuterStationSpacers) do local centerX = spacerCenterX(spacer) local halfWidth = spacerWidthHalf(spacer) * config.trackDist * 0.25 local occupiedStart, occupiedEnd = centerX - halfWidth, centerX + halfWidth local nextSegments = {} for _, segment in ipairs(segments) do if occupiedEnd <= segment[1] or occupiedStart >= segment[2] then nextSegments[#nextSegments + 1] = segment else if occupiedStart - segment[1] >= 1.99 then nextSegments[#nextSegments + 1] = { segment[1], occupiedStart } end if segment[2] - occupiedEnd >= 1.99 then nextSegments[#nextSegments + 1] = { occupiedEnd, segment[2] } end end end segments = nextSegments end for _, segment in ipairs(segments) do putMiddleSpacerInterval(segment[1], segment[2], ownerSlotId, fillDir) end end putFreeMiddleBand(leftStart, leftEnd, armSideDir) putFreeMiddleBand(rightStart, rightEnd, armSideDir) if middleGap.north and not middleGap.south then putSpacerInterval(leftX + 2.5, rightX - 2.5, ownerSlotId, -1) elseif middleGap.south and not middleGap.north then putSpacerInterval(leftX + 2.5, rightX - 2.5, ownerSlotId, 1) end else putGapSpacers(leftX, rightX, ownerSlotId) end put(bridgePrefixFor(ownerSlotId) .. "aussen", rightX - bridgeOuterOffset(ownerSlotId), 1, ownerSlotId) end end put(bridgePrefixFor(trackSlotIds[#trackSlotIds]) .. "aussen", nearR + bridgeOuterOffset(trackSlotIds[#trackSlotIds]), -1, trackSlotIds[#trackSlotIds]) local leftOuterSpacer = leftStationSpacerChain[#leftStationSpacerChain] local rightOuterSpacer = rightStationSpacerChain[#rightStationSpacerChain] for _, spacer in ipairs(activeOuterStationSpacers) do local unitCount = (spacer.trackOffset or 2) - 1 local freeUnits = {} for unitIndex = 1, unitCount do local slotId = (spacer.startHalfRaster + spacer.direction) * 0.5 + spacer.direction * (unitIndex - 1) if not trackSlotTypes[slotId] then freeUnits[#freeUnits + 1] = { index = unitIndex, slotId = slotId } end end if #freeUnits == unitCount then local model = spacer.trackOffset == 3 and "bruecke_gbahnhofs_spacer_single" or "bruecke_bahnhofs_spacer_single" local topOffset, bottomOffset = 0, 0 if spacer == leftOuterSpacer then if topLeftArm then topOffset = 0.5 end if bottomLeftArm then bottomOffset = 0.5 end elseif spacer == rightOuterSpacer then if topRightArm then topOffset = -0.5 end if bottomRightArm then bottomOffset = -0.5 end end putStationSpacerModel(model, spacerCenterX(spacer), spacer.stateSlotId, topOffset, bottomOffset) else for _, unit in ipairs(freeUnits) do local topOffset, bottomOffset = 0, 0 local touchesOuterArm = unit.index == unitCount if touchesOuterArm and spacer == leftOuterSpacer then if topLeftArm then topOffset = 0.5 end if bottomLeftArm then bottomOffset = 0.5 end elseif touchesOuterArm and spacer == rightOuterSpacer then if topRightArm then topOffset = -0.5 end if bottomRightArm then bottomOffset = -0.5 end end putStationSpacerModel("bruecke_bahnhofs_spacer_single", (unit.slotId - TRACK_ORIGIN) * config.trackDist, spacer.stateSlotId, topOffset, bottomOffset) end end end if leftStationSpacerChain[1] and leftStationSpacerChain[1].trackOffset == 3 then putCargoGapSpacers(leftStationSpacerAnchor, trackSlotIds[1] - 1, leftStationSpacerChain[1].stateSlotId, putStationSpacerModel) end if rightStationSpacerChain[1] and rightStationSpacerChain[1].trackOffset == 3 then putCargoGapSpacers(trackSlotIds[#trackSlotIds] + 1, rightStationSpacerAnchor, rightStationSpacerChain[1].stateSlotId, putStationSpacerModel) end local function putOuterStationSpacer(leftSide, margin, slotId) if margin <= 0 then return end if margin > 5 then local bridgeEdge = 4.02 local cornerEdge = margin + 1.52 local startX = leftSide and nearL - cornerEdge or nearR + bridgeEdge local endX = leftSide and nearL - bridgeEdge or nearR + cornerEdge local bridgeExtensions = { [-1] = 0.4, [1] = 0.2 } local armExtensions = {} local topArm = leftSide and topLeftArm or topRightArm local bottomArm = leftSide and bottomLeftArm or bottomRightArm if topArm then armExtensions[1] = 0.45 end if bottomArm then armExtensions[-1] = 0.45 end local startExtensions = leftSide and armExtensions or bridgeExtensions local endExtensions = leftSide and bridgeExtensions or armExtensions putMiddleSpacerInterval(startX, endX, slotId, nil, 0.1, startExtensions, endExtensions) return end local topArm = leftSide and topLeftArm or topRightArm local bottomArm = leftSide and bottomLeftArm or bottomRightArm local cornerDir = topArm and not bottomArm and -1 or bottomArm and not topArm and 1 or nil local bridgeEdge = trackHasConcreteBridge(slotId) and 4.12 or 4.14 local cornerEdge = margin + 1.52 local cornerCenterOffset = (bridgeEdge + cornerEdge) * 0.5 for _, sideDir in ipairs({ 1, -1 }) do local centerOffset = sideDir == cornerDir and cornerCenterOffset or margin * 0.5 + 2 local model = sideDir == cornerDir and "bruecke_mittelarm_spacer_2_5m_single" or "bruecke_bahnhofs_spacer_single" local centerX = leftSide and nearL - centerOffset or nearR + centerOffset putSingleSpacer(model, centerX, sideDir, slotId, nil, 0.1) end end if (topLeftArm or bottomLeftArm) and not modularPendingSpacerLeft then putOuterStationSpacer(true, stationRoadMarginLeft, trackSlotIds[1]) end if (topRightArm or bottomRightArm) and not modularPendingSpacerRight then putOuterStationSpacer(false, stationRoadMarginRight, trackSlotIds[#trackSlotIds]) end for i, x in ipairs(xs) do local slotId = trackSlotIds[i] local trackConcreteBridge = trackHasConcreteBridge(slotId) if trackConcreteBridge then putHighSpeedTrackModel("16m_high_speed", x, -0.95, slotId) putHighSpeedTrackModel("2m_high_speed", x, 8.0, slotId) else put("schiene_bruecke", x, 1, slotId, 0.265) end if trackHasCatenary(slotId) then put("oberleitung", x, 1, slotId, 0.52) end end local hasMiddleArms = false for _, gap in pairs(middleArmGaps) do if gap.north or gap.south then hasMiddleArms = true; break end end if topLeftArm or bottomLeftArm or topRightArm or bottomRightArm or hasMiddleArms then local wallLocalOffset = { mauer_aussen = { 0, 0, 0 }, mauer_innen = { -21.5844, -5.64482, -7.6 }, mauer_mitte = { 0, 0, 0 }, mauer_aussenecke = { 0, 0, 0 }, } local wallMirroredLocalOffset = { mauer_aussen = { 0, 0, 0 }, mauer_innen = { 29.5844, -5.64482, -7.6 }, mauer_mitte = { 0, 0, 0 }, mauer_aussenecke = { 0, 0, 0 }, } local function wallM(name, side, tb, x, y, z) local c2 = (side < 0) and -1 or 1 local c3 = (tb > 0) and 1 or -1 local o = wallLocalOffset[name] return { 0, c3, 0, 0, c2, 0, 0, 0, 0,0,1,0, x + c2 * o[2], y + c3 * o[1], o[3] + (z or 0), 1 } end local function wallS(name, side, tb, x, y, z) local c2 = (side < 0) and -1 or 1 local c3 = (tb > 0) and 1 or -1 local o = wallMirroredLocalOffset[name] return { 0, -c3, 0, 0, c2, 0, 0, 0, 0,0,1,0, x + c2 * o[2], y + c3 * o[1], o[3] + (z or 0), 1 } end local function mirroredMdl(name) return name:gsub("%.mdl$", "_s.mdl") end local outerWallY = armLength - 47 * 0.5 local innerWallY = (armLength + config.gap) * 0.5 local leftOuterX, rightOuterX = leftRoadX - 8, rightRoadX + 8 local innerWallShift = 0.05 local leftInnerX, rightInnerX = leftRoadX + 8 + innerWallShift, rightRoadX - 8 - innerWallShift local Trot = wallS("mauer_aussen", -1, 1, leftOuterX, outerWallY) local Tmirror = wallM("mauer_aussen", -1, -1, leftOuterX, -outerWallY) local R_top = wallS("mauer_aussen", 1, 1, rightOuterX, outerWallY) local R_bot = wallM("mauer_aussen", 1, -1, rightOuterX, -outerWallY) if topLeftArm and not armLow.TL then models[#models + 1] = { id = base .. "mauer_aussen_s.mdl", transf = Trot } end if topLeftArm and bottomLeftArm and not armLow.TL and not armLow.BL then models[#models + 1] = { id = base .. "mauer_mitte.mdl", transf = wallS("mauer_mitte", -1, 1, leftOuterX, 0) } end if bottomLeftArm and not armLow.BL then models[#models + 1] = { id = base .. "mauer_aussen.mdl", transf = Tmirror } end if topRightArm and not armLow.TR then models[#models + 1] = { id = base .. "mauer_aussen_s.mdl", transf = R_top } end if topRightArm and bottomRightArm and not armLow.TR and not armLow.BR then models[#models + 1] = { id = base .. "mauer_mitte.mdl", transf = wallM("mauer_mitte", 1, -1, rightOuterX, 0) } end if bottomRightArm and not armLow.BR then models[#models + 1] = { id = base .. "mauer_aussen.mdl", transf = R_bot } end local eckeRb = wallS("mauer_aussenecke", 1, -1, rightRoadX + 3.4, 0.05) local eckeRt = wallM("mauer_aussenecke", 1, 1, rightRoadX + 3.4, 0.05) local eckeLb = wallM("mauer_aussenecke", -1, -1, leftRoadX - 3.4, 0.05) local eckeLt = wallS("mauer_aussenecke", -1, 1, leftRoadX - 3.4, 0.05) local standardCornerModel = "mauer_aussenecke.mdl" local stationCornerModel = "mauer_aussenecke_pgbahnhof.mdl" local leftCornerModel = stationRoadMarginLeft > 0 and stationCornerModel or standardCornerModel local rightCornerModel = stationRoadMarginRight > 0 and stationCornerModel or standardCornerModel if bottomRightArm and (not topRightArm) and not armLow.BR then models[#models + 1] = { id = base .. mirroredMdl(rightCornerModel), transf = eckeRb } end if topRightArm and (not bottomRightArm) and not armLow.TR then models[#models + 1] = { id = base .. rightCornerModel, transf = eckeRt } end if bottomLeftArm and (not topLeftArm) and not armLow.BL then models[#models + 1] = { id = base .. leftCornerModel, transf = eckeLb } end if topLeftArm and (not bottomLeftArm) and not armLow.TL then models[#models + 1] = { id = base .. mirroredMdl(leftCornerModel), transf = eckeLt } end local innerWallModel = "mauer_innen.mdl" local stationInnerWallModel = "mauer_innen_bahnhof.mdl" local leftRampInnerWall = stationRoadMarginLeft > 0 and stationInnerWallModel or innerWallModel local rightRampInnerWall = stationRoadMarginRight > 0 and stationInnerWallModel or innerWallModel local leftRampInnerY = innerWallY local rightRampInnerY = innerWallY local leftRampInnerZ = stationRoadMarginLeft > 0 and 0.0 or 0 local rightRampInnerZ = stationRoadMarginRight > 0 and 0.0 or 0 if topLeftArm then models[#models + 1] = { id = base .. leftRampInnerWall, transf = wallM("mauer_innen", -1, 1, leftInnerX, leftRampInnerY, leftRampInnerZ) } end if bottomLeftArm then models[#models + 1] = { id = base .. mirroredMdl(leftRampInnerWall), transf = wallS("mauer_innen", -1, -1, leftInnerX, -leftRampInnerY, leftRampInnerZ) } end if topRightArm then models[#models + 1] = { id = base .. rightRampInnerWall, transf = wallM("mauer_innen", 1, 1, rightInnerX, rightRampInnerY, rightRampInnerZ) } end if bottomRightArm then models[#models + 1] = { id = base .. mirroredMdl(rightRampInnerWall), transf = wallS("mauer_innen", 1, -1, rightInnerX, -rightRampInnerY, rightRampInnerZ) } end for _, gap in pairs(middleArmGaps) do local leftFreeWidth = (gap.x - middleRoadHalf - middleWallClear) - (gap.leftX + 2.5) local rightFreeWidth = (gap.rightX - 2.5) - (gap.x + middleRoadHalf + middleWallClear) local leftMiddleWall = leftFreeWidth >= 1.99 and stationInnerWallModel or innerWallModel local rightMiddleWall = rightFreeWidth >= 1.99 and stationInnerWallModel or innerWallModel local leftMiddleY = innerWallY local rightMiddleY = innerWallY if gap.north then models[#models + 1] = { id = base .. mirroredMdl(leftMiddleWall), transf = wallS("mauer_innen", 1, 1, gap.x - middleRoadHalf - innerWallShift, leftMiddleY) } models[#models + 1] = { id = base .. rightMiddleWall, transf = wallM("mauer_innen", -1, 1, gap.x + middleRoadHalf + innerWallShift, rightMiddleY) } end if gap.south then models[#models + 1] = { id = base .. leftMiddleWall, transf = wallM("mauer_innen", 1, -1, gap.x - middleRoadHalf - innerWallShift, -leftMiddleY) } models[#models + 1] = { id = base .. mirroredMdl(rightMiddleWall), transf = wallS("mauer_innen", -1, -1, gap.x + middleRoadHalf + innerWallShift, -rightMiddleY) } end end end sub.models = models result.underpassTrackModels = trackModelMap result.underpassSpacerModels = spacerModelMap end -- Baukosten local function edgeLength(edges) local length = 0 for i = 1, #edges, 2 do local p0 = edges[i][1] local p1 = edges[i + 1][1] local dx, dy, dz = p1[1] - p0[1], p1[2] - p0[2], p1[3] - p0[3] length = length + math.sqrt(dx * dx + dy * dy + dz * dz) end return length end local function streetRates(resource) local old = string.find(resource, "_old_", 1, true) ~= nil local tram = string.find(resource, "tram_electrified", 1, true) ~= nil if old then return tram and 90 or 60, 10 end return tram and 270 or 180, 30 end local function trackRates(kind) if kind == "SIMPLE" then return 60, 10 end if kind == "HIGH_SPEED" then return 540, 90 end return 180, 30 end local cost, maintenanceCost = 0, 0 for _, edgeList in ipairs(result.edgeLists or {}) do local length = edgeLength(edgeList.edges) if edgeList.type == "STREET" then local price, maintenance = streetRates(edgeList.params.type) cost = cost + length * price maintenanceCost = maintenanceCost + length * maintenance elseif edgeList.type == "TRACK" then local resource = edgeList.params.type local kind = string.find(resource, "/simple/", 1, true) and "SIMPLE" or string.find(resource, "/high_speed/", 1, true) and "HIGH_SPEED" or "STANDARD" local price, maintenance = trackRates(kind) cost = cost + length * price maintenanceCost = maintenanceCost + length * maintenance end end local bridgeLength = config.gap * 2 local bridgeCostFactor = (roadDepth / 10) ^ 2.5 + 1 for _, slotId in ipairs(trackSlotIds) do local price, maintenance = trackRates(trackKindFor(slotId)) cost = cost + bridgeLength * price maintenanceCost = maintenanceCost + bridgeLength * maintenance cost = cost + bridgeLength * bridgeCostFactor * 200 end result.cost = math.floor(cost + 0.5) result.maintenanceCost = math.floor(maintenanceCost + 0.5) return result end function data() return { createTemplateFn = createTemplateFn, updateFn = updateFn, } end