All about Object Oriented Programming


ALL ABOUT OOP!


Prerequisites

  • An understanding of meta-tables (although the required code will be explained)
  • How tables work and a competent grasp of the Lua syntax

Parts

What is OOP?
How does it help me?
How do I make this work in Lua?
Integrating with module scripts
What about inheritance?


What is OOP?

OOP stands for Object Orientated Programming and is a way of laying out code in a more friendly way whilst also keeping large projects organised. You have used objects in programming, even if you didn’t know. Things such as Parts or models are objects. This may seem obvious however most of the Roblox api is made up of objects. Even Vectors are objects in the Roblox API. Let’s define what an object is.

“An object can be a variable, function, or data structure. In the object-oriented programming paradigm, “object” refers to a particular instance of a class where the object can be a combination of variables, functions, and data structures.” -Wikipedia

In simple terms it is simply something which contains data about itself and functions to manipulate that data more easily.

For example let’s say a variable “brick” is a Part. It has some data, its size, colour, shape, position and it also have some functions associated with it, Destroy, Clone, GetMass etc. Manipulating parts is easy for us and it intuitive, but have you ever wanted more functions or your own type of part?


How does it help me?

Dealing with objects is a great way to enforce an idea called abstraction. Abstraction is simply being able to do things without worrying about underlying processes. For example when you do something in Lua it is in fact being run in C which is turned into assembly and then machine code. (Yes I know that isn’t totally accurate but that isn’t what this tutorial is about). Although all that is going on you don’t need to worry about it. Likewise when you make a part in Roblox you don’t need to worry about it’s physics or rendering it or any of that, due to abstraction. Not only does this clear up code but it allows collaboration to work much more seamlessly.

Let’s say you’re making a racing game. It would be useful to be able to do car = Instance.new(“Car”, Workspace) and then be able to find useful information such as car.Position or use methods such as car:Respawn(). Instance.new is part of the Roblox API so we wont touch it however we can make something similar, Car.new().


How do I make this work in Lua?

In Lua (or at least the system I will be teaching you) an object is a table with some variables in. A car object might look something like this.
car = {"RacePosition" = "3", "Speed" = 50, "Driver" = "Guest12372", "WorldPosition" = vector(25.31, 3.23, 53.86)}

And each car would be a similar table but with different values at each index, depending on where it is. First let’s define the function which will let us make new cars. Our own personal Instance.new() sorta function.

function newCar(position, driver, model)

end

However this will get quite annoying very fast. Lonely functions are not a happy sight in OOP. So in Lua we should have a table to contain all of our functions. I will name this table Car (it will become obvious why later). Every function related to the Car object will be in this table. So now the code will look like.

Car = {}
Car["new"] = (function(position, driver, model)

end)

The problem with this is it doesn’t look very readable. Using some special Lua syntax candy we can do this instead.

Car = {}

function Car.new(position, driver, model)

end

This looks much nicer. Just by looking at it we can see what the function is designed to do. Since this function is creating new objects it is a special type of function. It is known as the constructor. It constructs new Car objects (yeah programmers are creative). Also note, here is a class called Car which makes Car objects. The constructor and functions are the class whereas the things made by the constructor are the objects.

Since each Car object is just a table, a new table will need to be constructed which all the relevant data in it.

Car = {}

function Car.new(position, driver, model)
    local newcar = {}

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

Now when we call Car.new() with the relevant arguments it will give us back a nice table. It’s organised but not very useful. There are no functions. It might be tempting just to put the functions in the table when it is constructed however this is both inefficient and messy. Since the functions are the same for every Car object they only need to be made once so it is better to provide Lua with the functions when it is trying to find a variable but cannot find it. Let’s say we are trying to get the driver of a car. We would do driver = Car.Driver. Lua looks in the car and finds the variable straight away without any extra variables in that table getting in the way. It would be useful if we could detect if the variable we are trying to find is a function, and in fact using meta-tables we can do exactly that.

Meta-tables are tables with a set of methods which perform various special tasks on other tables. I won’t go too much into detail on this. The metamethod we want to use is the .__index metamethod. It is fired whenever Lua tries to find an index in a table but it has a nil value. We can just redirect Lua to a new table which have all the functions in. Conveniently our Car table has exactly that!

Car = {}
Car.__index = Car

function Car.new(position, driver, model)
    local newcar = {}
    setmetatable(newcar, Car)

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

Now if we did newcar = Car.New() and then tried to call a function on car is would look through the Car table too, not just newcar. Let’s add a function to our car object to make it more useful.

Car = {}
Car.__index = Car

function Car.new(position, driver, model)
    local newcar = {}
    setmetatable(newcar, Car)

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

function Car:Boost()
    self.Speed = self.Speed + 5
end

Now we can do.

newcar = Car.new(Vector3.new(1,0,1), "Guest1892", game.ReplicatedStorage.F1Car)
newcar:Boost()

This creates a new car object and then calls :Boost() on it. What happens behind the scenes is Lua tries to find newcar[“Boost”] however this does not exist, sees that Car is at newcars meta-table’s .__index and then tries to find Car[“Boost”] which does exist! There is also a neat little feature in Lua where if you do function table:Method() self is auto declared. It is the same as doing function table.Method(self). Remember how boop:Beep() is the same as calling boop.Beep(boop). It passes the object to the function allowing the function to perform actions on that individual object. In this case :Boost() can perform the speed increase on individual cars.


Integrating with module scripts

This method of OOP works extremely well with module scripts. Simply put return Car at the bottom of the Car script and the module script will return the Car table ready for use, allowing you to do things like this.

--module script called Car in game.ReplicatedStorage
Car = {}
Car.__index = Car

function Car.new(position, driver, model)
    local newcar = {}
    setmetatable(newcar, Car)

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

function Car:Boost()
    self.Speed = self.Speed + 5
end

return Car

--main script

Car = require(game.ReplicatedStorage.Car)

newcar = Car.new()
newcar:Boost()

This gives you a way of neatly splitting potentially big scripts into little chunks which are easy to understand and change if needed.


What about inheritance?

A quick explanation of inheritance to those new to OOP. Inheritance is where a class can ‘inherit’ functions and behaviours from another class. So if we made a new class (type of object) for special types of cars, let’s say trucks. A truck is pretty similar to a car so there is no need to rewrite all of the code. Instead you would make truck ‘inherit’ all the methods of car. For the sake of scripting trucks can have power ups will allow special behaviour whilst cars cannot. Let’s make the truck constructor.

Truck = {}
Truck.__index = Truck

function Truck.new(position, driver, model, powerup)
    local newtruck = {}
    setmetatable(newtruck, Truck)

    return newtruck
end

return Truck

If we did this we would still need to put all of the declaring code into the constructor. This is pretty redundant so instead we would just create a car object inside of the constructor.

Car = require(game.ReplicatedStorage.Car)

Truck = {}
Truck.__index = Truck

function Truck.new(position, driver, model, powerup)
    local newtruck = Car.new(position, driver, model)
    setmetatable(newtruck, Truck)

    newtruck.Powerup = powerup

    return newtruck
end

return Truck

Great. Now the truck is properly constructed however if we tried to do

newtruck = Truck.new()
newtruck:Boost()

It would error saying something along the lines of “attempt to call method ‘Boost’, a nil value”. This is because is looks at newTruck[“Boost”] and sees nil and then looks at Truck[“Boost”] and sees nil. What we really want is for it then to look at Car[“Boost”] as that is where the method is. To do this we simple add a metatable to the Truck table to point Lua to Car. Like so.

Car = require(game.ReplicatedStorage.Car)

Truck = {}
Truck.__index = Truck
setmetatable(Truck, Car)

function Truck.new(position, driver, model, powerup)
    local newtruck = Car.new(position, driver, model)
    setmetatable(newtruck, Truck)

    newtruck.Powerup = powerup

    return newtruck
end

return Truck

Now we could use :Boost() on a truck.

Since Lua will look at the Truck table first it means if you re declared :Boost() in the Truck table it will override the method declared in the Car table. Pretty fun stuff. You can also inherit through as many classes as you wish with very little impact on performance.


Thanks for reading my tutorial on object oriented programming in Roblox, hope it helped!

1001 Likes
Crossing server-client boundary with custom OOP?
Most efficient way to have semi-large amount of NPCs
Optimal way to do hit detection
Triggering replication of custom objects with __newindex?
Module script help
Best way to store game items?
Game Development Resources [MEGA THREAD]
Data Structure Organization
What's the purpose of OOP?
Creating A Furniture Placement System
How far do you go with making objects?
OOPing Guidelines
Advice on how to set up script hierarchy for turned based combat system enemies
Why can I never use self?
Way to put module functions into event calls
Dungeon Generation: A Procedural Generation Guide
Help with Object Oriented Programming
Inheritance help
Most efficient way to create spellcasting system?
Is Vector3 a object?
What are the type of scenarios Metatables are useful for?
Modules and OOP
Need help understanding OOP
Inheriting in a pseudo-class-based structure
Creating a battle system: OOP style
Can somebody give me some basic stuff I can do with OOP?
Codebase involving ModuleScripts (not exactly modular) - requesting feedback
Help with metatables
How would I Organize My Scripting Better
Questions about OOP
Question about OOP
Tycoon Framework/system
Player = nil in module script. Why?
How to make api's
EZ Pathfinding V3 [OUTDATED]
OOP in Lua, elaborate
Question about ModuleScripts
Question about ModuleScripts
Understanding OOP and all of it's functions, any help?
Help with self and OOP
Can you create your own classes in lua?
Can you create your own classes in lua?
All you need to know about Metatables and Metamethods
Listening to field changes in a table (OOP Object Replication)
What does self mean?
Understanding Metatables
Question about OOP!
OLD Metatables, Metamethods, ModuleScripts and OOP
Luau Type Checking Beta!
I need help with scripting
What is the best practice for this?
Can you make a "class" object in Lua?
UI Code Spaghetti
How to datastore2 more than 1 variable?
Scripting Projects: Help
Is there a way to turn a string into a reference?
Question about oop
What things should I learn?
Custom Character/NPC without Humanoid
How do I make a module return a new table everytime, so scripts get their own table instead of sharing the table of that module?
What are you working on currently? (2020)
Object Oriented Programming
What are getters and setters?
Why do you need to use global functions instead of local when using metatables and OOP?
Trouble with index & newindex
I do not understand __index
Attempt to call a nil value - but doesn't look nil to me when debugging, guidance for lua/Roblox novice please?
Npc/monster attributes (Tags, DataStore, ?)
Npc/monster attributes (Tags, DataStore, ?)
How to fix my OOP raycasting module?
Any way to check how many bullets remain on ROBLOX's endorsed weapons?
Global function?
OOP help needed
A* Pathfinding: Don't Cross Corners
Anybody can help me about understanding basic OOP thingy?
How do i fix this
What is 'self' and how can it be used?
What Are the Pros and Cons of a Module Based Game?
How to fix my math.clamp() so objects when being moved stayed in a plot's area?
(OOP) Class Module
Controlling an OOP object on the server and client?
How to create objects in LUA?
ModuleScript not working no errors
Issue with results from OOP module
How can self be useful?
How can i get obj Properties
Quick question about object oriented programming
OOP Inheritance
Why thingy is not owrking
ModuleScripts and "local" variables
A quick question regarding OOP
Proper way to make a .new() function in a modulescript
Can you put events in module scripts? ( OOP )
Working w/ ModuleScripts?
Inverse Kinematics for Tripod
How do I make multiple parts form into a circle around the player?
How do I make multiple parts form into a circle around the player?
How can I improve scripting (or coding)?
What's the best way to handle the data for a forest of choppable trees?
Creating projectiles?
Little help with OOP
Player Folder Objects Dissappear Before DataStore Saving
How to make a stealth system
How can I store properties & a table of functions inside an object? (metatables?)
Efficient Object Oriented Programming Tutorial
How would I go about creating methods for objects?
How to use DateTime
Scripting Resume
Custom notifications help
Best way to handle perks/buffs
Viewmodel.lua || Viewmodel handler for FPS frameworks
Roblox Module scripts are structured differently than the wiki examples? Are they the same or am I doing it wrong?
The Beginners Guide To All Things Game Development
Help with tweening a cooldown indicator and global cooldown
I made a tycoon without someone else's pack and I hit a road block
Module scripts and metatables
How do i make a zombie?
Using require() on a Module that was created using Instance.new()
WeaponSystem Feedback
How should I use OOP?
Quick question, how can I sync loops?
Question regarding Modules
Module Script Help
Lobby System/Party System
Confused about Self
State Machine : Module Scripts and Animator Replication issues
Is there a more efficient way to do this?
OLD Metatables, Metamethods, ModuleScripts and OOP
OOP Formatting Help
What is "self"?
Question on returning
What exactly does self do?
Development Troubles
How can i make a function with arguments like this
Attempt to call a nil value
How can I run a system that the player owns some parts?
Chaining .__index in metatables
How to use oop?
OOP: Implentation Review
Roblox Scripting Roadmap & Learning Resource List
What is ".self"?
How can i "Box" this value
Are Prototype-Based Classes a better way to use OOP?
It's possible to use S.O.L.I.D principles in Lua?
Concepts: Object Oriented Programming in Roblox
Attempt to index nil with 'PlacedObjects'
OOP question: How to index objects from outside that object
ModuleScripts General queries?
OOP and __Index functions
Artificial (OOP-based) humanoids and how to create them?
Table with more then one level of metatables wont convert into JSON
How to Implement OOP in Rbx.Lua
Can you create actual classes and how?
How would I go about making a custom moveset for specific characters
Best way of manipulating Player Data
Should I use : or . when calling a ModuleScript function?
How to apply tool pseudoclasses in practice?
Can someone teach me metatables?
What does .new do in custom functions?
What does .new do in custom functions?
What is a Metatable?
OOP is pretty easy to mess up
Do I HAVE to include metatables in my games?
Help with Object Oriented Programming
78: attempt to perform arithmetic on local 'Value' (a nil value)
Game organization
Mass-Assigning Variables
Don't know why argument is nil
Creating a battle system: OOP style!
Sorting, Organizing Scripts and Workplace
When is a metatable 'really' required?
How to do OOP with modules?
What is a better way to get handle AI?

This is also pretty useful for OOP; http://www.lua.org/pil/16.html

It shows some pretty nifty ways of using inheritance, which I think is pretty cool.

27 Likes

[quote] This is also pretty useful for OOP; http://www.lua.org/pil/16.html

It shows some pretty nifty ways of using inheritance, which I think is pretty cool. [/quote]

This tutorial pretty much explains that in huge detail.

11 Likes

Nice tut, was clear since I’ve had courses in OOP but rather Java instead.

12 Likes

What? No audio? HAX

6 Likes

Interesting. I never called it OOP. I just called it framework and metatables.

Glad to know there’s a more confusing name for everything I script :stuck_out_tongue:

27 Likes

Never considered setting a metatable to a metatable before. Nice work.

17 Likes

Great introduction to metatables

11 Likes

I want to understand this, but I don’t understand this and don’t know what to do.

I feel so below everyone here.

I need to stop being a moron with all this theory stuff and get better with all this table stuff, but god damnit it’s hard to find where to start.

I only came here cause there’s another thread discussing OOP and I’m just here like ‘wtf is everyone talking about?’ and feeling really stupidly out of place.

34 Likes

[quote] I want to understand this, but I don’t understand this and don’t know what to do.

I feel so below everyone here.

I need to stop being a moron with all this theory stuff and get better with all this table stuff, but god damnit it’s hard to find where to start.

I only came here cause there’s another thread discussing OOP and I’m just here like ‘wtf is everyone talking about?’ and feeling really stupidly out of place. [/quote]
http://hastebin.com/oyivihiraf.lua

3 Likes

[quote] I want to understand this, but I don’t understand this and don’t know what to do.

I feel so below everyone here.

I need to stop being a moron with all this theory stuff and get better with all this table stuff, but god damnit it’s hard to find where to start.

I only came here cause there’s another thread discussing OOP and I’m just here like ‘wtf is everyone talking about?’ and feeling really stupidly out of place. [/quote]

Probably the best place to learn about metatables would be the Minecraft mod, Computercraft.

It’s where I learned all about metatables. By accident too, I was just looking at the vector API to be able to create my own stuff similar to it.

7 Likes

Well I understand metatables fairly well now and get how they work (still dont understand their use), but this whole OOP thing is like a bloody maze. It reminds me of first trying to understand FilterEnabled.

I take it OOP is the hardest thing Lua has to offer, it sure feels like it.
If any of you have some relatively easy OOP related scripts you don’t mind sharing so I can route through them, I’d be delighted to do so.

2 Likes

[quote] Well I understand metatables fairly well now and get how they work (still dont understand their use), but this whole OOP thing is like a bloody maze. It reminds me of first trying to understand FilterEnabled.

I take it OOP is the hardest thing Lua has to offer, it sure feels like it.
If any of you have some relatively easy OOP related scripts you don’t mind sharing so I can route through them, I’d be delighted to do so. [/quote]

Here you go, a ModuleScript to create your own custom class:

[code]Thing = {}
Thing.__index = Thing

–A function to construct the object (a constructor)
–Ideally, all values you want the object to have would be initialized here.
function Thing.new(value)
local new = {}
new.Value = value

setmetatable(new,Thing)
return new
end

–Define some methods. The object you’re calling it on will be referred to as ‘self’ inside the methods.
function Thing:setValue(value)
self.Value = value
end

function Thing:getValue()
return self.Value
end

return Thing
[/code]

Now, to use it:

[code]Thing = require(script.Parent.ModuleScript)

obj = Thing.new(3)
print(obj:getValue()) --> 3

obj:setValue(1234)
print(obj:getValue()) --> 1234
[/code]

I hope this helps!

16 Likes

I’d like to thank Emma for this guide because it really helped me with Redirection.

4 Likes

I’m having trouble understanding the use case for this stuff, apologies, but it all seems a bit confusing.

4 Likes

Stick with it Kevin. OOP is extremely useful, not just on ROBLOX, but in the industry. One way to figure it out that I’ve found helpful is using ROBLOX objects as sort of your model internally for how you expect the object you make to operate.

7 Likes

Even though this is two years old, still is a useful tutorial about Metatables (Though maybe @magnalite could reformat this to work on this forum ;)) But I do have a question and I rather not make a separate thread as it could easily be answered and added to the tutorial for future readers…

Once you have made an object using OOP, how can you make your own :Destroy() method that’ll remove the object by itself, so I don’t have to do something like:

Inventory.Primary:Destoy()
Inventory.Primary = nil

To completely remove the object, I thought doing something like:

function Object:Destroy()
local n = self.Name
self.ToolModel:Destroy()
self = nil
print(n, “was successfully destroyed!”)
end

Would work, however it only deletes the model and not the object as self is just a path(?) and not the object itself. How would I delete the object from a method inside the metatable?

7 Likes

Your example is pretty spot on for this. You simply create the Destroy method that internally calls Destroy on the necessary items.

However, setting self to nil isn’t going to do anything. What you’re trying to do is remove the object entirely, but we should just let the garbage collector do that for us.

When you call Destroy() on any normal object in ROBLOX, it will only be GC’d once all references to it are gone. The exact same thing goes for tables. Of course, you can configure your table a bit more by making it have weak keys or values.

In summary, just make your Destroy() method destroy the actual objects that need to be destroyed. And then once your other scripts get rid of any references to that object, it will eventually be GC’d.

13 Likes

I think I’m doing this wrong, following the example of OP for creating and inheriting metatables and their methods/functions. Below in the BaseTool section I create what I call a template for my custom Tool object that all other “tools” (such as a sword or a gun) use for a template. I have an odd thing going on, whenever I call Tool:Destroy() on a BaseTool (Tool) object it’ll destroy it, and when I call RangedWeapon:Destroy() it’ll also be destroyed. But when I try to call :Destroy() on the PropaneTank, whether it be done by calling PropaneTank:Destroy() (when the object is spawned by using PropaneTank.new()) or by calling self:Destroy(), the script will error and say that Destroy is a nil value.

I followed step 2.2 of the original post, except the only change I made was by adding __index inside of the PropaneTank.new() function so I could read the properties of the PropaneTank… But if I remove that then my script will also break… Yeah I’m not sure what’s going on anymore. Here’s my code to see what I’m doing.

---------------------
-- ROBLOX Services --
---------------------
local RunService = game:GetService("RunService")
local RS = game:GetService("ReplicatedStorage")
-------------------
-- ModuleScripts --
-------------------
local Utilities = require(RS:WaitForChild("Utilities")) 
---------------
-- Variables --
---------------
local Weapons = {
	Ranged = {},
	Melee = {},
	Other = {},
}

for Index, ModuleScript in pairs(RS:WaitForChild("Weapons"):WaitForChild("Ranged"):GetChildren()) do
	if ModuleScript:IsA("ModuleScript") then
		local DataToCopy = require(ModuleScript)
		local DataToSave = Utilities:DeepCopy(DataToCopy)
		DataToSave.Type = "Weapon"
		Weapons.Ranged[#Weapons.Ranged+1] = DataToSave
	end
end

for Index, ModuleScript in pairs(RS:WaitForChild("Weapons"):WaitForChild("Melee"):GetChildren()) do
	if ModuleScript:IsA("ModuleScript") then
		local DataToCopy = require(ModuleScript)
		local DataToSave = Utilities:DeepCopy(DataToCopy)
		DataToSave.Type = "Weapon"
		Weapons.Ranged[#Weapons.Ranged+1] = DataToSave
	end
end

for Index, ModuleScript in pairs(RS:WaitForChild("Weapons"):WaitForChild("Support"):GetChildren()) do
	if ModuleScript:IsA("ModuleScript") then
		local DataToCopy = require(ModuleScript)
		local DataToSave = Utilities:DeepCopy(DataToCopy)
		DataToSave.Type = "Support"
		Weapons.Other[#Weapons.Other+1] = DataToSave
	end
end

for Index, ModuleScript in pairs(RS:WaitForChild("Weapons"):WaitForChild("Other"):GetChildren()) do
	if ModuleScript:IsA("ModuleScript") then
		local DataToCopy = require(ModuleScript)
		local DataToSave = Utilities:DeepCopy(DataToCopy)
		DataToSave.Type = "Other"
		Weapons.Other[#Weapons.Other+1] = DataToSave
	end
end


local function FindTool(ToolName)
	for FolderName, FolderContents in pairs(Weapons) do
		for Index, Tool in pairs(FolderContents) do
			-- print(Tool.Name)
			if Tool.Name == ToolName then
				return Tool, FolderName
			end
		end
	end
end

	
local function CreatToolAnimationData(Parent, IndexName, OriginalTable, ToolName)
	local Type = type(OriginalTable)
	local copy
	if Type == "table" then
		copy = {}
		for orig_key, orig_value in next, OriginalTable, nil do
			copy[CreatToolAnimationData(Parent, orig_key, orig_key, ToolName)] = CreatToolAnimationData(Parent, orig_key, orig_value, ToolName)
		end
	elseif Type == "number" then		
		local AnimationObject = Instance.new("Animation")
		AnimationObject.Name = ToolName.."_"..IndexName
		AnimationObject.AnimationId = "http://www.roblox.com/asset/?id="..OriginalTable
		AnimationObject.Parent = Parent
		
		copy = {AnimationObject, nil}
	else
		copy = OriginalTable
	end
	return copy
end

local function FindHighestFiringMode(ToolData)
	local FiringModes = ToolData.FiringModes
	if #FiringModes > 1 then
		local SelectedMode = 1
		for Index, Mode in pairs(FiringModes) do
			for _,M in pairs({"Semi", "Burst", "Auto"}) do
				if Mode == M then
					SelectedMode = Index
				end
			end
		end
		return SelectedMode
	else
		return 1		
	end
end


local Service = {}
Service.RetrieveToolData = FindTool
-------------------------
-- Base Tool Class API --
-------------------------
Service.Tool = nil
do
	local Class = {}
	Class.__index = Class
	function Class.new(ToolName, Player)
		local Data = FindTool(ToolName)
		if Data ~= nil then
			local Tool = {}
	    	setmetatable(Tool, Class)	
			Tool = Utilities:DeepCopy(Data)
			Tool.Parent = Player	
			Tool.ToolModel = Tool.ToolModel:Clone()
			Tool.ToolModel.PrimaryPart = Tool.ToolModel.Handle
			Utilities:WeldTool(Tool.ToolModel)
			Tool.SavedWeldData = Utilities:SaveWeldData(Tool.ToolModel)
			Tool.ToolModel.Parent = Tool.Parent
			Tool.Animations = CreatToolAnimationData(Tool.ToolModel, Tool.Name, Tool.Animations, Tool.Name)
			-- Tool.Equipped
			local Equipped = Instance.new("BindableEvent", Tool.ToolModel)
			Class.Equipped = Equipped.Event
			-- Tool.Unequipped
			local Unequipped = Instance.new("BindableEvent", Tool.ToolModel)
			Class.Unequipped = Unequipped.Event
			
			return Tool
		else
			spawn(function() error("Error there is no such tool named "..tostring(ToolName)..".") end)
			return nil
		end
	end
	
	function Class:Destroy()
		local n = self.Name
		self.ToolModel:Destroy()
		self = nil
		print(n, "was successfully destroyed!")
	end
	
	Service.Tool = Class
end
-----------------------------
-- Ranged Weapon Class API --
-----------------------------
Service.RangedWeapon = nil
do
	local Class = {}
	Class.__index = Class
	setmetatable(Class, Service.Tool)
	function Class.new(WeaponName, Player, AmountOfAmmo)
		local RangedWeapon = Service.Tool.new(WeaponName, Player)
		if RangedWeapon == nil then spawn(function() error("Error, BaseWeapon is nil.") end) return end
		
		local MaximumAmountAllowed = (RangedWeapon.MaxAmountOfMagazines * RangedWeapon.MagazineCapacity)
		if AmountOfAmmo >= MaximumAmountAllowed then
			AmountOfAmmo = MaximumAmountAllowed
		end
		
		setmetatable(RangedWeapon, Class)
		RangedWeapon.SelectedFiringMode = FindHighestFiringMode(RangedWeapon)
		RangedWeapon.TotalAmmo = AmountOfAmmo
		RangedWeapon.MagAmmo = RangedWeapon.MagazineCapacity
		
		return RangedWeapon, AmountOfAmmo
	end

	Service.RangedWeapon = Class
end
----------------------------
-- Melee Weapon Class API --
----------------------------
Service.PropaneTank = nil -- this is the only proper metatable...
do
	local Class = {}
	Class.__index = Class
	setmetatable(Class, Service.Tool)
	function Class.new(Parent)
				
		local PropaneTank = Service.Tool.new("Propane Tank")
		
		local DamagableTag = Instance.new("BoolValue")
		DamagableTag.Name = "Damagable"
		DamagableTag.Value = true	
		DamagableTag.Parent = PropaneTank.ToolModel
		
		PropaneTank.BackingTable = {IsDead = false}
		PropaneTank.CreationTimeStamp = tick()
		PropaneTank.Name = nil ; PropaneTank.BackingTable.Name = "Propane_Tank" ; PropaneTank.ToolModel.Name = PropaneTank.BackingTable.Name
		PropaneTank.Health = nil; PropaneTank.BackingTable.Health = 5	
		PropaneTank.Position = nil
		PropaneTank.CFrame = nil
		PropaneTank.Parent = nil ; PropaneTank.ToolModel.Parent = Parent
	
		setmetatable(PropaneTank, Class)	
		
		Class.__index = function(self, Index)
			return self.BackingTable[Index]
		end
	
		Class.__newindex = function(self, Index, Value)
			--rawset(self, Key, Value)
			self.BackingTable[Index] = Value		
			-- rawset(PropaneTank.BackingTable, Key, Value)	
			if Index == "Name" then
				self.ToolModel.Name = Value
			elseif Index == "Parent" then
				self.ToolModel.Parent = Value
			elseif Index == "CFrame" then
				self.ToolModel:SetPrimaryPartCFrame(Value)
				self.BackingTable.Position = Value.p
			elseif Index == "Position" then
				--[[
				local OldCF = self.BackingTable.CFrame
				local angles = OldCF - OldCF.p
				self.BackingTable.Position = Value.p
				--]]
				self.BackingTable.Position = Value.p
				self.ToolModel.PrimaryPart.Position = Value	
			elseif Index == "Health" then
				if self.BackingTable.Health == 0 and not self.BackingTable.IsDead then
					self.BackingTable.IsDead = true
					
					local e = Instance.new("Explosion")
					e.Position = self.ToolModel.PrimaryPart.CFrame.p
					e.DestroyJointRadiusPercent = 0
					e.BlastRadius = 15
					e.Parent = game.Workspace

					for Index, Zombie in pairs(_G.Zombies) do
						local Distance = (e.Position - Zombie.Torso.Position).Magnitude
						if Distance <= e.BlastRadius then
							Zombie:TakeDamage(1000)
						end
					end
					
					if _G.MapData then
						for Index, Tank in pairs(_G.MapData.PropaneTanks) do
							if Tank ~= self and not Tank.BackingTable.IsDead and Tank.ToolModel.PrimaryPart ~= nil then
								local Distance = (e.Position - Tank.ToolModel.PrimaryPart.CFrame.p).Magnitude
								if Distance <= e.BlastRadius then
									Tank.Health = 0
								end
							end
						end
					end
					
					-- only for testing
					self:Destroy() -- delay(0.5, function() self.BackingTable.IsDead = false end)
				end	
			end
			-- warn(self.Name.."."..(Index).." property was changed to "..tostring(Value)..".")
		end

		return PropaneTank
	end
	
	function Class:Destroy()
		self.ToolModel:Destroy()
	end
	
	Service.PropaneTank = Class
end

return Service
3 Likes

@GuestCapone

If you do

PropaneTank.BackingTable.__index = Class

Does that fix it? Im hoping it doesn’t cause a circular loop. If that works I can explain why if you would like me to.

1 Like