Restoring Events in MHServerEmu 1.0.1

The recently released version 1.0.1 introduced native support for various weekly and seasonal events to MHServerEmu. There are some fun aspects of this system that I would like to talk about, so let’s dive in.

The Problem

Perhaps the most frequent question I have been hearing since the release of 1.0.0 is “How do I enable event X?”. This makes a lot of sense: events were a huge part of the game’s original run, with something different happening practically every week. One could easily assume that turning an event on is as simple as flipping a switch, but, unfortunately, it is somewhat more complicated than that.

There is no formal “Weekly/Seasonal Event” system in Marvel Heroes that would handle things like Cosmic Chaos and Operation Omega. When these events were first introduced, Gazillion were releasing game updates almost every week, and these patches would often make changes to game data to “enable” or “disable” particular events. Later in the game’s lifespan the developers began gradually transitioning towards making event data toggleable via the Live Tuning system. This is pure speculation on my part, but I suspect this might had been at least partially motivated by the console port: game patches on consoles generally require passing a certification process, which makes patching just to toggle an event a less viable approach. Despite this gradual shift, some aspects of some events still required patching all the way to the game’s shutdown, and there were still no formal events defined in game data.

Event-specific game data changes were made and toggled in a rather inconsistent manner, which was further complicated by the fact that there were multiple iterations of some events, and several events were implemented by overwriting existing files for past events. Therefore, restoring each event required carefully analyzing many game versions, cross-referencing them with archived patch notes, and figuring out the best way to make adjustments to the final shipped game build to replicate legacy events.

With loot tables there were generally two approaches that were taken by Gazillion. One was to add event loot tables across the game with the LiveTuningDefaultEnabled flag set to False, requiring them to be toggled on via the Live Tuning system. Another was to add a loot table to the SpecialEventsTable, which is the loot table used to make something drop “everywhere”, often paired with a cooldown that works similarly to Eternity Splinters. In some cases these two approaches were combined: for example, CosmicWorldstonesTable used in the Cosmic Chaos event was permanently added to the SpecialEventsTable while also being disabled by default. Loot tables are not categorized by event in game data, and different tables for the same event can be both disabled and enabled by default. An example of this is the A.R.M.O.R. Incursion event: the SharedQuestARMORDrives table that rolls event currency for completing Shared Quests is enabled by default, while all other tables for this event are disabled.

Missions are another important aspect of some events. As I discovered, many event missions are enabled by default and need to be disabled with Live Tuning. Not doing this created a state of superposition that you could observe in 1.0.0 and before, where parts of different events were all active all the time, like the daily Holo-Sim mission that is supposed to be available only during Operation Omega, and the Cosmo pet mission that should be limited to the anniversary celebration in June. Some missions, such as daily missions for the Mardi Gras event, were “hard” disabled by setting the DesignState field in their prototypes to NotInGame. Unfortunately, these missions cannot be properly enabled back without a client-side patch.

One particularly problematic event mission turned out to be A.R.M.O.R. Defender: it needs to be completed four times across different periods of the A.R.M.O.R. Incursion event to unlock the A.R.M.O.R. Defense achievement and its exclusive reward, Howard the Duck team-up. The issue was that just turning the mission off and back on does not reset its completion state, making the achievement unobtainable. I had to do a thorough investigation of all game versions that were released between July 2015 and January 2016 to figure out exactly how it was handled. What I uncovered was a string of classic Gazillion mess-ups:

  • July 24: A.R.M.O.R. Defender is added as a weekly mission, making it potentially repeatable within the same event period.

  • September 3: The mission is disabled for undisclosed reasons.

  • November 5: A.R.M.O.R. Defender becomes a regular one-time mission.

  • December-January: Players begin reporting issues with the mission on the game’s official forums.

  • January 22: Asros confirms a fix in the pipeline for the “next patch”.

  • January 24: A new test center build is published that introduces the eMTV_EventInstance Live Tuning variable.

  • January 29: The patch that contains this change is officially released.

This mission is just a single aspect of a single event. Doing this for all events required pretty considerable effort, but thankfully I had previously documented all released game versions in a spreadsheet, which helped significantly.

Some events made other special changes that cannot be toggled via Live Tuning. For example, demonic enemies are added to Midtown Patrol during the Mystic Mayhem event by replacing the MidtownAmbientPopBosses MetaState with MidtownAmbientPopBossesMysticMayhem in the PatrolMidtownMode prototype. Another special change would be adding MooCowSummonAffix to the BossAffixesBaseSet prototype, allowing bosses to roll a cow-summoning affix during the Cowtastrophe event. Changes were also made to assets on the Unreal Engine side, such as adding Halloween decorations to Avengers Tower or making it snow in Midtown for Christmas. None of these changes are possible without a server restart, and some of them are impossible without client-side patches.

As you can see, events in Marvel Heroes are actually a bit of a mess to say the least. However, because of how important they are to the game, something needed to be done to make them accessible without players manually tinkering with patches and Live Tuning presets.

Implementation

The main goal I had for the implementation of the native event system was for it to be completely automated and foolproof. Therefore, events needed to be systemized.

The resulting format for event definitions is effectively a set of metadata for a Live Tuning data file, which includes its proper display name, optional daily login gift item, a flag to hide it, and an optional list of missions that need to be reset per event period, such as the previously mentioned A.R.M.O.R. Defender mission. These event definitions are stored in a new file located in Data/Game/LiveTuning/Events.json. The event system is hooked into the existing Live Tuning system: when Live Tuning data is loaded, the server picks what event files to include based on a set of rules defined in the Data/Game/LiveTuning/EventSchedule.json file. To allow events to rotate automatically, the server will now automatically reload Live Tuning data when the daily mission reset happens at 10:00 UTC+0.

Both Events.json and EventSchedule.json can be overriden by adding files named EventsOverride.json and EventScheduleOverride.json, which is a way to easily keep your custom schedule when you update the server, even if there are changes to the default data. The server comes bundled a wide selection of events in its default schedule, so you do not have to do this if you are not a tinkerer.

The rules are perhaps the most interesting aspect of the new scheduler. In this initial iteration we support five types of events:

  • AlwaysOn: should be self-explanatory.

  • WeeklyRotation: represents a rotation of events lasting a week each. The rollover happens on the day of the week specified in the StartDayOfWeek field. This is what we use for the “core” rotation, which includes Cosmic Chaos, Odin’s Bounty, Operation Omega, Mystic Mayhem, and A.R.M.O.R. Incursion. We also have a secondary weekly rotation that runs in parallel with Heroes for Hire, The Wealth of Kings County, and Orders from the Hand.

  • DayOfWeek: events with this rule run on the day of the week specified in the StartDayOfWeek field. This is what is used by the Midtown Madness event, which happens every Monday.

  • SpecialDate: events that run every year on the month and day specified in StartMonth and StartDay fields for the duration defined in the DurationDays field. This covers the vast majority of seasonal events, including Anniversary in June, Winter Holiday in December, and so on.

  • SpecialDateLunar: a very special case designed to handle the Lunar New Year event. For this we use the ChineseLunisolarCalendar class provided by the .NET standard library to programmatically determine the correct Chinese New Year start date for a given year. Technically, the current implementation provided by Microsoft will work only until 2101, but hopefully it gets updated before then.

Along with the new scheduler I also did a major pass on our default Live Tuning data to make sure event-specific content is available only during associated events. This may be a net nerf, especially if you manually enabled event loot tables before this update, but it brings the game to a state that is more closely aligned with the original experience.

While this initial implementation is a significant step forward compared to moving files around, there are still aspects that can be improved in future iterations:

  • There are more events that can be potentially restored and added to the default schedule, including Cowtastrophe / Cowpocalypse, Mardi Gras, Easter, and the Guardians of the Galaxy themed event with Planet X Bark drops.

  • Odin’s Bounty event currently boosts all possible content at the same time, although it is supposed to rotate on a 4 hour cadence.

  • The server prints a list of currently running events in a secondary message of the day in chat, but this could be made better by making use of the News panel with dynamically generated content.

I hope to explore these and other changes in future updates, but there are no ETAs or guarantees.


Thank you for following the development of MHServerEmu, I hope you all enjoyed this more focused blog post format. See you next time!