[OPEN] Scripter, Project Manager | 4+ Years of Experience


Hi! I’m RenKa. I’ve been on the platform since 2016 and have been developing on it for about 5 years.

I like performance, code readability, and quality-of-life in everything I do. I aim to create clean, extensible systems that are effective to use and extend.

  • :gear: I write Modular, extensible, and responsive systems
  • :soap: I am totally obsessed with code cleanliness, from naming to memory management
  • :rocket: I design performance-first, avoiding unnecessary abstractions

I have experience in:

  • :card_index_dividers: Trello & Notion for project management
  • :hammer_and_wrench: Git, GitHub & Rojo
  • :brain: OOP (Object-Oriented Programming)
  • :jigsaw: Frameworks like Knit, Nevermore Engine, and other Luau frameworks. including one I made myself
  • :writing_hand: Building organized, type-safe, and memory-safe codebases
  • :brick: Working with large existing codebases
  • :busts_in_silhouette: Efficiently work and communicate with large groups
  • :stopwatch: Performing well under pressure
  • :clipboard: Project management

🔧 Tool Framework

I have designed a Tool Framework for my personal project, designed to work as a framework for all my tool types and make production easier and faster.

One of the tool types in my game is melee, which works with state machines and proving huge performance!

BaseFramework.luau is ~300 lines.

Melee Type
local replicatedStorage = game:GetService("ReplicatedStorage")
local httpService = game:GetService("HttpService")

local customPackages = replicatedStorage.CustomPackages
local packages = replicatedStorage.Packages
local shared = replicatedStorage.Shared

local robloxStateMachine = require(customPackages.RobloxStateMachine)
local shapecastHitbox = require(customPackages.ShapecastHitbox)
local baseFramework = require(script.Parent.BaseFramework)
local instanceUtils = require(packages.InstanceUtils)
local actionBinder = require(packages.ActionBinder)
local valueObject = require(packages.ValueObject)
local tableUtils = require(packages.TableUtils)
local combat = require(customPackages.Combat)
local vfxActions = require(shared.VFXActions)
local utils = require(customPackages.Utils)
local rx = require(packages.Rx)

local defaultSettings = {
	HasHitboxes = true,
	CanReplicate = true,
}

local melee = {}
melee.__index = melee

function melee.new(tool: Tool, config: typeof(defaultSettings)?)
	local self = setmetatable(baseFramework.new(tool), melee) :: Melee
	self.MaxSwings = self.Janitor:Add(valueObject.new(#tableUtils.Values(self.ToolData.Animations.Swings), "number"))
	self.SwingIndex = self.Janitor:Add(valueObject.new(0, "number"))
	self.LastSwing = self.Janitor:Add(valueObject.new(0, "number"))
	self._Hitboxes = {}
	self.Config = tableUtils.Reconcile(config or {}, defaultSettings)
	
	self.StateMachine = self.Janitor:Add(robloxStateMachine.new("None", robloxStateMachine:LoadDirectory(script), self))

	self:LoadAnimList(self.ToolData.Animations)

	local swingParams = RaycastParams.new()
	swingParams.FilterType = Enum.RaycastFilterType.Exclude
	swingParams.FilterDescendantsInstances = {self.Entity, workspace.Terrain}
	swingParams.IgnoreWater = true

	for name in self.ToolData.Animations.Swings do
		local track = self:GetTrack(name)
		if not track then
			continue
		end

		local hitboxIndex = httpService:JSONDecode(track.Animation:GetAttribute("HitboxIndex"))
		for _, side in hitboxIndex do
			local hitboxPart = self.Tool.Hitboxes:FindFirstChild(side)
			if not side then
				warn("No side hitbox for", side, tool)
				continue
			end

			table.insert(self._Hitboxes, side, self.Janitor:Add(shapecastHitbox.new(hitboxPart, swingParams)))
		end
	end

	self.Janitor:Add(self.Equipped:Connect(function()
		self:_onEquipped()
	end))

	self.Janitor:Add(self.Unequipped:Connect(function()
		self:_onUnequipped()
	end))

	if self.IsEquipped then
		self:_onEquipped()
	end
	
	return self
end

function melee.Replicate(self: Melee, data: {any})
	if not self.Config.CanReplicate then
		return
	end

	data.Name = self.ToolData.Name
	data.ToolID = self:GetID()
	vfxActions:Replicate("ToolAction", self.Entity:GetAttribute("ID"), data)
end

function melee._onEquipped(self: Melee)
	self.StateMachine:ChangeState("Equip")
	
	for name in self.ToolData.Animations.Swings do
		local track = self:GetTrack(name)
		if not track then
			continue
		end
		
		local hitboxIndex = httpService:JSONDecode(track.Animation:GetAttribute("HitboxIndex"))
		self.EquippedJanitor:Add(track:GetMarkerReachedSignal("startHit"):Connect(function()
			for _, side in hitboxIndex do
				local hitbox = self._Hitboxes[side]
				local hitParts = {}
				hitbox:HitStart(nil):OnHit(function(raycastResult: RaycastResult) 
					local instance = raycastResult.Instance
					if table.find(hitParts, instance) then
						return
					end

					local humanoid = instance.Parent:FindFirstChildWhichIsA("Humanoid")
					if not humanoid or table.find(hitParts, humanoid) or not humanoid.Parent:GetAttribute("ID") then
						return
					end

					table.insert(hitParts, humanoid)

					local hitData = {
						TargetID = humanoid.Parent:GetAttribute("ID"),
						ToolID = self:GetID()
					}
					utils:ContextAction("Server", function()
						combat:MeleeHit(self.Entity:GetAttribute("ID"), hitData)
					end)

					utils:ContextAction("Client", function()
						local result = combat:MeleeHit(nil, hitData)
						if result then
							utils:ShakeCamera({
								FadeInTime = 0,
								FadeOutTime = 0.3,
								Frequency = 0.1,
								Amplitude = math.min(self.ToolData.Damage, 100),
								PositionInfluence = Vector3.one * 0.1,
								RotationInfluence = Vector3.zero
							})
							utils:ImpulseFov(self.ToolData.Damage / 10)
						end
					end)
				end):OnStopped(function(clearCallbacks) 
					clearCallbacks()	
					table.clear(hitParts)
				end)
			end
		end))

		local function stopHitboxes()
			for _, side in hitboxIndex do
				local hitbox = self._Hitboxes[side]
				hitbox:HitStop()
			end
		end

		self.EquippedJanitor:Add(track:GetMarkerReachedSignal("stopHit"):Connect(stopHitboxes))
		self.EquippedJanitor:Add(instanceUtils.ObserveProperty(track, "IsPlaying"):Pipe({
			rx.where(function(isPlaying: boolean)
				return isPlaying == false
			end)
		}):Subscribe(stopHitboxes))
	end
	
	utils:ContextAction("Client", function()
		for _, state in self.StateMachine:GetStates() do
			if not state.InputData then
				continue
			end
			
			self.EquippedJanitor:Add(actionBinder.BindActionAtPriority(state.Name, function(inputState: Enum.UserInputState) 
				if inputState == Enum.UserInputState.Begin then
					local result, wasCalled = utils:OptionalCall(state, "ShouldActivate")
					if not result and wasCalled then
						return
					end
					
					self.StateMachine:ChangeState(state.Name)
					return
				end
				
				return Enum.ContextActionResult.Pass
			end, state.InputData.Priority, table.unpack(state.InputData.Keys)))
		end
	end)
end

function melee._onUnequipped(self: Melee)
	self.StateMachine:ChangeState("None")
	self:Replicate({
		Action = "Unequip"
	})
end

export type Melee = baseFramework.BaseFramework & {
	StateMachine: robloxStateMachine.RobloxStateMachine,
	MaxSwings: valueObject.ValueObject<number>,
	SwingIndex: valueObject.ValueObject<number>,
	Hitboxes: {shapecastHitbox.Hitbox},
	Replicate: (self: Melee, Data: {any}) -> (),
	Config: typeof(defaultSettings),
}

return melee
💾 DataStore Module

Wraps ProfileStore and ReplicaService.

local replicatedStorage = game:GetService("ReplicatedStorage")
local players = game:GetService("Players")

local packages = replicatedStorage.Packages

local replicaServer = require(script.ReplicaServer)
local playerUtils = require(packages.PlayerUtils)
local profileStore = require(script.ProfileStore)
local janitor = require(packages.Janitor)
local signal = require(packages.Signal)

local dataStore = {
	Active = {},
	NewDataStore = signal.new() :: signal.Signal<string, dataStore>
}
dataStore.__index = dataStore

function dataStore.new<T>(name: string, template: T, createReplica: boolean?)
	local self = setmetatable({}, dataStore)
	
	self.Name = name
	self.Janitor = janitor.new()
	self.ProfileLoaded = self.Janitor:Add(signal.new()) :: signal.Signal<Player, profileStore.Profile<T>, replicaServer.Replica>
	self.ProfileReleasing = self.Janitor:Add(signal.new()) :: signal.Signal<Player, profileStore.Profile<T>>
	self.Profiles = {}
	
	self.Store = profileStore.New(name, template)
	if createReplica then
		self.ReplicaToken = replicaServer.Token(name)
	end
	
	self.Janitor:Add(
		playerUtils.observePlayers(nil, function(player: Player, playerJanitor)
			local profile = playerJanitor:Add(
				self.Store:StartSessionAsync(`{player.UserId}`, {
					Cancel = function()
						return not player:IsDescendantOf(players)
					end,
				}), "EndSession"
			)
			
			if profile then
				profile:AddUserId(player.UserId)
				profile:Reconcile()
				
				playerJanitor:Add(function()
					self.ProfileReleasing:Fire(player, profile)
				end)
				profile.OnSessionEnd:Connect(function()
					self.Profiles[player] = nil
					player:Kick("Session end - Please rejoin")
				end)
				
				if player:IsDescendantOf(players) then
					self.Profiles[player] = {
						Profile = profile,
					}
					
					if createReplica then
						local replica = replicaServer.New({
							Token = self.ReplicaToken,
							Data = profile.Data
						})
						self.Profiles[player].Replica = replica
						replica:Subscribe(player)
					end
					
					self.ProfileLoaded:Fire(player, profile, self.Profiles[player].Replica)
				else
					profile:EndSession()
				end
			else
				player:Kick("Failed to load data - Please rejoin")
			end
		end)
	)
	
	dataStore.Active[name] = self
	dataStore.NewDataStore:Fire(self.Name, self)
	return self
end

function dataStore.GetProfile(self: dataStore, player: Player)
	return self.Profiles[player]
end

function dataStore.Destroy(self: dataStore)
	self.Janitor:Destroy()
	dataStore.Active[self.Name] = nil
	setmetatable(self, nil)
	table.clear(self)
end

function dataStore.Get(name: string)
	local target = dataStore.Active[name]
	
	if not target then
		local thread = coroutine.running()
		local addedConnection
		local startTime = os.clock()
		task.spawn(function()
			repeat
				task.wait()
			until os.clock() - startTime >= 5 or target ~= nil
			
			if coroutine.status(thread) == "suspended" then
				coroutine.resume(thread)
			end
		end)
		
		addedConnection = dataStore.NewDataStore:Connect(function(_name: string, _dataStore) 
			if _name ~= name then
				return
			end
			target = _dataStore
			addedConnection:Disconnect()
			
			if coroutine.status(thread) == "suspended" then
				coroutine.resume(thread)
			end
		end)
		coroutine.yield()
		return target :: dataStore
	end
	return target
end

function dataStore.GetProfileFromDataStore(player: Player, name: string)
	return dataStore.Get(name):GetProfile(player)
end

export type dataStore = typeof(dataStore.new())

return dataStore


Discord: renkamens

2 Likes

Hey, nice melee weapon, did u do that animations?

1 Like

Thank you! @Xoaterz made the animations.

1 Like

This is the most juicy argument I’ve ever seen.

This is an amazing code how long did it take?

1 Like

Thanks! It took me a few hours to do, but it’s mainly thanks of my years of practice.

Feel free to use it as a reference or use it as you want.

2 Likes