--!optimize 2 --!native --!strict --Set relay tags through studio command line --[[ local s = game.Selection:Get()[1] for i,v in s:GetChildren() do if v:IsA("Model") then local main = v:FindFirstChild("main") if main then local label = v:FindFirstChildWhichIsA("TextLabel", true) if label then main:AddTag(`Otis1960_RelayButton_{label.Text}`) end end end end ]] local Elevators = script.Parent local MainDir = Elevators.Parent local EnumsDir = MainDir:WaitForChild("Enums") local LoadDir = MainDir:WaitForChild("Load") local TagsDir = LoadDir:WaitForChild("Tags") local Storage = game:GetService("ReplicatedStorage") local RS = game:GetService("RunService") local ButtonTags = require(TagsDir:WaitForChild("Buttons")) local Enums = require(Storage:WaitForChild("Enums")) local SoundEnums = require(EnumsDir:WaitForChild("Sounds")) local LevelingModule = require(script:WaitForChild("Leveling")) local Doors = require(script:WaitForChild("Doors")) local MovingObjects = require(script:WaitForChild("MovingObjects")) local RelayAlgorithm = require(script:WaitForChild("RelayAlgorithm")) local Relay = require(script:WaitForChild("Relay")) local HallDisplays = require(Elevators:WaitForChild("HallDisplays")) local ElevatorMover = require(Elevators:WaitForChild("Mover")) local TractionRopes = require(Elevators:WaitForChild("TractionRopes")) local Lanterns = require(Elevators:WaitForChild("Lanterns")) local Buttons = require(Elevators:WaitForChild("ButtonsManager")) local Tags = require(LoadDir:WaitForChild("Tags")) type Tags = Tags.ExportedTags type TagsConstructor = Tags.TagsConstructor type ClassConstructor = typeof(setmetatable({} :: Constructor_Return_Props, {} :: Impl_Constructor)) type Impl_Constructor = { __index: Impl_Constructor, constructor: Constructor_Fun, --Class functions Leveled: (self: ClassConstructor, RequestedLevel: number) -> (), Leveling: (self: ClassConstructor, RequestedLevel: number) -> (), FloorPassingUp: (self: ClassConstructor, ElevatorPositionY: number, RequestedLevel: number) -> (), FloorPassingDown: (self: ClassConstructor, ElevatorPositionY: number, RequestedLevel: number) -> (), RequestLevel: (self: ClassConstructor, RequestedLevel: number) -> boolean, __MoveTo: (self: ClassConstructor, GoingUp: boolean, GoalFloor_Y: number) -> (), __MovingHeartbeat: (self: ClassConstructor, GoingUp: boolean, GoalFloor_Y: number) -> (), } & Impl_Static_Props type Impl_Static_Props = { Name: Enums.ElevatorValues, Responsiveness: number, MaxVelocity: number, FloorLevelingDistance: number, DoorOpeningDistance: number, LeveledDistance: number, QueueWaitTime: number, Sounds: { LanternChimeDirection: SoundEnums.Otis1960LanternChimeDirection, LanternChimeLanding: SoundEnums.Otis1960LanternChimeLanding, }, Colors: { ButtonActivated: Color3, ButtonDeactivated: Color3, LanternDisplayOn: Color3, LanternDisplayOff: Color3, }, Attributes: { CurrentFloor: IntValue, Moving: BoolValue, GoingUp: BoolValue, Stopped: BoolValue, Relay: { Goal: IntValue, GoalYLevel: NumberValue, Ready: BoolValue, } }, Events: { ButtonActivated: BindableEvent } } type Constructor_Fun = (TagsConstructor: TagsConstructor, ButtonsTags: Tags.ElevatorButtons, LanternsTags: Tags.Lanterns, LandingDoors: Tags.LandingTags) -> ClassConstructor type Constructor_Return_Props = { Tags: Tags, MOConstructor: MovingObjects.MovingObjectsConstructor, LanternsConstructor: Lanterns.LanternsConstructor, HallDisplaysConstructor: HallDisplays.HallDisplaysConstructor, ElevatorBox_1960: UnionOperation, ElevatorDoor1: BasePart, ElevatorDoor2: BasePart, ElevatorDoorSensor: Folder, BoxAttachment: Attachment, BoxAlignPosition: AlignPosition, BoxAlignOrientation: AlignOrientation, ElevatorDoorsConstructor: Doors.DoorConstructor, Ropes: {Instance}, TractionRopesConstructor: TractionRopes.TractionRopesConstructor, Pulley: UnionOperation, Pulley2: UnionOperation, Governor: UnionOperation, GovernorFlyballs: Part, PieplatePulley: UnionOperation, MachineRoom: MovingObjects.MachineRoom, HallDisplays: {Instance}, ButtonsConstructor: Buttons.ButtonsConstructor, RelayAlgorithmConstructor: RelayAlgorithm.RelayAlgorithmConstructor, RelayConstructor: Relay.RelayConstructor, __MovingConnection: RBXScriptConnection?, } local Elevator = {} :: Impl_Constructor Elevator.__index = Elevator Elevator.Name = Enums.Elevator.Otis1960 Elevator.FloorLevelingDistance = 2.5 Elevator.DoorOpeningDistance = Elevator.FloorLevelingDistance/2.8 Elevator.LeveledDistance = 0.5 Elevator.Responsiveness = 10 Elevator.MaxVelocity = 10 Elevator.QueueWaitTime = 5 Elevator.Sounds = { LanternChimeDirection = SoundEnums.Otis1960.LanternChimeDirection, LanternChimeLanding = SoundEnums.Otis1960.LanternChimeLanding } Elevator.Colors = { ButtonActivated = Color3.fromRGB(180,0,0), ButtonDeactivated = Color3.fromRGB(139,139,139), LanternDisplayOn = Color3.fromRGB(255,114,71), LanternDisplayOff = Color3.fromRGB(55,55,55), } Elevator.Attributes = { CurrentFloor = Instance.new("IntValue") :: IntValue, Moving = Instance.new("BoolValue") :: BoolValue, GoingUp = Instance.new("BoolValue") :: BoolValue, Stopped = Instance.new("BoolValue") :: BoolValue, Relay = { Ready = Instance.new("BoolValue") :: BoolValue, Goal = Instance.new("IntValue") :: IntValue, GoalYLevel = Instance.new("NumberValue") :: NumberValue, }, } Elevator.Events = { ButtonActivated = Instance.new("BindableEvent") :: BindableEvent } Elevator.Attributes.CurrentFloor.Value = 1 Elevator.Attributes.Moving.Value = false Elevator.Attributes.GoingUp.Value = false Elevator.Attributes.Relay.Goal.Value = 1 Elevator.Attributes.Relay.Ready.Value = false local Attributes = Elevator.Attributes --My clever math function for determining if the elevator goal is to move upwards or not local function ElevatorGoingUpDirection(Floor: number, RequestedFloor: number): boolean return -(Floor-RequestedFloor)>0 end local function _ActivatedFloorButton(self: ClassConstructor, ButtonFloor: number, ButtonTree: Tags.ButtonPropertiesSafe) ButtonTree.Prompt.Enabled = false local Some = self:RequestLevel(ButtonFloor) if Some then local FloorTracker: RBXScriptConnection FloorTracker = Attributes.CurrentFloor:GetPropertyChangedSignal("Value"):Connect(function() if Attributes.CurrentFloor.Value == ButtonFloor and Attributes.Relay.Goal.Value == ButtonFloor then FloorTracker:Disconnect() self.ButtonsConstructor:__DeactivateButton(ButtonTree.Inst :: BasePart, (ButtonTree.Inst :: BasePart):FindFirstChild("Glass") :: BasePart?) ButtonTree.Prompt.Enabled = true end end) else warn(`Failed to call floor: {ButtonFloor}`) ButtonTree.Prompt.Enabled = true end end --Special cases inbound local function HookButtons(self: ClassConstructor, ButtonNameType: Enums.ButtonTreeValues, ButtonID: string, ButtonTree: Tags.ButtonPropertiesSafe) if ButtonNameType == Enums.ButtonTree.Car then self.ButtonsConstructor:CarButton(ButtonID, ButtonTree, function(ButtonFloor: number) _ActivatedFloorButton(self, ButtonFloor, ButtonTree) end) elseif ButtonNameType == Enums.ButtonTree.Landing then self.ButtonsConstructor:LandingButton(ButtonID, ButtonTree, function(ButtonFloor: number) _ActivatedFloorButton(self, ButtonFloor, ButtonTree) end) elseif ButtonNameType == Enums.ButtonTree.Special then if Elevator.Name == Enums.Elevator.Otis1960 then if ButtonTree.Name == Enums.SpecialButton.Stop then ButtonTree.Attachment.Position-=Vector3.xAxis/10 ButtonTree.Prompt.HoldDuration = 1 end end self.ButtonsConstructor:SpecialButton(ButtonTree.Name :: Enums.SpecialButtonValues, ButtonID, ButtonTree, function(Toggled: Buttons.SpecialButtonToggle) Attributes.Stopped.Value = Toggled if Toggled then (ButtonTree.Inst :: BasePart).Position+=Vector3.new(0,0,.05) if self.__MovingConnection and self.__MovingConnection.Connected then self.__MovingConnection:Disconnect() end self.BoxAlignPosition.MaxVelocity = 0 else (ButtonTree.Inst :: BasePart).Position-=Vector3.new(0,0,.05) self.LanternsConstructor:Reset() self:__MoveTo(ElevatorGoingUpDirection(Attributes.CurrentFloor.Value, Attributes.Relay.Goal.Value), LevelingModule.Leveling[self.RelayAlgorithmConstructor.__FloorQueue[1]]) self.BoxAlignPosition.MaxVelocity = Elevator.MaxVelocity end end) elseif ButtonNameType == Enums.ButtonTree.Relays then if Elevator.Name == Enums.Elevator.Otis1960 then ButtonTree.Attachment.Position-=Vector3.zAxis/6 end elseif ButtonNameType == Enums.ButtonTree.Unknown then else warn(`[{Elevator.Name}]: Could not iterate a button, ButtonNameType={ButtonNameType}`) end end local function IterateButtons(self: ClassConstructor, ButtonsTagsConstructor: ButtonTags.ButtonsTagsConstructor) for ButtonNameType, ButtonList in ButtonsTagsConstructor.Buttons do for ButtonID, ButtonTree in ButtonList do if ButtonTree.Prompt and ButtonTree.Inst and ButtonTree.Attachment then HookButtons(self, ButtonNameType :: Enums.ButtonTreeValues, ButtonID, ButtonTree :: Tags.ButtonPropertiesSafe) else warn(`{ButtonTree} is missing a field, {ButtonTree}`) end end end end function Elevator.constructor(TagsConstructor, ButtonsTags, LanternsTags, LandingDoors) local self = {} :: Constructor_Return_Props self.MachineRoom = {_CFrame = {}} :: MovingObjects.MachineRoom self.ElevatorBox_1960 = TagsConstructor:Request("ElevatorMover_1960") :: UnionOperation self.ElevatorDoor1 = TagsConstructor:Request("ElevatorDoor_1960_1") :: BasePart self.ElevatorDoor2 = TagsConstructor:Request("ElevatorDoor_1960_2") :: BasePart self.ElevatorDoorSensor = TagsConstructor:Request("ElevatorDoor_Sensor_1960") :: Folder self.Ropes = TagsConstructor:Request("1960_ElevatorPulleyRope") :: {Instance} self.HallDisplays = TagsConstructor:Request("Otis1960_LandingFloorDisplay") :: {Instance} self.MachineRoom.Pulley = TagsConstructor:Request("Otis1960_Pulley") :: UnionOperation self.MachineRoom.Pulley2 = TagsConstructor:Request("Otis1960_Pulley2") :: UnionOperation self.MachineRoom.Governor = TagsConstructor:Request("Otis1960_Governor") :: UnionOperation self.MachineRoom.GovernorFlyballs = TagsConstructor:Request("Otis1960_GovernorFlyballs") :: Part self.MachineRoom.PiePlatePulley = TagsConstructor:Request("Otis1960_PieplatePulley") :: UnionOperation self.MachineRoom.PiePlatePlates = TagsConstructor:Request("Otis1960_PiePlatePlates") :: UnionOperation self.MachineRoom.PiePlateSelector = TagsConstructor:Request("Otis1960_PiePlateSelector") :: UnionOperation local LanternDisplay = TagsConstructor:Request("Otis1960_LanternDisplayMain") :: UnionOperation self.MOConstructor = MovingObjects.constructor({ MachineRoom = self.MachineRoom } :: MovingObjects.InstanceTree) self.HallDisplaysConstructor = HallDisplays.constructor(Attributes.CurrentFloor, self.HallDisplays) self.ElevatorDoorsConstructor = Doors.constructor(LandingDoors, self.ElevatorBox_1960, self.ElevatorDoor1, self.ElevatorDoor2, self.ElevatorDoorSensor) self.TractionRopesConstructor = TractionRopes.constructor(self.Ropes, self.ElevatorBox_1960, LevelingModule.Leveling) self.LanternsConstructor = Lanterns.constructor(LanternDisplay, LanternsTags, Elevator.Sounds, Elevator.Colors) local ButtonsTagsConstructor = ButtonTags.constructor(TagsConstructor, ButtonsTags) local Otis1960_Buttons = ButtonsTagsConstructor:CreatePromptButtons() self.ButtonsConstructor = Buttons.constructor(Elevator.Attributes, Elevator.Events, Elevator.Colors) self.HallDisplaysConstructor:BindHallDisplays() self.BoxAttachment, self.BoxAlignPosition, self.BoxAlignOrientation = ElevatorMover(self.ElevatorBox_1960, self.ElevatorBox_1960.Position, Elevator.Responsiveness, Elevator.MaxVelocity) self.RelayAlgorithmConstructor = RelayAlgorithm.constructor(self.BoxAlignPosition, Attributes, Doors.Attributes.Relay) self.RelayConstructor = Relay.constructor(self.RelayAlgorithmConstructor, Attributes, Doors.Attributes, LevelingModule, self.BoxAlignPosition, self.ElevatorBox_1960) self.RelayConstructor:BulkConnect() local ClassConstructor = setmetatable(self, Elevator) IterateButtons(ClassConstructor, ButtonsTagsConstructor) --Open the elevator doors on server start task.spawn(function() self.LanternsConstructor:Toggle(true, Attributes.CurrentFloor.Value) self.HallDisplaysConstructor:SetHallDisplays(Attributes.CurrentFloor.Value) self.ElevatorDoorsConstructor:ToggleElevatorDoorsAsync(true, Attributes.CurrentFloor.Value) --Some hacks Doors.Attributes.Relay.Open.Value = true end) print(LevelingModule.LevelingBetween) print(`🔝 {Elevator.Name} initialized and ready`) return ClassConstructor end function Elevator:Leveled(RequestedLevel) (self.__MovingConnection :: RBXScriptConnection):Disconnect() Attributes.Moving.Value = false Attributes.CurrentFloor.Value = RequestedLevel self.BoxAlignPosition.MaxVelocity = Elevator.MaxVelocity self.LanternsConstructor:Reset(Attributes.CurrentFloor.Value) task.wait(Elevator.QueueWaitTime) local ElevatorGoingUp = ElevatorGoingUpDirection(Attributes.CurrentFloor.Value, RequestedLevel) if self.RelayAlgorithmConstructor:Check(ElevatorGoingUp) then --More floors in the queue self:__MoveTo(ElevatorGoingUp, LevelingModule.Leveling[self.RelayAlgorithmConstructor.__FloorQueue[1]]) end end function Elevator:Leveling(RequestedLevel) self.BoxAlignPosition.MaxVelocity = 1 self.LanternsConstructor:Toggle(true, RequestedLevel) end function Elevator:FloorPassingUp(ElevatorPositionY, RequestedLevel) if ElevatorPositionY>=LevelingModule.Leveling[Attributes.CurrentFloor.Value+1] then Attributes.CurrentFloor.Value+=1 self.LanternsConstructor:Toggle(true, Attributes.CurrentFloor.Value) self.LanternsConstructor:Toggle(false, Attributes.CurrentFloor.Value-1) end end function Elevator:FloorPassingDown(ElevatorPositionY, RequestedLevel) if ElevatorPositionY<=LevelingModule.Leveling[Attributes.CurrentFloor.Value-1] then Attributes.CurrentFloor.Value-=1 self.LanternsConstructor:Toggle(true, Attributes.CurrentFloor.Value) self.LanternsConstructor:Toggle(false, Attributes.CurrentFloor.Value+1) end end function Elevator:__MovingHeartbeat(GoingUp, GoalFloor_Y) Attributes.GoingUp.Value = GoingUp if self.__MovingConnection and self.__MovingConnection.Connected then self.__MovingConnection:Disconnect() end self.MOConstructor:UpdateCFrame() local Delta = 0 local DoorsOpeningEvent = false self.__MovingConnection = RS.Heartbeat:Connect(function(_dt) Delta+=1 local FloorGoal: number = self.RelayAlgorithmConstructor.__FloorQueue[1] Attributes.Moving.Value = true Attributes.Relay.Goal.Value = FloorGoal local ElevatorPosition: Vector3 = self.ElevatorBox_1960.Position local ElevatorPositionY: number = ElevatorPosition.Y local BoxAlignY: number = self.BoxAlignPosition.Position.Y local ElevatorVelocityY: number = self.ElevatorBox_1960:GetVelocityAtPosition(ElevatorPosition).Y self.TractionRopesConstructor:Move(27, self.ElevatorBox_1960.Position) self.MOConstructor:Frame_Pullies(Delta, ElevatorVelocityY) --Kill the connection if GoingUp then self:FloorPassingUp(ElevatorPositionY, FloorGoal) if ElevatorPositionY>=BoxAlignY-Elevator.FloorLevelingDistance then self:Leveling(FloorGoal) if not DoorsOpeningEvent and ElevatorPositionY>=BoxAlignY-Elevator.DoorOpeningDistance then DoorsOpeningEvent = true self.ElevatorDoorsConstructor:ToggleElevatorDoorsAsync(true, FloorGoal) end end if ElevatorPositionY>=BoxAlignY-Elevator.LeveledDistance then self:Leveled(FloorGoal) end else self:FloorPassingDown(ElevatorPositionY, FloorGoal) if ElevatorPositionY<=BoxAlignY+Elevator.FloorLevelingDistance then self:Leveling(FloorGoal) if not DoorsOpeningEvent and ElevatorPositionY>=BoxAlignY-Elevator.DoorOpeningDistance then DoorsOpeningEvent = true self.ElevatorDoorsConstructor:ToggleElevatorDoorsAsync(true, FloorGoal) end end if ElevatorPositionY<=BoxAlignY+Elevator.LeveledDistance then self:Leveled(FloorGoal) end end end) end function Elevator:__MoveTo(GoingUp, GoalFloor_Y) if Doors.Attributes.Relay.Open.Value then self.ElevatorDoorsConstructor:ToggleElevatorDoorsAsync(false, Attributes.CurrentFloor.Value) end if GoingUp then self.LanternsConstructor:DirectionUp(true) else self.LanternsConstructor:DirectionDown(true) end self:__MovingHeartbeat(GoingUp, GoalFloor_Y) end function Elevator:RequestLevel(RequestedLevel) local GoalFloor_Y: number? = LevelingModule.Leveling[RequestedLevel] if GoalFloor_Y and RequestedLevel ~= Attributes.CurrentFloor.Value then local ElevatorGoingUp = ElevatorGoingUpDirection(Attributes.CurrentFloor.Value, RequestedLevel) local Proceeding = self.RelayAlgorithmConstructor:AddFloor(ElevatorGoingUp, RequestedLevel) if Proceeding then self:__MoveTo(ElevatorGoingUp, GoalFloor_Y) end else warn(`[{Elevator.Name}]: landing out of range or equals the same range as the goal, requested landing: {tostring(RequestedLevel)}`) return false end return true end return Elevator