Okay, so we have our character class info data asset.
The question is where do we store this?
Now we can have one on every character, but that seems a little bit redundant.
Every character doesn't need the data asset, right?
We can store it in one central location.
Now the game mode is a possibility as the game mode is where the rules of the game are determined.
And in a sense, this data asset contains a lot of rules, such as what attribute values enemies should
have based on their level.
So we could store a data asset on the game mode, and that way it's only on a single class.
We're only taking up memory for a single one of these data assets.
So what I'd like to do is store the data asset on the game mode and be able to set this from Blueprint.
So we're going to forward declare this data asset type.
It's a you character class info and we're going to have a variable of that type.
This can be a T object pointer of type U character class info called character class info.
And this can be a u property.
We can set its value with edit defaults only we can give it a category.
And this will be character class defaults.
And if we want to access it from outside of the class, we can make it public.
So now our game mode will have this.
And the question is, how are we going to access it and apply this information to our enemies?
Well, we have a blueprint library, which is a pretty good spot for these types of functions, ways
to apply the default information.
If I go to my ability system folder and open aura ability system library, we can get the H file and
the CPP file open.
We can make a function for this.
So I'm going to make one.
It's going to be a void function.
I'll go ahead and make it static as well.
And this is going to be a function that will initialize default attributes based on a character class
and the level.
So we're going to call this initialize default attributes.
It's going to take in an E character class called character class, and we'll say a float called level.
Now, character class is not really defined here, right?
This is not a defined enum that's defined in character class info.
So we can just include character class info in here.
That's in data.
So we're going to say right here, let's include include data slash character class info.
Now this can be a blueprint callable function.
It's in a blueprint library after all.
So I'm going to make it blueprint callable and it's going to be in category or a ability system, library
slash character class defaults.
So let's make a function definition.
Now, what this needs to do is it needs to get the data asset, the character class info, data asset,
and that exists on the game mode.
So I'm going to use you gameplay statics to get the game mode.
So we're going to call get game mode.
Now this requires a world context object.
So that means to initialize default attributes.
We need a world context object.
So let's add that to the function that could be a new object.
And notice our other functions have this input.
We can just copy that and we can paste it here in our new function.
So it's going to take a world context object as the first input now, and that's what we're going to
pass in to get game mode.
Now get game mode returns a game mode base.
We're going to cast this to a game mode base.
So let's cast.
To a ora game mode base.
And only if this cast succeeds.
Should this function continue.
So we'll say a ora game mode.
Base.
We'll call it ora game mode.
We can even call it ora GM if we wanted to.
And we'll just say if ora game mode.
Is null.
Then we'll return.
So once we have the game mode, we can access that data asset character class info.
We can say you character class info.
Class info equals or a game mode character class info.
And now that we have class info, we can access data based on the character class because character
class info has a lookup function.
Get class default info that returns this struct f character class default info.
So here in our ability system library we can create a character class default info called class.
Default info and we can take our class info data asset and we can call get class default info passing
in character class.
Now, there's really no need to have a local variable called class info.
We could just take or a game mode right here and use that so we can say or a game mode character class
info, get class default info and we can remove this local variable here.
Either way we're getting the class default info.
Now we're going to need to apply some gameplay effects that exist in this struct.
And what are we applying those effects to?
Well, we need an ability system component to apply to.
So we're going to need to pass in an ability system component to this function as well.
So let's add that as the last input parameter.
So this will be a new ability system component pointer and we'll go ahead and add a forward declaration
for that type.
And this will just be called ASC.
So we'll add an ASC input parameter here in the CPP file as well after level and that's how we're going
to apply the gameplay effect.
So we need to apply a gameplay effect and we know how to do that with an ability system component,
don't we?
We can apply a gameplay effect spec.
If we take our ASC, we can call apply gameplay effect spec to self, apply gameplay effect spec to
self.
Now I always like to start with this function.
Call and work backwards because the input parameters always remind me of what I need.
This function needs a gameplay effect spec and I know that I can make that using my ability system component
using ASC I can call what's the function name?
Make outgoing spec and make outgoing spec requires a subclass of that's a gameplay effect class and
a level and an effect context handle.
Now we'll worry about that effect class in a second.
Let's make that context handle.
How do I do that?
I use the ability system component and call make effect context.
So pretty simple so I can pass that in.
So really, I don't need to call it here.
I can just call it directly in the function call here.
But first I need a gameplay effect, so I'm going to go ahead and just remove that line.
And what's the gameplay effect?
Well, we have class default info.
If I just type class default info.
This is that structure and in the struct we have a primary attributes to subclass of gameplay effect
so we can apply that.
Right.
So if I hit enter then I have access to this subclass of I'm going to go ahead and control X and control
V to place it right into the function call.
And I can also pass in a level.
Now this function takes a level as an input.
I can pass that in and then we need our gameplay effect context handle.
I can simply call ASC, make effect context and this returns me an gameplay effect spec handle so I
can make an f gameplay effect spec handle called and I'll call this primary attributes.
Spec handle.
And we'll pass this in to apply gameplay effects back to self and of course apply gameplay effects.
Back to self requires an effect spec, not a spec handle.
So we take the handle, we get the data we call, get on that and dereference it with the star and we've
got it.
And of course this could be a const spec handle so we can make it a const spec handle.
Okay.
So now we have a function that can take a context object, a character class enum constant, a level
and an ability system component, and it'll get the data asset from the game mode called class default
info.
And from that get the primary attributes.
Now, after we've applied the gameplay effect for primary attributes, we can apply the secondary and
the vital gameplay effects as well.
Now that's going to be relatively easy.
We're just going to do the same thing again.
We're going to make a new spec handle and apply that only this one will call this secondary attribute
spec handle.
We'll call make outgoing spec, but instead of the primary attributes class, we're going to get the
secondary and that's actually not even in the class default info struct that's going to be in effect
that we have.
That's common among all enemies.
If we go back to character class info, it's not in our map of structs, it's just on the data asset
itself.
It's called secondary attributes.
So we can access that directly.
And in this case, we're going to either take the game mode and get the class info and access that class
right there, or we can go back to having a local variable of type U character class info pointer.
We can call this, I'll just call it character class info and that's going to be from our game mode
character class info, and then we can use that right here instead of going through the game mode.
And we can use that here in our make outgoing spec character class info, accessing secondary attributes
and we can pass in the level and when we apply the effect spec, we have to use the secondary attribute
spec handle instead of the primary and we'll do the same thing for Vital.
So we're going to copy these two lines, paste them here and we'll call this one Vital attributes spec
handle.
And this will be making an outgoing spec from character class info vital attributes.
And then when we apply it, we're going to apply vital instead of secondary.
And now we're applying all three gameplay effects with our initialized default attributes.
So all of this requires that the game mode have the data asset on it.
Otherwise we'll be attempting to access a null pointer and we'll get a crash here.
So if we get a crash on the lines in this function, particularly line 50, we know it's because character
class info is null.
So once we have that set on the game mode, we can actually call initialize default attributes to initialize
the attributes of our enemies provided that our enemies have a valid level and a character class.
Now we haven't added a character class to the enemy.
Why don't we go ahead and do that so we can go into character and enemy.
This should be in the public folder.
So public character or an enemy and we can add a character class.
Now if we're going to use the character class enum, we should include the header here.
Include and that's going to be in ability system slash data slash character class info dot h and we'll
put this down here by the level and then this should be edit anywhere blueprint read only category character
class default.
So we'll go ahead and just copy level and instead of level we're going to have an E character class
called character class.
We can give it a default value of E character class and we can choose one, say warrior, but this will
be editable from the blueprint.
And now that we have a character class, we can initialize our default attributes.
So let's do that.
We'll go to Aura Enemy CP and we can do this after our ability actor info has been initialized because
at that point we know that our ability system component has had its Avatar actor set.
And in fact what we're doing is calling initialize default attributes here, which is defined on the
base class.
So we can actually override this by making it a virtual function.
Let's go to or character base and see what that function looks like.
Here it is.
Initialize default attributes.
We can go ahead and make this a virtual function and then we can override it in aura enemy.
So that's a protected function.
So here in Aura Enemy in the protected section, we'll go ahead and override.
Initialize default attributes and we'll generate the definition.
And instead of calling super, we're going to implement this a little differently.
We're going to use the aura ability system library.
So your aura ability system, library and using that, we have to have the header included and we're
going to call initialize default attributes.
And this needs a world context object.
We'll pass in this.
It needs a character class.
We'll pass in character class.
It needs a level we'll pass in level and it needs an ability system component.
We'll pass in ability system component.
So now we're initializing the default attributes using our new function that we added to our ability
system library that gets the data asset stored on the game mode, gets the class information based on
our character's class, and applies a gameplay effect at the level passed in and we're passing in our
character's level here on the enemy class.
So now we should see our enemies come in with the correct stats based on their class and their level.
So why don't we go ahead and run this in debug mode?
We've done quite a few changes here, so let's first make sure this compiles and we can launch.
So with a successful compile, we now need to set the data asset on our game mode.
Okay, I'm going to go ahead and click open.
Looks like we have quite a few assets open.
I think I can close out of my curve tables, I can close out of my gameplay effects and I can close
out of my character.
Class info.
Let's go into blueprints game or a game mode and here's the character class info.
We can set the data asset, character class info, compile and save.
And now we should see that our characters will be initialized with their attributes if we press play.
Oh, and we have a exception here in Maxhealth.
Interesting.
And it looks like we're making the same mistake that we have made before, which is we're applying gameplay
effects without setting the source object.
Because as soon as a gameplay effect uses this mod mag, calc, this MSI, it's going to get the combat
interface from the source object.
And that's okay.
It's easy to forget these little details.
So what I can do is I can hit stop and we can make sure that we're adding a source object from which
to get that combat interface.
Now we can do that here before we apply these gameplay effects and we can get that source object from
the ability system component because the ability system component has the avatar.
So we're going to say a actor avatar actor equals and we'll get the ASC and call get Avatar actor.
That can be the source for these gameplay effects.
So how do we set the source object?
Well, we set that on the effect context, don't we?
So we're going to take the ASC effect context and actually make a local variable now.
So I'm going to say make effect context, which returns a gameplay effect context handle.
So we'll make one.
F gameplay effect.
Context.
Handle and we'll call this one primary attributes context handle.
And after making it, we take it primary attributes, context, handle and call add source object.
And we're going to pass in Avatar actor for that.
Now we're going to use this as the context handle instead of just calling make.
Effect context right here.
And we're going to do the same thing.
Making a new context handle and setting the source object for the secondary.
Effect.
So we're going to call this one secondary.
Attributes context handle.
We're going to add the source object to that and use that as the context handle.
And we'll do the same thing for the vital attributes.
So once again, making an effect context.
This one will be called vital attributes context handle, adding the source, object to it and then
using it here in make outgoing spec.
And now we have source objects for all of these effects.
So once again, let's compile and launch and run a debug mode here.
Okay, so we're back open.
I'm going to get that game mode back open, make sure it has character class info, which it does,
and we'll go ahead and press play.
All right.
So we have our game working here.
And of course we can cause damage to the enemies, but it's a little bit hard to tell whether or not
these enemies have their default attributes set to the correct values.
And that's because, well, we don't really have a way to see those values unless we just print a bunch
of debug messages.
So that makes it a little hard.
But of course we have the ability to run in debug mode.
So we could place a breakpoint here for example, and just play the game and we can see what the world
context object is.
Looks like it's the goblin slingshot.
We can check out the value for the character class while it's warrior because we haven't set that to
Ranger, but we can always check out that spec handle for the primary attribute spec handle.
We can check that out.
We can look at its data and see that its definition here is primary attributes warrior.
We can look at the modifiers and we can see that here's the strength modifier.
We can see the modifier op, the modifier magnitude calculation type.
Here we can see the scalable float magnitude.
We can see that its value is one, but that's because we're scaling that value by the curve and we can
see the curve table, we can see that we're indexing the strength row name from the table and so on.
So we can see that we're actually accessing all the correct data here.
And if we wanted to go out of our way, we could print some debug messages and see those values.
But for now I trust it.
So I know that we're actually initializing our enemy values and we can go to our slingshot enemies here
and select them and take their details, panels and search for class defaults.
And we can see that here's their level and here's their character class and we can change that.
I'm going to change these guys to Ranger.
So both of these will be Rangers at level one and they'll be initialized with the Ranger gameplay effect
and we can at least make sure that's happening.
We can have that breakpoint here and we can make sure the character class is Ranger, which it is.
Of course, we have three enemies being initialized here, so this could have easily been the warrior
and I could just click resume and then we'd get this for the next enemy being initialized.
But this is one of the slingshot enemies, and I can see that the primary attribute spec handle has
a valid value.
It has data, we can expand def, we can look at modifiers and we see that we're modifying the four
primary attributes.
We have modifier magnitude with scalable float using the primary attributes Ranger So it's using the
correct curve table.
And that was all I wanted to see.
Perfect.
All right.
So now that we're initializing attributes for the enemies, we can start worrying about causing damage
to the enemies.
And that's what we'll be concerned with in the videos to come.
Great job and I'll see you soon.