Help understanding this open-source script

hiya ! I’ve come across this open-source train controller and I want to make my own additions to it, but I’ve been scratching my head at this one part for ages and can’t get my head around it.

I want to add extra keybinds to the UIS around line 127 in the local script, but I don’t know where the server fire is recieved, and the documentation doesnt say anywhere and I can’t find it myself, can anyone help explain to me where it goes so I can add my own inputs ? Thanks !

Here are the scripts + documentation

Local script:

--[[
	// FileName: LocalRun.client.lua
	// Written by: Jake Baxter
--]]


--//Variables
local VehicleSeat = game.Players.LocalPlayer.Character.Humanoid.SeatPart
local RemoteClientEvent = VehicleSeat["PlayerRemoteEvent"] --//At the top to cause an error if not.
local Velocity = 0
local RawSelf = {}
local finishedInit = false

--//Init function
VehicleSeat.AncestryChanged:connect(function()
	script.Parent:Destroy()
end)

VehicleSeat.ChildRemoved:connect(function(child)
	if not (child.Name == "SeatWeld") then
		return false
	end
	script.Parent:Destroy()
end)

local tempFunction = function() end
tempFunction = RemoteClientEvent.OnClientEvent:connect(function(modName, classSelf)
	if not (modName == "Register") then
		return
	end

	RawSelf = classSelf
	finishedInit = true
	tempFunction:Disconnect()
end)
RemoteClientEvent:FireServer("Register", {})

repeat task.wait(1) until finishedInit --// We don't want to continue.


local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local baseParts = RawSelf["baseParts"]
local throttle = RawSelf["throttle"]
local brake = RawSelf["brake"]
local throttlePower = RawSelf["throttlePower"]
local brakePower = RawSelf["brakePower"]
local throttleFullTime = RawSelf["throttleFullTime"]
local throttleIdleTime = RawSelf["throttleIdleTime"]
local brakeFullTime = RawSelf["brakeFullTime"]
local brakeIdleTime = RawSelf["brakeIdleTime"]
local isReversed = RawSelf["reversed"]
local generalPower = RawSelf["MaxPower"]
local maxSpeed = RawSelf["maxSpeed"]
local baseStud = RawSelf["baseStud"]
local developerMode = RawSelf["rawData"]["debugMode"]


local targettedThrottle = 0
local targettedBrake = 0
local currentThrottle = 0
local currentBrake = 0

local debounce = {}
debounce.up = false
debounce.down = false
debounce.keyboardsliderup = false
debounce.keyboardsliderdown = false
debounce.touchsliderup = false
debounce.touchsliderdown = false


local IterateBaseParts = function(func)
	for _,v in pairs(baseParts) do
		func(v)
	end
end

--//TO EDIT IF NECESARRY
local setVelocity = function(selectedVel)
	if isReversed == false then
		--//This uses hhwheats simple driving calculations. Change if you wish.
		local vectorpower = generalPower*selectedVel.CFrame.lookVector
		selectedVel["BodyVelocity"].MaxForce = Vector3.new(vectorpower.X>0 and vectorpower.X or -vectorpower.X,
			vectorpower.Y>0 and vectorpower.Y or -vectorpower.Y,
			vectorpower.Z>0 and vectorpower.Z or -vectorpower.Z
		)
		selectedVel["BodyVelocity"].Velocity = (Velocity)*selectedVel.CFrame.lookVector
	end
end

local function UpdateStatistics(delta)
	if currentThrottle > (targettedThrottle / throttle) then
		currentThrottle = math.clamp(currentThrottle - ((delta) / (throttleIdleTime)), (targettedThrottle/throttle), 1)
	end
	if currentThrottle < (targettedThrottle / throttle) then
		currentThrottle = math.clamp(currentThrottle + ((delta) / (throttleFullTime)), 0, (targettedThrottle/throttle))
	end
	if currentBrake > (targettedBrake / brake) then
		currentBrake = math.clamp(currentBrake - ((delta) / (brakeIdleTime)), (targettedBrake/brake), 1)
	end
	if currentBrake < (targettedBrake / brake) then
		currentBrake = math.clamp(currentBrake + ((delta) / (brakeFullTime)), 0, (targettedBrake/brake))
	end
end


local function PerformVelocityChanges(delta)
	Velocity = math.clamp((Velocity + (delta*currentThrottle*(throttlePower * baseStud)) - (delta*currentBrake*(brakePower*baseStud))), 0, maxSpeed*baseStud)
	IterateBaseParts(function(SelectedBasePart)
		if SelectedBasePart.Anchored == true then
			Velocity = 0
		end
	end)

end


RunService.Heartbeat:connect(function(delta)
	UpdateStatistics(delta)
	PerformVelocityChanges(delta)
	IterateBaseParts(function(selectedvelocity)
		setVelocity(selectedvelocity)
	end)
end)

UserInputService.InputBegan:connect(function(input) -- // This is the bit I'm trying to figure out
	if input.KeyCode == Enum.KeyCode.W then
		if debounce.up then
			return
		end
		targettedThrottle = math.clamp(targettedThrottle + 1, 0, throttle)
		RemoteClientEvent:FireServer("base", {action = "MovementUpdate", throttle = targettedThrottle, brake = targettedBrake})
	end
	if input.KeyCode == Enum.KeyCode.S then
		if debounce.up then
			return
		end
		targettedThrottle = math.clamp(targettedThrottle - 1, 0, throttle)
		RemoteClientEvent:FireServer("base", {action = "MovementUpdate", throttle = targettedThrottle, brake = targettedBrake})
	end
	if input.KeyCode == Enum.KeyCode.A then
		if debounce.down then
			return
		end
		targettedBrake = math.clamp(targettedBrake + 1, 0, brake)
		RemoteClientEvent:FireServer("base", {action = "MovementUpdate", throttle = targettedThrottle, brake = targettedBrake})
	end
	if input.KeyCode == Enum.KeyCode.D then
		if debounce.down then
			return
		end
		targettedBrake = math.clamp(targettedBrake - 1, 0, brake)
		RemoteClientEvent:FireServer("base", {action = "MovementUpdate", throttle = targettedThrottle, brake = targettedBrake})
	end
end)

if (developerMode) then
	local devModeFrame = Instance.new("Frame", script.Parent)
	devModeFrame.AnchorPoint = Vector2.new(0,1)
	devModeFrame.Position = UDim2.new(0,0,1,0)
	devModeFrame.Size = UDim2.new(0.2,0,0,100)
	local devModeThrottle = Instance.new("TextLabel", devModeFrame)
	devModeThrottle.Size = UDim2.new(1,0,0,33)
	local devModeBrake = Instance.new("TextLabel", devModeFrame)
	devModeBrake.Size = UDim2.new(1,0,0,33)
	devModeBrake.Position = UDim2.new(0,0,0,33)
	local devModeSpeed = Instance.new("TextLabel", devModeFrame)
	devModeSpeed.Size = UDim2.new(1,0,0,33)
	devModeSpeed.Position = UDim2.new(0,0,0,66)
	coroutine.wrap(function()
		while true do
			devModeThrottle.Text = "Throttle: "..tostring(currentThrottle).." - "..tostring(targettedThrottle)
			devModeBrake.Text = "Brake: "..tostring(currentBrake).." - "..tostring(targettedBrake)
			devModeSpeed.Text = "Speed: "..tostring(Velocity)
			wait(.1)
		end
	end) ()
end

Module Script:

--[[
	// **READ-ONLY**
	// FileName: TrainModule.lua
	// Written by: Jake Baxter
--]]

local TrainModule = {}
TrainModule.Version = {0, 0, 1, 1} --//Update per version
TrainModule.__index = TrainModule
local LocalModule = {}
LocalModule.__index = LocalModule


--// Main Function //

function TrainModule.new(trainModel, data)
	local classSelf = {}
	setmetatable(classSelf, LocalModule)
	classSelf.Properties = {}
	classSelf.Properties.trainModel = trainModel
	classSelf.Properties.rawData = data
	classSelf.Properties.currentDriver = nil
	classSelf.Properties.reversed = false
	classSelf.Properties.canReverse = false
	classSelf.Properties.remoteEvent = nil
	classSelf.Properties.remoteFunction = nil
	classSelf.Properties.Anchored = true
	classSelf.Properties.UIEnabled = true
	local moduleEvent = Instance.new("BindableEvent")
	classSelf.Properties.Event = moduleEvent
	classSelf.Properties.baseStud = 1
	if data["baseStud"] and type(data["baseStud"]) == "number" then
		classSelf.Properties.baseStud = data["baseStud"]
	end


	assert(data["vehicleSeat"], "Include Vehicle Seat Object Reference")
	if not (data["vehicleSeat"]:IsA("VehicleSeat") or data["vehicleSeat"]:IsA("Seat")) then
		error("VehicleSeat must be a vehicle seat (Duh)!")
	end
	classSelf.Properties.vehicleSeat = data["vehicleSeat"]
	local tempRemoteEvent = Instance.new("RemoteEvent")
	tempRemoteEvent.Parent = data["vehicleSeat"]
	tempRemoteEvent.Name = "PlayerRemoteEvent"
	classSelf.Properties.remoteEvent = tempRemoteEvent



	if not (data["baseParts"]:IsA("BasePart") or type(data["baseParts"]) == "table") then
		error("baseParts object needs setting as a base part.")
	end
	if data["baseParts"]:IsA("BasePart") then
		classSelf.Properties.baseParts = {data["baseParts"]}
	end
	if type(data["baseParts"]) == "table" then
		classSelf.Properties.baseParts = data["baseParts"]
	end
	classSelf:IterateBaseParts(function(basepart)
		if data["MoverType"] == "BodyVelocity" then
			local mover = Instance.new("BodyVelocity", basepart)
			mover.MaxForce = Vector3.new(0,0,0)
			mover.Velocity = Vector3.new(0,0,0)
			mover.P = 1250
			classSelf["MoverType"] = "BodyVelocity"
		end
		if data["MoverType"] == "BaseVelocity" then
			classSelf["MoverType"] = "BaseVelocity"
		end
		if data["MoverType"] == "LinearVelocity" then
			classSelf["MoverType"] = "LinearVelocity"
		end
		local mover = Instance.new("BodyVelocity", basepart)
		mover.MaxForce = Vector3.new(0,0,0)
		mover.Velocity = Vector3.new(0,0,0)
		mover.P = 1250
		classSelf["MoverType"] = "BodyVelocity"

	end)




	if (data["revVehicleSeat"]) then
		if (data["revVehicleSeat"]:IsA("VehicleSeat")) then
			classSelf.Properties.canReverse = true
			classSelf.Properties.revVehicleSeat = data["revVehicleSeat"]
		end
	end


	if not (typeof(data["throttle"]) == "number" and math.floor(data["throttle"]) == data["throttle"]) then
		error("Throttle must be an integer!")
	end
	if data["throttle"] < 1 then
		error("Throttle must be greater to or 1!")
	end
	classSelf.Properties.throttle = data["throttle"]


	if not (typeof(data["brake"]) == "number" and math.floor(data["brake"]) == data["brake"]) then
		error("Brake must be an integer!")
	end
	if data["brake"] < 1 then
		error("Brake must be greater to or 1!")
	end
	classSelf.Properties.brake = data["brake"]


	if not (typeof(data["throttlePower"]) == "number") then
		error("Throttle Power  must be a number!")
	end
	if data["throttlePower"] < 0 then
		error("Throttle Power must be greater than 0!")
	end
	classSelf.Properties.throttlePower = data["throttlePower"]


	if not (typeof(data["brakePower"]) == "number") then
		error("Brake Power  must be a number!")
	end
	if data["brakePower"] < 0 then
		error("Brake Power must be greater than 0!")
	end
	classSelf.Properties.brakePower = data["brakePower"]


	if not (typeof(data["throttleFullTime"]) == "number") then
		error("Throttle Up Time  must be a number!")
	end
	if data["throttleFullTime"] < 0 then
		error("Throttle Up Time must be greater than 0!")
	end
	classSelf.Properties.throttleFullTime = data["throttleFullTime"]


	if not (type(data["throttleIdleTime"]) == "number") then
		error("Throttle Down Time  must be a number!")
	end
	if data["throttleIdleTime"] < 0 then
		error("Throttle Down Time must be greater than 0!")
	end
	classSelf.Properties.throttleIdleTime = data["throttleIdleTime"]



	if not (typeof(data["brakeFullTime"]) == "number") then
		error("Brake Up Time  must be a number!")
	end
	if data["brakeFullTime"] < 0 then
		error("Brake Up Time must be greater than 0!")
	end
	classSelf.Properties.brakeFullTime = data["brakeFullTime"]


	if not (typeof(data["brakeIdleTime"]) == "number") then
		error("Brake Down Time  must be a number!")
	end
	if data["brakeIdleTime"] < 0 then
		error("Brake Down Time must be greater than 0!")
	end
	classSelf.Properties.brakeIdleTime = data["brakeIdleTime"]


	if not (typeof(data["maxSpeed"]) == "number") then
		error("maxSpeed  must be a number!")
	end
	if data["maxSpeed"] < 0 then
		error("maxSpeed must be greater than 0!")
	end
	classSelf.Properties.maxSpeed = data["maxSpeed"]


	if (data["bodyVelocityP"]) then
		if not (typeof(data["bodyVelocityP"]) == "number" and math.floor(data["bodyVelocityP"]) == data["bodyVelocityP"]) then
			error("bodyVelocityP must be an integer!")
		end
		if data["bodyVelocityP"] < 1 then
			error("bodyVelocityP must be greater to or 1!")
		end
		classSelf.Properties.bodyVelocityP = data["bodyVelocityP"]
		classSelf:IterateBodyVelocity(function(mover)
			mover.P = data["bodyVelocityP"]
		end)
	end


	classSelf.Properties.MaxPower = 100000
	if (data["MaxPower"]) then
		if not (typeof(data["MaxPower"]) == "number" and math.floor(data["MaxPower"]) == data["MaxPower"]) then
			error("MaxPower must be an integer!")
		end
		if data["MaxPower"] < 1 then
			error("MaxPower must be greater to or 1!")
		end
		classSelf.Properties.MaxPower = data["MaxPower"]
	end


	assert(data["GUI"], "There must be a GUI set!")
	if not (typeof(data["GUI"]) == "Instance") then
		error("GUI needs to be a ScreenGUI instance!")
	end
	if not (data["GUI"]:IsA("ScreenGui")) then
		error("GUI needs to be a ScreenGUI instance!")
	end
	classSelf.Properties["GUI"] = data["GUI"]


	classSelf.modules = {}
	if (data["customModules"]) then
		if not(type(data["customModules"]) == "table") then
			error("customModules must be a table!")
		end
		for _, scriptReference in pairs(data["customModules"]) do
			if not (scriptReference:IsA("ModuleScript")) then
				continue
			end
			local tempScriptReferenceRequire = require(scriptReference)
			if not (tempScriptReferenceRequire.Version[0] == TrainModule.Version[0]) then
				continue
			end
			if not (tempScriptReferenceRequire.Version[1] == TrainModule.Version[1]) then
				continue
			end
			local tempScriptInit = tempScriptReferenceRequire.init(classSelf, moduleEvent.Event)
			table.insert(classSelf.modules, {required = tempScriptInit, name = tempScriptReferenceRequire.Name})
		end
	end


	classSelf.functions = {}


	classSelf.functions.SeatRegister = classSelf.Properties.vehicleSeat.ChildAdded:connect(function(child)
		if not (child.Name == "SeatWeld") then
			return false
		end
		if not (child.Part1.Name == "HumanoidRootPart") then
			return false
		end
		local tempPlayerStore = game.Players:GetPlayerFromCharacter(child.Part1.Parent)
		if not (tempPlayerStore) then
			return false
		end
		local tempPlayerGui
		if classSelf.Properties.UIEnabled then
			tempPlayerGui = classSelf.Properties["GUI"]:Clone()
			tempPlayerGui.Parent = tempPlayerStore.PlayerGui
		end
		classSelf.Properties["currentDriver"] = tempPlayerStore
		classSelf.Properties.Event:Fire("base", {class = classSelf.Properties, action = "DriverIn", player = tempPlayerStore, UI = tempPlayerGui})
	end)


	classSelf.functions.SeatUnRegister = classSelf.Properties.vehicleSeat.ChildRemoved:connect(function(child)
		if not (child.Name == "SeatWeld") then
			return false
		end
		classSelf.Properties["currentDriver"] = nil
		classSelf:IterateBodyVelocity(function(mover)
			mover.MaxForce = Vector3.new(0,0,0)
			mover.Velocity = Vector3.new(0,0,0)
		end)
		classSelf:AnchorTrain()
		classSelf.Properties.Event:Fire("base", {class = classSelf.Properties, action = "DriverOut"})
	end)


	classSelf.functions.clientregister = classSelf.Properties.remoteEvent.OnServerEvent:connect(function(player, moduleName, TableR)
		if not (moduleName == "Register") then
			return false
		end
		if not (type(TableR) == "table") then
			return false
		end
		if not (player == classSelf.Properties.currentDriver) then
			return false
		end
		classSelf.Properties.remoteEvent:FireClient(player, moduleName, classSelf.Properties)
		classSelf.Properties.Event:Fire("base", {class = classSelf.Properties, action = "ClientRegistered", player = player})
		classSelf:UnanchorTrain()
	end)


	for _,basePartsUnanchor in pairs(trainModel:GetDescendants()) do
		if basePartsUnanchor:IsA("BasePart") then
			basePartsUnanchor.Anchored = true
		end
	end


	return classSelf
end


--// Main Function //


function LocalModule:GetDriver()
	return self.Properties.currentDriver
end


function LocalModule:EnableModule(moduleReference)
	if not (moduleReference:IsA("ModuleScript")) then
		return false
	end
	local tempScriptReferenceRequire = require(moduleReference)
	if not (tempScriptReferenceRequire.Version[0] == TrainModule.Version[0]) then
		return false
	end
	if not (tempScriptReferenceRequire.Version[1] == TrainModule.Version[1]) then
		return false
	end
	local tempScriptInit = tempScriptReferenceRequire.init(self, self.Properties.Event)
	table.insert(self.modules, {required = tempScriptInit, name = tempScriptReferenceRequire.Name})
	return true
end


function LocalModule:DisableModule(moduleName)
	if not (type(moduleName) == "string") then
		return false
	end
	for _,scriptReference in pairs(self.modules) do
		if not (scriptReference.name == moduleName) then
			continue
		end
		scriptReference.required:OnDisable()
		table.remove(self.modules, table.find(self.modules, scriptReference))
		return true
	end
	return false
end

function LocalModule:GetModule(moduleName)
	for _,scriptReference in pairs(self.modules) do
		if (scriptReference.name == moduleName) then
			return scriptReference.required
		end
		return nil
	end
end

function LocalModule:ForceReverse()
	if self.Properties.canReverse then
		if self.Properties.reversed == false then
			self.Properties.reversed = true
			local seat = self.Properties.vehicleSeat
			if self.Properties.revVehicleSeat then
				seat = self.Properties.revVehicleSeat
			end
			self:SendPlayerMessage("base", {
				action = "reversed",
				seat = seat
			})
			self.Properties.Event:Fire("base", {class = self, action = "Reversed", seat = seat})
			return true
		end
		if self.Properties.reversed == true then
			self.Properties.reversed = false
			self:SendPlayerMessage("base", {
				action = "reversed",
				seat = self.vehicleSeat
			})
			self.Properties.Event:Fire("base", {class = self, action = "Reversed", seat = self.vehicleSeat})
			return true
		end
	end
	return false
end


function LocalModule:SendMessage(moduleName, Table)
	self.Properties.Event:Fire(moduleName, Table)
	return true
end


function LocalModule:SendPlayerMessage(moduleName, Table)
	if self.Properties.remoteEvent and self.Properties.currentDriver then
		self.Properties.remoteEvent:Fire(self.Properties.currentDriver, moduleName, self, Table)
		return true
	end
	return false
end


function LocalModule:GetClientEventConnection()
	return self.Properties.remoteEvent.OnServerEvent
end


function LocalModule:GetServerEventConnection()
	return self.Properties.Event.Event
end


function LocalModule:UnanchorTrain()
	for _,basePartsUnanchor in pairs(self.Properties.trainModel:GetDescendants()) do
		if basePartsUnanchor:IsA("BasePart") then
			basePartsUnanchor.Anchored = false
		end
	end

	for _,basePartsUnanchor in pairs(self.Properties.trainModel:GetDescendants()) do
		if basePartsUnanchor:IsA("BasePart") then
			if basePartsUnanchor:CanSetNetworkOwnership() then
				basePartsUnanchor:SetNetworkOwner(self.Properties.currentDriver)
			end
		end
	end

	self.Properties.Anchored = false
	return true
end


function LocalModule:AnchorTrain()
	for _,basePartsUnanchor in pairs(self.Properties.trainModel:GetDescendants()) do
		if basePartsUnanchor:IsA("BasePart") then
			basePartsUnanchor.Anchored = true
		end
	end
	self.Properties.Anchored = true
	return true
end


function LocalModule:IsAnchored()
	return self.Properties.Anchored
end


function LocalModule:DisableDefaultUI()
	self.Properties.UIEnabled = false
	return true
end


function LocalModule:EnableDefaultUI()
	self.Properties.UIEnabled = true
	return true
end

function LocalModule:IterateBodyVelocity(BodyVelFunc)
	if not self then
		return false
	end
	if not self.Properties.baseParts then
		return false
	end
	if not BodyVelFunc then
		return false
	end
	for _,basePart in pairs(self.Properties.baseParts) do
		if basePart:FindFirstChild("BodyVelocity") then
			BodyVelFunc(basePart:FindFirstChild("BodyVelocity"))
		end
	end
end


function LocalModule:IterateBaseParts(Function)
	if not self then
		return false
	end
	if not self.Properties.baseParts then
		return false
	end
	if not Function then
		return false
	end
	for _,basePart in pairs(self.Properties.baseParts) do
		Function(basePart)
	end
end

function LocalModule:GetProperty(property)
	return self.Properties[property]
end


return TrainModule

Script that goes in every train:

local TrainModule = require(game:GetService("ServerStorage"):WaitForChild("TrainSystem"):WaitForChild("TrainModule"))
local newTrain = TrainModule.new(script.Parent,
	{
		vehicleSeat = script.Parent.Frame.Drive, --Vehicle Seat Reference
		baseParts = script.Parent.Frame.Base, --Base Part Reference
		throttle = 5, -- 5 Throttle notches
		brake = 3, -- 3 brake notches
		throttlePower = 3, -- 3 studs per second accel at max throttle
		brakePower = 5, -- 5 studs per second deccel at max brake
		throttleFullTime = 5, -- 5 seconds to get to full throttle from idle
		throttleIdleTime = 2, -- 2 seconds to get to idle from full
		brakeFullTime = 3, -- 3 seconds to get to full from released
		brakeIdleTime = 3, -- 3 seconds to get to released (idle) to full
		maxSpeed = 90, -- 60 studs is maximum speed
		GUI= game:GetService("ServerStorage"):WaitForChild("TrainSystem"):WaitForChild("ScreenGui"), -- ScreenGUI object for train
		customModules = {
			
		},
		debugMode = true,
		baseStud = 1
	}
)

Documentation:

1 Like

in the 2nd actual line of code:

local RemoteClientEvent = VehicleSeat["PlayerRemoteEvent"]

This is useful information for finding references as you can see in the module script, it creates a remote event that it references for inputs in these lines:

local tempRemoteEvent = Instance.new("RemoteEvent")
tempRemoteEvent.Parent = data["vehicleSeat"]
tempRemoteEvent.Name = "PlayerRemoteEvent"

The remote event can be seen being processed in these lines:

	classSelf.functions.clientregister = classSelf.Properties.remoteEvent.OnServerEvent:connect(function(player, moduleName, TableR)
		if not (moduleName == "Register") then
			return false
		end
		if not (type(TableR) == "table") then
			return false
		end
		if not (player == classSelf.Properties.currentDriver) then
			return false
		end
		classSelf.Properties.remoteEvent:FireClient(player, moduleName, classSelf.Properties)
		classSelf.Properties.Event:Fire("base", {class = classSelf.Properties, action = "ClientRegistered", player = player})
		classSelf:UnanchorTrain()
	end)

The chunk of code above fires to a bindable event that is created right before the remote event, however I don’t know where that one leads yet, although from context clues I can assume that it goes to a server-sided function that sends arguments and handles whatever is passed through to them (in the client’s case when you press WASD it’ll send

{action = "MovementUpdate", throttle = targettedThrottle, brake = targettedBrake}
1 Like

this helps a lot, thank you ! I’ll keep looking from there to see if I can figure out where the bindable event goes

1 Like

the main thing I’m confused about is I do not know where the bindable event is, does it need to be somewhere in the explorer ? I cannot find it when I search for it and nowhere in the script does it get assigned a parent, although I am not too knowledgeable on bindable events

The bindable event gets created in the TrainModule constructor.

function TrainModule.new()
    --block of code
    	local moduleEvent = Instance.new("BindableEvent")
	classSelf.Properties.Event = moduleEvent
    --rest of code

The parent can be nil so long as the code maintains a reference to the instance, this prevents it from being garbage collected.
So to fire the event or set listeners you just need to use the object’s reference to it.

--Something like this:
local myTrain = TrainModule.new()
myTrain.Properties.Event.Event:Connect(function() print("Choo Choo") end)
myTrain.Properties.Event:Fire()

Edit: hadn’t noticed but there is actually a built in method in the module to use the bindable event called :SendMessage(moduleName, Table)

1 Like

oh awesome, thank you ! never knew anything about that with bindable events

another question if anyone can help me with this, sorry if I’m asking a lot !

with the codeblock that recieves the remote event, all the inputs given to it are returned as false, because the module is not listed as a “Register”

	classSelf.functions.clientregister = classSelf.Properties.remoteEvent.OnServerEvent:connect(function(player, moduleName, TableR)
		if not (moduleName == "Register") then
            print("1")
			return false
		end
		if not (type(TableR) == "table") then
            print("2")
			return false
		end
		if not (player == classSelf.Properties.currentDriver) then
            print("3")
			return false
		end
		classSelf.Properties.remoteEvent:FireClient(player, moduleName, classSelf.Properties)
		classSelf.Properties.Event:Fire("base", {class = classSelf.Properties, action = "ClientRegistered", player = player})
		classSelf:UnanchorTrain()
		print("4")
	end)

whenever I for example, press W, it’ll print 1, but never 4 at the same time, if that’s the case, it’s never firing the bindable event when I press a keybind, so how does it update the stats ?

That particular remote event seems to be solely to register a player as the train’s driver when they sit in the seat.

I couldn’t actually find any other listener which would accept “base” as the moduleName and handle movement. So I’m wondering if this is another module?

1 Like

There’s only one module that came with it, maybe it has something to do with how it has a base part ?
image
by default, it comes with a part called base, but I don’t know if that’s important

(image from when the game is running)
image

okay after looking for it more, I’m assuming this is how the movement is handled

	classSelf:IterateBaseParts(function(basepart)
		if data["MoverType"] == "BodyVelocity" then
			local mover = Instance.new("BodyVelocity", basepart)
			mover.MaxForce = Vector3.new(0,0,0)
			mover.Velocity = Vector3.new(0,0,0)
			mover.P = 1250
			classSelf["MoverType"] = "BodyVelocity"
		end
		if data["MoverType"] == "BaseVelocity" then
			classSelf["MoverType"] = "BaseVelocity"
		end
		if data["MoverType"] == "LinearVelocity" then
			classSelf["MoverType"] = "LinearVelocity"
		end
		local mover = Instance.new("BodyVelocity", basepart)
		mover.MaxForce = Vector3.new(0,0,0)
		mover.Velocity = Vector3.new(0,0,0)
		mover.P = 1250
		classSelf["MoverType"] = "BodyVelocity"
	end)

i’m not entirely sure how it works, but it runs off this in the local script

RunService.Heartbeat:connect(function(delta)
	UpdateStatistics(delta)
	PerformVelocityChanges(delta)
	IterateBaseParts(function(selectedvelocity)
		setVelocity(selectedvelocity)
	end)
end)

and multiple other blocks in the local script call that function, do not know how it updates the server, but I think I understand how the movement is being sent

I’m still confused to as how I’d send my own custom keypresses over, but I’m understanding the script more

Hello this script uses a concept known as “Object Orientated Programming” OOP for short. In this particular version it is used on the server side.

This is a function which goes through all the baseparts containing either BodyVelocity BasVelocity LinearVelocity etc. If the bodyVelocity in that particular basepart is not a BodyVelocity then it creates a new body velocity.

UpdateStatistics is a function which updates the calculations for things like the notch and brake and also the time in which it takes to increase/decrease them.
IterateBaseParts Goes through all the baseparts and returns a basepart (SelectedVelocity) which is then sent to the setVelocity function.

You must have a base within your train at all times as it is important (it keeps all the movement instances like BodyVelocity.

this particular function is fired when the DriversGui has been moved into the playerGui by the server. At that point the local script within the DriversGui will automatically run as soon as it is placed within a client sided directory. After that I believe the remoteEvent is fired from the client. But I could be wrong.

It seems that you lack the coding capabilities for this system, you should use the simple driving system provided by the same person who created this source as it is more user friendly for you. However it is good to look at this more advanced version and try to learn from it, if you have anymore enquiries don’t hesitate to drop a reply and let me know!