--!optimize 2 --!native --!strict local Elevators = script.Parent local MainDir = Elevators.Parent local LoadDir = MainDir:WaitForChild("Load") local RunService = game:GetService("RunService") local StorageService = game:GetService("ReplicatedStorage") local Enums = require(StorageService:WaitForChild("Enums")) local Out = require(StorageService:WaitForChild("Output")) local Algebra = require(StorageService:WaitForChild("Algebra")) local ElevatorTypes = require(MainDir:WaitForChild("Types"):WaitForChild("Elevator")) local Tags = require(LoadDir:WaitForChild("Tags")) local RelayAlgorithm = require(script:WaitForChild("RelayAlgorithm")) 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 GetLevel: (self: ClassConstructor, Level: number) -> Vector3?, RequestLevelAsync: (self: ClassConstructor, RequestedLevel: number, Direction: Enums.ElevatorCallDirectionValues) -> boolean, StartTraveling: (self: ClassConstructor) -> (), __TravelToFloorAsync: (self: ClassConstructor, LevelInt: number, VEC3_Y_WRAP: Vector3) -> (), } type FloorLevelingPositions = {number} type Constructor_Fun = (ElevatorBoxModel: BasePart, ElevatorConfigurationTable: ElevatorTypes.ElevatorConfigurationTable, FloorLevelingPositions: FloorLevelingPositions) -> ClassConstructor type Constructor_Return_Props = { RelayAlgorithm: RelayAlgorithm.RelayAlgorithmConstructor, FloorLevelingPositions: FloorLevelingPositions, eprint: (T...) -> (), ewarn: (T...) -> (), eprintStudio: (T...) -> (), ewarnStudio: (T...) -> (), Elevator: { TravelingDirection: Enums.ElevatorCallDirectionValues, BoxModel: UnionOperation, AlignPosition: AlignPosition, Configuration: ElevatorTypes.ElevatorConfigurationTable }, Attributes: { PreviousFloor: IntValue, CurrentFloor: IntValue, NextFloor: IntValue, Goal: IntValue, TravelingUpwards: BoolValue, Stopped: BoolValue }, Events: { Progression: RBXScriptSignal, Traveling: RBXScriptSignal, Parked: RBXScriptSignal, Leveling: RBXScriptSignal, Leveling3Phase: RBXScriptSignal, __eventInstances__: { Progression: BindableEvent, Traveling: BindableEvent, Parked: BindableEvent, Leveling: BindableEvent, Leveling3Phase: BindableEvent, } }, __functionEvents: { StartTraveling: BindableEvent }, __Connections: { Moving: RBXScriptConnection?, } } local Elevator = {} :: Impl_Constructor Elevator.__index = Elevator local function ElevatorGoingUpDirection(CurrentFloor: number, RequestedFloor: number): boolean -- -(CurrentFloor-RequestedFloor)>0 -- -CurrentFloor+RequestedFloor>0 return CurrentFloor1, `"{ElevatorConfigurationTable.Name}" requires more floors to operate. Floors={FloorLevelingPositions}, #Floors={#FloorLevelingPositions}.`) local function eprint(...: T...) print(`[{ElevatorConfigurationTable.Name}]:`, ...) end local function ewarn(...: T...) warn(`[{ElevatorConfigurationTable.Name}]:`, ...) warn(debug.traceback()) end local function eprintStudio(...: T...) Out.printStudio(`[{ElevatorConfigurationTable.Name}]:`, ...) end local function ewarnStudio(...: T...) Out.warnStudio(`[{ElevatorConfigurationTable.Name}]:`, ...) end local _BoxAttachment, BoxAlignPosition, _BoxAlignOrientation = Mover(ElevatorBoxModel, ElevatorConfigurationTable.Responsiveness, ElevatorConfigurationTable.MaxVelocity) local RelayAlgorithmConstructor = RelayAlgorithm.constructor(BoxAlignPosition) local CabProgression = Instance.new("BindableEvent") :: BindableEvent local CabTraveling = Instance.new("BindableEvent") :: BindableEvent local Parked = Instance.new("BindableEvent") :: BindableEvent local Leveling = Instance.new("BindableEvent") :: BindableEvent local Leveling3Phase = Instance.new("BindableEvent") :: BindableEvent local Attributes = { PreviousFloor = Instance.new("IntValue") :: IntValue, CurrentFloor = Instance.new("IntValue") :: IntValue, NextFloor = Instance.new("IntValue") :: IntValue, Goal = Instance.new("IntValue") :: IntValue, TravelingUpwards = Instance.new("BoolValue") :: BoolValue, Stopped = Instance.new("BoolValue") :: BoolValue } Attributes.CurrentFloor.Value = 1 Attributes.PreviousFloor.Value = Attributes.CurrentFloor.Value Attributes.NextFloor.Value = Attributes.CurrentFloor.Value+1 Attributes.TravelingUpwards.Value = true Attributes.Goal.Value = 1 local ElevatorClass = setmetatable({ RelayAlgorithm = RelayAlgorithmConstructor, FloorLevelingPositions = FloorLevelingPositions, eprint = eprint, ewarn = ewarn, eprintStudio = eprintStudio, ewarnStudio = ewarnStudio, Elevator = { TravelingDirection = Enums.ElevatorCallDirection.Up, BoxModel = ElevatorBoxModel, AlignPosition = BoxAlignPosition, Configuration = ElevatorConfigurationTable, }, Events = { Progression = CabProgression.Event, Traveling = CabTraveling.Event, Parked = Parked.Event, Leveling = Leveling.Event, Leveling3Phase = Leveling3Phase.Event, __eventInstances__ = { Progression = CabProgression, Traveling = CabTraveling, Parked = Parked, Leveling = Leveling, Leveling3Phase = Leveling3Phase, } }, Attributes = Attributes, __functionEvents = { StartTraveling = Instance.new("BindableEvent") :: BindableEvent }, __Connections = {} }, Elevator) RelayAlgorithmConstructor.Events.Sorted:Connect(function(AddedFloorDirection: Enums.ElevatorCallDirectionValues, FloorDirectionQueue: RelayAlgorithm.FloorDirectionQueue) if AddedFloorDirection == (ElevatorClass.Attributes.TravelingUpwards.Value and Enums.ElevatorCallDirection.Up or Enums.ElevatorCallDirection.Down) then local NextFloorAsTraveling = FloorDirectionQueue[1] if NextFloorAsTraveling then local Level = FloorLevelingPositions[NextFloorAsTraveling] ElevatorClass:__TravelToFloorAsync(Level, Vector3.new(0, Level, 0)) end ElevatorClass.eprintStudio(`Floors sorted in proceeding direction. direction={AddedFloorDirection}, FloorDirectionQueue={FloorDirectionQueue}`) end end) print(`🛗 [{ElevatorConfigurationTable.Name}]: Initialized and ready`) return ElevatorClass end local function ProceedToNextLevel(self: ClassConstructor, Level_Int: number): Vector3? local VEC3_Y_WRAP = self:GetLevel(Level_Int) if VEC3_Y_WRAP then self:__TravelToFloorAsync(Level_Int, VEC3_Y_WRAP) self.eprintStudio(`Traveling to Level={Level_Int} VEC3_Y_WRAP={VEC3_Y_WRAP}`) else self.ewarn(`Failed to get the requested level's Y position. VEC3_Y_WRAP={VEC3_Y_WRAP} Level={Level_Int}`) end return VEC3_Y_WRAP end local function GoingUpDirectionToDirectionEnum(CurrentFloor: number, RequestedFloor: number): Enums.ElevatorCallDirectionValues return ElevatorGoingUpDirection(CurrentFloor, RequestedFloor) and Enums.ElevatorCallDirection.Up or Enums.ElevatorCallDirection.Down end local function CheckFloorQueue(self: ClassConstructor) local DirectionToDirectionQueue = self.Attributes.TravelingUpwards.Value and self.RelayAlgorithm.FloorQueue.Up or self.RelayAlgorithm.FloorQueue.Down local DirectionToOppositeDirectionQueue = self.Attributes.TravelingUpwards.Value and self.RelayAlgorithm.FloorQueue.Down or self.RelayAlgorithm.FloorQueue.Up if DirectionToDirectionQueue[1] ~= self.Attributes.CurrentFloor.Value then self.ewarn("The floor queue first index did not match the elevator's current floor, CurrentFloor=", self.Attributes.CurrentFloor.Value, "FloorQueue[1]=", DirectionToDirectionQueue[1]) end table.remove(DirectionToDirectionQueue, 1) local IdenticalOpposite = table.find(DirectionToOppositeDirectionQueue, self.Attributes.CurrentFloor.Value) if IdenticalOpposite then table.remove(DirectionToOppositeDirectionQueue, IdenticalOpposite) self.eprintStudio(`Removed the current floor from the opposite direction, ElevatorTravelingUpwards={self.Attributes.TravelingUpwards.Value} CurrentFloor={self.Attributes.CurrentFloor.Value}`) end self.eprintStudio("Checking more floors in direction queue. DirectionToDirectionQueue=", DirectionToDirectionQueue) local NextFloorInQueue = DirectionToOppositeDirectionQueue[1] if NextFloorInQueue then local NewDirection = GoingUpDirectionToDirectionEnum(self.Attributes.CurrentFloor.Value, NextFloorInQueue) self.RelayAlgorithm:Sort(NewDirection :: Enums.ElevatorCallDirectionValues) local ProceedingToTheNextLevel = ProceedToNextLevel(self, NextFloorInQueue) if not ProceedingToTheNextLevel then self.RelayAlgorithm:Sort(Enums.ElevatorCallDirection.Down) ProceedToNextLevel(self, 1) end else --No more floors in the current direction? --Check the opposite self.eprintStudio(`No more floors in the direction TravelingUpwards="{self.Attributes.TravelingUpwards.Value}" checking the opposite direction floors`) if #DirectionToOppositeDirectionQueue ~= 0 then local NextLevelOpposite = DirectionToOppositeDirectionQueue[1] if NextLevelOpposite then local NewDirection = GoingUpDirectionToDirectionEnum(self.Attributes.CurrentFloor.Value, NextLevelOpposite) self.RelayAlgorithm:Sort(NewDirection :: Enums.ElevatorCallDirectionValues) self.eprint("Floors found in the opposite direction. Direction=", Enums.ElevatorCallDirection.Down, "NextLevel=", NextLevelOpposite) ProceedToNextLevel(self, NextLevelOpposite) end end end end local function FloorsClamp(self: ClassConstructor, n: number): number return Algebra.minmax(1, n, #self.FloorLevelingPositions) end local function CabTraveling(self: ClassConstructor, deltaTime: number, LEVEL_VEC3_Y_WRAP: Vector3) local ElevatorPosition = self.Elevator.BoxModel.Position local AtFloorY = self.FloorLevelingPositions[FloorsClamp(self, self.Attributes.TravelingUpwards.Value and self.Attributes.CurrentFloor.Value+1 or self.Attributes.CurrentFloor.Value-1)] if self.Attributes.TravelingUpwards.Value then --Detecting between the floors if ElevatorPosition.Y>=AtFloorY-self.Elevator.Configuration.FloorLevelingDistance then self.Attributes.PreviousFloor.Value = self.Attributes.CurrentFloor.Value self.Attributes.CurrentFloor.Value+=1 self.Attributes.NextFloor.Value = FloorsClamp(self, self.Attributes.CurrentFloor.Value+1) self.Events.__eventInstances__.Progression:Fire(self.Attributes.PreviousFloor.Value, self.Attributes.CurrentFloor.Value, self.Attributes.NextFloor.Value) end --Elevator is riding upwards towards the destination if ElevatorPosition.Y>=LEVEL_VEC3_Y_WRAP.Y-self.Elevator.Configuration.FloorLevelingDistance then self.Events.__eventInstances__.Leveling:Fire() self.Elevator.AlignPosition.MaxVelocity = self.Elevator.Configuration.LevelingVelocity if ElevatorPosition.Y>=LEVEL_VEC3_Y_WRAP.Y-self.Elevator.Configuration.FloorLeveling3PhaseDistance then self.Events.__eventInstances__.Leveling3Phase:Fire() self.Elevator.AlignPosition.MaxVelocity = self.Elevator.Configuration.Phase3LevelingVelocity if ElevatorPosition.Y>=LEVEL_VEC3_Y_WRAP.Y-self.Elevator.Configuration.ParkedDistance then self.Events.__eventInstances__.Parked:Fire() CheckFloorQueue(self); (self.__Connections.Moving :: RBXScriptConnection):Disconnect() end end end else if ElevatorPosition.Y<=AtFloorY+self.Elevator.Configuration.FloorLevelingDistance then self.Attributes.PreviousFloor.Value = self.Attributes.CurrentFloor.Value self.Attributes.CurrentFloor.Value-=1 self.Attributes.NextFloor.Value = FloorsClamp(self, self.Attributes.CurrentFloor.Value-1) self.Events.__eventInstances__.Progression:Fire(self.Attributes.PreviousFloor.Value, self.Attributes.CurrentFloor.Value, self.Attributes.NextFloor.Value) end --Elevator is riding upwards towards the destination if ElevatorPosition.Y<=LEVEL_VEC3_Y_WRAP.Y+self.Elevator.Configuration.FloorLevelingDistance then self.Events.__eventInstances__.Leveling:Fire() self.Elevator.AlignPosition.MaxVelocity = self.Elevator.Configuration.LevelingVelocity if ElevatorPosition.Y<=LEVEL_VEC3_Y_WRAP.Y+self.Elevator.Configuration.FloorLeveling3PhaseDistance then self.Events.__eventInstances__.Leveling3Phase:Fire() self.Elevator.AlignPosition.MaxVelocity = self.Elevator.Configuration.Phase3LevelingVelocity if ElevatorPosition.Y<=LEVEL_VEC3_Y_WRAP.Y+self.Elevator.Configuration.ParkedDistance then self.Events.__eventInstances__.Parked:Fire() CheckFloorQueue(self); (self.__Connections.Moving :: RBXScriptConnection):Disconnect() end end end end self.Events.__eventInstances__.Traveling:Fire(deltaTime, ElevatorPosition) end function Elevator:GetLevel(Level_Int) local Level = self.FloorLevelingPositions[Level_Int] if Level then --local VEC3_Y_WRAP_LOSSY = Vector3.yAxis*Level return Vector3.new(0, Level, 0) end return nil end function Elevator:__TravelToFloorAsync(Level_Int, LEVEL_VEC3_Y_WRAP) if self.Elevator.Configuration.Functions.ManualTravelStart then self.__functionEvents.StartTraveling.Event:Wait() end if self.__Connections.Moving and self.__Connections.Moving.Connected then self.__Connections.Moving:Disconnect() end assert(not self.Elevator.BoxModel.Anchored, "The elevator cannot move! Its box model is Anchored.") local ElevatorTravelingUpwards = ElevatorGoingUpDirection(self.Attributes.CurrentFloor.Value, Level_Int) self.Attributes.Goal.Value = Level_Int self.Attributes.TravelingUpwards.Value = ElevatorTravelingUpwards self.__Connections.Moving = RunService.Heartbeat:Connect(function(deltaTime: number) CabTraveling(self, deltaTime, LEVEL_VEC3_Y_WRAP) end) --Set the elevator's AlignPosition to the floor Y vector self.Elevator.AlignPosition.Position = Vector3.new(self.Elevator.AlignPosition.Position.X, LEVEL_VEC3_Y_WRAP.Y, self.Elevator.AlignPosition.Position.Z) --Set the elevator's max velocity to its fastest speed when moving starts self.Elevator.AlignPosition.MaxVelocity = self.Elevator.Configuration.MaxVelocity end function Elevator:RequestLevelAsync(RequestedLevel, Direction) local Level = self:GetLevel(RequestedLevel) if Level then if (RequestedLevel == 1 and Direction == Enums.ElevatorCallDirection.Down) or (RequestedLevel == #self.FloorLevelingPositions and Direction == Enums.ElevatorCallDirection.Up) then self.ewarn(`Impossible direction requested, Direction={Direction}, RequestedLevel={Level}`) return false else local DirectionQueue = Direction == Enums.ElevatorCallDirection.Up and self.RelayAlgorithm.FloorQueue.Up or self.RelayAlgorithm.FloorQueue.Down -- local OppositeQueue = Direction == Enums.ElevatorCallDirection.Up and self.RelayAlgorithm.FloorQueue.Down or self.RelayAlgorithm.FloorQueue.Up self.RelayAlgorithm:AddFloor(Direction :: Enums.ElevatorCallDirectionValues, RequestedLevel) if #DirectionQueue == 1 then self:__TravelToFloorAsync(RequestedLevel, Level) end end return true end self.ewarn(`Requested floor: "{RequestedLevel}" does not exist for this elevator`) return false end function Elevator:StartTraveling() self.__functionEvents.StartTraveling:Fire() end return Elevator