Able Ability System Info and Support Thread

Can you talk me through the multiplayer control flow of abilities that have branching?

For branch conditionals like UAblBranchConditionOnInput that rely on an input component that only exists on the auth client, how are abilities with input driven branching meant to be kept in sync on the client and server in multiplayer?

If you start ability A, and it branches to ability B, the server isn’t going to be able to properly evaluate that branch if it uses input conditionals, so the ability is going to get out of wack. Is the intention that in these circumstances the auth client will act as authority and ‘correct’ the server when it decides to branch?

For input conditionals, yes. You have to, for the reason you suspect.

Effectively:

Client tests for Input Condition and passes -> Calls ServerBranchAbility -> Server sees the Client wants to branch to that Ability and says “sure” -> Server tells the Client to branch (Client already is branching locally at this point so this is more for remote clients).

So the issue I’m running into is that I have a branch conditional that branches to an ability if an input is NOT held, and so the server is not able to check that properly, so it doesn’t hold in the first ability state as I am expecting. Seems to me that if a branch has ANY conditionals that rely on input(or are auth client reliant), the server should basically ignore that branch task altogether. Sorta like the conditionals themselves need to have a EAblAbilityTaskRealm, and if UAblBranchTask::CheckBranchCondition comes across a condition that it can’t evaluate due to the EAblAbilityTaskRealm mismatch, it should abort the function and return false, with the expectation that the auth client is going to take full responsibility for that branch.

What do you think about adding a simpler thing, such as

EAblAbilityTaskRealm m_ConditionsRealm;

to UAblBranchTask so that there is a mechanism to assign sole responsibility for evaluating a branch. It could default to ClientAndServer so as not to change existing behavior, but it would allow a user to assign sole branching responsibility to one side of the network. In this case knowing there are input components to the conditional logic, I could set it to client. The list of alternatives seem far less desirable, such as networking button state, or building in netmode logic into all the blueprint branch conditionals.

Actually, rather than a conditions realm, it should probably be a bool for only evaluating the conditions if the owner IsLocallyControlled?

Conditions should be shared between Client and Server for security reasons, Input is the one outlier. It’s such a specific case that I would just inherit from the base Conditional class and create your own variation. That’s the simplest solution (and it’s safe for future integrations - just make sure you setup a vendor folder or what not and integrate your changes over when a new version of Able comes out).

That’s effectively what the Input Conditional checks. Again, Input is a very specific case and it doesn’t really apply to all the other conditionals (for example, checking some stat or looking for another running Ability). I would just code a very specific behavior for Input and leave the API the same.

@ Just checking in, it’s weird that the UE4.23 Able isn’t available in the marketplace yet. You mentioned that you submitted the update a week ago. Could something have gone wrong where it didn’t actually get submitted?

It came back late last week with an error, I resubmitted that evening and haven’t heard anything since (says it’s still processing). Generally they tend to be pretty backed up since EVERYONE has to update their plugins. I know as much as you do at this point. I’ll poke some people if I don’t hear anything in a few days.

I’m running into an issue with an ability that references a custom task. I can select the custom task to add it to the timeline, and it compiles and saves without any apparent issue, but next time I open the editor, if I try to save that ability again, I get “check” assets in engine code for that referenced task

void UBlueprintGeneratedClass::InitPropertiesFromCustomList(uint8* DataPtr, const uint8* DefaultDataPtr)
{
FScopeLock SerializeAndPostLoadLock(&SerializeAndPostLoadCritical);
check(bCustomPropertyListForPostConstructionInitialized); // Something went wrong, probably a race condition

Also, perhaps related, but the Task name in the timeline changes to have a REINST_ prefix.

Anyone else see this sort of issue?

Yea, you don’t want REINST_. This is fixed in the latest version (which still hasn’t gone out - I’m writing up an email to marketplace support right now).

It didn’t have the REINST_ when I first selected it. Only after saving and reloading did it start asserting. Does the fix address that? Any idea how long it will take to go live?

Also, I’m running into a more serious crash for some reason.

UObject* UClass::CreateDefaultObject()
{
if ( ClassDefaultObject == NULL )
{
ensureMsgf(!HasAnyClassFlags(CLASS_LayoutChanging), TEXT(“Class named %s creating its CDO while changing its layout”), *GetName());

I have a Punch_Right_Into ability that seems to be compiling in the blueprint but one of the exports references itself, so when it attempts to create the default class for it, this ensure fails and crashes the game. Not sure what is causing this self reference.

callstack.txt (7.49 KB)

Possibly. The REINST means the blueprint was reinstanced under it. I wouldn’t think that would cause your issue, but I honestly don’t know. Marketplace support just got back to me and said they are experiencing longer than normal delays but the update was received and is working its way through the system.

For the CDO crash, here’s where it looks like it’s self referencing. In FLinkerLoad::FindExistingExport

The lines
TheClass->GetDefaultObject(); // build the CDO if it isn’t already built
Export.Object = StaticFindObject(TheClass, OuterObject, *Export.ObjectName.ToString(), 1);

It’s blowing up on that GetDefaultObject() [TABLE]

:arrow_forward:
this
0x000002cfe9de5600 {LoadFlags=0 bHaveImportsBeenVerified=false bDynamicClassLinker=false …}
FLinkerLoad *

:arrow_forward:
TheClass
0x000002cdf2696b00 (Name=0x000002cdaafd9114 “Punch_Right_Into_C”)
UClass * {UE4Editor-AbleCore-Win64-DebugGame.dll!UAblAbilityBlueprintGeneratedClass}

:arrow_forward:
OuterObject
0x000002cdf26dc480 (Name=0x000002cdc9cf91dc “/Game/Timeless/Spells/PlayerAbilities/Punch_Right_Into”)
UObject * {UPackage}

:arrow_forward:
Export.ObjectName
0x000002cdcb8b8c04 “Default__Punch_Right_Into_C”
FName

Any idea what might cause this?

This may be unrelated, but is there a possibility that if you duplicate an ability, that it could be broken? I want to say the abilities I am having these issues with are ones that I had duplicated from another similar ability to avoid recreating them from scratch.

The Default Object is created when you load that object (normally you can call GetDefaultObject and it will force a load). If it’s a reinstance of a class, then it may not have a default object yet (or it’s still compiling it). There’s a lot of reasons why it may not have a CDO yet (SKEL’s don’t get CDOs at all for example).

Blueprints work well, but they have lots of gotchas. I find duplicating them to be a dubious task at best. If you find yourself doing a lot of duplication, you may want to try and take one layer of complexity out by putting that into a custom C++ task with whatever values you are just tweaking exposed.

I re-created the problematic abilities from scratch and so far I’m not getting the issue described. I’m thinking there is definitely an issue with duplicating abilities.

Can you explain the logic of UAblCancelAbilityTask::ShouldCancelAbility ?

Specifically, why does the function have that first early return true?

if (*m_Ability && m_Ability->GetDefaultObject<UAblAbility>()->GetAbilityNameHash() == Ability.GetAbilityNameHash())

I was trying to make a stackable DOT fire effect with a task that cancelled itself if the conditions of the ShouldCancelAbilityBP were met, so it can manage its own decay and take advantage of the RemoveOneStack functionality already implemented here, but this early out was preventing my function from being called. Curious why this early out exists?

I believe the case was to prevent an Ability from cancelling itself mid update… that may no longer be a problem with the delayed cancelling of abilities that went in a few updates ago. I’ll have to poke at it.

Fyi, there’s a crash in HandlePendingCancels that I fixed as follows.

Didn’t make sense to me at first until I read a few posts about it, such as https://answers.unrealengine.com/que…ml?sort=oldest

Basically the CheckAddress call in the Remove function verifies that you aren’t passing a reference that is within the array, because it can get corrupted during removal. The fix is just to grab a local reference to the object being removed and pass that in.

Yea, that theoretically is the same. Thanks for the fix, I’ll toss it in the next update - if only to be a bit more explicit.