[Closed] Team Vesteria is recruiting a Junior Developer - Abilities

About Us

Vesteria is one of the largest and most ambitious Roblox experiences ever developed, with many contributors and a strong community of QA testers, artists, content creators and players. The game features innovative technology in content management/deployment, custom character/entity replication, LiveOps, analytics, and user data.

VesteriaTwitter

Team Vesteria
@sk3let0n - Creative Director
@berezaa - Lead Developer
@Polymorphic - Senior Developer
@Kensai666 - DevOps & Community Manager
@BrainDeadCommunity - 3D Artist

Play our game here: Vesteria - Roblox

About The Job

We’re looking for an enthusiastic abilities developer who isn’t afraid to get their hands dirty and who is eager to learn and grow their career path on Roblox. This role is perfect for developers with exceptional combat effects talent who love to ask questions and are actively looking to improve their hard coding and interpersonal skills.

The Vesteria codebase is extremely large and often complicated, but as an abilities developer you will be working exclusively within our custom replication player abilities system with support from the rest of the development team. This Junior-level job does not involve being assigned mission-critical tasks, and is more focused on helping you learn and grow. It’ll be a tough job, but you don’t need to worry about many of the stresses that come with managing a game of this scale.

Vesteria’s abilities are ModuleScripts which contain certain fields and methods which allows them to be executed by client and server ability managers. A single ability module contains all of the code clients need to independently render the ability as well what the server needs to verify and process the ability.

Below are two sample ability modules. While these modules may seem very intimidating at first, many of the code patterns you need to implement new abilities have already been made, and it’s mostly about finding the code you need from the most similar ability. The most important thing is being able to create exceptional effects, we’ll help you figure out how to make ability modules out of them.

Key terms:

abilityExecutionData - A table that is passed around various ability methods to track the state and validity of an ability call. This table is first generated on the client, and is then validated by the server before being sent to other clients. For example, an explosive projectile ability that gains radius as it is upgraded would access abilityExecutionData["ability-statistics"].radius to figure out how big the explosion should be for that particular cast and it would access abilityExecutionData["mouse-target-position-respecting-range"] to know where to fire the projectile.

abilityData:execute - The method called by the client to render an ability call and also to iniatilize certain values before sending to the server. execute is called by the casting client before it is sent to the server, and it is called by all other clients after the server validates the call. execute can modify abilityExecutionData before it is sent to the server, and can be called multiple times with begin, change and end states to properly handle complex abilities. The arguments of execute are:
execute(renderCharacterContainer, abilityExecutionData, isAbilitySource, guid)

abilityData:execute_server - The method called by the server to preform certain tasks such as applying status effects. This method is only called when requested by the casting client’s execute method and is not required for a basic ability to function. The arguments of execute-server are:
execute_server(player, abilityExecutionData, isAbilitySource, guid, targetPoint)

Warrior Ground Slam Spell:

local abilityAnimations = game:GetService("ReplicatedStorage"):WaitForChild("abilityAnimations")

local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
	local projectile = modules.load("projectile")
	local placeSetup = modules.load("placeSetup")
	local client_utilities = modules.load("client_utilities")
	local network = modules.load("network")
	local tween = modules.load("tween")
	local damage = modules.load("damage")
	local detection = modules.load("detection")
	local ability_utilities = modules.load("ability_utilities")
	local utilities = modules.load("utilities")

local entityManifestCollectionFolder = placeSetup.awaitPlaceFolder("entityManifestCollection")
local entitiesFolder = placeSetup.getPlaceFolder("entities")

local httpService = game:GetService("HttpService")

local abilityData = {
	--> identifying information <--
	id = 5;
	
	--> generic information <--
	name = "Ground Slam";
	image = "rbxassetid://3736598447";
	description = "Leap forward and slam your blade down!";
	mastery = "Larger impact area.";
	
	damageType = "physical";
	
	prerequisite = {{id = 26; rank = 3}};
	layoutOrder = 3;		
		
	--> execution information <--
	windupTime = 0.5;
	maxRank = 10;
	cooldown = 7;
	cost = 10;
	
	
	speedMulti = 1.5;
	
	--> combat stats <--
	statistics = {
		[1] = {
			damageMultiplier = 3;
			radius = 15;
			cooldown = 7;
			manaCost = 24;
		}; [2] = {
			damageMultiplier = 3.05;
			manaCost = 25;
		}; [3] = {
			damageMultiplier = 3.1;
			manaCost = 26;
		}; [4] = {
			damageMultiplier = 3.15;
			manaCost = 28;
			radius = 16;
		}; [5] = {
			damageMultiplier = 3.2;
			manaCost = 29;
		}; [6] = {
			damageMultiplier = 3.25;
			manaCost = 30;
		}; [7] = {
			damageMultiplier = 3.3;
			manaCost = 32;
			radius = 17;
		}; [8] = {
			damageMultiplier = 3.35;
			manaCost = 33;
		}; [9] = {
			damageMultiplier = 3.4;
			manaCost = 34;
		}; [10] = {
			damageMultiplier = 3.45;
			manaCost = 36;
			radius = 18;
		}; 																		
		
	};
	
	securityData = {
		playerHitMaxPerTag = 3;
		isDamageContained = true;
	};
	
	maxRange = 30;
	equipmentTypeNeeded = "sword";
}

function abilityData._serverProcessDamageRequest(sourceTag, baseDamage)
	if sourceTag == "shockwave" then
		return baseDamage, "physical", "aoe"
	elseif sourceTag == "shockwave-outer" then
		return baseDamage / 2, "physical", "aoe"
	elseif sourceTag == "slash" then
		return baseDamage, "physical", "direct"
	elseif sourceTag == "aftershock" then
		return baseDamage / 2, "physical", "aoe"
	end
end

local function onAnimationStopped()
	
end

function abilityData._abilityExecutionDataCallback(playerData, ref_abilityExecutionData)
	ref_abilityExecutionData["bounceback"] = playerData and playerData.nonSerializeData.statistics_final.activePerks["bounceback"]
	ref_abilityExecutionData["aftershock"] = playerData and playerData.nonSerializeData.statistics_final.activePerks["aftershock"]
end
local function getKnockbackAmount(abilityExecutionData)
	return abilityExecutionData["bounceback"] and 15000 or 1000
end
function abilityData:execute_server(player, abilityExecutionData, isAbilitySource, targetPoint, monster, knockbackAmount)
	if not isAbilitySource then return end
	if abilityExecutionData["bounceback"] then
		utilities.playSound("bounce", monster)
	end		
	ability_utilities.knockbackMonster(monster, targetPoint, knockbackAmount, 0.2)
end
function abilityData:doKnockback(abilityExecutionData, target, point, amount)
	if	target:FindFirstChild("entityType") and
		target.entityType.Value == "monster"
	then
		network:fireServer("abilityFireServerCall", abilityExecutionData, self.id, point, target, amount)
	end
	
	local player = game.Players.LocalPlayer
	local char = player.Character
	if not char then return end
	local manifest = char.PrimaryPart
	if not manifest then return end
	
	if target == manifest then
		ability_utilities.knockbackLocalPlayer(point, amount)
	end
end

function abilityData:execute(renderCharacterContainer, 	abilityExecutionData, isAbilitySource, guid)
	
	-- todo: fix
	if not renderCharacterContainer:FindFirstChild("entity") then return end
	
	local currentlyEquipped = network:invoke("getCurrentlyEquippedForRenderCharacter", renderCharacterContainer.entity)
	local currentWeaponManifest = currentlyEquipped["1"] and currentlyEquipped["1"].manifest
	if not currentWeaponManifest then return end

	local animationTrack = renderCharacterContainer.entity.AnimationController:LoadAnimation(abilityAnimations.warrior_forwardDownslash)

	local trail

	if currentWeaponManifest then
		local sound = script.cast:Clone()
		sound.Parent = currentWeaponManifest
		-- we should start regularly making other people's ability cast sounds quieter
		if not isAbilitySource then
			sound.Volume = sound.Volume * 0.7
		end
		sound:Play()
		game.Debris:AddItem(sound,5)
		
		local attach0 = currentWeaponManifest:FindFirstChild("bottomAttachment")
		local attach1 = currentWeaponManifest:FindFirstChild("topAttachment")
		
		if attach0 and attach1 then
			trail = script.Trail:Clone()
			trail.Name = "groundSlamTrail"
			trail.Parent = currentWeaponManifest
			trail.Attachment0 = attach0
			trail.Attachment1 = attach1
			trail.Enabled = true
		end
		
		
	end	
	
	local stopParticles_con
	stopParticles_con = animationTrack.Stopped:connect(function()
		--[[
		glowEffect.Enabled = false
		
		game.Debris:AddItem(glowEffect, 5)
	
		stopParticles_con:disconnect()
		]]
	end)
	
	local localCharacter = game.Players.LocalPlayer.Character
	
	if isAbilitySource and localCharacter then

		while animationTrack.Length == 0 do
			wait(0)
		end
		
	end
	
	animationTrack:Play(0.1, 1, self.speedMulti or 1)
	
	wait(animationTrack.Length * 0.06 / animationTrack.Speed)
	
	if isAbilitySource then
	
	
		local movementVelocity = network:invoke("getMovementVelocity")
	
		local movementDirection 
		if movementVelocity.magnitude > 0 then
			movementDirection = movementVelocity.unit 
		else
			movementDirection = localCharacter.PrimaryPart.CFrame.lookVector * 0.05
		end
		
		network:invoke("setCharacterArrested", true)
		
		local bodyGyro 		= localCharacter.PrimaryPart.hitboxGyro
		local bodyVelocity 	= localCharacter.PrimaryPart.hitboxVelocity
		
		bodyGyro.CFrame = CFrame.new(Vector3.new(), movementDirection)
		
		movementDirection =	movementDirection + Vector3.new(0, 1, 0)
		
		
		
		network:invoke("setMovementVelocity", movementDirection * 23 * animationTrack.Speed)
	
	end
	
	wait(animationTrack.Length * 0.36 / animationTrack.Speed)
	
	-- todo: fix
	if not renderCharacterContainer:FindFirstChild("entity") then return end
	
	network:invoke("setMovementVelocity", Vector3.new())
	
	local timeLeft = 30
	local startTime = tick()
	
	local animationTimePosition = animationTrack.TimePosition
	
	
	local hitPart, hitPosition, hitNormal
			
	
	
	local runService = game:GetService("RunService")
	
	animationTrack:AdjustSpeed(0)
	
	if isAbilitySource then
		network:invoke("setCharacterArrested", false)
	end

	
	repeat 
		local ray = Ray.new(currentWeaponManifest.Position + (renderCharacterContainer.PrimaryPart.CFrame.lookVector * 3 or prevcharspot) + Vector3.new(0, 2.5, 0), Vector3.new(0, -10, 0))
		prevcharspot = renderCharacterContainer.PrimaryPart.CFrame.lookVector * 3 or prevcharspot
		hitPart, hitPosition, hitNormal = workspace:FindPartOnRayWithIgnoreList(ray, {renderCharacterContainer, currentWeaponManifest}) 
		wait()
	until hitPart or tick() - startTime >= timeLeft
	
	if trail then
		trail:Destroy()
	end
	
	if isAbilitySource then
		network:invoke("setCharacterArrested", true)
	end
	
	animationTrack:AdjustSpeed(self.speedMulti)
	
	spawn(function()
		if not hitPart then
			return false
		end
		
		if abilityExecutionData["aftershock"] then
			local radius = 8
			local duration = 0.5
			
			local function aftershock(position)
				-- visuals
				local part = Instance.new("Part")
				part.Anchored = true
				part.CanCollide = false
				part.TopSurface = Enum.SurfaceType.Smooth
				part.BottomSurface = part.TopSurface
				part.Shape = Enum.PartType.Cylinder
				part.BrickColor = BrickColor.new("Dirt brown")
				part.CFrame = CFrame.new(position) * CFrame.Angles(0, 0, math.pi / 2)
				part.Size = Vector3.new(1, 1, 1)
				
				part.Parent = entitiesFolder
				
				tween(part, {"Size", "Transparency"}, {Vector3.new(1, radius * 2, radius * 2), 1}, duration)
				game:GetService("Debris"):AddItem(part, duration)
				
				-- damage
				if isAbilitySource then
					for _, target in pairs(damage.getDamagableTargets(game.Players.LocalPlayer)) do
						local targetPosition = detection.projection_Box(target.CFrame, target.Size, position)
						if (targetPosition - position).magnitude <= radius then
							network:fire("requestEntityDamageDealt", target, target.Position, "ability", self.id, "aftershock", guid)
						end
					end
				end
			end
			
			local function aftershocks()
				local aftershockCount = 8
				for aftershockNumber = 1, aftershockCount do
					local theta = aftershockNumber / aftershockCount * math.pi * 2
					local r = abilityExecutionData["ability-statistics"]["radius"] * 0.75
					local dx = math.cos(theta) * r
					local dz = math.sin(theta) * r
					aftershock(hitPosition + Vector3.new(dx, 0, dz))
				end
				
				utilities.playSound(script.aftershock, hitPosition)
			end
			
			delay(0.5, aftershocks)
		end
		
		local shockwave1 = game.ReplicatedStorage.shockwaveEntity:Clone()
		local shockwave2 = game.ReplicatedStorage.shockwaveEntity:Clone()
		
		shockwave1.Parent = entitiesFolder
		shockwave2.Parent = entitiesFolder
		
		local radius = abilityExecutionData["ability-statistics"].radius
		
		local dustPart = script.dustPart:Clone()
		dustPart.Parent = workspace.CurrentCamera
		dustPart.CFrame = CFrame.new(hitPosition)
		
		dustPart.Dust.Speed = NumberRange.new(30 * (radius/10), 50 * (radius/10))
		
		dustPart.Dust:Emit(100)
		game.Debris:AddItem(dustPart,6)

		local sound = script.impact:Clone()
		
		if not isAbilitySource then
			sound.Volume = sound.Volume * 0.7
		end		
		
		sound.Parent = dustPart
		sound:Play()
		
		if isAbilitySource then
--			local radius = 0.25 + 15 * (0.8 ^ 3)
			
			
			for i, v in pairs(damage.getDamagableTargets(game.Players.LocalPlayer)) do
				local targetPosition = detection.projection_Box(v.CFrame, v.Size, hitPosition)
				
				if ((targetPosition - hitPosition) * Vector3.new(1,0,1)).magnitude <= radius * 0.7 and ((targetPosition - hitPosition) * Vector3.new(0,1,0)).magnitude <= (radius/2) * 0.7 then
					network:fire("requestEntityDamageDealt", v, hitPosition, "ability", self.id, "shockwave", guid)
					
					--knockback
					self:doKnockback(abilityExecutionData, v, hitPosition, getKnockbackAmount(abilityExecutionData))
					
				elseif ((targetPosition - hitPosition) * Vector3.new(1,0,1)).magnitude <= radius and ((targetPosition - hitPosition) * Vector3.new(0,1,0)).magnitude <= (radius/2) then
					network:fire("requestEntityDamageDealt", v, hitPosition, "ability", self.id, "shockwave-outer", guid)
					
					--knockback
					self:doKnockback(abilityExecutionData, v, hitPosition, getKnockbackAmount(abilityExecutionData))
				end
			end
		end				
		
		local startTime = tick()
		local cf = CFrame.new(hitPosition, hitPosition + hitNormal) * CFrame.Angles(math.pi / 2, 0, 0)
		local multi = Vector3.new(radius * 1.7, -1.1, radius * 1.7)
		local base = Vector3.new(0.25, 1.5, 0.25)
		local duration = 0.8
		--[[
		local anim_con = game:GetService("RunService").RenderStepped:connect(function()
			local i = (tick() - startTime) / duration
			
			shockwave1.Size = base + multi * (i ^ 3)
			shockwave2.Size = base + multi * (i ^ 2)
			
			shockwave1.Transparency = math.clamp(i ^ 1.5, 0, 1)
			shockwave2.Transparency = math.clamp(i, 0, 1)
			
			shockwave1.CFrame = cf
			shockwave2.CFrame = cf
		end)
		]]
		
		shockwave1.Size = base
		shockwave2.Size = base
		
		shockwave1.CFrame = cf
		shockwave2.CFrame = cf		
		
		shockwave1.Transparency = 0
		shockwave2.Transparency = 0
		
		tween(shockwave1, {"Size", "Transparency"}, {base + multi, 1}, 1, Enum.EasingStyle.Quad)
		tween(shockwave2, {"Size", "Transparency"}, {base + multi, 1}, 1, Enum.EasingStyle.Quint)
		
		
		
		game.Debris:AddItem(shockwave1, 1)
		game.Debris:AddItem(shockwave2, 1)
		
		--[[
		wait(1)
	
		
		shockwave1:Destroy()
		shockwave2:Destroy()
		anim_con:disconnect()
		]]
	end)
	
	animationTrack:AdjustSpeed(self.speedMulti * 1.65)
		
	wait(animationTrack.Length * 0.4 / animationTrack.Speed)
	
	if isAbilitySource then
		network:invoke("setCharacterArrested", false)
	end
	
	return true

end

return abilityData

Sorcerer Frostcall Spell:

local abilityAnimations = game:GetService("ReplicatedStorage"):WaitForChild("abilityAnimations")
local debris = game:GetService("Debris")
local runService = game:GetService("RunService")

local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
	local network = modules.load("network")
	local damage = modules.load("damage")
	local placeSetup = modules.load("placeSetup")
	local tween	= modules.load("tween")
	local ability_utilities = modules.load("ability_utilities")

local entitiesFolder = placeSetup.awaitPlaceFolder("entities")

local abilityData = {
	id = 37,
	
	name = "Frostcall",
	image = "rbxassetid://4079577683",
	description = "Unleash a line of falling icicles before you, damaging enemies. (Requires staff.)",
	mastery = "More icicles.",
	layoutOrder = 0;
	maxRank = 10,
	statistics = {
		[1] = {
			damageMultiplier = 1.7;
			manaCost = 40;
			icicles = 4;
			cooldown = 2;
		}; [2] = {
			damageMultiplier = 1.75;
			manaCost = 44;
		}; [3] = {
			icicles = 6;
			manaCost = 56;
		}; [4] = {
			damageMultiplier = 1.8;
			manaCost = 60;			
		}; [5] = {
			damageMultiplier = 1.85;
			manaCost = 64;	
		}; [6] = {
			icicles = 8;
			manaCost = 76;	
		}; [7] = {
			damageMultiplier = 1.9;
			manaCost = 80;	
		}; [8] = {
			damageMultiplier = 1.95;
			manaCost = 84;
		}; [9] = {
			icicles = 10;
			manaCost = 96;		
		}; [10] = {
			damageMultiplier = 2.0;
			manaCost = 100;	
		}; 
	};	

	
	windupTime = 0.3,
	
	securityData = {
		playerHitMaxPerTag = 64,
		isDamageContained = true,
		projectileOrigin = "character",
	},
	
	equipmentTypeNeeded = "staff",
	disableAutoaim = true,
	
	targetingData = {
		targetingType = "line",
		
		width = function(executionData)
			local explosionRadius = 8
			local icicles = executionData["ability-statistics"]["icicles"]
			if icicles <= 6 then
				return explosionRadius
			elseif icicles <= 10 then
				return explosionRadius * 1.7
			else
				return explosionRadius * 1.9
			end
		end,
		
		length = function(executionData)
			local explosionRadius = 8
			local icicles = executionData["ability-statistics"]["icicles"]
			if icicles <= 6 then
				return icicles * explosionRadius
			elseif icicles <= 10 then
				return icicles * explosionRadius * 0.65
			else
				return icicles * explosionRadius * 0.55
			end
		end,
		
		onStarted = function(entityContainer, executionData)
			local attachment = network:invoke("getCurrentlyEquippedForRenderCharacter", entityContainer.entity)["1"].manifest.magic
			
			local emitter = script.icicle.emitter:Clone()
			emitter.Lifetime = NumberRange.new(0.5)
			emitter.Parent = attachment
			
			local light = Instance.new("PointLight")
			light.Color = BrickColor.new("Electric blue").Color
			light.Parent = attachment
			
			return {emitter = emitter, light = light, projectionPart = attachment}
		end,
		
		onEnded = function(entityContainer, executionData, data)
			data.emitter.Enabled = false
			game:GetService("Debris"):AddItem(data.emitter, data.emitter.Lifetime.Max)
			
			data.light:Destroy()
		end
	},
}

function createEffectPart()
	local part = Instance.new("Part")
	part.Anchored = true
	part.CanCollide = false
	part.TopSurface = Enum.SurfaceType.Smooth
	part.BottomSurface = Enum.SurfaceType.Smooth
	return part
end

function abilityData._serverProcessDamageRequest(sourceTag, baseDamage)
	if sourceTag == "iceExplosion" then
		return baseDamage, "magical", "aoe"
	end
end

function abilityData:execute(renderCharacterContainer, abilityExecutionData, isAbilitySource, guid)
	local root = renderCharacterContainer.PrimaryPart
	if not root then return end
	local entity = renderCharacterContainer:FindFirstChild("entity")
	if not entity then return end
	local animator = entity:FindFirstChild("AnimationController")
	if not animator then return end
	
	-- acquire the weapon for some fancy animations
	local weapons = network:invoke("getCurrentlyEquippedForRenderCharacter", renderCharacterContainer.entity)
	local weaponManifest = weapons["1"] and weapons["1"].manifest
	if not weaponManifest then return end
	local weaponMagic = weaponManifest:FindFirstChild("magic")
	if not weaponMagic then return end
	local castEffect = weaponMagic:FindFirstChild("castEffect")
	if not castEffect then return end
	
	-- activate particles and wind up the attack
	castEffect.Enabled = true
	
	for _, trackName in pairs{"mage_thrust_top", "mage_thrust_bot"} do
		local track = animator:LoadAnimation(abilityAnimations[trackName])
		track:Play()
	end
	
	wait(self.windupTime)
	
	-- casting sound
	local castSound = script.castSound:Clone()
	castSound.Parent = root
	castSound:Play()
	debris:AddItem(castSound, castSound.TimeLength / castSound.PlaybackSpeed)
	
	castEffect.Enabled = false
	
	-- basic constants
	local explosionRadius = 16
	
	-- dealing damage
	local function aoeDamageAt(position)
		if isAbilitySource then
			local targets = damage.getDamagableTargets(game.Players.LocalPlayer)
				
			for _, target in pairs(targets) do
				if (target.Position - position).magnitude <= explosionRadius then
					network:fire("requestEntityDamageDealt", target, target.position, "ability", self.id, "iceExplosion", guid)
				end
			end
		end
	end
	
	-- ice explosion function
	local function iceExplosion(position)
		local duration = 1
		
		local sphere = createEffectPart()
		sphere.Material = Enum.Material.Foil
		sphere.Color = Color3.fromRGB(184, 240, 255)
		sphere.CFrame = CFrame.new(position)
		sphere.Size = Vector3.new(0, 0, 0)
		sphere.Shape = Enum.PartType.Ball
		sphere.Parent = entitiesFolder
		
		local sound = script.shatterSound:Clone()
		sound.Parent = sphere
		sound:Play()
		
		tween(
			sphere,
			{"Size", "Transparency"},
			{Vector3.new(2, 2, 2) * explosionRadius, 1},
			duration
		)
		
		delay(duration, function()
			sphere:Destroy()
		end)
		
		-- deal damage
		aoeDamageAt(position)
	end
	
	local function bonusIce(position, spin, fadeTime)
		local ice = script.icicle:Clone()
		ice.emitter:Destroy()
		
		local minTilt = math.pi / 4
		local maxTilt = math.pi / 3
		
		ice.CFrame =
			CFrame.new(position) *
			CFrame.Angles(0, spin, 0) *
			CFrame.Angles(minTilt + (maxTilt - minTilt) * math.random(), 0, 0)
		
		local goalCFrame = ice.CFrame * CFrame.new(0, 2, 0)
		local goalSize = Vector3.new(
			ice.Size.X * 5,
			ice.Size.Y * 2,
			ice.Size.Z * 5
		)
		
		tween(ice, {"CFrame", "Size"}, {goalCFrame, goalSize}, 1, Enum.EasingStyle.Linear)
		tween(ice, {"Transparency"}, 1, fadeTime, Enum.EasingStyle.Linear)
		debris:AddItem(ice, fadeTime)
		
		ice.Parent = entitiesFolder
	end
	
	-- drop icicle function
	local function dropIcicle(position)
		local fallSpeed = 128
		local fallDistance = 32
		local fallTime = fallDistance / fallSpeed
		local activationTime = fallTime * 0.4
		
		local startCFrame =
			CFrame.new(position) *
			CFrame.Angles(0, math.pi * 2 * math.random(), 0) *
			CFrame.Angles(math.pi / 8 * math.random(), 0, 0) *
			CFrame.new(0, fallDistance, 0)
		
		local icicle = script.icicle:Clone()
		local emitter = icicle.emitter
		
		icicle.CFrame = startCFrame
		
		local fallStart = tick()
		
		local heartbeatConnection do
			local function onImpact(part)
				local since = tick() - fallStart
				if since < activationTime then return end
				
				heartbeatConnection:Disconnect()
							
				-- housekeeping
				emitter.Enabled = false
				
				-- fade out
				local fadeDuration = emitter.Lifetime.Max + 1
				tween(icicle, {"Transparency"}, 1, fadeDuration, Enum.EasingStyle.Linear)
				debris:AddItem(icicle, fadeDuration)
				
				-- bonus ice spikes visual
				local bonusIcePosition = icicle.Position--(icicle.CFrame * CFrame.new(0, -icicle.Size.Y * 0.3, 0)).Position
				local bonusIceSpin = math.pi * 2 * math.random()
				local bonusIceCount = 3
				for bonusIceNumber = 1, bonusIceCount do
					local spin = math.pi * 2 * (bonusIceNumber / bonusIceCount)
					bonusIce(bonusIcePosition, bonusIceSpin + spin, fadeDuration)
				end
				
				-- impact sound effect
				local sound = script["shatterSound"..math.random(1, 5)]:Clone()
				sound.Parent = icicle
				sound:Play()
				
				-- deal damage
				aoeDamageAt(icicle.Position)
			end
		
			local function onHeartbeat(dt)
				local origin = icicle.Position
				local direction = -icicle.CFrame.UpVector * fallSpeed * dt
				local part, point = ability_utilities.raycastMap(Ray.new(origin, direction))
				icicle.Position = point
				if part then
					onImpact()
				end
			end
			
			heartbeatConnection = runService.Heartbeat:Connect(onHeartbeat)
		end
		
		icicle.Parent = entitiesFolder
		
		local goalSize = Vector3.new(
			icicle.Size.X * 8,
			icicle.Size.Y * 3,
			icicle.Size.Z * 8
		)
		tween(icicle, {"Size"}, goalSize, 1, Enum.EasingStyle.Linear)
	end
	
	-- get the position of the character's foot
	local here = root.Position + Vector3.new(0, -2.5, 0)
	local there = abilityExecutionData["mouse-world-position"]
	there = Vector3.new(there.X, here.Y, there.Z)
	local direction = (there - here).Unit
	
	local icicles = abilityExecutionData["ability-statistics"]["icicles"]
	
	local cframe = CFrame.new(Vector3.new(), direction)
	
	for icicleNumber = 1, icicles do
		local delta
		
		if icicles <= 6 then
			delta = direction * icicleNumber * explosionRadius
			
		elseif icicles <= 10 then
			local backAndForth = (icicleNumber % 2 == 0) and -1 or 1
			delta = cframe * CFrame.new(backAndForth * explosionRadius * 0.35, 0, -icicleNumber * explosionRadius * 0.65)
			delta = delta.Position
			
		else
			local xMult = (icicleNumber % 3) - 1
			delta = cframe * CFrame.new(xMult * explosionRadius * 0.45, 0, -icicleNumber * explosionRadius * 0.55)
			delta = delta.Position
		end
		
		dropIcicle(here + delta)
		wait(0.1)
	end
end

return abilityData

We prefer candidates who are not locked into a particular coding paradigm. Lots of Vesteria’s code follows functional coding patterns, and in the past we’ve had problems with developers who were too locked into object-oriented patterns. While this position works more with object-oriented patterns than others, it is important to be able to be flexible and use the best paradigm for the job.

During last summer we unsuccessfully attempted to convert Vesteria to a Rojo project. You can explore this branch of the game on GitHub to get some insight into how the game works. Keep in mind that this branch was not used in the production version of the game. https://github.com/berezaa/vesteria

Payment

Compensation is percent-based using Roblox’s group auto-payouts. As a Junior Developer you will receive 5% of all game revenue for as long as you are on the project. Note that this percent is conditional on your continued involvement. At the time of writing, you can reasonably expect to start immedietly earning at least :robux_gold: 200,000 a month from your percent, and this amount will likely increase if the game’s revenue continues to trend upwards as it has been recently.

There is a :robux_gold: 50,000 onboarding bonus awarded after successful introduction onto the project, and additional Robux bonuses offered for exceptional performance. A significant portion of the game’s revenue percent is undistributed and additional percent is available to be earned with a promotion.

Contact Us

To apply please send a detailed application to @berezaa in DevForum DMs. Make sure to include:

  • Short bio
  • Screenshots/Gifs/Videos of effects you’ve made
  • Project you’ve worked on and your roles in them
  • What are your strongest skills?
  • What are the skills you want to improve on the most?
  • Why do you create on Roblox?
  • Where do you see yourself in the future?
  • Any other relevant info

You must be 13 years or older to apply.

Thanks for reading! :slight_smile:

43 Likes

This topic was automatically closed after 1 minute. New replies are no longer allowed.