V41.0.0 released -- significant changes!

EDIT: Also check out the small followup v42.0.0 which transitions the interact event as well

This update contains two big upgrades:

  • No longer use outfitStrings, only currentlyEquippedWearables, which are actual typed objects instead of JSON, and only contain the essential data, wearableId.
  • Upgrade objects from numeric keys to string keys, and furthermore, these keys are finally stable and unique (within a map), something that is not true of object ids. These are a much better identifier and should eliminate a whole class of possible bugs that would happen with ids

breaking changes

  • removed Player["outfitString"], event playerSetsOutfitString, and game.setOutfitString in favor of currentlyEquippedWearables-equivalents of each – this is now a typed object, with only the wearableIds.
  • changed GameMap["objects"] type from Record<number, MapObject> to Record<string, MapObject>. They now match the keys returned from the http API
  • deleteObjectByKey takes string keys instead of numbers now
  • game.setMapObjects now expects { [key: string]: Partial<MapObject> } for the 2nd arg, instead of being keyed by numbers (these are the new keys that everything else is switching to too)
  • game.setObject now uses underlying action mapUpdateObjects, which does not apply partial updates. Previous action mapSetObjects would apply valid updates and warn about invalid ones. Now, the whole action will succeed or the whole action will fail.
  • game.setObject now allows you to set objects’ zIndex instead of ignoring any zIndex values passed in
  • event mapSetObjects has been replaced with mapSetObjectsV2, which is the same except objects is keyed by the new string keys instead of the old numeric keys

other changes

  • added game.addObject for compile-time checks that all required fields are present
  • object keys are now stable and unique! (*Unique within one map.) Unless you deliberately overwrite the whole map via the http API, the same key will refer to the same logical object across edits, restarts, etc. This is an upgrade from object ids, which are not enforced to be unique or stable.
    • game.updateObject is the improved equivalent to the old game.setObject, which takes a key instead of an id. game.getObject(mapId, objId, ...) is no longer necessary; just use game.partialMaps[mapId][objectKey]

:warning: Deprecation warning :warning:

We would like to drop support for the old outfitString and objects as soon as possible, since they come at a large performance cost server-side. If you need more than a week to migrate, please let me know and we may be able to accommodate

Awesome news, glad to see some updates on the object keys and outfit strings. We had actually discussed the raw cost of the stringified Outfit JSON field in the player object a few weeks ago in one of the developer meetings.

Quick question about the future of object referencing in function and event callback arguments: You mention that the keys are more unique and stable compared to ids. Currently, event callback arguments like playerInteracts, playerTriggersObject, etc include the object id as an identifier, not the objectKey. This means that each event will still need to use game.getObject instead of the proposed accessor pattern of game.partialMaps[mapId].objects[objectKey]. While not a huge issue, performance-wise, I raise the concern only as a bit of a pattern break.

I realize it would be a considerable shift in the event callback types to replace objId with objKey (for example), but maybe we could eventually see a shift where objKey is included in the PlayerInteracts type? This would allow the proposed accessor pattern to be used more often (especially as PlayerInteracts already includes an optional mapId).


export interface PlayerInteracts {
  encId: number;
  objId: string;
    | string
    | undefined;
  /** JSON string */
  dataJson?: string | undefined;


export interface PlayerInteracts {
  encId: number;
  objId: string;
  objKey: string;
  mapId: string;
  dataJson?: string;

Overall, really cool changes, glad to see improvements and QoL stuff like this.

1 Like

Ah shoot, you are totally right. I’ll push an update this week to include keys in those events too!

Also to be clear, ids still work as normal and are fine – you don’t need to stop using them to upgrade to the new version. That was more of an optional bonus that comes with these upgrades

Updated version for interacts! V42.0.0 released

Hi there!
I believe this is my first post on this forum so please forgive me if there is something wrong with my message.

I appreciate the updates and I think this is a step in a good direction (making objects / outfit strings more reliable / consistent across the board).

However, I did stumble upon a weird pattern when trying to adapt to the new game.updateObject method:

As you can see, in the case of this function, I have access to an object from within my code and am having to filter for it inside game.partialMaps[mapId].objects in order to actually get access to the key that I need to use to update said object.

Is there a utility method I’m missing in order to know which object key I’m meant to be using when making updates to an object? Or perhaps this should be a more general change in approach on my side, whereby I need to prioritize keeping references to the object’s keys I mean to update?

It struck me as a bit odd that I couldn’t find a simple way to know which object key is associated with which object for situations like this (without having to filter through all the objects in the map looking for something inside the object definition itself).

Keep up the good work. If you have any insights for me, please let me know!


Welcome! We knew you would eventually find a reason to show up here. :slight_smile:

This feels like a fairly good catch, especially as objects used to hold their own keys inside them. Removing that brings about some of the same issues that cropped up with player ID.

That said, I would probably suggest a refactor to match better the current accessor patterns suggested. Because you are passing both the Game object and the MapObject, you are technically passing (by reference or by value) the same information twice or more (because Game includes 1-2 copies of the MapObject to begin with). As you are already accessing the MapObject before the function call, it would be more efficient if you passed the ObjectKey, then accessed the object from the Game instance directly from inside the function.

Alternately, if you are mutating the MapObject before the function call, you could add the ObjectKey as an extra key in the MapObject with (I hope) little to no side effects on the updateObject call.

Example of the former:

const updateWeatherObject = async (game: Game, mapId: string, objectKey:string, location: GeographicalLocation) {
const forcast = await getForcast([location.lat, location.lon]);
const object = game.completeMaps[mapId].objects[objectKey];

In theory this will also prevent desync between the object and the state of that object inside the Game instance. (There is a situation where the object, passed by value, would have different properties from the original by the time the getForcast resolves, meaning you would be overwriting and changing more than you knew when updateObject fires. Similarly, a pass by reference could have side effects from other functions/actions introducing race conditions on the information, leading to unstable outcomes.)

Anyway, sorry for the extended reply. Let me know what you think.

+1 to Bill’s answer as the best option

but to answer your question about the helper… game.getObject(objId, mapId) returns {mapId: string, obj: MapObject, key: string} is the legacy function

Thank you @Bill_Uncork-It , for the insightful comments / strategies and @npfoss for pointing the legacy function out!

I was actually using game.getObject() to have access to the object that I wanted to update anyway but was unaware that this was returning the key as well. With this information I was able to streamline the function by actually accessing the key instead of the object reference itself.

I think the reason I was using the { ...object , extraProp: 'value' } syntax in game.updateObject was because this used to be a game.setObject function call, in which I felt safer passing in the whole object again.

Is this necessary in the case of game.updateObject as well, to try to ensure all properties remain the same (to be honest I’m not even sure it was necessary to do in game.setObject in the first place)?

On another note, since game.getObject is now a legacy function, I would have purposefully avoided it if I had realized it was still there, fearing that it’d be discontinued soon, in line with the latest changes. For this reason, wouldn’t it make sense for the object to carry the key value within itself, as @Bill_Uncork-It mentioned, in similar fashion as the Player objects?

Thanks again, for your kind responses!

Yeah… I should maybe add key back to objects. We weren’t using it internally at all (which is why I removed it in the first place) but it is kinda convenient. Might include it in the next update but no promises yet


Yeah you don’t need to set the whole thing, just the changed fields are fine!

getObject is legacy

It still works in the meantime, so as long as all your objects have ids (not a required field, so be careful), it’ll keep working. I try not to break stuff until we actually remove it from the client entirely

1 Like