- Title in README + GDD, UI/log prefixes FnF -> Wangan - Rojo project name -> TokyoWanganRacing - Fix undefined `H` in CarManager seat CFrame (selene error) -> Chassis.BODY_SIZE.Y
708 lines
21 KiB
Luau
708 lines
21 KiB
Luau
--!strict
|
|
--[[
|
|
MapBuilder.luau (SERVER)
|
|
v3 — "Shuto Night Loop": a Tokyo-expressway-inspired CIRCUIT, still built
|
|
100% from code. The straight strip is gone; the road is now a closed
|
|
rounded-rectangle loop (~7,400 studs/lap) modelled on the Shuto C1 vibe,
|
|
with real-Tokyo landmark zones:
|
|
|
|
SOUTH straight — Daikoku Futo PA: the legendary meet spot. A big
|
|
drivable parking area off the expressway with painted
|
|
bays, light towers, and the spawn pad. Cars spawn HERE.
|
|
EAST straight — Rainbow Bridge: white suspension towers, cross beams,
|
|
rainbow-lit rails. Odaiba sits beyond it: a blocky
|
|
Unicorn-Gundam-style statue + a giant neon ferris
|
|
wheel (Palette Town wheel).
|
|
NORTH straight — Yamate-style tunnel: long enclosed bore with strip
|
|
lights (the C1/Yamate underground feel).
|
|
WEST straight — City skyline run past a Tokyo Tower replica
|
|
(orange/white, twin decks, antenna).
|
|
Everywhere — procedural neon skyline inside and outside the loop,
|
|
Tatsumi-PA-style overlook pad (visual, NE).
|
|
|
|
NOT geographically accurate — it's a themed driving loop, not a map of
|
|
Tokyo. Gameplay parts keep their contract names (SpawnPad, FinishLine,
|
|
KillWall_*) so nothing else changes.
|
|
]]
|
|
|
|
local Workspace = game:GetService("Workspace")
|
|
|
|
local MapBuilder = {}
|
|
|
|
-- Loop geometry ----------------------------------------------------------------
|
|
local ROAD_WIDTH = 60
|
|
local HX = 1200 -- half-extent X
|
|
local HZ = 800 -- half-extent Z
|
|
local CORNER_R = 300
|
|
local STEP = 60 -- straight sample spacing
|
|
local ARC_STEPS = 12 -- samples per 90-degree corner
|
|
local WALL_HEIGHT = 26
|
|
|
|
local NEON_PINK = Color3.fromRGB(255, 40, 120)
|
|
local NEON_BLUE = Color3.fromRGB(40, 160, 255)
|
|
local NEON_TEAL = Color3.fromRGB(40, 230, 200)
|
|
local RAINBOW = {
|
|
Color3.fromRGB(255, 60, 60),
|
|
Color3.fromRGB(255, 160, 40),
|
|
Color3.fromRGB(250, 230, 60),
|
|
Color3.fromRGB(80, 230, 90),
|
|
Color3.fromRGB(60, 150, 255),
|
|
Color3.fromRGB(170, 90, 255),
|
|
}
|
|
|
|
local UP = Vector3.new(0, 1, 0)
|
|
|
|
local function part(props: { [string]: any }): Part
|
|
local p = Instance.new("Part")
|
|
p.Anchored = true
|
|
p.TopSurface = Enum.SurfaceType.Smooth
|
|
p.BottomSurface = Enum.SurfaceType.Smooth
|
|
for k, v in pairs(props) do
|
|
(p :: any)[k] = v
|
|
end
|
|
return p
|
|
end
|
|
|
|
-- Deterministic pseudo-random for scenery (same city every server) ------------
|
|
local function det(seed: number): number
|
|
local x = math.sin(seed * 12.9898) * 43758.5453
|
|
return x - math.floor(x)
|
|
end
|
|
|
|
-- Green highway sign with bilingual text ---------------------------------------
|
|
local function makeSign(text: string, cf: CFrame, parent: Instance)
|
|
local board = part({
|
|
Name = "Sign",
|
|
Size = Vector3.new(30, 9, 1),
|
|
CFrame = cf,
|
|
Color = Color3.fromRGB(18, 90, 60),
|
|
Material = Enum.Material.SmoothPlastic,
|
|
})
|
|
board.Parent = parent
|
|
local gui = Instance.new("SurfaceGui")
|
|
gui.Face = Enum.NormalId.Front
|
|
gui.CanvasSize = Vector2.new(600, 180)
|
|
gui.Parent = board
|
|
local label = Instance.new("TextLabel")
|
|
label.Size = UDim2.fromScale(1, 1)
|
|
label.BackgroundTransparency = 1
|
|
label.Font = Enum.Font.GothamBold
|
|
label.TextColor3 = Color3.new(1, 1, 1)
|
|
label.TextScaled = true
|
|
label.Text = text
|
|
label.Parent = gui
|
|
end
|
|
|
|
-- Centerline samples ------------------------------------------------------------
|
|
type Sample = { pos: Vector3, piece: string }
|
|
|
|
local function genSamples(): { Sample }
|
|
local samples: { Sample } = {}
|
|
local sx = HX - CORNER_R
|
|
local sz = HZ - CORNER_R
|
|
|
|
local function push(pos: Vector3, piece: string)
|
|
table.insert(samples, { pos = pos, piece = piece })
|
|
end
|
|
local function arc(cx: number, cz: number, a0: number, piece: string)
|
|
for i = 1, ARC_STEPS do
|
|
local a = a0 + (i / ARC_STEPS) * (math.pi / 2)
|
|
push(Vector3.new(cx + CORNER_R * math.sin(a), 0, cz + CORNER_R * math.cos(a)), piece)
|
|
end
|
|
end
|
|
|
|
-- South straight (Daikoku side), heading +X
|
|
local x = -sx
|
|
while x <= sx do
|
|
push(Vector3.new(x, 0, HZ), "south")
|
|
x += STEP
|
|
end
|
|
arc(sx, sz, 0, "corner_se")
|
|
-- East straight (Rainbow Bridge), heading -Z
|
|
local z = sz - STEP
|
|
while z >= -sz do
|
|
push(Vector3.new(HX, 0, z), "east")
|
|
z -= STEP
|
|
end
|
|
arc(sx, -sz, math.pi / 2, "corner_ne")
|
|
-- North straight (Yamate tunnel), heading -X
|
|
x = sx - STEP
|
|
while x >= -sx do
|
|
push(Vector3.new(x, 0, -HZ), "north")
|
|
x -= STEP
|
|
end
|
|
arc(-sx, -sz, math.pi, "corner_nw")
|
|
-- West straight (Tokyo Tower run), heading +Z
|
|
z = -sz + STEP
|
|
while z <= sz do
|
|
push(Vector3.new(-HX, 0, z), "west")
|
|
z += STEP
|
|
end
|
|
arc(-sx, sz, 3 * math.pi / 2, "corner_sw")
|
|
|
|
return samples
|
|
end
|
|
|
|
-- Daikoku PA entrance gap on the south straight's OUTER side -------------------
|
|
local DAIKOKU_GAP_HALF = 270
|
|
|
|
local function isDaikokuGap(mid: Vector3, piece: string, outer: boolean): boolean
|
|
return piece == "south" and outer and math.abs(mid.X) < DAIKOKU_GAP_HALF
|
|
end
|
|
|
|
-- The loop road itself ----------------------------------------------------------
|
|
local function buildLoop(map: Folder, samples: { Sample })
|
|
local roadFolder = Instance.new("Folder")
|
|
roadFolder.Name = "Expressway"
|
|
roadFolder.Parent = map
|
|
|
|
local n = #samples
|
|
for i = 1, n do
|
|
local a = samples[i]
|
|
local b = samples[(i % n) + 1]
|
|
local delta = b.pos - a.pos
|
|
local len = delta.Magnitude + 2
|
|
local mid = (a.pos + b.pos) / 2
|
|
local dir = delta.Unit
|
|
local right = dir:Cross(UP) -- always points OUTWARD on this loop
|
|
local cf = CFrame.lookAt(mid, mid + dir)
|
|
|
|
local road = part({
|
|
Name = "Road_" .. i,
|
|
Size = Vector3.new(ROAD_WIDTH, 2, len),
|
|
CFrame = cf,
|
|
Color = Color3.fromRGB(28, 28, 34),
|
|
Material = Enum.Material.Asphalt,
|
|
})
|
|
road.Parent = roadFolder
|
|
|
|
local line = part({
|
|
Name = "Lane_" .. i,
|
|
Size = Vector3.new(1, 2.1, len),
|
|
CFrame = cf * CFrame.new(0, 0.1, 0),
|
|
Color = Color3.fromRGB(230, 200, 60),
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
line.Parent = roadFolder
|
|
|
|
local inTunnel = a.piece == "north"
|
|
for _, side in ipairs({ -1, 1 }) do
|
|
local outer = side == 1
|
|
if isDaikokuGap(mid, a.piece, outer) then
|
|
continue
|
|
end
|
|
local off = right * (side * (ROAD_WIDTH / 2))
|
|
local rail = part({
|
|
Name = "Guardrail",
|
|
Size = Vector3.new(1.5, 4, len),
|
|
CFrame = CFrame.lookAt(mid + off, mid + off + dir) * CFrame.new(0, 2, 0),
|
|
Color = Color3.fromRGB(180, 180, 190),
|
|
Material = Enum.Material.Metal,
|
|
})
|
|
rail.Parent = map
|
|
|
|
local woff = right * (side * (ROAD_WIDTH / 2 + 2))
|
|
local wall = part({
|
|
Name = "KillWall",
|
|
Size = Vector3.new(2, WALL_HEIGHT, len + 6),
|
|
CFrame = CFrame.lookAt(mid + woff, mid + woff + dir) * CFrame.new(0, WALL_HEIGHT / 2, 0),
|
|
Transparency = 1,
|
|
CanCollide = true,
|
|
})
|
|
wall.Parent = map
|
|
|
|
-- Rainbow rail lights on the bridge; neon pylons elsewhere (not in tunnel)
|
|
if a.piece == "east" then
|
|
local lamp = part({
|
|
Name = "BridgeLight",
|
|
Size = Vector3.new(1.2, 1.2, 1.2),
|
|
CFrame = CFrame.new(mid + off + Vector3.new(0, 4.6, 0)),
|
|
Color = RAINBOW[(i % #RAINBOW) + 1],
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
lamp.Parent = map
|
|
elseif not inTunnel and i % 3 == 0 then
|
|
local pylon = part({
|
|
Name = "Pylon",
|
|
Size = Vector3.new(1, 14, 1),
|
|
CFrame = CFrame.new(mid + right * (side * (ROAD_WIDTH / 2 + 3)) + Vector3.new(0, 7, 0)),
|
|
Color = outer and NEON_PINK or NEON_BLUE,
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
pylon.Parent = map
|
|
end
|
|
end
|
|
|
|
-- Yamate tunnel bore over the north straight -----------------------------
|
|
if inTunnel then
|
|
local h = 22
|
|
for _, side in ipairs({ -1, 1 }) do
|
|
local woff = right * (side * (ROAD_WIDTH / 2 + 1.5))
|
|
local wallT = part({
|
|
Name = "TunnelWall",
|
|
Size = Vector3.new(3, h, len + 2),
|
|
CFrame = CFrame.lookAt(mid + woff, mid + woff + dir) * CFrame.new(0, h / 2, 0),
|
|
Color = Color3.fromRGB(38, 38, 46),
|
|
Material = Enum.Material.Concrete,
|
|
})
|
|
wallT.Parent = map
|
|
end
|
|
local ceiling = part({
|
|
Name = "TunnelCeiling",
|
|
Size = Vector3.new(ROAD_WIDTH + 6, 2, len + 2),
|
|
CFrame = cf * CFrame.new(0, h + 1, 0),
|
|
Color = Color3.fromRGB(30, 30, 38),
|
|
Material = Enum.Material.Concrete,
|
|
})
|
|
ceiling.Parent = map
|
|
if i % 2 == 0 then
|
|
local strip = part({
|
|
Name = "TunnelLight",
|
|
Size = Vector3.new(ROAD_WIDTH * 0.5, 0.6, 2),
|
|
CFrame = cf * CFrame.new(0, h - 0.5, 0),
|
|
Color = i % 4 == 0 and NEON_BLUE or NEON_TEAL,
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
strip.Parent = map
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Daikoku Futo PA ----------------------------------------------------------------
|
|
local function buildDaikoku(map: Folder)
|
|
local folder = Instance.new("Folder")
|
|
folder.Name = "DaikokuPA"
|
|
folder.Parent = map
|
|
|
|
local z0 = HZ + ROAD_WIDTH / 2 -- pad starts at the road's outer edge
|
|
local depth = 220
|
|
local halfW = 260
|
|
|
|
local pad = part({
|
|
Name = "DaikokuPad",
|
|
Size = Vector3.new(halfW * 2, 2, depth),
|
|
CFrame = CFrame.new(0, 0, z0 + depth / 2),
|
|
Color = Color3.fromRGB(34, 34, 40),
|
|
Material = Enum.Material.Asphalt,
|
|
})
|
|
pad.Parent = folder
|
|
|
|
-- Painted parking bays (two rows, like the real lot's herringbone rows)
|
|
for row = 0, 1 do
|
|
local zRow = z0 + 70 + row * 90
|
|
for bx = -5, 5 do
|
|
local lineP = part({
|
|
Name = "BayLine",
|
|
Size = Vector3.new(1, 2.1, 22),
|
|
CFrame = CFrame.new(bx * 42, 0.1, zRow),
|
|
Color = Color3.fromRGB(230, 230, 235),
|
|
Material = Enum.Material.SmoothPlastic,
|
|
})
|
|
lineP.Parent = folder
|
|
end
|
|
end
|
|
|
|
-- Light towers (the tall lot floods) + perimeter kill walls
|
|
for _, corner in ipairs({
|
|
{ -halfW + 20, z0 + 30 },
|
|
{ halfW - 20, z0 + 30 },
|
|
{ -halfW + 20, z0 + depth - 20 },
|
|
{ halfW - 20, z0 + depth - 20 },
|
|
}) do
|
|
local px, pz = corner[1], corner[2]
|
|
local pole = part({
|
|
Name = "LightPole",
|
|
Size = Vector3.new(2, 34, 2),
|
|
CFrame = CFrame.new(px, 17, pz),
|
|
Color = Color3.fromRGB(120, 120, 130),
|
|
Material = Enum.Material.Metal,
|
|
})
|
|
pole.Parent = folder
|
|
local head = part({
|
|
Name = "LightHead",
|
|
Size = Vector3.new(8, 1.5, 8),
|
|
CFrame = CFrame.new(px, 34.5, pz),
|
|
Color = Color3.fromRGB(255, 240, 200),
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
head.Parent = folder
|
|
end
|
|
|
|
-- Perimeter walls (invisible) on the three non-road sides
|
|
local wallDefs = {
|
|
{ Vector3.new(halfW * 2 + 4, WALL_HEIGHT, 2), CFrame.new(0, WALL_HEIGHT / 2, z0 + depth + 1) },
|
|
{ Vector3.new(2, WALL_HEIGHT, depth + 4), CFrame.new(-halfW - 1, WALL_HEIGHT / 2, z0 + depth / 2) },
|
|
{ Vector3.new(2, WALL_HEIGHT, depth + 4), CFrame.new(halfW + 1, WALL_HEIGHT / 2, z0 + depth / 2) },
|
|
}
|
|
for _, def in ipairs(wallDefs) do
|
|
local w = part({
|
|
Name = "KillWall_PA",
|
|
Size = def[1],
|
|
CFrame = def[2],
|
|
Transparency = 1,
|
|
CanCollide = true,
|
|
})
|
|
w.Parent = folder
|
|
end
|
|
|
|
-- Spawn pad (cars appear here — Daikoku is the meet) -----------------------
|
|
-- Subtle painted zone, NOT neon: the cars are the stars of the meet, the
|
|
-- floor must never outshine them.
|
|
local spawn = part({
|
|
Name = "SpawnPad",
|
|
Size = Vector3.new(120, 1, 60),
|
|
CFrame = CFrame.new(0, 1.2, z0 + 130),
|
|
Color = Color3.fromRGB(45, 60, 55),
|
|
Material = Enum.Material.Asphalt,
|
|
Transparency = 0,
|
|
})
|
|
spawn.Parent = folder
|
|
|
|
makeSign("大黒PA DAIKOKU PA", CFrame.new(0, 12, z0 + 4) * CFrame.Angles(0, math.pi, 0), folder)
|
|
end
|
|
|
|
-- Rainbow Bridge towers over the east straight -----------------------------------
|
|
local function buildRainbowBridge(map: Folder)
|
|
local folder = Instance.new("Folder")
|
|
folder.Name = "RainbowBridge"
|
|
folder.Parent = map
|
|
|
|
for _, tz in ipairs({ 250, -250 }) do
|
|
for _, side in ipairs({ -1, 1 }) do
|
|
local tx = HX + side * (ROAD_WIDTH / 2 + 5)
|
|
local tower = part({
|
|
Name = "BridgeTower",
|
|
Size = Vector3.new(6, 95, 6),
|
|
CFrame = CFrame.new(tx, 47.5, tz),
|
|
Color = Color3.fromRGB(235, 235, 240),
|
|
Material = Enum.Material.Concrete,
|
|
})
|
|
tower.Parent = folder
|
|
local beacon = part({
|
|
Name = "TowerBeacon",
|
|
Size = Vector3.new(2, 2, 2),
|
|
CFrame = CFrame.new(tx, 96, tz),
|
|
Color = Color3.fromRGB(255, 80, 80),
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
beacon.Parent = folder
|
|
end
|
|
local beam = part({
|
|
Name = "TowerBeam",
|
|
Size = Vector3.new(ROAD_WIDTH + 22, 4, 5),
|
|
CFrame = CFrame.new(HX, 62, tz),
|
|
Color = Color3.fromRGB(235, 235, 240),
|
|
Material = Enum.Material.Concrete,
|
|
})
|
|
beam.Parent = folder
|
|
end
|
|
makeSign(
|
|
"レインボーブリッジ RAINBOW BRIDGE",
|
|
CFrame.new(HX, 14, 330) * CFrame.Angles(0, math.pi / 2, 0),
|
|
folder
|
|
)
|
|
end
|
|
|
|
-- Odaiba: blocky Unicorn-Gundam-style statue + giant ferris wheel ----------------
|
|
local function buildOdaiba(map: Folder)
|
|
local folder = Instance.new("Folder")
|
|
folder.Name = "Odaiba"
|
|
folder.Parent = map
|
|
|
|
local gx, gz = HX + 150, 480 -- statue stands beyond the bridge's south end
|
|
local white = Color3.fromRGB(235, 235, 240)
|
|
|
|
local function block(name: string, size: Vector3, cf: CFrame, color: Color3, mat: Enum.Material?)
|
|
local b = part({
|
|
Name = name,
|
|
Size = size,
|
|
CFrame = cf,
|
|
Color = color,
|
|
Material = mat or Enum.Material.SmoothPlastic,
|
|
})
|
|
b.Parent = folder
|
|
return b
|
|
end
|
|
|
|
-- Base plinth
|
|
block(
|
|
"GundamBase",
|
|
Vector3.new(40, 4, 40),
|
|
CFrame.new(gx, 2, gz),
|
|
Color3.fromRGB(60, 60, 70),
|
|
Enum.Material.Concrete
|
|
)
|
|
-- Legs
|
|
block("GundamLegL", Vector3.new(7, 20, 9), CFrame.new(gx - 6, 14, gz), white)
|
|
block("GundamLegR", Vector3.new(7, 20, 9), CFrame.new(gx + 6, 14, gz), white)
|
|
-- Hips + torso
|
|
block("GundamHips", Vector3.new(16, 6, 10), CFrame.new(gx, 27, gz), white)
|
|
block("GundamTorso", Vector3.new(15, 14, 10), CFrame.new(gx, 37, gz), white)
|
|
block("GundamChest", Vector3.new(9, 5, 3), CFrame.new(gx, 39, gz - 6), Color3.fromRGB(60, 90, 220))
|
|
block("GundamCore", Vector3.new(4, 3, 2), CFrame.new(gx, 34, gz - 6), Color3.fromRGB(230, 60, 60))
|
|
-- Shoulders + arms
|
|
block("GundamShoulderL", Vector3.new(6, 5, 7), CFrame.new(gx - 11, 42, gz), white)
|
|
block("GundamShoulderR", Vector3.new(6, 5, 7), CFrame.new(gx + 11, 42, gz), white)
|
|
block("GundamArmL", Vector3.new(4.5, 16, 5), CFrame.new(gx - 11, 32, gz), white)
|
|
block("GundamArmR", Vector3.new(4.5, 16, 5), CFrame.new(gx + 11, 32, gz), white)
|
|
-- Head with glowing eyes + unicorn horn
|
|
block("GundamHead", Vector3.new(6, 5.5, 5.5), CFrame.new(gx, 47, gz), white)
|
|
block("GundamEyes", Vector3.new(4, 1, 0.6), CFrame.new(gx, 47.5, gz - 2.8), NEON_TEAL, Enum.Material.Neon)
|
|
block(
|
|
"GundamHorn",
|
|
Vector3.new(0.8, 6, 0.8),
|
|
CFrame.new(gx, 53, gz - 1) * CFrame.Angles(math.rad(15), 0, 0),
|
|
Color3.fromRGB(250, 220, 90),
|
|
Enum.Material.Neon
|
|
)
|
|
-- Psycho-frame glow strips on the legs/torso (the Unicorn's pink frame)
|
|
block("GundamFrameL", Vector3.new(1, 14, 1), CFrame.new(gx - 6, 14, gz - 4.6), NEON_PINK, Enum.Material.Neon)
|
|
block("GundamFrameR", Vector3.new(1, 14, 1), CFrame.new(gx + 6, 14, gz - 4.6), NEON_PINK, Enum.Material.Neon)
|
|
|
|
-- Giant ferris wheel (Palette Town) -----------------------------------------
|
|
local wx, wz = HX + 240, 120
|
|
local radius = 55
|
|
local hub = CFrame.new(wx, radius + 24, wz)
|
|
block(
|
|
"WheelSupportL",
|
|
Vector3.new(4, radius + 26, 4),
|
|
CFrame.new(wx - 18, (radius + 24) / 2, wz) * CFrame.Angles(0, 0, math.rad(-14)),
|
|
Color3.fromRGB(120, 120, 130),
|
|
Enum.Material.Metal
|
|
)
|
|
block(
|
|
"WheelSupportR",
|
|
Vector3.new(4, radius + 26, 4),
|
|
CFrame.new(wx + 18, (radius + 24) / 2, wz) * CFrame.Angles(0, 0, math.rad(14)),
|
|
Color3.fromRGB(120, 120, 130),
|
|
Enum.Material.Metal
|
|
)
|
|
block("WheelHub", Vector3.new(6, 6, 6), hub, Color3.fromRGB(200, 200, 210), Enum.Material.Metal)
|
|
for k = 1, 16 do
|
|
local ang = (k / 16) * math.pi * 2
|
|
local cab = part({
|
|
Name = "WheelCabin",
|
|
Size = Vector3.new(4, 4, 4),
|
|
Shape = Enum.PartType.Ball,
|
|
CFrame = hub * CFrame.new(math.cos(ang) * radius, math.sin(ang) * radius, 0),
|
|
Color = RAINBOW[(k % #RAINBOW) + 1],
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
cab.Parent = folder
|
|
if k % 2 == 0 then
|
|
local spoke = part({
|
|
Name = "WheelSpoke",
|
|
Size = Vector3.new(0.8, radius, 0.8),
|
|
CFrame = hub * CFrame.Angles(0, 0, ang) * CFrame.new(0, radius / 2, 0),
|
|
Color = Color3.fromRGB(150, 150, 160),
|
|
Material = Enum.Material.Metal,
|
|
})
|
|
spoke.Parent = folder
|
|
end
|
|
end
|
|
|
|
makeSign("お台場 ODAIBA", CFrame.new(HX + 60, 12, 300) * CFrame.Angles(0, math.pi / 2, 0), folder)
|
|
end
|
|
|
|
-- Tokyo Tower replica on the west side --------------------------------------------
|
|
local function buildTokyoTower(map: Folder)
|
|
local folder = Instance.new("Folder")
|
|
folder.Name = "TokyoTower"
|
|
folder.Parent = map
|
|
|
|
local tx, tz = -HX - 220, 0
|
|
local orange = Color3.fromRGB(235, 100, 40)
|
|
local levels = {
|
|
{ w = 64, h = 50, c = orange },
|
|
{ w = 44, h = 45, c = Color3.fromRGB(240, 240, 245) },
|
|
{ w = 28, h = 45, c = orange },
|
|
{ w = 15, h = 40, c = Color3.fromRGB(240, 240, 245) },
|
|
}
|
|
local y = 0
|
|
for li, lv in ipairs(levels) do
|
|
local sec = part({
|
|
Name = "TowerSection" .. li,
|
|
Size = Vector3.new(lv.w, lv.h, lv.w),
|
|
CFrame = CFrame.new(tx, y + lv.h / 2, tz),
|
|
Color = lv.c,
|
|
Material = Enum.Material.Metal,
|
|
})
|
|
sec.Parent = folder
|
|
y += lv.h
|
|
end
|
|
-- Observation decks + antenna
|
|
local deck1 = part({
|
|
Name = "MainDeck",
|
|
Size = Vector3.new(36, 8, 36),
|
|
CFrame = CFrame.new(tx, 98, tz),
|
|
Color = Color3.fromRGB(250, 245, 235),
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
deck1.Parent = folder
|
|
local deck2 = part({
|
|
Name = "TopDeck",
|
|
Size = Vector3.new(20, 6, 20),
|
|
CFrame = CFrame.new(tx, 152, tz),
|
|
Color = Color3.fromRGB(250, 245, 235),
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
deck2.Parent = folder
|
|
local antenna = part({
|
|
Name = "Antenna",
|
|
Size = Vector3.new(2, 40, 2),
|
|
CFrame = CFrame.new(tx, 200, tz),
|
|
Color = orange,
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
antenna.Parent = folder
|
|
end
|
|
|
|
-- Tatsumi-style overlook pad (visual scenery, NE corner outside) ------------------
|
|
local function buildTatsumi(map: Folder)
|
|
local folder = Instance.new("Folder")
|
|
folder.Name = "TatsumiPA"
|
|
folder.Parent = map
|
|
local px, pz = HX - 380, -HZ - 120
|
|
local pad = part({
|
|
Name = "TatsumiPad",
|
|
Size = Vector3.new(160, 2, 80),
|
|
CFrame = CFrame.new(px, 0, pz),
|
|
Color = Color3.fromRGB(34, 34, 40),
|
|
Material = Enum.Material.Asphalt,
|
|
})
|
|
pad.Parent = folder
|
|
for i = 0, 4 do
|
|
local lamp = part({
|
|
Name = "TatsumiLamp",
|
|
Size = Vector3.new(1, 12, 1),
|
|
CFrame = CFrame.new(px - 70 + i * 35, 6, pz - 30),
|
|
Color = Color3.fromRGB(255, 240, 200),
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
lamp.Parent = folder
|
|
end
|
|
makeSign("辰巳PA TATSUMI PA", CFrame.new(px, 11, pz + 30), folder)
|
|
end
|
|
|
|
-- Neon skyline: towers inside the loop (city core) and outside ---------------------
|
|
local function buildSkyline(map: Folder)
|
|
local folder = Instance.new("Folder")
|
|
folder.Name = "Skyline"
|
|
folder.Parent = map
|
|
local accents = { NEON_PINK, NEON_BLUE, NEON_TEAL, Color3.fromRGB(200, 120, 255) }
|
|
|
|
local function tower(seed: number, x: number, z: number)
|
|
local width = 40 + det(seed) * 50
|
|
local depth = 40 + det(seed + 1) * 50
|
|
local height = 50 + det(seed + 2) * 170
|
|
local t = part({
|
|
Name = "Tower",
|
|
Size = Vector3.new(width, height, depth),
|
|
CFrame = CFrame.new(x, height / 2 - 10, z),
|
|
Color = Color3.fromRGB(16, 16, 24),
|
|
Material = Enum.Material.Slate,
|
|
})
|
|
t.Parent = folder
|
|
local accent = accents[(math.floor(seed) % #accents) + 1]
|
|
local band = part({
|
|
Name = "TowerBand",
|
|
Size = Vector3.new(width + 0.4, 2 + det(seed + 5) * 4, depth + 0.4),
|
|
CFrame = CFrame.new(x, height * (0.35 + det(seed + 6) * 0.4), z),
|
|
Color = accent,
|
|
Material = Enum.Material.Neon,
|
|
Transparency = 0.2,
|
|
})
|
|
band.Parent = folder
|
|
local roof = part({
|
|
Name = "TowerRoof",
|
|
Size = Vector3.new(width * 0.3, 1.5, depth * 0.3),
|
|
CFrame = CFrame.new(x, height - 9, z),
|
|
Color = accent,
|
|
Material = Enum.Material.Neon,
|
|
})
|
|
roof.Parent = folder
|
|
end
|
|
|
|
-- City core inside the loop
|
|
local seed = 1
|
|
for gx = -2, 2 do
|
|
for gz = -1, 1 do
|
|
seed += 7
|
|
local x = gx * 300 + (det(seed) - 0.5) * 120
|
|
local z = gz * 300 + (det(seed + 3) - 0.5) * 120
|
|
tower(seed, x, z)
|
|
end
|
|
end
|
|
-- Outer ring (skip landmark zones: Odaiba east, Tokyo Tower west-center,
|
|
-- Daikoku south-center)
|
|
for i = 1, 26 do
|
|
seed += 11
|
|
local ang = (i / 26) * math.pi * 2
|
|
local rad = 1750 + det(seed + 4) * 350
|
|
local x = math.cos(ang) * rad
|
|
local z = math.sin(ang) * rad * 0.75
|
|
local skip = (x > HX and math.abs(z) < 620) -- Odaiba
|
|
or (x < -HX and math.abs(z) < 260) -- Tokyo Tower
|
|
or (z > HZ and math.abs(x) < 420) -- Daikoku
|
|
if not skip then
|
|
tower(seed, x, z)
|
|
end
|
|
end
|
|
end
|
|
|
|
function MapBuilder.build(): Folder
|
|
local existing = Workspace:FindFirstChild("Map")
|
|
if existing then
|
|
existing:Destroy()
|
|
end
|
|
|
|
local map = Instance.new("Folder")
|
|
map.Name = "Map"
|
|
|
|
local samples = genSamples()
|
|
buildLoop(map, samples)
|
|
buildDaikoku(map)
|
|
buildRainbowBridge(map)
|
|
buildOdaiba(map)
|
|
buildTokyoTower(map)
|
|
buildTatsumi(map)
|
|
buildSkyline(map)
|
|
|
|
-- Finish line across the south straight just past the Daikoku exit --------
|
|
local finish = part({
|
|
Name = "FinishLine",
|
|
Size = Vector3.new(ROAD_WIDTH, 1, 6),
|
|
CFrame = CFrame.new(420, 1.5, HZ),
|
|
Color = Color3.fromRGB(240, 240, 240),
|
|
Material = Enum.Material.Neon,
|
|
Transparency = 0.3,
|
|
})
|
|
finish.Parent = map
|
|
|
|
-- Safety net floor under everything ----------------------------------------
|
|
local floor = part({
|
|
Name = "SafetyFloor",
|
|
Size = Vector3.new((HX + 700) * 2, 2, (HZ + 700) * 2),
|
|
CFrame = CFrame.new(0, -60, 0),
|
|
Transparency = 1,
|
|
CanCollide = true,
|
|
})
|
|
floor.Parent = map
|
|
|
|
-- Ground plane far below road level (the "bay" the expressway floats over)
|
|
local ground = part({
|
|
Name = "Ground",
|
|
Size = Vector3.new((HX + 700) * 2, 2, (HZ + 700) * 2),
|
|
CFrame = CFrame.new(0, -12, 0),
|
|
Color = Color3.fromRGB(10, 12, 20),
|
|
Material = Enum.Material.Slate,
|
|
})
|
|
ground.Parent = map
|
|
|
|
map.Parent = Workspace
|
|
return map
|
|
end
|
|
|
|
return MapBuilder
|