• This is a read only backup of the old Emudevs forum. If you want to have anything removed, please message me on Discord: KittyKaev

Kayle - Boss Script (WiP)

NoobxGenesis

Enthusiast
Introduction:
Basically, I have been on hiatus from modding for a while (almost 2 years) after getting rather burnt out on it. I have wanted to come back after a while but most stuff is too tedious to enjoy, until I heard about Eluna. While I can do C++ and multiple other languages, Lua is just simple and relaxing to me. On top of that, rather than just picking up where I left off, I would start from scratch and relearn a lot of stuff I have forgotten. That said I thought I would start with a boss script, loosely based off the League of Legends character Kayle. Took about 8 hours and is still a work in progress, as it was my first attempt at Eluna and I am not familiar with the API yet.

Keep in mind this is a WiP (Work in Progress)

To Do:
- Optimize and clean the script.
- Add mob spawns stage.
- Change certain spell functions to utilize loops rather than singular calls.
- Add death sequence
- Upload video example

- LEARN MORE!

Script:
I tried to keep it modular and clean for others to modify and use if they so choose, but again most of it is written for me to simply relearn things I forgot as well as learn the Eluna API. It isn't fully optimized yet, so if you have suggestions feel free to post them.

As mentioned I don't want to get back into spell modding for a while, so I just went with player spells, meaning the boss is not balanced at all, it is merely a script.

Code:
--------------------------
--                               --
--		Overview      --
--			          --
--------------------------

-- Author: NoobxGenesis/XxXGenesisXxX
-- YouTube: youtube.com/NoobxGenesis

--------------------------
--                               --
--		  Locals       --
--				 --
--------------------------

-----------
-- Stage --
-----------

stage = 0

-- These are the percentages of health required to progress to the numbered stage.
stage2 = 95 -- Stage 1 --> Stage 2
stage3 = 90 -- Stage 2 --> Stage 3
stage4 = 85 -- Stage 3 --> Stage 4
stage5 = 80 -- Stage 4 --> Stage 5

-----------
-- State --
-----------
cast_state = 0 -- Required to prevent cast interruptions.
cast_state_time = 0 -- To allow dynamic use of cast states, the spell functions themselves can adjust this number as required by the spells cast time.

------------
-- Spoken --
------------

yell_test = "TESTING" -- Simply to do checks during development.
yell_burn = "May you burn in the fires of the righteous!"
yell_spiritform = "Only the truly righteous shall be spared my wrath!"
yell_holyexplosion = "I will cleanse the world if I have to!"

------------
-- Spells --
------------

spell_divineshield = 642 -- Divine Shield

-- Stage 1 --
spell_smite = 48123 -- Smite Rank 12
spell_powershield = 48066 -- Power Word: Shield Rank 14
spell_consecration = 48819 -- Consecration Rank 8 (AoE)

-- Stage 2 --
spell_holyshock = 48825 -- Holy Shock Rank 7
spell_flashheal = 48071 -- Flash Heal Rank 11
--spell_greaterheal = 48063 -- Greater Heal Rank 9

-- Stage 3 --
spell_sanctuary = 20911 -- Blessing of Sanctuary (Buff)
--spell_holyfire = 48135 -- Holy Fire Rank 11
spell_massdispel = 32375 -- Mass Dispel (AoE)
spell_renew = 48068 -- Renew Rank 14
spell_holynova = 48078 -- Holy Nova Rank 9 (AoE)

-- Stage 4 --
spell_manaburn = 8129 -- Mana Burn
spell_psychicscream = 10890 -- Psychic Scream Rank 4 (AoE)
spell_holylight = 48782 -- Holy Light Rank 13

-- Stage 5 --
spell_desperateprayer = 48173 -- Desperate Prayer Rank 9
spell_holywrath = 48817 -- Holy Wrath Rank 5 (AoE)


----------------
-- Model ID's --
----------------
model_normalform = 28894
model_spiritform = 26077

-------------
-- Summons --
-------------



--------------------------
--                               --
--		Functions	  --
--				  --
--------------------------

----------------
-- Dependency --
----------------

-- Stage check and progression
function stage_check(event, delay, pCall, creature)
	if stage == 1 then
		if (creature:HealthBelowPct(stage2)) then
			stage = 2
			creature:RegisterEvent(Kayle_S2, 1, 1)
		end
	end
	
	if stage == 2 then
		if (creature:HealthBelowPct(stage3)) then
			creature:RegisterEvent(Kayle_Transform, 1, 1)
		end
	end
	
	if stage == 3 then
		if (creature:HealthBelowPct(stage4)) then
			stage = 4
			creature:RegisterEvent(Kayle_S4, 1, 1)
		end
	end
	
	if stage == 4 then
		if (creature:HealthBelowPct(stage5)) then
			stage = 5
			creature:RegisterEvent(Kayle_S5, 1, 1)
		end
	end
	
end

-- These are to prevent cast interruptions
function add_cast_state(event, delay, pCall, creature)
	cast_state = 1
	CreateLuaEvent(remove_cast_state, cast_state_time, 1)
end

function remove_cast_state(event, delay, pCall, creature)
	cast_state = 0
end

-------------------
-- Spell Chances --
-------------------
function spell_chance(event, delay, pCall, creature)
	
	chance = math.random(1,100)
	-- creature:SendUnitSay(chance, 1) -- For testing only.
	
	-- Stage 1 Chances --
	if stage == 1 then
		if chance <= 15 then -- Continues auto-attacking (15%)
	
		elseif chance <= 45 then -- Casts Smite (30%)
			creature:RegisterEvent(Kayle_Smite, 1, 1)
			
		elseif chance <= 70 then -- Casts Powershield (25%)
			creature:RegisterEvent(Kayle_Powershield, 1, 1)
	
		elseif chance <= 100 then -- Casts Consecration (30%)
			creature:RegisterEvent(Kayle_Consecration, 1, 1)
		end
		creature:RegisterEvent(Kayle_S1, 1, 1)
	
	-- Stage 2 Chances --
	elseif stage == 2 then
		if chance <= 10 then -- Continues auto-attacking (10%)
	
		elseif chance <= 25 then -- Casts Smite (15%)
			creature:RegisterEvent(Kayle_Smite, 1, 1)
			
		elseif chance <= 45 then -- Casts Powershield (20%)
			creature:RegisterEvent(Kayle_Powershield, 1, 1)
	
		elseif chance <= 65 then -- Casts Consecration (20%)
			creature:RegisterEvent(Kayle_Consecration, 1, 1)
		
		elseif chance <= 90 then -- Casts Holy Shock (25%)
			creature:RegisterEvent(Kayle_HolyShock, 1, 1)
		
		elseif chance <= 100 then -- Flash Heal (10%)
			creature:RegisterEvent(Kayle_FlashHeal, 1, 1)
		end
		creature:RegisterEvent(Kayle_S2, 1, 1)
		
	-- Stage 3 Chances --
	elseif stage == 3 then
		if chance <= 5 then -- Continues auto-attacking (5%)
	
		elseif chance <= 15 then -- Casts Powershield/Holy Nova (10%)
			creature:RegisterEvent(Kayle_Powershield, 1, 1)
			
		elseif chance <= 35 then -- Casts Consecration (20%)
			creature:RegisterEvent(Kayle_Consecration, 1, 1)
	
		elseif chance <= 65 then -- Casts Holy Shock (30%)
			creature:RegisterEvent(Kayle_HolyShock, 1, 1)
		
		elseif chance <= 80 then -- Casts Flash Heal (15%)
			creature:RegisterEvent(Kayle_FlashHeal, 1, 1)
		
		elseif chance <= 90 then -- Casts Renew (10%)
			creature:RegisterEvent(Kayle_Renew, 1, 1)
			
		elseif chance <= 100 then -- Casts Mass Dispel (10%)
			creature:RegisterEvent(Kayle_MassDispel, 1, 1)
		end		
		creature:RegisterEvent(Kayle_S3, 1, 1)
		
	-- Stage 4 Chances --
	elseif stage == 4 then
		if chance <= 5 then -- Continues auto-attacking (5%)
	
		elseif chance <= 30 then -- Casts Powershield/Holy Nova (25%)
			creature:RegisterEvent(Kayle_Powershield, 1, 1)
			
		elseif chance <= 65 then -- Casts Holy Shock (35%)
			creature:RegisterEvent(Kayle_HolyShock, 1, 1)
	
		elseif chance <= 75 then -- Casts Mass Dispel (10%)
			creature:RegisterEvent(Kayle_MassDispel, 1, 1)
		
		elseif chance <= 85 then -- Casts Mana Burn (10%)
			creature:RegisterEvent(Kayle_ManaBurn, 1, 1)
		
		elseif chance <= 90 then -- Casts Psychic Scream (5%)
			creature:RegisterEvent(Kayle_PsychicScream, 1, 1)
			
		elseif chance <= 100 then -- Casts Holy Light (10%)
			creature:RegisterEvent(Kayle_HolyLight, 1, 1)
		end				
		creature:RegisterEvent(Kayle_S4, 1, 1)
		
	-- Stage 5 Chances --
	elseif stage == 5 then
		if chance <= 35 then -- Casts Holy Shock (35%)
			creature:RegisterEvent(Kayle_HolyShock, 1, 1)
			
		elseif chance <= 45 then -- Casts Mass Dispel (10%)
			creature:RegisterEvent(Kayle_MassDispel, 1, 1)
	
		elseif chance <= 55 then -- Casts Mana Burn (10%)
			creature:RegisterEvent(Kayle_ManaBurn, 1, 1)
		
		elseif chance <= 60 then -- Casts Psychic Scream (5%)
			creature:RegisterEvent(Kayle_PsychicScream, 1, 1)
		
		elseif chance <= 70 then -- Casts Desperate Prayer (10%)
			creature:RegisterEvent(Kayle_DesperatePrayer, 1, 1)
			
		elseif chance <= 90 then -- Casts Holy Nova (20%)
			creature:RegisterEvent(Kayle_HolyNova, 1, 1)
			
		elseif chance <= 100 then -- Casts Holy Explosion (10%)
			creature:RegisterEvent(Kayle_HolyExplosion, 1, 1)
		end			
		creature:RegisterEvent(Kayle_S5, 1, 1)
	end	

	
end

-------------
-- Initial --
-------------
function Kayle_Spawn(event, creature)

end

function Kayle_OnDeath(event, creature, killer)
	creature:SetDisplayId(model_normalform)
	stage = 0
end

function Kayle_LeaveCombat(event, creature)
	creature:SetDisplayId(model_normalform)
	stage = 0
end

function Kayle_EnterCombat(event, creature, target)
	creature:SendUnitYell(yell_burn, 0)
	creature:RegisterEvent(Kayle_S1, 1, 0)
	creature:RegisterEvent(stage_check, 1000, 0)
	stage = 1
end

-------------
-- Stage 1 --
-------------
function Kayle_S1(event, delay, pCall, creature)
	
	creature:RemoveEventById(event) -- Not sure if needed
	
	if stage == 1 then
		creature:RegisterEvent(spell_chance, 3000, 1)
	end
	
end

-- Spells --
function Kayle_Smite(event, delay, pCall, creature)
	if cast_state == 0 then
		CreateLuaEvent(add_cast_state, 1, 1)
		cast_state_time = 2500
		creature:CastSpell(creature:GetVictim(), spell_smite)
	end
end

function Kayle_Powershield(event, delay, pCall, creature)
	if cast_state == 0 then
		if stage == 1 then
			creature:CastSpell(creature, spell_powershield)
			
		elseif stage == 3 then
			creature:CastSpell(creature, spell_powershield)
			creature:CastSpell(creature, spell_holynova)
		elseif stage == 4 then
			creature:CastSpell(creature, spell_powershield)
			creature:CastSpell(creature, spell_holynova)
		end
	end
end

function Kayle_Consecration(event, delay, pCall, creature)
	if cast_state == 0 then
		creature:CastSpell(creature:GetVictim(), spell_consecration)
	end
end

-------------
-- Stage 2 --
-------------
function Kayle_S2(event, delay, pCall, creature)
	
	creature:RemoveEventById(event) -- Not sure if needed
	
	if stage == 2 then
		creature:RegisterEvent(spell_chance, 2500, 1)
	end
	
end

-- Spells --
function Kayle_HolyShock(event, delay, pCall, creature)
	if cast_state == 0 then
		if stage == 2 then
			creature:CastSpell(creature:GetVictim(), spell_holyshock)
		
		elseif stage == 4 then
			target = creature:GetVictim()
			creature:CastSpell(target, spell_holyshock)
			creature:CastSpell(target, spell_holyshock)
			creature:CastSpell(target, spell_holyshock)
		
		elseif stage == 5 then
			target = creature:GetVictim()
			creature:CastSpell(target, spell_holyshock)
			creature:CastSpell(target, spell_holyshock)
			creature:CastSpell(target, spell_holyshock)
			creature:CastSpell(target, spell_holyshock)
			creature:CastSpell(target, spell_holyshock)
		end
	end
end

function Kayle_FlashHeal(event, delay, pCall, creature)
	if cast_state == 0 then
		CreateLuaEvent(add_cast_state, 1, 1)
		cast_state_time = 1500
		creature:CastSpell(creature, spell_flashheal)
	end
end

function Kayle_Transform(event, delay, pCall, creature) -- Transformation between stage 1 & 2
	-- Cast Spell to stop all players interacting.
	
	creature:SendUnitYell(yell_spiritform, 0)
	
	-- Short channelled spell to transform
	CreateLuaEvent(add_cast_state, 1, 1)
	cast_state_time = 0	
	creature:SetDisplayId(model_spiritform)
	creature:RegisterEvent(Kayle_Sanctuary, 1, 1)
	creature:RegisterEvent(Kayle_Consecration, 1, 1)
	creature:RegisterEvent(Kayle_S3, 1, 1)
	stage = 3
end

-------------
-- Stage 3 --
-------------
function Kayle_S3(event, delay, pCall, creature)
	
	creature:RemoveEventById(event) -- Not sure if needed
	
	if stage == 3 then
		creature:RegisterEvent(spell_chance, 2500, 1)
	end
	-- creature:SendUnitYell("test2", 0)	
end

function Kayle_Sanctuary(event, delay, pCall, creature)
	creature:CastSpell(creature, spell_sanctuary)
end
-- Spells --
function Kayle_Renew(event, delay, pCall, creature)
	if cast_state == 0 then
		creature:CastSpell(creature, spell_renew)
	end
end

function Kayle_MassDispel(event, delay, pCall, creature)
	if cast_state == 0 then
		CreateLuaEvent(add_cast_state, 1, 1)
		cast_state_time = 1500
		creature:CastSpell(creature:GetVictim(), spell_massdispel)
	end
end

-------------
-- Stage 4 --
-------------
function Kayle_S4(event, delay, pCall, creature)
	
	creature:RemoveEventById(event) -- Not sure if needed
	
	if stage == 4 then
		creature:RegisterEvent(spell_chance, 2500, 1)
	end		
	--creature:SendUnitYell("test3", 0)
	
end

-- Spells --
function Kayle_ManaBurn(event, delay, pCall, creature)
	if cast_state == 0 then
		CreateLuaEvent(add_cast_state, 1, 1)
		cast_state_time = 3000
		creature:CastSpell(creature:GetVictim(), spell_manaburn)
	end
end

function Kayle_PsychicScream(event, delay, pCall, creature)
	if cast_state == 0 then
		creature:CastSpell(creature, spell_psychicscream)
	end
end

function Kayle_HolyLight(event, delay, pCall, creature)
	if cast_state == 0 then
		CreateLuaEvent(add_cast_state, 1, 1)
		cast_state_time = 2500
		creature:CastSpell(creature, spell_holylight)
	end
end

-------------
-- Stage 5 --
-------------
function Kayle_S5(event, delay, pCall, creature)
	
	creature:RemoveEventById(event) -- Not sure if needed
	
	if stage == 5 then
		creature:RegisterEvent(spell_chance, 2500, 1)
	end		
	-- creature:SendUnitYell("test4", 0)
	
end

-- Spells --
function Kayle_DesperatePrayer(event, delay, pCall, creature)
	if cast_state == 0 then
		creature:CastSpell(creature, spell_desperateprayer)
	end
end

function Kayle_HolyNova(event, delay, pCall, creature) -- Need new spell
	if cast_state == 0 then
		creature:CastSpell(creature:GetVictim(), spell_holynova)
		creature:CastSpell(creature:GetVictim(), spell_consecration)
	end
end

function Kayle_HolyExplosion(event, delay, pCall, creature) --need new spell
	if cast_state == 0 then
		creature:SendUnitYell(yell_holyexplosion, 0)
		creature:CastSpell(creature:GetVictim(), spell_holywrath)
		creature:CastSpell(creature:GetVictim(), spell_holywrath)
		creature:CastSpell(creature:GetVictim(), spell_holywrath)
		creature:CastSpell(creature:GetVictim(), spell_holywrath)
		creature:CastSpell(creature:GetVictim(), spell_holywrath)
	end
end

---------------------
-- Register Events --
---------------------
RegisterCreatureEvent(100000, 5, Kayle_Spawn)
RegisterCreatureEvent(100000, 4, Kayle_OnDeath)
RegisterCreatureEvent(100000, 2, Kayle_LeaveCombat)
RegisterCreatureEvent(100000, 1, Kayle_EnterCombat)
 

Rochet2

Moderator / Eluna Dev
Nice nice. :)

TODO;
Use local functions
Replace all CreateLuaEvent with creature:RegisterEvent if possible.
This is because creature registered events wont run if the creature doesnt exist and they can be easily removed with creature:RemoveEvents() for example.
Note that all the variables are SHARED with all creatures that use the script.
This means that 2 spawns of the boss (example: 2 instances of a dungeon) will collide

Possibly create utility functions for casting the spells? - Probably what you were already thinking of doing
This would mean that there is one function that takes the creature, spell, the cast_state_time etc as a parameter
Then casts the spell, sets the timers etc accordingly. This would reduce the script size and repetitiveness a lot.
There would also be less need for making an unique new function for every single spell casted.

I didnt look through the whole script yet, but decided to post anyways.
 

NoobxGenesis

Enthusiast
Nice nice. :)

TODO;
Use local functions
Replace all CreateLuaEvent with creature:RegisterEvent if possible.
This is because creature registered events wont run if the creature doesnt exist and they can be easily removed with creature:RemoveEvents() for example.
Note that all the variables are SHARED with all creatures that use the script.
This means that 2 spawns of the boss (example: 2 instances of a dungeon) will collide

Possibly create utility functions for casting the spells? - Probably what you were already thinking of doing
This would mean that there is one function that takes the creature, spell, the cast_state_time etc as a parameter
Then casts the spell, sets the timers etc accordingly. This would reduce the script size and repetitiveness a lot.
There would also be less need for making an unique new function for every single spell casted.

I didnt look through the whole script yet, but decided to post anyways.

Thanks for the feedback :)

- Yeah, I have a bad habit with not localizing things. I'll get to that this weekend.
- I was rather hesitant about the correct usage of the CreateLuaEvent and creature:RegisterEvent specifically because of the removal of events. Is there a way to specify exactly which event is to be removed, and how is handled by default?
- I did not know it would be effected by different instances. Gonna have to reread the script to see exactly what you mean (really tired at the moment).

- Gonna have to reply to this part tomorrow night, i am way too tired to comprehend things currently lol, but I do know you're right, the spells currently are taking up much more than necessary.

At the moment I think I need to do a bunch of tests and possibly document some stuff to do with Eluna API, cause I am just using current scripts as references atm, but their aren't too many boss scripts out at the moment, so I am just improvising without enough knowledge on the specific API's. But I'll probably do this on the weekend as I have Uni the next 2 days. Will also probably check out the engine files too to get a grasp.
 

neglected

Enthusiast
Localize your "locals" section and it might be a good idea to come up with a convention to identify that variables are constant. Most people will capitalize a variable name if the variable itself is constant. Not required but it's nice for semantics for other people looking at your script.

Note that all the variables are SHARED with all creatures that use the script.
To combat this, you can always store session-local variables inside a table indexed by the creature's GUID (or whatever equivalent that is in Eluna - the smaller the field in terms of memory, the better. GUIDs are pretty large so if you can go with a simple number, great)
 

Rochet2

Moderator / Eluna Dev
To combat this, you can always store session-local variables inside a table indexed by the creature's GUID (or whatever equivalent that is in Eluna - the smaller the field in terms of memory, the better. GUIDs are pretty large so if you can go with a simple number, great)

GetGUID() gets the whole guid, which is unique for all creature/gameobject/player/... as a string and GetGUIDLow() gets the uint32 guid that is unique for each creature only.
So GetGUIDLow() would be the one to use.
Additionally / instead I would possibly store the data for the instance ID of the creature if possible.

There was some talk about whether we should try to implement creature AI scripting so that you could feed a table to the register.
This would also allow the table to be used as storage much like in the C++ scripting there is a class to store stuff.
Lets say like this:
Code:
local customAI = {}

-- Lets say the function name needs to be exactly the same to be used as on combat
function customAI:OnCombat(event, creature, target)
    self.somevar = 5; -- self would be the customAI table
end

function customAI:OnDeath(event, creature)
    print(self.somevar)
end

RegisterCreatureAI(123123, customAI)

Additionally if / when multithreading is implemented, it will likely limit a lua state per map, meaning that each instance has its own state.
This means that there wont be collision, except with spawns in the same instance.

This should be a topic : )


I was rather hesitant about the correct usage of the CreateLuaEvent and creature:RegisterEvent specifically because of the removal of events. Is there a way to specify exactly which event is to be removed, and how is handled by default?
CreateLuaEvent just creates a global lua event that ticks all the time.
creature:RegisterEvent creates a creature timed event that runs when the creature AI ticks run. So it wont progress if the creature is not spawned / visible etc, while the global event just runs all the time.
RegisterEvent exists for creature, player and gameobject.

https://github.com/ElunaLuaEngine/Eluna/blob/master/LuaFunctions.cpp#L323
Code:
:RegisterEvent(function, delay, repeats) - The timer ticks if this unit is visible to someone. The function is called with arguments ([B][U]eventid[/U][/B], delay, repeats, unit) after the time has passed if the unit exists. Returns [B][U]EventId[/U][/B]
:RemoveEventById([B][U]eventID[/U][/B]) - Removes a Registered (timed) event by it's ID.
:RemoveEvents() - Removes all registered timed events
If you see the eventid mentioned in above, its all the same event id.
This means that you can use it to remove the specific event from the creature / player etc.
RemoveEvents() removes all timed events from a creature, it is custom to do this on death and leave combat usually. (at least was on arcemu)
RegisterEvent() returns the event ID, but since it is also passed to the executed function, it is usually just taken from there and removed with RemoveEventById.

The global events have the same syntax and methods.
https://github.com/ElunaLuaEngine/Eluna/blob/master/LuaFunctions.cpp#L88
Just the registering function is different. This comes from arcemu and tries to mostly differentiate the 2 types of timed events (global and "local")
There are some differences with the global methods though, see the function parameters and description. Rarely used though.
 

neglected

Enthusiast
To combat this, you can always store session-local variables inside a table indexed by the creature's GUID (or whatever equivalent that is in Eluna - the smaller the field in terms of memory, the better. GUIDs are pretty large so if you can go with a simple number, great)
To prevent myself from looking silly, ignore the bolded part if Lua stores table indices by reference.
 
Top