Important: On a map vs. not on the map
When you start Isle of Nex, you're in a cage with a branch in front of you. This branch is on the map. On every frame, its components are updated and its ModelComponent is drawn. If you leave the level, it will still be on a map, just not on the current map. Its components will still update, but not every frame (maps other than the current one take turns updating, so it might update every second frame or every 500th frame depending on how many maps the dungeon has and how how close you are to the map), and it won't be drawn.
However, when you pick up that branch, and it becomes the mouse item, it is removed from the map. The GameObject's 'map' field is set to nil, and its components stop updating. Furthermore:
- The object cannot be retrieved by findEntity() if it is not on a map.
- Obviously, since it is not on any map, Map:entitiesAt() and Map:allEntities() won't find it either.
- The following methods will CRASH if you call them with an object that is not on a map:
Various component methods
Interestingly, GameObject:getElevation(), GameObject:getWorldPosition(), GameObject:getWorldPositionY(), and GameObject:getWorldRotation() will still return the position/rotation from when the object was last on a map, which I suppose has vague potential to be useful. Also, notice that GameObject:setWorldPositionY() and GameObject:setWorldRotation() and its derivatives still work on objects that are not on a map, but since calling them won't add the object back to a map, it's hard to think of a use for this.
- If an object not on a map has any connectors, and it triggers them, it will crash, because triggering a connector uses the triggering object's map. The raw hooks in the component definition will still work; they don't use the map. This means that you can imitate connector functionality without needing a map. There is already a Grimrock 2 library that can do this: viewtopic.php?f=22&t=8345
- An object that is not on a map will not run any of its components' onInit() hooks. Even if the object is added to a map later, the onInit() hooks will still not run. Hooks like ControllerComponent.onInitialActivate() will still work, however.
- An object that is not on a map does not enforce its unique id. If there is an object in a champin's inventory with the id "rock1", and you spawn a new object with the id "rock1", it won't crash; you'll end up with two objects that both have the id "rock1". You should NEVER do this, because it will result in a crash when the game is saved and loaded.
- The savegame code will not directly reach an object that is not on a map, but since it will reach inside containers and inventories, this is really only relevant when you do something that renders an object completely unretrievable like removing it from an inventory without returning it to the map (or another inventory). This is a good thing; it acts as a sort of "garbage collection" for those objects when the game is saved and loaded.
Inventories, surfaces, and sockets
It is important that, across all inventories, SurfaceComponents, SocketComponents, ContainerItemComponents, etc., you maintain the discipline of having only *one* reference to an object at a time. Should two SurfaceComponents (or two inventories, or an inventory and a SocketComponent, etc.) have a reference to the same item at the same time, one of the following will happen when the game is saved and loaded:
1. If the game is able to find the item, it will probably duplicate the object. This is obviously not something you want.
2. If the game is not able to find the item, it will crash. This is also not something you want.
Moving an item, adding it to the map, or adding it to another inventory or surface or socket or container does NOT remove it from its current inventory or surface or socket or container, if it has one. You need to either formally remove the item with removeItem(), destroy the item's object, or destroy the surface or inventory. The party using the mouse to pick up an item will also remove it from its surface or socket if it has one, but you obviously do not have the ability to make that happen with the scripting interface.
This poses a problem with SurfaceComponent because it lacks a removeItem() method. To remove an item from a SurfaceComponent via script, you must either:
1. Destroy the ItemComponent with GameObject:removeComponent(). By extension, GameObject:removeAllComponents(), GameObject:destroy(), and GameObject:destroyDelayed() will also do the job, but since those (essentially) destroy the entire object, it's probably not what you were looking for.
2. Destroy the SurfaceComponent in the same way.
Option 1. is preferable, since option 2. removes *all* items from the surface. But it's still almost useless, because after destroying the ItemComponent, you can't get it back. You can copy most of the properties of the original ItemComponent to the new one, but you cannot copy the onThrowAttackHitMonster, onEquipItem, or onUnequipItem hooks; you would need to spawn a completely new object to get the hooks back. You will encounter the same problem if you try to use option 2.
If an object has an ItemComponent, you can easily remove it from the map by adding that ItemComponent to a champion or monster's inventory, or a ContainerItemComponent, or setting it as the mouse item. However, once again, THIS DOES NOT REMOVE THE ITEM FROM SURFACES OR SOCKETS, and I just explained why that is a big problem.
Remember that you can check if an object is in a surface, container, etc. by checking GameObject:getFullId(). But make sure that you do this BEFORE you add the object to an inventory or whatever, because that will reset the full id, even though the ItemComponent is actually still in a surface/container/whatever!
So, the short version of all this is: Do not, under any circumstances, move an item that might be in a surface.
P.S. Destroying a sack does not destroy the items inside it, it just renders them inaccessible (they will be effectively "destroyed" when the game is saved and reloaded, like items that are not in any map or inventory).