Yes I am Halestorm, or HalestormXV if you prefer. I am both and both are me. If you would like proof that I am me then send me a PM if you wish
So I see a small amount of C++ ones. Now I am going to give this warning once. This is not easy to understand. I recommend this ONLY if you really want to spend a lot of time becoming frustrated and many attempts at Trial and Error, and many more attempts and recompiling your brains out because unlike Lua you can't just reload the .dll
PART I - What You Need
If you do not have the following then do not even continue reading this guide. It will be a waste of your time. Here is your check list:
- The ability to compile your own server
- The ability to understand C++ code or the mental capacity to learn it. (This is Fact, not to be mean)
- Microsoft Visual Studio C++ Express 2010 (Arc no longer has a 2008 solution)
- Knowledge on how to compile your own scripts either by creating a new project or using a copy of the existing solution.
- The ability to understand that text within () is extra information that someone can validate me on if they see that I have made an error. Otherwise you can ignore it.
PART II - The Background Info
When you create a C++ Script and compile it you are not creating an actual script. You are creating an extension or external library or a .dll file. This file gets loaded with your server on startup. Yes you are creating a script but once you compile it, it becomes an extension to the application. It is loaded once during server load up and the core takes care of it from there.
The ArcScript Team has created an ArcScript library (or at least it was called that at the time of creating this example boss). The ArcScript Library is a preset compilation of function and command that you can use to set up your AI. There are two libraries. One for Bosses and one for Basic Trash. You can use either or for whatever you want but the Boss library obviously has more functionality. (I know it was based off of MoonScript and I believe you can still use the Moon Script Class as well. For those who have no idea what I am talking about relating to this just ignore it.)
PART III - Let's Begin - Create the Blueprints
C++ Scripts always need a general same header for this tutorial's purpose. The header is as follows:
Code:
#include "StdAfx.h" //This uses the existing functions in Arc's Core
#include "Setup.h" //So does this
#include "Base.h" // This is used to include the ArcAI engine commands
#ifdef WIN32 //This is our structure
#pragma warning(disable:4305) //This disables an annoying error that may pop up while compiling
#endif
Now we are creating a boss named Captain Randle. He is a mean person who likes to beat you up. Now what I am going to show you below is good coding practice however you do not actually need to define your constants/enumerations but I like to define everything at the top of my scripts so this is the way I teach others how to do it.
Code:
//Captain Randal
#define COT_CAPTAIN_RANDAL 48264 //This is the entry number of the good captain
//Everything below this is his ability ID numbers.
#define RANDAL_THUNDER 43583
#define RANDAL_DECAY 56359
#define RANDAL_BLADE 63784
#define RANDAL_BLOOD 59130
#define RANDAL_ICY 60952
#define RANDAL_CLEAVE 31345
#define RANDAL_FEAR 22686
We have defined the entry of the good captain and given him a nice list of spells to use. He looks really mean. Scripting him will be even meaner
PART IIIII - Let's Begin - Create the Structure
Now we have to tell the scripting engine what we will be pulling from and we have to create our structure by telling the extension what we will be doing.
Code:
class RandalBossAI : public MoonScriptBossAI
{
public:
MOONSCRIPT_FACTORY_FUNCTION(RandalBossAI, MoonScriptBossAI);
RandalBossAI(Creature* pCreature) : MoonScriptBossAI(pCreature)
{
randalThunder = AddSpell(RANDAL_THUNDER, Target_Current, 13, 0, 10);
randalDecay = AddSpell(RANDAL_DECAY, Target_Destination, 9, 3, 12);
randalBlood = AddSpell(RANDAL_BLOOD, Target_Current, 15, 0, 8);
AddPhaseSpell(2, AddSpell(RANDAL_ICY, Target_Current, 14, 0, 8));
AddPhaseSpell(2, AddSpell(RANDAL_BLADE, Target_Current, 8, 0, 12));
AddPhaseSpell(3, AddSpell(RANDAL_CLEAVE, Target_Current, 11, 0, 12));
AddPhaseSpell(3, AddSpell(RANDAL_FEAR, Target_Current, 9, 1.5f, 15));
AddEmote(Event_OnCombatStart, "You have faced many challenges, pity they were all in vain. Soon your people will kneel to my lord!", Text_Yell, 10292);
AddEmote(Event_OnTargetDied, "It is over! Finished!", Text_Yell, 10297);
}
HOLY SHIT WHAT JUST HAPPENED!! Oh Noez! A Big Wall of Text in C++ No Wai!! If you are saying this to yourself right now you may want to turn away as this is just the beginning. Now lets take it apart piece by piece for those brave adventurers still reading on.
Code:
class RandalBossAI : public MoonScriptBossAI
All we did here was tell the script that we will be using the ArcBossAI/MoonBossAI "Library" it is public because it can be pulled from.
All this did was open up our function.
Code:
public:
MOONSCRIPT_FACTORY_FUNCTION(RandalBossAI, MoonScriptBossAI);
RandalBossAI(Creature* pCreature) : MoonScriptBossAI(pCreature)
This assigns the name of our AI, for this our AI is named RandalBossAI. The MoonScriptBossAI is basically assigning our RandalBossAI to this. The RandalBossAI(Creature* pCreature) : MoonScriptBossAI(pCreature) is setting up our pointer. We are saying that RandalBossAI is a creature and we are declaring it as pCreature. The MoonScriptBossAI(pCreature) is telling the MoonScriptBossAI library to use creature arguments so to speak.
This digs deeper into our function making it nested so that C++ knows to run this as all part of the same code block
Code:
randalThunder = AddSpell(RANDAL_THUNDER, Target_Current, 13, 0, 10);
This line of code and all of those that look like it simply add the spell to the AI of the boss. AddSpell is a function in the library that takes 5 arguments. The are as follows as shown in the line above respectively.
- Spell ID (Remember we declared this at the top)
- The target of the spell Target_Current means who he is currently attacking
- The percent chance that it will occur. We have a 13 percent chance here.
- The cast time. 0 means instant cast
- The cooldown, which is self explanatory
Code:
AddPhaseSpell(2, AddSpell(RANDAL_ICY, Target_Current, 14, 0, 8));
This is a neat little feature that does the same as above however adds it to a phase of the boss. So the boss will only use this ability if he is in phase 2 of the encounter which is what the first argument is.
Code:
AddEmote(Event_OnTargetDied, "It is over! Finished!", Text_Yell, 10297);
This adds an emote to the boss. What it translates to is simply - When his target dies yell It is over! Finished! in the yell text and play soundID 10297. That's it. Pretty neat huh?
All this did was close the block of code that we entered above.
PART V - Let's Continue - The Heart
While dealing with C++ we need multiple functions and multiple things to occur within each function just as we would with Lua. However in C++ we call this void.
Code:
void OnCombatStart(Unit *pTarget)
Looks easy right Well that part is very east. This is the function that will occur once combat starts. Now have your eyes look at this
Code:
{
// Phase 1
if (randalThunder != NULL)
{
randalThunder->mChance = 13.0f;
randalThunder->mEnabled = true;
}
if (randalDecay != NULL)
{
randalDecay->mChance = 9.0f;
randalDecay->mEnabled = true;
}
if (randalBlood != NULL)
{
randalBlood->mChance = 15.0f;
randalBlood->mCooldown = 8;
randalBlood->mEnabled = true;
}
ParentClass::OnCombatStart(pTarget);
}
OMG HALESTORM I HATE YOU I HATE YOU I HATE YOU!! WHAT IS ALL OF THIS SHIT YOU ARE THROWING IN MY FACE! I AM GOING TO ASSPLOAD!!
Easy now young grasshopper. Every boss automatically when combat begins starts in phase 1. All this is doing is as follows. During phase 1 all of this will occur.
Code:
if (randalThunder != NULL)
{
randalThunder->mChance = 13.0f;
randalThunder->mEnabled = true;
}
Let's break this down. if (randalThunder != NULL) means that if it is not non-existent and is infact there and has a value then proceed.
Code:
randalThunder->mChance = 13.0f;
This adjusts the chance this will occur to 13%.
Code:
randalThunder->mEnabled = true;
This means that the spell will be enabled during phase 1. The two { open and close our if statement. Now lets move to the next line of code shall we?
Code:
void OnCombatStop(Unit* pTarget)
Well this function will run when CombatStops. And now for the meat of the function itself.
Code:
{
if(GetHealthPercent() >= 1)
{
sEventMgr.AddEvent(TO_UNIT(GetUnit()), &Unit::SendChatMessage (uint8)CHAT_MSG_MONSTER_YELL, (uint32)LANG_UNIVERSAL, "Is there no one left to test me?", EVENT_UNIT_CHAT_MSG, 2000, 1, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT);
sEventMgr.AddEvent(TO_OBJECT(GetUnit()), &Object::PlaySoundToSet, (uint32)10293, EVENT_UNK, 2000, 1, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT);
GetUnit()->SetHealthPct(100);
}
ParentClass::OnCombatStop(pTarget);
}
So who wants to kill me right now? Well I know I would. So here is what we are doing here. When combat ends we are checking the bosses HP. If it is above 1% that means that the group wiped right? So what we do is we take control of the event manager. Which is one of the most power features the C++ library has.
Code:
sEventMgr.AddEvent(TO_UNIT(GetUnit()), &Unit::SendChatMessage (uint8)CHAT_MSG_MONSTER_YELL, (uint32)LANG_UNIVERSAL, "Is there no one left to test me?", EVENT_UNIT_CHAT_MSG, 2000, 1, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT);
sEventMgr.AddEvent(TO_OBJECT(GetUnit()), &Object::PlaySoundToSet, (uint32)10293, EVENT_UNK, 2000, 1, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT);
What we are doing is adding an event to the handler. Here is what it does in english terms. We are getting the unit (Randal) and he is sending a chat message in the yell format with a universal language with the message "Is there no one left to test me?". The 2000 means that we execute this 2 seconds after the CombatStop function is called and only once and we execute this outside of world context.
The second event we have basically assigns object from the Unit (Randal) and that object we are assigning is going to Play a sound which is an unsigned integer 32 bytes long, which is really useless to you if you do not care to know background information. But the ID of said Int is 10293. We do EVENT_UNK because there are no specific flags for the sound event. We also have a delay of 2 seconds after the CombatStop function is called so that it lines up with our send chat message event and once again we execute this only once and not within world context so it is destroyed afterward.
Code:
GetUnit()->SetHealthPct(100);
What this simply does is get the unit (Randal) and set his health percentage back to 100. Yes we have to tell C++ to do this because it won't always do it automatically.
Code:
}
ParentClass::OnCombatStop(pTarget);
}
The first bracket closes our if statement then the ParentClass is called which is basically calling the combatstop function from the library. Then the second bracket closes our entire function.
Code:
void OnDied(Unit* pKiller)
{
Emote("My lord will be the end of you...all..", Text_Yell, 10299);
ParentClass::OnDied(pKiller);
}
This is our OnDied function. All this does is say. Hey when the boss dies play that emote that we declared up top and then make sure to call the OnDied function from our Library.
PART VI - Let's Continue - The Meat
This is one of the most essential commands to any AI. This function is automatically called every second and this is where all of your phases, and anything else you want to happen will happen. Here is what we will use for this tutorial. Hold on to your hat as this is a biggie.
Code:
{
// Phase 2
if(GetHealthPercent() <= 95 && GetPhase() == 1)
{
SetPhase(2);
if (randalThunder != NULL)
{
randalThunder->mEnabled = false;
randalThunder->mChance = 15.0f;
}
if (randalDecay != NULL)
{
randalDecay->mChance = 11.0f;
}
if (randalBlood != NULL)
{
randalBlood->mChance = 12.0f;
randalBlood->mCooldown = 10;
}
}
// Phase 3
if(GetHealthPercent() <= 65 && GetPhase() == 2)
{
Emote("Your days are done!", Text_Yell, 10298);
SetPhase(3);
if (randalDecay != NULL)
randalDecay->mEnabled = false;
if (randalBlood != NULL)
randalBlood->mEnabled = false;
if (randalThunder != NULL)
randalThunder->mEnabled = true;
}
// Phase 4
if(GetHealthPercent() <= 40 && GetPhase() == 3)
{
Emote("You are nothing! I answer a higher call!", Text_Yell, 10295);
SetPhase(4);
}
ParentClass::AIUpdate();
}
HALESTORM I AM GOING TO KILL YOU! WHY DOES THIS SHIT SPEW OUT SO MUCH!! WHEN WILL IT END!!!!
Well little one. You are almost done actually. Once this part is complete you are pretty much done. But lets break this done shall we.
Code:
if(GetHealthPercent() <= 95 && GetPhase() == 1)
This basically returns the NPCs Health Pct and if it is less then or equal to 95 AND his current phase is equal to 1 then do the following.
- Set his phase to number 2
- Disable his thunderclap
- change the percentage of his Death and Decay
- etc. etc. etc.
Now remember this is all being done within the AiUpdate function and remember that his runs every second so you can pretty much follow the code now to see what we do in phase 3.
We then at the end do our standard parent class.
PART VII - The End Game - Your Done
Code:
protected:
SpellDesc * randalBlood;
SpellDesc * randalThunder;
SpellDesc * randalDecay;
};
This is our segment of code that protects our declarations so that cannot be called from other functions.
Code:
void SetupStratInstance(ScriptMgr * mgr)
{
mgr->register_creature_script(COT_CAPTAIN_RANDAL, &RandalBossAI::Create);
}
This is an entirely new function that creates our AI. It is run once the creature is loaded and it becomes unique to that creature.
DISCALIMER: ArcScripts had undergone some changes since I last created this guide. This guide should still be a valid resource for you to learn some basic C++ scripting. One big change may be the name of the actual library. I know one time it was once called.
Code:
ARCSCRIPT_FACTORY_FUNCTION(<Name Your AI>, ArcScriptBossAI);
However upon inspection of the existing scripts it looks they have all been renamed to what I have above. So keep your eyes on the SVN log. This is not an easy guide to follow nor is it easy to learn C++. I will not make any promises to you by following this guide. I do not go over syntax in this guide or methods, arrays, tables, enumeration, etc. It is unnecessary for a basic guide. Follow this an learn from it.[/COLOR]
Here is the full completed code. However in this code it is not renamed to MOONSCRIPT it remains as ARCSCRIPT
http://halestorm.pastebin.com/wWTzxFpp