MHServerEmu Progress Report - July 2024

We have a lot of exciting updates to share. It has been one year since this project started, and this month combat has returned to Marvel Heroes, which I would dare to call our biggest leap forward since we first got in-game in July 2023.

You Got the Power

We have laid the foundation for one of the most important and complex systems of Marvel Heroes - powers. From a certain perspective, it would not be an exaggeration to call it the most complex system in the entire game because of its deep interconnection with other systems and just the sheer amount of stuff it involves. Covering all of it is outside the scope of a single report, but I will give a brief overview of the work that has been done so far and what is left to do.

One important thing to note is that prior to this month we barely had any power-related functionality implemented server-side. The fact that you could “use” a lot of powers before was just the magic of client-side prediction, and the server was almost completely unaware of what was actually happening.

So where do we even begin? First of all, there are many different kinds of powers. Powers are classified by categories: NormalPower, ComboEffect, EmotePower, GameFunctionPower, HiddenPassivePower, HotspotEffect, ItemPower, MissileEffect, ProcEffect, ThrowablePower, and ThrowableCancelPower. Powers can have various targeting shapes: ArcArea, BeamSweep, CapsuleArea, CircleArea, RingArea, Self, TeamUp, SingleTarget, SingleTargetOwner, SingleTargetRandom, SkillShot, SkillShotAlongGround, or Wedge. A power’s targeting style can have various flags applied to it, such as AOESelfCentered, NeedsTarget, TurnsToFaceTarget, AlwaysTargetMousePos, and more. This list can continue on and on, but the point is that there is a lot of factors to consider here, which is why our reimplementation of the Power class is already at over 6000 lines of code, and it continues to grow.

But all of that is just static data defined by game designers, and powers are dynamic beasts that live in runtime. Before a power can be used it needs to be assigned to a world entity’s PowerCollection. The assignment process includes the creation of an instance of the Power class that is in many ways not unlike an entity: it has its own PropertyCollection, as well as various other pieces of state. When a power is assigned it gets passed a set of information used for scaling referred to as power index properties: power rank, character level, combat level, item level, and item variation. This is one of the places where we can see that BUE was not as thorough as it may appear: although the update “removed” power ranks, they still exist internally, with most powers instead being set to rank 0 or 1 and rebalanced accordingly.

With the power assigned and initialized it can now be used. Every time you use a power it goes through a pipeline consisting of four main stages:

  • Activation: this is when the game checks if you can use the power (you have enough resources, your target is valid if the power needs one, etc.), starts the animation, and schedules a power application for when the contact frame is supposed to happen. Although the server does not actually play animations, it still needs to know how long they are, taking into account variables such as attack speed modifiers. In many cases it is possible to cancel activation before the animation reaches its contact frame.

  • Power Application: at the contact frame of the animation we reach the point of no return, which is when the power can actually begin doing something. At this moment the properties of the power and its owner are snapshotted and recorded into an instance of the PowerPayload class. Depending on the power, this payload may either get processed as soon as it is created, or it can be scheduled to be processed later. At the time of writing we do not have delayed processing implemented yet, which is why, for example, throwable objects deal damage instantly rather than when they actually reach their target destination. The correct way of doing this involves calculating the time the throwable object is going to spend in the air and delaying the processing of the payload by that time.

  • Payload Processing: this is where the fun begins. The snapshot of the power and its owner recorded in the payload is processed, and the results are calculated, which include damage, healing, and conditions (buffs and/or debuffs) that need to be applied to the target. Depending on the calculations, various result flags may be set, such as critical strike and dodge.

  • Application of Results: the results of the payload calculations are applied to the target. This involves adjusting the health of the target, potentially killing it, and applying conditions to it. Clients also get sent a copy of the results to display damage numbers and additional hit visual effects.

At various points in this process additional power events may get triggered. In total there are 30 event types that can trigger 36 types of actions. Some of these actions are very specific, like BodySlide, SwitchAvatar, and PetItemDonate, but others are more general purpose, such as UsePower, EndPower, and CooldownStart. One of the most common use cases for power events is the activation of combo powers: when a power needs to do more than one thing, like moving your character and dealing AoE damage around your destination, this is usually achieved by having two different powers, with one triggering the other as a combo.

While all kinds of entities can use powers, avatars are a very special case, because they are controlled by players connected remotely. One example of special treatment in this regard is the concept of continuous powers. You may recall how in our earlier iteration of “combat” that you can still see in stable builds most powers would deal damage only on the initial activation, with no effect if you held the button down or pressed it frequently enough. The reason for this is that the game classifies a lot of powers as continuous based on various criteria, such as animation length, category, override flags, and more. If a power is continuous, activations after the initial one are not communicated between the client and server: the server just assumes the client is still holding the button down and continuously reactivates it until it receives a cancellation message. This requires the server to be able to run the entire power activation process on its own in parallel to the client, which was not possible previously with hacks, but can now be done.

Another example of avatar-specific behavior is the priority queue system. The basic idea of it is that if a player input cannot be acted on immediately because another power is already being used, this input is put into a very small queue with a size of one or two commands. If there is a new command that is different from the rest of commands that have been issued recently, such as when you try to activate a defensive power inbetween spamming regular attacks, this different command takes priority so it doesn’t get lost. This system works almost exactly the same as, for example, Diablo III, and if you are interested in this topic I strongly recommend watching the talk Through the Grinder: Refining Diablo III’s Game Systems by Wyatt Cheng. Not only will you learn interesting stuff, you will also discover Wyatt’s origin story before he became infamously known as the “do you guys not have phones” guy. Currently we do not have command queueing implemented server side, which is why some of your inputs may occasionally get lost in the heat of battle, especially with higher latency.

So far we have implemented a lot of the foundation, which allows most powers that deal direct damage to enemies to function, but there is still a lot of work left to do, including:

  • Various additional aspects of payload processing, including taking the target’s defensive properties into account, paying resource costs, and more. The current implementation is a very simplified one based on formulas the client uses to calculate tooltip damage.

  • Hidden damage scaling mechanics: dynamic combat level (DCL) and scaling player damage output based on the total number of players in proximity.

  • Implementations for the rest of power event triggers and actions.

  • Avatar power activation priority queue.

  • Condition (buff and debuff) application and removal, including those that deal damage over time (DoTs).

  • Hotspot powers. Hotspots are zones that apply certain effects when entities stand in them, such as fire that deals damage when you stand in it.

  • Procs.

  • Summon powers.

For now though we have enough of this system working to have the game feel like a real game again, especially when combined with the AI implementation by AlexBond.

Loot Tables for Dummies

While it is nice to be able to smash things and beat bad guys, the core thing that makes you do it again and again in games like these is the loot. Although we did have a placeholder system put in place last month, I felt this aspect started falling behind, especially with how authentic the rest of the game is now looking.

We are extremely lucky to have all the original loot tables in the client. The underlying loot system is very complex, but this month I have made significant progress in untangling it. Although items still lack affixes and stats, their rarities and base types should now be accurate, making the lootsplosions from defeating bosses way more satisfying. So how does it all work?

Although they are called loot “tables”, they are actually tree structures consisting of LootNodePrototype instances. Each node can be either a LootTablePrototype or a LootDropPrototype. Here is what a typical “loot table” looks like:

Loot Tables

When you want to use a loot table, you recursively iterate through the nodes of this tree, with each “table” representing a branch and “drops” representing end points that get recorded (more on that later). Visiting a node is referred to as selecting it, and activating the node is called rolling.

When you select a loot table node, it gets rolled one or more times, with each roll determining branches to go down to. When rolling loot tables the game uses the concept of nodrop similar to Diablo II: rather than specifying the drop chance of something, instead each table node has a nodrop chance defined that determines whether or not the table will be skipped entirely. So a 1% drop chance is represented as a 99% nodrop chance. If the roll passes this nodrop chance check, branches of the table are picked using one of three methods:

  • PickWeight: picks one of the branch nodes randomly based on their defined weight.

  • PickWeightTryAll: excludes branch nodes randomly based on their weight until only one remains, which gets picked.

  • PickAll: picks all branch nodes.

Picked nodes are selected and rolled, and this process continues recursively until it reaches a loot drop node.

In total there are 17 different types of loot drop nodes representing various things that can “drop”. The most obvious and widely-used one is LootDropItemPrototype that represents an item dropping. There is also LootDropAgentPrototype used for spawning entities, like health and experience orbs, that are actually implemented as AI-controlled agent entities rather than items. But just like it was with inventories, the definition of a “drop” is quite broad here: there are drop nodes such as LootDropPlayVisualEffectPrototype, LootDropChatMessagePrototype, and LootDropBannerMessagePrototype that are more like actions triggered by rolling rather than representations of physical things that you can pick up.

As you navigate a loot tree and reach various drop nodes, you need to record your journey to make use of it later. For this the game uses an abstract IItemResolver interface class the gets implemented by various systems that need to interact with loot tables. Successfully rolled drop nodes are “pushed” onto the resolver, and the accumulated data from the resolver is then used to spawn rolled items in the world, add them to an inventory, or just output the results as text for testing.

One last essential piece of the loot table puzzle are loot roll modifiers. When you interact with a loot table tree you also pass to it an instance of LootRollSettings that contains the context, from obvious things, such as level and difficulty tier, to more esoteric factors, such as the current day of the week. Each node in the tree can then have modifiers that process and/or override these settings. One of the simplest examples would be filtering drops based on level range. But you can also, for instance, override the current avatar and always drop items for somebody else. Or you could have the rare item find stat apply to an arbitrary selection of items. When modifiers are applied to a drop node they affect only that drop node, but when they are applied to a table node, the affect all branches of that table. This is how, for example, eternity splinters drops are controlled: splinters have their own branches in common loot tables with applied modifiers.

And with all of that we have basic loot table rolling working. We still have work to do, including implementing all the special drop types, additional modifiers, and some more drop processing, such as taking item quality bonuses into account and applying affixes to items, but the foundation is now in place. If only we could have our loot persist when we transfer between regions and log out…

The Barrel Made Me Do It

AlexBond is back yet again this month to talk about AI profile overriding.


Hey everyone, this is AlexBond. Let the combat begin! In this report I would like to share how I enabled AI and brought all the enemies in the game to life.

This month there were a lot of major updates: enemies and bosses now attack and deal damage, and the game is really starting to come to live. But let’s talk about how it all works.

Enemy movement uses the AIController class that I already covered in previous reports. This time I would I would like to talk about AIOverride and how it is used.

AI Override

An agent’s prototype generally specifies its AI profile that defines its behavior. All attack-based AI profiles (ProceduralProfileWithAttackPrototype) function in two modes:

  • DefaultSensory - scanning mode (based on BehaviorSensorySystem)

  • HandleContext - action mode

Scanning involves searching for a target. If no target is found using SelectTargetEntity(), the AI profile is overriden with a NoTargetOverrideProfile. Usually it is ProceduralProfileDefaultActiveOverridePrototype that makes the agent wander around (WanderInPlace) or return to its spawn location (Wander). While doing so it continues scanning its surroundings, and if it finds an enemy, it switches to the attack profile.

But it gets more complicated. Enemies created via a PopulationObject contain an EntitySelector, which has another AI override called DefaultBrainOnSimulated that uses ProceduralProfileSenseOnly

The basic idea of this profile is that instead of wandering or attacking, it simply plays an idle animation (such as NPCs talking to each other), and when a player enters its scanning radius, it switches to one of the talking profiles. Generally it picks a random profile from the AIOverrides list:

AI 1

All of these switches are controlled by the ProcessEntityAction() function.

So when you see an NPC talking or calling you, this happens via complex AI profile switching.

AI 2

This is how previously silent NPCs learned how to speak.

AI 3

As for combat, Crypto did a lot of work here by implementing the power system. I assisted with implementing MissilePower, since it involves AI, and I am quite familiar with this topic.

This game has so many AI profiles, and it is hard to see them at first glance. For example, drops lying on the ground, such as orbs, are not pulled by some kind of magnetic force. Instead, they all have follow target AI profiles, with your avatar being that target. Every moving entity in the game is “alive”. This includes projectiles, such as Captain America’s shield, Thor’s hammer, or Iron Man’s micro-missiles - all of them are moved using AI profiles. But it gets more interesting!

You may remember bosses or other powerful enemies picking objects up and throwing them at you. You think this is because the boss’s AI is so smart? Wrong answer! Actually, throwable objects themselves have brains!

AI 4

Yes, I am not joking, every single one of Mr. Hyde’s barrels has its own brain!

AI 5

Every 10 seconds the barrel, just like Emma Frost, attempts to take control of its owner, Mr. Hyde, and override his AI behavior profile. And the boss takes orders from this barrel, picks it up, and throws it at the avatar. Entities that act like this have their own class - ThrowableSmartProp.

There are not too many of them in the game, and they still do not function completely right, but now you know that when the Hulk boss in Holo-Sim picks up a car, he was forced to do so by the car itself!

Overriding AI profiles is a complicated process, and I am still working on it. For instance, timing the activation of the appropriate AI profile when an entity enters the world and/or starts being simulated still has a lot issues, which is why some of the idle animations got replaced with wandering. In the future we will come back and fix these issues. For now I am working on missions, which also involves some AI overriding and idle animation playaback via MissionActionEntityPerformPower(), but this is a story for another report. That’s it for now, have fun playing, and until the next report!


Back to work now. See you in August!