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.

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
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
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! 
