Hi. My name is Adnan el-Bedawi.

I am a software developer with a passion for game design. A few of my all-time favorite games are Star Wars Galaxies (SOE), World of Warcraft (Blizzard), FFXIV (Square Enix), Jade Dynasty (Perfect World Entertainment), and Baldur’s Gate 3 (Larian Studios).

I am pursuing a Bachelor of Science in Software Engineering at Western Governors University while working full-time as an Infrastructure Engineer at NBME. In this role, I leverage my technical expertise and systems knowledge to deliver exceptional customer service to internal and external users.

Game development has become my creative outlet and passion. I have led multiple projects, managing teams to ensure content is delivered on time while incorporating player feedback to enhance the experience.

Balancing a full-time job and academic responsibilities has honed my focus and self-learning abilities, as evidenced by the projects showcased in this portfolio—all completed outside of my busy schedule.

Experience and Projects

ROBLOX - Clover: Kingdom of Magic

Anticipated Release Date: Q4 2024

About the project

My studio, Round Table Interactive, LLC, is currently developing Clover: Kingdom of Magic. It is a multiplayer, open-world action RPG. The game world is lightly inspired by animanga like Black Clover. The level design, enemies, spell effects, and storyline are being developed around a “magic” theme. As players first embark on their adventure, they choose which magical “Tome” to use, allowing them access to different abilities based on the chosen element.

In addition to leading a team of 4 for this project, I am the main system designer and programmer, working on all parts of the game. This includes level design, character models and abilities, UX/UI, and gameplay systems. 

This is a game that is built on Roblox’s internal engine. Leveraging Lua, I am coding all of the game’s core systems, from NPC pathfinding to ability selection and attack patterns, hitbox detection, client-server data replication, projectile physics simulation, calculating collision detections, and finite-state machines for combat states.

Players will progress vertically by defeating mobs in the open world, leveling their character as they gain exp. At any point in time, players can engage in PvP combat and earn additional rewards. Additionally, C: KoM features a racing system where players can challenge each other in “flying broom” races for rewards.

My roles:
  • Lead Programmer
  • Technical Artist
  • 3D Animator
 
Responsibilities:
  • Collaborate on a team of 4 to design & develop an online multiplayer game hosted on Roblox’s servers
  • Using Lua, program video game systems:  inventory management, projectile physics, pathfinding AI, client-server hitbox detection and verification, client-server object replication, server-side database push + pull requests
  • Implement core game design concepts such as player progression and gameplay loops, combat mechanics, UX/UI, accessibility
  • Develop and animate 3D and 2D assets with Blender, using scripting to incorporate the keyframe sequences into the game as character animations

One of the functions developed by me for C:KoM. Applies velocity to a target character via Linear Velocity instance.

Display Lua Code

--[[ Applies a given velocity to the target's abilityLV instance. 
This function was also used to develop and test another method of movement involving tweening an anchored HRP, which has since been deprecated. ]]
function physicsModule.applyVelocity(character:Model, physData:{})
	local effectsFolder = basicFunctions.getEffectsFolderFromCharacter(character)
	
	local humrp = character:FindFirstChild("HumanoidRootPart")
	local humanoid:Humanoid = character:FindFirstChild("Humanoid")
	if humrp and humanoid and humanoid.Health > 0 then
		
		local useLinearVelocity = true -- Set to false when testing non-lv movement
		
		local distance:number = physData.knockbackDistance
		local duration:number = physData.knockbackDuration
		local direction = physData.knockbackDirection
		local speed = distance/duration
		
		-- Formula for init velocity is U = 2 * (d / t) - Vf 
		--	Initial velocity is calculated here so that we can tween down to the final velocity (0) with given duration, while still covering given distance.
		local finalVelocity = humanoid.WalkSpeed * (direction).Unit
		local initialVelocity = (2 * (distance/duration) - finalVelocity.Magnitude) * (direction).Unit
		
		--print("DEBUG: Applying velocity", initialVelocity, "to", character.Name, ". Final velocity:", finalVelocity)
		--print("DEBUG: Final velocity:", finalVelocity)
		
		if duration > 0 then
			local lockPart = effectsFolder:FindFirstChild("CharacterLockPart")
			lockPart.AlignOrientation.Enabled = false
			
			if useLinearVelocity == true then
				local lv = humrp:FindFirstChild("abilityLV")
				
				local timeDelta = 0
				local timer = lv.Timer
				local maxTime = timer.Max
				local startTime = timer.Start
				startTime.Value = basicFunctions.convertTo2f(tick())
				
				if timer.Value <= 0 then
					-- This is a fresh status, first enable Linear Velocity instance.
					lv.VectorVelocity = initialVelocity
					lv.Enabled = true

					-- Set timer vals
					timer.Value = duration
					maxTime.Value = duration

					-- Start the timer
					local timerConn = nil
					
					local function stopTimer()
						if lv.Parent == nil then
							timerConn:Disconnect()
							timerConn = nil
							return
						end

						--print("DEBUG: Clearing timer for physics effect. Timer val left:", timer.Value, "Current time:", tick())
						--print("DEBUG Physics effect timer ended. Final vector velocity value:", lv.VectorVelocity)
						timer.Value = 0
						maxTime.Value = 0

						lv.Enabled = false

						timerConn:Disconnect()
						timerConn = nil
						return
					end
					
					local elapsed = 0
					
					timerConn = RunService.Heartbeat:Connect(function(dt)
						elapsed += dt
						
						if timer.Value > 0 then
							if character.Parent == nil or (not humanoid or humanoid.Health <= 0) then
								stopTimer()
								return
							end

							timeDelta = elapsed
							timer.Value = basicFunctions.convertTo2f(maxTime.Value - timeDelta)
							
							if timer.Value <= 0 then
								stopTimer()
								return
							end
							
							local alphaValue = ts:GetValue(math.min(elapsed / duration, 1), Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
							local calculatedVelocity = basicFunctions.lerp(initialVelocity, finalVelocity, alphaValue)
							lv.VectorVelocity = calculatedVelocity
						else
							stopTimer()
						end
					end)
				else
					--print("DEBUG: Updating timer for NEW vector.")
					-- Reset the timer if new effect is being applied.
					timer.Value = duration
					maxTime.Value = duration	

					-- Set new velocity val
					lv.VectorVelocity = initialVelocity

					--print(timer.Value)
					--print(maxTime.Value)
				end
			else -- Tween HRP directly to a set position.
				local targetPart = Instance.new("Part")
				targetPart.Name = "Target Part"
				targetPart.CanCollide = false
				targetPart.CanQuery = false
				targetPart.CanTouch = false
				targetPart.Size = Vector3.new(3, 3, 3)
				targetPart.Transparency = 0.4
				targetPart.Anchored = true
				targetPart.Color = Color3.new(0, 0.517647, 1)
				targetPart.CFrame = humrp.CFrame
				targetPart.Position = targetPart.CFrame.Position + (direction * distance)
				targetPart.Parent = effectsFolder
				
				Debris:AddItem(targetPart, duration + 1)
				
				print("DEBUG: Tweening", character.Name, "to", targetPart.CFrame)
				local hrpTween = ts:Create(humrp, TweenInfo.new(duration),{["CFrame"] = targetPart.CFrame})
				
				local tweenCompleted;
				
				if physicsModule.characterList[character] == nil then
					physicsModule.characterList[character] = {
						isMoving = true
					}
				else
					physicsModule.characterList[character] = {
						isMoving = true
					}
				end
				
				tweenCompleted = hrpTween.Completed:Connect(function(playbackState)
					physicsModule.characterList[character].isMoving = false
					humrp.Anchored = false
					
					tweenCompleted:Disconnect()
				end)
				
				humrp.Anchored = true
				hrpTween:Play()
			end
		end
	end
end
  

One of the functions developed by me for C:KoM. Handles projectile physics.

Display Lua Code

--[[Function 'castProjectile' called by casting ability. Performs some of the setup for 'fireProjectile'. Determines what happens when the projectile has reached the end of its path, through the 'castEnded' function.]]
toolsModule.castProjectile = function(stringID:string, abilityPhase:string, abilityData:{}, states:{}, spellVars:{}, keyfrConns:{}, hitboxPart:Part, craterParams:{}, castRotation:CFrame)
	-- Enable damage detection
	spellVars.canDmg = true
	
	-- Will shoot a projectile so init projectile terminated to false.
	spellVars.projectileTerminated = false

	-- Face character toward cursor before firing projectile.
	if abilityData.followMouse == nil or abilityData.followMouse == true then
		local rootPos = humrp.Position
		humrp.CFrame = CFrame.new(rootPos, Vector3.new(calculatedCrosshair.X, rootPos.Y, calculatedCrosshair.Z))
	end

	-- calculate raycast info
	local origin, direction = sendRaycastData(spellVars.renderedSpell.PrimaryPart)

	local pathBlocked = hitboxModule.detectPartsAtOrigin(player, spellVars.renderedSpell.PrimaryPart)

	-- Render on other clients.
	reAbilityPhases:FireServer(stringID, abilityData.inputKey, "executeAbility", origin, calculatedCrosshair, pathBlocked, "dmgOn", spellVars)
	
	-- Initialize hit entities data for hitbox
	local touchedEntities = {}
	

	local function castEnded(raycastResult:RaycastResult)
		--print("DEBUG: Cast terminating.")

		spellVars.projectileTerminated = true

		if not touchedEntities.Break then
			--print("DEBUG: This spell collided with the environment. Generating crater. Total length travelled:", distTravelled, "last point:", lastProjectilePoint)
			--print("DEBUG: last position:", cast:GetPosition())

			local segments = craterParams.segments
			local radius = craterParams.radius
			local height = craterParams.height
			local width = craterParams.width
			local xAngle = craterParams.xAngle
			local speed = craterParams.speed
			local lifetime = craterParams.lifetime

			--spellVars.craterParams = {origin = origin, direction = direction, segments = segments, radius = radius, height = height, width = width, xAngle = xAngle, speed = speed, lifetime = lifetime}

			rockModule.crater(nil, nil, segments, radius, height, width, xAngle, speed, lifetime, raycastResult)
		else
			spellVars.breakEarly = true
		end

		toolsModule.fadeOut(stringID, abilityPhase, abilityData, true, states, spellVars, keyfrConns)
	end
	
	local function lengthChanged(projectileConn:RBXScriptConnection)
		if hitboxModule.findInstanceInEffectsFolder(spellVars.renderedSpell, player) then -- Verify that the instance being manipulated is not already destroyed before attempting to move it.
			
			hitboxModule.createHitbox(player, hitboxPart, touchedEntities, stringID, abilityData, origin, direction)
			
			if touchedEntities.Break then
				if projectileConn ~= nil then
					projectileConn:Disconnect()
					projectileConn = nil
				end

				castEnded()
			end
		end
	end

	
	local maxDistance = 0
	
	if pathBlocked then
		--print("DEBUG: Path was blocked.")
	else
		maxDistance = abilityData["hitbox1"].maxDistance
	end

	spellVars.projectileFired = true
	
	local raycastParams = RaycastParams.new()
	raycastParams.CollisionGroup = "Spells" -- adds the raycast to the "Spells" collision group so that it cannot collide with anything unless specified in the collision group's settings
	raycastParams.FilterType = Enum.RaycastFilterType.Exclude
	raycastParams.FilterDescendantsInstances = {character, effectsFolder}
	raycastParams.IgnoreWater = true
	raycastParams.RespectCanCollide = true

	projectileModule.fireProjectile(spellVars.renderedSpell, origin, direction, maxDistance, abilityData["hitbox1"].speed, castRotation, lengthChanged, castEnded, raycastParams)
	
end
  

One of the functions developed by me for C:KoM. Handles projectile physics.

Display Lua Code

--[[ 

Primary projectile mover. Used to move a given model (param_1 projectile) over the length of a specified direction and max distance.

This function creates an update event that fires each frame. The delta in OS time is measured between each frame to ensure that the projectile's speed is not effected by poor framerate.

Additionally, this function uses raycasts to check if the projectile has hit a wall or other object. If so, the projectile is destroyed and the update event is disconnected.

Optionally pass in functions to perform on frame update and on projectile reached last point.

Function "fireProjectile" is called from the toolsModule library "castProjectile" function. castProjectile performs preliminary steps like calculating origin, direction, and determining raycast parameters.
]]
projectileModule.fireProjectile = function(projectile:Model, origin:Vector3, direction:Vector3, maxDistance:number, speed:number, projectileOrientation:CFrame, lengthChangedFunc:"Func", castEndedFunc:"Func", raycastParams:RaycastParams)
	for i, child in workspace.Testing.junk:GetChildren() do
		child:Destroy()
	end

	-- Calculate goal position based on given origin, direction, and max distance.
	local goalPosition = origin + direction * maxDistance
	
	if projectileModule.debugMode then
		-- Create a small part at the point
		local smallPart = Instance.new("Part")
		smallPart.CanCollide = false
		smallPart.CanQuery = false
		smallPart.Size = Vector3.new(5, 5, 5) -- Size of the small part
		smallPart.Color = Color3.new(1, 0, 0.0156863)
		smallPart.Position = goalPosition
		smallPart.Anchored = true
		smallPart.Parent = workspace.Testing.junk	
	end
	
	local timeToReachGoal = maxDistance / speed -- Time to reach goal is checked on each frame update. If time elapsed > timeToReachGoal, projectile will stop moving.
	
	-- Look @ goal posiiton before firing projectile.
	if projectileOrientation ~= nil then
		projectile:PivotTo(CFrame.lookAt(projectile.PrimaryPart.Position, goalPosition) * projectileOrientation) 
	else
		projectile:PivotTo(CFrame.lookAt(projectile.PrimaryPart.Position, goalPosition))
	end
	
	
	local elapsed = 0
	local originalCFrame = projectile:GetPivot()
	local numSteps = 0
	
	--print("DEBUG: Firing projectile. Time to reach goal:", timeToReachGoal, "seconds.")
	
	local conn = nil
	
	conn = runService.Heartbeat:Connect(function(dt) -- Called after every frame update. 'dt' is the time delta (in seconds) between last update and current update.
		elapsed += dt

		if elapsed < timeToReachGoal then
			if projectile ~= nil and projectile.Parent ~= nil then
				local currentPosition = projectile.PrimaryPart.Position
				local distanceTravelled = (currentPosition - origin).Magnitude
				local distanceLeft = maxDistance - distanceTravelled

				local alphaValue = ts:GetValue(math.min(elapsed / timeToReachGoal, 1), Enum.EasingStyle.Linear, Enum.EasingDirection.Out) -- Get the alpha val to determine how far the projectile should move along its determined trajectory.
				local lerpPos = basicFunctions.lerp(originalCFrame.Position, goalPosition, alphaValue) -- Translate alpha value to world position.
				--print("DEBUG: Elapsed time:", elapsed, "alphaValue:", alphaValue, "lerpPos:", lerpPos)

				local raycastDistance = (lerpPos - currentPosition).Magnitude -- Determine how many studs to raycast out in front of the projectile's root part.

				-- Fire the raycast
				local raycastResult:RaycastResult = workspace:Raycast(currentPosition, direction * raycastDistance, raycastParams)

				if raycastResult then -- If there is a result, that means there is an obstacle between the projectile and its intended destination. Simulate collision here.
					if projectileModule.debugMode then
						print("DEBUG: Found object to collide with. I had this much distance left on the projectile cast:", distanceLeft)
						print("DEBUG: Raycast hit something. \n", raycastResult)
					end

					projectile:PivotTo(originalCFrame - originalCFrame.Position + raycastResult.Position)

					conn:Disconnect()
					conn = nil

					if lengthChangedFunc ~= nil then
						lengthChangedFunc(conn)
					end

					--[[Any necessary projectile cleanup not already handled by the parent castProjectile function happens here.
					Common use case for this function is to include the crater spawning logic for when a projectile hits a wall. ]]
					if castEndedFunc ~= nil then
						castEndedFunc(raycastResult)
					end
				else

					projectile:PivotTo(originalCFrame - originalCFrame.Position + lerpPos)

					if lengthChangedFunc ~= nil then
						lengthChangedFunc(conn)
					end
				end



				if projectileModule.debugMode then
					-- Show points along path
					local stepSize = 1 -- Distance between each small part
					numSteps += 1

					local point = origin + direction * (numSteps * stepSize)

					-- Create a small part at the point
					local smallPart = Instance.new("Part")
					smallPart.CanCollide = false
					smallPart.CanQuery = false
					smallPart.Size = Vector3.new(0.2, 0.2, 0.2) -- Size of the small part
					smallPart.CFrame = projectile:GetPivot() --point
					smallPart.Anchored = true
					smallPart.Parent = workspace.Testing.junk	
				end
			end
		else
			-- The time to reach the goal has elapsed, so the projectile has reached its destination (or should be terminated to avoid incorrect positioning due to lag.)
			conn:Disconnect()
			conn = nil
			
			-- After connection is disconnected, run this.
			if castEndedFunc ~= nil then
				castEndedFunc()
			end
		end
	end)
end
  

Minecraft - Saiyan Craft

April 2014 - April 2018; September 2020 - June 2021

About the project

Saiyan Craft was a customized Minecraft server and subsequent community of gamers. I designed, configured, and implemented all of the backend processes supporting this server. I also designed the community website, managed user permissions both in-game and on the website, and coded updates for the game with JavaScript.

This effort was accomplished with the help of a team of 4 other staff, who helped to design, manage and communicate updates to our player base.

At its core, the server was themed around a “Dragon Ball Z” Minecraft mod. Players could create their own character, follow a “Dragon Ball” – themed questline, level up, spend stat points on new abilities, and participate in both PvP and PvE activities.

Snippet from the server’s voting platform: “Saiyan Craft is a 24/7 Dragon Block C server. Dragon Block C is a mod for Minecraft based on the popular show and Manga Dragon Ball Z. You can become a Human, Namekian, or even a Saiyan like Goku and fight against enemies like Frieza and Saibamen in order to train and become strong enough to use powerful Ki blasts! The mod also includes a saga feature which allows you to complete sagas while you train and fight enemies like Vegeta and the Androids. (The Dragon Block C mod and its components are required to join the server.)”

My roles:
  • Server Architect
  • Community Manager
  • Gameplay Programmer
  • Website Administrator
 
Responsibilities:
  • Led a team of 5 consisting of developers, community managers, and artists to design and implement server events, story content patches, and new zones, communicating updates to our player base.
  • Developed code for NPC AI and combat with JavaScript.
  • Managed and supported user data, including purchase records.
  • Performed disaster recovery actions in the event of change management failure or malware attack.

JavaScript code by me: initializes "Raid Boss" mob, tracks and attacks targets, tracks player damage, and gives rewards on death based on damage done.

				
					// ## Created by Kish
// ## © SAIYANCRAFT 2020
// ## FOR USE ON SAIYAN CRAFT OFFICIAL SERVER ****ONLY****
/* ## Labyrinth customnpc mini-boss for SC Halloween event 2020.*/

// ** INIT **

//Attack and Health
npc.setMeleeStrength(14750000);
npc.setRangedStrength(14750000);

npc.setMaxHealth(2000000000);
npc.setHealth(2000000000);

npc.setTempData("halfHpLineSaid", false);
npc.setTempData("nearDeathLineSaid", false);
npc.setTempData("transformed", false);
npc.setTempData("dead", false);

npc.setTempData("hitsTracker", []);
npc.setTempData("damageTracker", []);
npc.setTempData("lastHp", npc.getHealth());

var x = npc.x;
var y = npc.y;
var z = npc.z;
world.spawnClone(x-5, y, z-5, 6, "MinionFinale");
world.spawnClone(x-5, y, z+5, 6, "MinionFinale");
world.spawnClone(x+5, y, z+5, 6, "MinionFinale");
world.spawnClone(x+5, y, z-5, 6, "MinionFinale");

world.spawnClone(x-10, y, z-10, 6, "MinionFinale");
world.spawnClone(x-10, y, z+10, 6, "MinionFinale");
world.spawnClone(x+10, y, z+10, 6, "MinionFinale");
world.spawnClone(x+10, y, z-10, 6, "MinionFinale");

world.spawnClone(x-15, y, z-15, 6, "MinionFinale");
world.spawnClone(x-15, y, z+15, 6, "MinionFinale");
world.spawnClone(x+15, y, z+15, 6, "MinionFinale");
world.spawnClone(x+15, y, z-15, 6, "MinionFinale");


// ** UPDATE **
// ## Created by Kish
// ## © SAIYANCRAFT 2020
// ## FOR USE ON SAIYAN CRAFT OFFICIAL SERVER ****ONLY****
var rand = Math.floor(Math.random() * 10);
var x = (npc.x+(" "));
var y = (npc.y+(" "));
var z = (npc.z+(" "));
var coords = (x+y+z);
var dead = npc.getTempData("dead");

// Variables for damage tracker.
var dmg = npc.getTempData("lastHp") - npc.getHealth();
var playerName = npc.getTempData("damagedBy");
var inHitsTracker = false;
var inDamageTracker = false;
var hitsTracker = npc.getTempData("hitsTracker");
var damageTracker = npc.getTempData("damageTracker"); 

// DAMAGE TRACKER CODE
// Prevents npc from adding to damage trackers if it hasn't been hit.
if (npc.getTempData("addToDamage"))
{
	//Check for player name in hits tracker. If player has damaged npc, add +1 to total hits. If it hasn't, add player name to list of hits tracked.
	for (var i in hitsTracker){
		if (i == playerName)
		{
			hitsTracker[i] += 1;
			npc.setTempData("hitsTracker", hitsTracker);
			inHitsTracker = true;
			//npc.say("Player " + playerName + " found in hit tracker. Adding 1 hit."); DEBUG
		}
	}
	if (!inHitsTracker)
	{
		hitsTracker[playerName] = 1;
		npc.setTempData("hitsTracker", hitsTracker);
		//npc.say("Player " + playerName + " NOT found in hit tracker. Adding 1 hit."); DEBUG
	}

	// Check for player name in damage tracker. If player has damaged npc, add dmg amount to total damage. If it hasn't, add player name and current damage to damage tracked.
	for (var i in damageTracker){
		if (i == playerName)
		{
			damageTracker[i] += dmg;
			npc.setTempData("damageTracker", damageTracker);
			inDamageTracker = true;
			//npc.say("Player " + playerName + " found in damage tracker. Adding " + dmg + " damage."); DEBUG
		}
	}
	if (!inDamageTracker)
	{
		damageTracker[playerName] = dmg;
		npc.setTempData("damageTracker", damageTracker);
		//npc.say("Player " + playerName + " NOT found in damage tracker. Adding " + dmg + " damage."); DEBUG
	}		
}

npc.setTempData("addToDamage", false);

// Initialize target var
var target = npc.getTempData("target");

// ** NOTE TO SELF: NPC'S SET TO "DEFENSE FACTION MEMBERS" WILL BREAK THE INSTANT TRANSMISSION SCRIPT. THIS FACTION SETTING SEEMS TO HANDLE TARGETING DIFFERENTLY.
// Instant transmission
function instantTransmission(x, y, z){
	// Teleports close to player.
	var newX = Math.random()*2 + x;
	var newZ = Math.random()*2 + z;
	npc.setPosition(newX, y, newZ);
	npc.executeCommand("playsound mob.wither.shoot " + target.getName());
}
if((npc.isAttacking()) && (dead != true) && (target != null))
{
	// If npc isn't within range of the player, teleport to player OR shoot ki blast (lower chance for blast). This npc randomly picks between both options.
	if((Math.abs(target.x - npc.x) > 20) || (Math.abs(target.y - npc.y) > 10) || (Math.abs(target.z - npc.z) > 20))
	{
		if(rand <= 3) /// Lower # = lower chance (range from 0 - 10)
		{
			npc.executeCommand("/dbcspawnki 1 1 9000000 0 5 1 1 100 " +coords);
		}
		else
		{
			instantTransmission(target.x, target.y, target.z);
		}
	}
}

// ** TARGET **
npc.setTempData("target", event.getTarget());

// ** DAMAGED **
// ## Created by Kish
// ## © SAIYANCRAFT 2020
// ## FOR USE ON SAIYAN CRAFT OFFICIAL SERVER ****ONLY****
var source = event.getSource();
var dmg = npc.getMaxHealth() - npc.getHealth();
var halfHpLineSaid = npc.getTempData("halfHpLineSaid");
var nearDeathLineSaid = npc.getTempData("nearDeathLineSaid");
var rand = Math.floor(Math.random() * 10);


// Start gathering information for damage tracker when damaged.
var playerName = source.getName();
npc.setTempData("lastHp", npc.getHealth());
npc.setTempData("damagedBy", playerName);
npc.setTempData("addToDamage", true);

// Say half hp & near-death lines
if ((dmg >= npc.getMaxHealth() / 2) && !halfHpLineSaid)
{
	npc.say("This world will be mine. Bow before greatness.");
	halfHpLineSaid = true;
	npc.setTempData("halfHpLineSaid", halfHpLineSaid);		
}
else if ((dmg >= npc.getMaxHealth() / 1.25) && !nearDeathLineSaid)
{
	npc.say(".");
	nearDeathLineSaid = true;
	npc.setTempData("nearDeathLineSaid", nearDeathLineSaid);
}

// ** KILLED **
// ## Created by Kish
// ## © SAIYANCRAFT 2020
// ## FOR USE ON SAIYAN CRAFT OFFICIAL SERVER ****ONLY****
var source = event.getSource();
var playerName = source.getName();
var oneShotKill = true;

// Send message on death
//source.sendMessage("Goku: (Thank you...)");
npc.setTempData("dead", true);

// Reward players based on their contribution to the kill.
var hitsTracker = npc.getTempData("hitsTracker");
var damageTracker = npc.getTempData("damageTracker");

var maxHealth = npc.getMaxHealth();
var minReward = maxHealth / 100; // 1% of boss's hp.
var regReward = maxHealth / 20; // 5% of boss's hp.
var goldReward = maxHealth / 10; // 10% of boss's hp.
var diamondReward = maxHealth / 3; // 30% of boss's hp.
var legendaryReward = maxHealth / 2; // half of boss's hp.

for (var i in hitsTracker)
{
	
	// If the player that kills this NPC isn't found in the damage tracker (because they got last hit or killed it in one hit,) oneShotKill flag stays true, and they are rewarded after players who contributed damage are rewarded.
	if (i == playerName)
	{
		oneShotKill = false;
	}
	
	if (damageTracker[i] >= legendaryReward)
	{
	    npc.executeCommand("/citems give -p " + i + " 500milticket -a 10"); 
		npc.executeCommand("/citems give -p " + i + " SoulSack -a 10");
		npc.executeCommand("/citems give -p " + i + " immortalsoul -a 10");
		npc.executeCommand("/citems give -p " + i + " DemonSlayerGlaive -a 1");		
		npc.executeCommand("/msg " + i + " You have received the &4Legendary &flevel reward for your contribution in defeating " + npc.getName() + ".");
	}	
	else if (damageTracker[i] >= diamondReward)
	{
	    npc.executeCommand("/citems give -p " + i + " 500milticket -a 8"); 
		npc.executeCommand("/citems give -p " + i + " SoulSack -a 7");
		npc.executeCommand("/citems give -p " + i + " immortalsoul -a 5");
		npc.executeCommand("/citems give -p " + i + " DemonSlayerGlaive -a 1");	
		npc.executeCommand("/msg " + i + " You have received the &bDiamond &flevel reward for your contribution in defeating " + npc.getName() + ".");
	}
	else if (damageTracker[i] >= goldReward)
	{
	    npc.executeCommand("/citems give -p " + i + " 500milticket -a 7"); 
		npc.executeCommand("/citems give -p " + i + " SoulSack -a 5");
		npc.executeCommand("/citems give -p " + i + " immortalsoul -a 4");
		npc.executeCommand("/citems give -p " + i + " DemonSlayerGlaive -a 1");	
		npc.executeCommand("/msg " + i + " You have been rewarded for your contribution in defeating " + npc.getName() + ".");
	}
	else if (damageTracker[i] >= regReward)
	{
	    npc.executeCommand("/citems give -p " + i + " 500milticket -a 6"); 
		npc.executeCommand("/citems give -p " + i + " SoulSack -a 2");
		npc.executeCommand("/citems give -p " + i + " immortalsoul -a 4");
		npc.executeCommand("/citems give -p " + i + " DemonSlayerGlaive -a 1");	
		npc.executeCommand("/msg " + i + " You have been rewarded for your contribution in defeating " + npc.getName() + ".");
	}
	else if ((hitsTracker[i] >= 15) || (damageTracker[i] >= minReward))
	{
	    npc.executeCommand("/citems give -p " + i + " 500milticket -a 4"); 
		npc.executeCommand("/citems give -p " + i + " SoulSack -a 1");
		npc.executeCommand("/citems give -p " + i + " immortalsoul -a 4");
		npc.executeCommand("/citems give -p " + i + " DemonSlayerGlaive -a 1");	
		npc.executeCommand("/msg " + i + " You have been rewarded for your contribution in defeating " + npc.getName() + ".");
	}
	
}

if (oneShotKill)
{
	npc.executeCommand("/citems give -p " + playerName + " 500milticket -a 4"); 
	npc.executeCommand("/citems give -p " + playerName + " SoulSack -a 1");
	npc.executeCommand("/citems give -p " + playerName + " immortalsoul -a 4");
	npc.executeCommand("/citems give -p " + playerName + " DemonSlayerGlaive -a 1");	
	npc.executeCommand("/msg " + playerName + " You have been rewarded for your contribution in defeating " + npc.getName() + ".");
}
else
{
//	npc.executeCommand("/msg " + playerName + " Debug! OneShotKill was " + oneShotKill + "!");
}

				
			
SC Kami House
SC "Kami House"
Screenshot of a Web Forum
SC Forums

Player-made video from the server's early days

Au Luxe 79

Release Date: Q1 2020

About the project

Au Luxe 79 was a high-end custom “Travel Box” store. I was hired as a full-stack developer to help design, develop and implement a full web application. The site was designed based on customer feedback and intent.

I developed the entire solution, both front-end and backend, and utilized a dedicated web server host to run the site. JavaScript, HTML5 and CSS were used on the front-end to style the web pages, while PHP and SQL were utilized to communicate information to and from the site’s database. 

Au Luxe 79 featured a custom “buzz-feed” style product purchase flow. Customers would create an account, choose a product, and then fill out a stylized survey. The answers to the survey were attached to the customer’s order, and sent to the store owner on product purchase. The answers were then used to create a personalized product. Users could sign in and view the answers to their past surveys.

My role:
  • Full-Stack Developer
 
Responsibilities:
  • Designed and delivered in full a custom website, leveraging CSS, JavaScript and jQuery, implementing changes based on customer feedback.
  • Fully designed and implemented a server-side database solution with mySQL and PHP.
  • Met regularly with stakeholders to ascertain customer satisfaction, adjusting design as required, while aiming to stay within negotiated timeframes.

Portfolio page designed for and by Adnan el-Bedawi.

Contact:
Email: elbedawia@yahoo.com
LinkedIn: https://www.linkedin.com/in/aedev98/