MrxAchievements
Module: mrxachievements.lua
Overview
The MrxAchievements module is responsible for managing and tracking player achievements in the game. It handles granting achievements, adding counts to achievements, checking if an achievement has been granted, and processing support events that may trigger certain achievements. Additionally, it manages faction mood changes that can unlock specific achievements.
Inheritance
- Inherits from:
none(base/utility module) - Imports:
MrxFactionManager
Instance pattern
This is a stateless singleton/utility module — no inherit(), no getfenv():Create(), no tInstance/ uGuid-keyed instances anywhere in the file. All state is plain module-level globals:
tSupportUsed,tSupportUsedInCoop: tables tracking used support types (declared at module scope but only ever read/written viaSaveSingleton/LoadSingletonin this file —ProcessSupportEventreads and writestSupportUsedInCoopdirectly by support name).tSupportFilter: a static lookup table of support-module names (MrxSupportTransit,MrxBoatDelivery,MrxCrateDelivery,MrxSoldierDelivery,MrxSupportCopterDelivery,MrxSupportPickup) that are excluded from triggering the coop “damage inc” achievement.tAchievementsList: a static array of{sName, nRequiredCount}entries for every achievement in the game — used as the source of truth byGetNameFromHash,AchievementGrant,AchievementAddCount, andAchievementIsGranted(matched bysName, with the achievement’s list index passed toPg.AchievementAddCount/Pg.AchievementIsGrantedas a 0-based counter ID viai - 1).EVENT_GRANTACHIEVEMENT,EVENT_ACHIEVEMENTGRANT,EVENT_ACHIEVEMENTADDCOUNT: integer constants (0, 1, 2) used as the custom net-event type tag dispatched throughNet.SendCustomEvent(...)and received viaNetEventCallback— notEvent.*engine event constants (see Events below).nAlliedMood,nChinaMood,nGuerillaMood,nOCMood,nPirMood: set as bare globals (no declaration/initialization visible in this file) inside the closures created byFactionMoodAchievements, one per faction, flipped to1when that faction turns hostile to the PMC.
Functions
GetNameFromHash(nameHash)
Converts a hash value to the corresponding achievement name. Returns the achievement name if found, otherwise logs an error message and returns nil.
NetEventCallback(nType, tArgs)
Handles network events related to achievements. It processes different types of events (EVENT_GRANTACHIEVEMENT, EVENT_ACHIEVEMENTGRANT, EVENT_ACHIEVEMENTADDCOUNT) and calls the appropriate functions based on the event type.
NetGrantAchievement(sAchievementName, vPlayers)
If vPlayers is not provided, grants directly via Net.GrantAchievement(sAchievementName) and, if this is the server, broadcasts EVENT_GRANTACHIEVEMENT to all clients. If vPlayers is provided (a table of GUIDs, or a single userdata GUID wrapped into a one-element table; returns early for any other type), splits the list into local vs. remote via Player.IsLocal, grants locally if any local player is in the list, and — if any remote player is present and this is the server — broadcasts EVENT_GRANTACHIEVEMENT.
GetLocalRemote(vPlayers)
Determines whether the specified players are local or remote. Returns two boolean values indicating if there are local and remote players.
AchievementGrant(sAchievementName, vPlayers)
Grants an achievement to the specified players. It checks if the achievement exists in the list and updates it using Pg.AchievementAddCount.
AchievementAddCount(sAchievementName, nDeltaCount, vPlayers, bCustomEvent)
Adds a count to an achievement for the specified players. It checks if the achievement exists in the list and updates it using Pg.AchievementAddCount. If bCustomEvent is true, it sends a network event.
AchievementIsGranted(sAchievementName)
Looks up sAchievementName in tAchievementsList; if found, returns whatever Pg.AchievementIsGranted(sAchievementName, i - 1, tAchievement.nRequiredCount) reports (true/false). If the name isn’t in the list at all, logs "AchievementIsGranted(...) Failed: No such an achievement." and returns false — the error log fires only for an unrecognized achievement name, not merely an ungranted one.
AchievementAddCount_MASTER_HIJACK(vPlayers)
Adds +1 to "ACHIEVEMENT_MASTER_HIJACK", "ACHIEVEMENT_MASTER_5_HIJACK", and "ACHIEVEMENT_MASTER_10_HIJACK" for the given players.
None of those three ids appear in
tAchievementsList, so eachAchievementAddCountcall falls through the not-found branch and logs"... Failed: No such an achievement."— i.e. this function currently records nothing. If you’re adding hijack-mastery achievements, you must also add these ids totAchievementsList.
FactionMoodAchievements()
Sets up faction mood change events that trigger the “ACHIEVEMENT_NO_MORE_MR_NICE_GUY” achievement when all factions become hostile towards the PMC. It also sets up a persistent event to process support events that may trigger the “ACHIEVEMENT_DAMAGE_INC” achievement.
ProcessSupportEvent(tData)
Processes support events and checks if they should trigger the “ACHIEVEMENT_DAMAGE_INC” achievement. If the support type is filtered or already used in coop, it returns early.
SaveSingleton()
Saves the current state of tSupportUsed and tSupportUsedInCoop to a table for saving purposes.
LoadSingleton(tSaveData)
Loads the saved state of tSupportUsed and tSupportUsedInCoop from the provided save data. If no save data is available, it initializes these tables as empty.
Events
Event.ScriptEvent(the only realEvent.*constant referenced in this file) — registered viaEvent.CreatePersistent(Event.ScriptEvent, {"SupportUsed", function() return true end}, ProcessSupportEvent)insideFactionMoodAchievements. FiresProcessSupportEventfor every"SupportUsed"script event, unconditionally (the filter function always returnstrue).- Custom net events, not
Event.*constants:NetEventCallback(nType, tArgs)is this module’s handler forNet.SendCustomEvent("MrxAchievements", ...)messages, dispatching on the integer type tagEVENT_GRANTACHIEVEMENT(0),EVENT_ACHIEVEMENTGRANT(1), orEVENT_ACHIEVEMENTADDCOUNT(2). These are this module’s own constants, wired through the engine’s generic custom-net-event mechanism — they are notEvent.Create/Event.*engine event registrations. MrxFactionManager.CreateAttitudeChangeEvent(called 5 times inFactionMoodAchievements, once per faction:All/Gur/Chi/Oil/PirvsPmcgoingHostile) is a call into another module’s API, not anEvent.*constant either — each callback flips that faction’s mood global to1and grants"ACHIEVEMENT_NO_MORE_MR_NICE_GUY"once all five moods are1.- No other
Event.Create/Event.CreatePersistentcalls exist in this file. There is noOnActivate/OnDeactivate/OnDeathin this module — it’s a pure logic/utility singleton, not a world-object script.
Achievement catalog
tAchievementsList is a flat 41-entry array of {sName, nRequiredCount}. nRequiredCount is the count threshold before the achievement unlocks (Pg.AchievementAddCount is passed the entry’s 0-based index i - 1 as the counter ID). Most are single-shot (nRequiredCount = 1); the count-based ones are the interesting tunables, e.g.:
| Achievement | Required count |
|---|---|
ACHIEVEMENT_NOTHIN_BUT_GOODTIME | 200 |
ACHIEVEMENT_HAIL_AND_KILL / _HOLY_SMOKE / _LITTLE_SAVAGE / _QUICK_OR_DEAD | 50 |
ACHIEVEMENT_SHOOTTHRILL | 25 |
ACHIEVEMENT_ARMAGGEDON / _DAMAGE_INC | 20 |
ACHIEVEMENT_HEAVY_METAL_THUNDER | 10 |
ACHIEVEMENT_DIGITAL_MAN / _BURN_THE_SKY | 5 |
ACHIEVEMENT_HIGHWAY_TO_HELL | 3 |
The five max-relation faction achievements (STAND_UP_AND_SHOUT, FOREVER_FREE, ISLAND_DOMINATION, LONGING_FOR_FIRE, DIRTY_DEEDS) are the ones MrxFactionManager grants on hitting max relation.
Notes for modders
- Grant path: use
NetGrantAchievement(sName)for a plain grant (server broadcasts to clients);AchievementAddCount(sName, nDelta, vPlayers, bCustomEvent)for count-based ones — passbCustomEvent = trueif you want the delta replicated to remote players. - Every function looks the name up in
tAchievementsListfirst. An id not in that table silently no-ops (logs"... Failed: No such an achievement."). Add your id to the list before granting/counting it. - The “turn everyone hostile” achievement (
ACHIEVEMENT_NO_MORE_MR_NICE_GUY) is wired via fiveMrxFactionManager.CreateAttitudeChangeEvent(..., "Hostile")hooks flippingnAlliedMood/nChinaMood/nGuerillaMood/nOCMood/nPirMoodto1; it grants only once all five are1. Those mood globals are never re-set to0in this file, so it’s effectively a one-way latch per session. tSupportFilterexcludes the delivery/pickup support modules from the coopACHIEVEMENT_DAMAGE_INCtrigger; add a module name there to exclude your custom support from that achievement.