# SBQ Engine 0.1
There are a number of changes from the original retail build of Starbound when using the SBQ Engine. All changes will be listed in this document.

SBQ Engine CANNOT be used on multiplayer with other players or servers using the retail build of the game. A number of networking elements would become unsynced due to additional features implemented for SBQ.

SBQ Engine is incompatible with any .dll mods designed for the retail build of the game, so things such as StarExtensions must be removed.

SBQ Engine is built with the Steam integration enabled, but not the Discord integration. Discord has since deprecated the API version that Starbound interfaced with so nothing of value was lost.

## Lua Callbacks
A number of lua callback tables have been modified, expanded upon, or are just new entirely.

### root

#### `JsonObject` root.modMetadata(`String` modId)
Returns the metadata object for a mod. modId can be either the mod's name or Steam content ID.

#### `Json` root.fetchJson(`String` path, [`String` relativePath])
Similar to root.assetJson, but prepends relativePath arg if path is not a global path.

#### `bool` root.assetExists(`String` path, [`String` relativePath])
Returns if the asset exists.

#### `JsonObject` root.fetchConfigArray(`JsonArray` config, [`String` relativePath])
Iterates through an array to find string paths to more configs and jsonObjects to merge together, in order to return a resulting jsonObject.

#### `Maybe<JsonObject>` root.speciesConfig(`String` species)
Returns the config for the species if valid.

#### `Maybe<JsonObject>` root.effectConfig(`String` effect)
Returns the config for the status effect if valid.

#### `JsonObject` root.version()
Returns versioning information.

---

### sb

#### `String` sb.printJson(`Json` value, [`int` indentation], [`bool` sort]) `modified`
`value` The json value to print as a string.
`indentation` The amount of spaces to indent for each object's contents.
`sort` Whether to sort the output's keys.
This was poorly explained in the original Starbound documents. It also didn't pass the third argument that the engine already supported!

#### `Json` sb.parseJson(`String` value)
Parses a string into a Json value.

#### `Json` sb.mergeJson([`LuaValue` args ...])
Attempts to merge all arguments into one input.

#### `String` sb.assetPath(`String` path, `String` relativePath)
Returns path with `relativePath` prepended if the asset path is not a global path.

#### `RectF` sb.drawableBoundBox(`JsonArray` drawables, `bool` crop = false)
Returns the bound box of the drawables. If `crop` is true, it will crop transparent pixels from images.

---

### world

#### `List<EntityId>` world.entityQuery(`Vec2F` position, `Variant<Vec2F, float` positionOrRadius, [`Json` options]) `modified`
Works same as it did before. However, options now has an additional argument `withoutEntityIds` that is a list of entityIds not to include in the query.

#### `EntityId` world.getUniqueEntityId(`String` uniqueId)
Returns an entityId if the unique entity is currently loaded in the map. Works on clients.

#### `JsonArray` world.entitiesLounging(`EntityId` entityId)
Returns a list of the entities lounging in the entity if applicable.

#### `JsonObject` world.entityCurrentLounge(`EntityId` entityId)
Returns data about the entity's lounge anchor.

#### `float` world.entityArea(`EntityId` entityId)
Returns the area of the entity's collision poly.

#### `RectF` world.entityCollisionBoundBox(`EntityId` entityId)
Returns the entity's collision bounding box.

#### `int` world.entityFacingDirection(`EntityId` entityId)
Returns the entity's facing direction.

#### `Json` world.entityStatusProperty(`EntityId` entityId, `String` property)
Returns the value of a status property from the entity if applicable.

#### `float` world.entityStat(`EntityId` entityId, `String` stat)
Returns the value of a stat on the entity.

#### `bool` world.entityStatPositive(`EntityId` entityId, `String` stat)
Returns if the entity's stat is positive.

#### `bool` world.entityIsResource(`EntityId` entityId, `String` resource)
Returns if the entity's status controller has the resource.

#### `float` world.entityResource(`EntityId` entityId, `String` resource)
Returns the value of the entity's resource.

#### `bool` world.entityResourceLocked(`EntityId` entityId, `String` resource)
Returns if the entity's resource is locked.

#### `float` world.entityResourcePercentage(`EntityId` entityId, `String` resource)
Returns the percentage value of the entity's resource.

#### `bool` world.entityResourcePositive(`EntityId` entityId, `String` resource)
Returns if the entity's resource is positive.


---

### status

#### `void` status.addPersistentEffect(`String` effectCategory, `Json` effect, [`EntityId` sourceEntity]) `modified`
Adds a status effect to the specified effect category.

#### `void` status.setPersistentEffects(`String` effectCategory, `JsonArray` effects, [`EntityId` sourceEntity]) `modified`
Sets the list of effects of the specified effect category. Replaces the current list's active effects.

---

### effect

#### `String` effect.name()
Returns the name of the status effect.

#### `void` effect.setToolUsageSuppressed(`bool` suppress)
Suppresses tools on the parent entity. This disables tools for players/NPCs as well as disables contact damage and physics forces on NPCs and monsters.

---

### mcontroller
Used by vehicles, projectiles, and stagehands.

#### `void` mcontroller.setScale(`float` scale)
Scale is applied to:
- Hitbox
- Gravity
- Sprites

#### `float` mcontroller.scale()
Returns current scale.

#### `float` mcontroller.area()
Returns area of current collision poly.

#### `void` mcontroller.setFlipped(`bool` flip)
Flips collision poly.

#### `bool` mcontroller.isFlipped()
Returns if the collision poly is flipped.

---

### mcontroller (actor)

Used by players, NPCs, and monsters.
Is considered a different thing from the main mcontroller. It does inherit from it and do everything it does, however the function that generates its lua callbacks is separate on the engine side. It does not inherently have all the same callbacks as the basic mcontroller, which explains a lot of discrepancies between actors and non-actors.

SBQ adds in additional `movementParameters`:
`crawlEnabled` if set to `true` will allow the entity to move while crouching using their `crawlSpeed` parameter.
(Currently only used by players. Though NPCs can technically do it, they haven't had their pathing AI changed to account for this as I currently only have one species configured to be able to do this).

#### `void` mcontroller.setScale(`float` scale)
Scale is applied to:
- Hitbox
- Gravity
- Sprites
- Movement speeds: `walkSpeed`, `runSpeed`, `crawlSpeed`, `jumpSpeed`, `flySpeed`, `swimSpeed`
- `powerMultiplier` and `protection` stats, as a stat base multiplier of `sqrt(scale)`
(Fall damage is handled in lua. starbecue.pak adds additional code to handle this).

#### `float` mcontroller.scale()
Returns current scale.

#### `RectF` mcontroller.collisionBoundBox()
Returns bound box of current collision poly.

#### `float` mcontroller.area()
Returns area of current collision poly.

---

### player

#### `void` player.interactWithEntity(`EntityId` entityId)
Attempts to interact with the entity in the same manner as if the player had pressed the interact key.

#### `void` player.stopLounging() `modified`
Makes the player stop lounging if the loungeable entity allows dismounting.

#### `void` player.queueUIMessage(`String` message)
Sends an arbitrary string to the little box at the bottom of the screen that is usually used for item pickup notifications.

#### `void` player.setScriptContext(`String` genericContext)
Sets the context that gets used when using `player.callScript`.

#### `String` player.currentScriptContext()
Returns the current configured context that is set for `player.callScript`.

#### `LuaValue` player.callScript(`String` functionName, [`LuaValue` args ...])
Used for calling a script in the set generic context on the player and getting the value back without an RPC.

#### `Vec2F` player.aimPosition()
Returns player's current aim position.

---

### monster

#### `void` monster.setLoungeControlHeld(`String` control)
Sets the control as held for loungeable entities.

#### `void` monster.releaseLoungeControl(`String` control)
Releases the control for loungeable entities.

#### `bool` monster.isLoungeControlHeld(`String` control)
Returns whether the entity is currently set as holding the control.

#### `bool` monster.setLounging(`EntityId` loungeable, [`size_t` anchorIndex])
Sets the monster to lounge in a loungeable entity. Can optionally specify which anchor (seat) to use.
Returns whether the monster successfully lounged.

#### `void` monster.resetLounging()
Makes the monster stop lounging if the loungeable entity allows dismounting.

#### `bool` monster.isLounging()
Returns whether the monster is currently lounging.

#### `Maybe<EntityId>` monster.loungingIn()
Returns the EntityId of the loungeable entity the monster is currently lounging in. Returns nil if not lounging.

---

### npc

#### `void` npc.setLoungeControlHeld(`String` control)
Sets the control as held for loungeable entities.

#### `void` npc.releaseLoungeControl(`String` control)
Releases the control for loungeable entities.

#### `bool` npc.isLoungeControlHeld(`String` control)
Returns whether the entity is currently set as holding the control.

#### `void` npc.resetLounging() `modified`
Makes the NPC stop lounging if the loungeable allows dismounting.

---

### humanoid
The `humanoid` table was added in to access functions used on `humanoid` entities (players/NPCs).

#### `void` humanoid.setIdentity(`JsonObject` identity)
Sets the humanoid entity's identity.

#### `JsonObject` humanoid.randomIdentity(`String` species, [`int` personality], [`int` seed] )
Returns a randomly generated humanoid identity for a species. There are optional args for a certain personality index or a certain seed.

#### `String` humanoid.species()
Returns current species.

#### `String` humanoid.gender()
Returns current gender.

#### `Vec2F` humanoid.mouthPosition()
Returns current mouth position.

#### `void` humanoid.getItemSlot(`String` slot)
Gets the item in the slot.

#### `void` humanoid.setItemSlot(`String` slot, `ItemDescriptor` item)
Sets the item in the slot.

---

### activeItem
#### `void` activeItem.setFriendlyName(`String` shortDescription)
Sets the displayed name on the item.

#### `void` activeItem.setDescription(`String` description)
Sets the displayed description on the item.

#### `String` activeItem.directory()
Returns the directory path of the item.

---

### object
#### `List<pair<EntityId, unsigned>>` object.getInputNodeConnections(`int` nodeIndex)
Returns a list of the connections that the specified node index has in a more sensible form than `object.getInputNodeIds` as that one returns the entity ids in a map with which they end up as string keys, this one returns an ordered list with the first part of the pair being the entity ID and the second being the node index it is connected to on that entity.

This is important because if the node (somehow) has multiple connections to different outputs on the same entity, the map would only provide data from the last connection.

#### `List<pair<EntityId, unsigned>>` object.getOutputNodeConnections(`int` nodeIndex)
Returns a list of the connections that the specified node index has in a more sensible form than `object.getOutputNodeIds` as that one returns the entity ids in a map with which they end up as string keys, this one returns an ordered list with the first part of the pair being the entity ID and the second being the node index it is connected to on that entity.

This is important because if the node has multiple connections to different inputs on the same entity, the map would only provide data from the last connection.

---

### animator

#### `bool` animator.setAnimationState(`String` stateType, `String` state, `bool` startNew = false, `bool` reversed = false) `modified`
Sets an animation state. If startNew is true, restart the animation loop if it's already active. If reversed is true, the state plays in reverse. Returns whether the state was set.

#### `void` animator.setPartTag(`String` partType, `String` tagName, [`String` tagValue]) `modified`
Sets a local animator tag. A part tag replaces any tag `tagName` with the specified `tagValue` in the partType animation part only. If `tagValue` is left empty, it will clear the part tag and allow it to once more be applied from the globalTags.

#### `Json` animator.animationStateNextProperty(`String` stateType, `String` propertyName)
Returns the value of the specified property for the next frame of a state type.

#### `Json` animator.partNextProperty(`String` partName, `String` propertyName)
Returns an animation part property for the next frame without applying any transformations.

#### `Vec2F` animator.transformPointFromGroups(`Vec2F` point, `StringList` groups)
Transforms a point by a list of arbitrary transformation groups.

#### `PolyF` animator.transformPolyFromGroups(`PolyF` point, `StringList` groups)
Transforms a poly by a list of arbitrary transformation groups.

#### `void` animator.setStateReversed(`String` stateType, `bool` reversed)
Sets whether a stateType is playing its animation in reverse.

#### `bool` animator.isStateReversed(`String` stateType)
Returns whether a stateType is playing in reverse.

#### {`int` frame, `int` frames, `float` frameProgess, `int` nextFrame} animator.getStateFrame(`String` stateType)
Returns an array containing relevant values: frame being the current frame index, frames being the number of frames in the current anim state, frameProgress being the percentage of time in the current frame (useful for interpolation), and nextFrame being the next frame Index.

#### `bool` animator.animationEnded(`String` stateType)
Returns whether the animation has ended.

#### {`float` timer, `float` cycle} animator.animationTimer(`String` stateType, [`String` state])
Returns whether the current animation timer, and the full cycle length of the current animation for a stateType. if state is defined, it will return the current time if that anim is currently active, or 0 if it isn't the active animation, cycle will be returned as the cycle for the state input.

#### `bool` animator.hasPart(`String` part)
Returns whether the animation has the part.

#### `bool` animator.hasState(`String` stateType, [`String` state])
Returns whether the animation has the stateType, and if that state contains the input state if given.

#### `void` animator.reloadConfig(`Json` config, `String` relativePath)
Attempts a hot reload of the current animation config with the provided one, while also pointing assets to the provided relative path.
DO NOT USE THIS. IT IS INCOMPLETE! I AM ONLY DOCUMENTING THIS BECAUSE IT EXISTS AND I'M NOT TRYING TO HIDE IT!
As of current, it will only perform the hot reload on the client running the script. It shouldn't be volatile but still should not be used yet.
NPCs and Players doing a hot reload when transforming species only works because all clients have the same configs locally, thus are told when and what to reload engine side.

#### `JsonArray` animator.drawables()
Returns drawables for the current state of the animator.

#### `String` animator.applyTags(`String` input, [`String` part])
Applies the current global and part tags active in the animator to the string and returns it.

#### `JsonObject` animator.getTags()
Returns a jsonObject of the current global and part tags.

#### `bool` animator.flipped()
Returns whether the animator is flipped.

---

### loungeable
The `loungeable` entity class is inherited by loungeable objects, vehicles, players, NPCs, and monsters.
Loungeable objects must be specifically configured to use the vehicle lounge position behavior, else attempting to use these functions may crash them.
Most of these functions are identical to the original `vehicle` lua callback table, because they are the exact same functions. `vehicle` also possesses all functions in `loungeable` alongside its original callbacks, which remained specific to itself.

#### `bool` loungeable.controlHeld(`String` loungeName, `String` controlName)
Returns `true` if the specified control is currently being held by an occupant of the specified lounge position and `false` otherwise.

#### `Vec2F` loungeable.aimPosition(`String` loungeName)
Returns the world aim position for the specified lounge position.

#### `EntityId` loungeable.entityLoungingIn(`String` loungeName)
Returns the entity id of the entity currently occupying the specified lounge position, or `nil` if the lounge position is unoccupied.

#### `void` loungeable.setLoungeEnabled(`String` loungeName, `bool` enabled)
Enables or disables the specified lounge position.

#### `void` loungeable.setLoungeOrientation(`String` loungeName, `String` orientation)
Sets the lounge orientation for the specified lounge position. Valid orientations are "sit", "stand" or "lay".

#### `void` loungeable.setLoungeEmote(`String` loungeName, [`String` emote])
Sets the emote to be performed by entities occupying the specified lounge position or clears it if no emote is specified.

#### `void` loungeable.setLoungeDance(`String` loungeName, [`String` dance])
Sets the dance to be performed by entities occupying the specified lounge position or clears it if no dance is specified.

#### `void` loungeable.setLoungeStatusEffects(`String` loungeName, `JsonArray` statusEffects)
Sets the list of status effects to be applied to entities occupying the specified lounge position. To clear the effects, set an empty list.

#### `void` loungeable.setToolUsageSuppressed(`String` loungeName, `bool` suppress)
Suppresses tools on the lounging entity. This disables tools for players/NPCs as well as disables contact damage and physics forces on NPCs and monsters.

#### `void` loungeable.setDismountable(`String` loungeName, `bool` dismountable)
Controls whether the lounging entity can dismount from the lounge position.

#### `void` loungeable.setHidden(`String` loungeName, `bool` hidden)
Controls whether the lounging entity will be drawn.

#### `void` loungeable.setItemWhitelist(`String` loungeName, [`JsonArray` items])
#### `void` loungeable.setItemBlacklist(`String` loungeName, [`JsonArray` items])
Whitelists/Blacklists the list of items by ID for the entity in the lounge position.

#### `void` loungeable.setItemTagWhitelist(`String` loungeName, [`JsonArray` tags])
#### `void` loungeable.setItemTagBlacklist(`String` loungeName, [`JsonArray` tags])
Whitelists/Blacklists the items with the listed tags for the entity in the lounge position.

#### `void` loungeable.setItemTypeWhitelist(`String` loungeName, [`JsonArray` types])
#### `void` loungeable.setItemTypeBlacklist(`String` loungeName, [`JsonArray` types])
Whitelists/Blacklists the items of the listed types for the entity in the lounge position.

#### `String` loungeable.getNameFromIndex(`int` loungeIndex)
#### `int` loungeable.getIndexFromName(`String` loungeName)
Returns the given loungeName/loungeIndex for the given input.

#### `List<EntityId>` loungeable.entitiesLounging()
Returns a list of all the entities lounging.

---

## Engine Changes

### Monster
- See Loungeable Entities.
- Monsters now have full lounging entity implementation to match players and NPCs.
	- Layer correctly when they lounge in a loungeable entity.
	- Have directives and status effects from lounge entities apply correctly.
	- Can now interface with lounge controls with new lua callbacks.

---

### Player
- See Humanoid
- See Loungeable Entities
- Players no longer send their entire customization data table to the server every frame because it thinks it has been updated after it receives it. Yes, that is something that happens in the retail build.
- Species can have proper unique movement parameters for players that will override the movement parameters defined in `player.config:movementParameters` by defining the object `playerMovementParameters` in their species' humanoid config or humanoid overrides. This is simply merged on top of the parameters in `player.config:movementParameters` after it has already merged on top of the `movementParameters` in the humanoid config. Which is the values that NPCs for the species use.
- `pickupSuppressed` stat when positive prevents a player from picking up items.
- Item pickup range is an extension from the player's hitbox rather than the center of their entity, therefore making things better for larger species.
- Make suffocation actually take the `mouthOffset` from species and anims and set it in `mouthPosition` status property, rather than only ever using the default in `player.config`.
- Players drop items from their `mouthOffset` rather than the center of their entity.
- Humanoid identity doesn't force `color` to always be locked to the default `/player.config:defaultHumanoidIdentity.color`. This value is used for the color of the beam and place previews of the matter manipulator, meaning one can now set it to custom values should they wish.
- Object placement previews only update when the cursor moves to a different block position. This prevents the game from seriously chugging if the player is placing an object with a lot of data attached to it. In retail, it is checking if an object can be placed every single frame an object is held (This does mean that if another block is modified by someone else, the preview will not update until the cursor moves again, but this is incredibly minor).
- The entity's scale is applied to matter manipulator break area and power.
- If `humanoid.setIdentity(identity)` has the player's name get changed, it will save to data correctly. However, it will pretend they did `/nick <newName>` as the player's server name cannot be hot swapped. This way, they will load in with the new name proper next time they leave and join.
- Players will now reset their rotation when dismounting, as well as when warping to make sure they are oriented correctly for their `feetOffset` to align them correctly to the floor.
- All script invocation for `humanoid` or `loungeable` happens in a player's generic script contexts.
- Loading a character with an inventory or hotbar size that does not match the size configured by current mods will have their data adjusted.
	- Overflow items from a mismatched inventory size will be dropped when player loads in (Items have a flag set to never despawn).
- Player's ship species is stored independent of player's current species, and will not break by upgrading to a different type if player changes species.
	- Player's SAIL will match ship species, not player species.
- Player's camera zoom level will be divided by their current scale, however it has its lower bounds as the lowest allowed zoom level setting.
---

### NPC
- See Humanoid.
- See Loungeable Entities.
- Can now interface with lounge controls with new lua callbacks.

---

### Humanoid
Extensive reworks to how humanoid entities are handled by the game engine side have been made.
- If humanoidIdentity loads with its `color` as white, reset it back to the default from `/player.config:defaultHumanoidIdentity.color`. This is because of an error in earlier SBQ Engine builds, but is a safeguard I've left in.
- New crawl state if enabled in the mcontoller.
- `/humanoid/any/humanoidEntity.config` is used to configure lounge position data on player and NPC entity types.
- lua script invocation whenever equipment is changed to something new `equipmentSlotUpdated(String slot, ItemDesccriptor item)`.
- Every customization value for identity from NPCgen or player Customization is saved individually from eachother, as well as a few new values are saved.
	- "New" Values `hairColor`, `bodyColor`, `altColor`, `personalityIndex` all technically existed before, but were combined together for the identity directives values.
	- New Values `shirt`, `shirtColor`, `pants`, `pantsColor` technically inaccurate labels, but they're named as such because of the pickers on the charcreation menu that are used.
	- set `shirtOptionAsIdentityString` or `pantsOptionAsIdentityString` in species root to have the pickers for pants and shirt to not be used for clothing, but instead be for identity values.

Ok, this is going to be a lot of vomit of words just to document things. This is meant to be largely configurable, but since it still has to hook into engine stuff to work the same as how things were originally, there's still a lot of hard-coded aspects. Just knowing about what can and can't be controlled via lua and configurations should give people the freedom to do a whole shitton more with it than the original humanoid entity system.

- Use the Networked Animator that the rest of the game uses for loading animations from a json config rather than a hard-coded set of animation parts.
- `globalTagDefaults` in animation config will have values overwritten based on every string value in a humanoid's identity.
- In the `.species` file for a respective species, `animationConfig` will be the path to a json animation config. If undefined it will default to `/humanoid/any/humanoid.animation`.
- In the `.species` file `animationCustom`, one can define an object that gets merged on top of the animation pointed to with the path. This can also be an array containing objects or string paths to other configs to merge in sequence.
- In the `.species` file, `identityAnimationCustom` is used to define more custom parameters in the animation to be set based on values in a species' identity. It fetches its config in much the same way as `animationCustom` and then checks objects within the result for an object whose key matches the key in humanoid identity as well as if that object contains another object at the key for that identity's string value. If not defined, it fetches from `/humanoid/any/identityAnimationCustom.config`.
- All animation configs for humanoids merge over a config at `/humanoid/any/dummy.animation`, which is used to make sure that all humanoids share the same parts and statetypes and priority. Therefore they all share the same placement of net elements, allowing them to be hot reloaded over each other if a humanoid is transformed from one species to another.
- Humanoids also merge over the animation whose path is defined at `/player.config:effectsAnimator`, which is responsible for the warp animation as well as a number of other minor things. This animator was used in the player entity even before I did this overhaul and its responsibilities have been absorbed, which means custom species may also define unique warp animations should they desire to.
- To keep in line with retail's behavior and not become incompatible with modded races, values in the loaded animation config are overwritten with values from the species' humanoid config:
	- Set `noHumanoidConfigOverride` to true in the root of the anim config to disable any value being overwritten from the humanoid config.
	- Set `animatedParts.stateTypes.bodyState.noHumanoidConfigOverride` to true to disable values within that stateType from being overwritten.
	- Set `animatedParts.stateTypes.emoteState.noHumanoidConfigOverride` to true to disable values within that stateType from being overwritten.
	- Individual states within `emoteState` and `bodyState` can have `noHumanoidConfigOverride` set to true to disable values within that state from being overwritten.
	- Frame count and cyles of states in `emoteState` and `bodyState` will be overwritten based off of the respective `humanoidTiming` values in their `humanoidConfig`.
	- If not defined in the `properties`/`frameProperties` of each state in `bodyState`, the transformGroups `movementOffset`, `headOffset`, and `armOffset` will have transforms set for them based off of the various offset values in the species' `humanoidConfig`.
	- The `<armSequenceFrame>` tag will be set in the `frameProperties` of `bodyState.run` and `bodyState.walk` based on `humanoidConfig`'s `armRunSeq` and `armWalkSeq` respectively.
	- `animatedParts.parts.head.properties.mouthOffset` is set based on the `mouthOffset` value in `humanoidConfig`.
	- `animatedParts.parts.head_cosmetic.properties.armorOffset` is set based on the `headArmorOffset` value in `humanoidConfig`.
	- `animatedParts.parts.chest_cosmetic.properties.armorOffset` is set based on the `chestArmorOffset` value in `humanoidConfig`.
	- `animatedParts.parts.legs_cosmetic.properties.armorOffset` is set based on the `legsArmorOffset` value in `humanoidConfig`.
	- `animatedParts.parts.back_cosmetic.properties.armorOffset` is set based on the `backArmorOffset` value in `humanoidConfig`.
	- `animatedParts.parts.anchor.properties.feetOffset` is set based on the `feetOffset` value in `humanoidConfig`.
	- `animatedParts.parts.anchor.properties.offset` is set based on the `globalOffset` value in `humanoidConfig`.
	- `animatedParts.parts.frontarm.properties.rotationCenter` is set based on the `frontArmRotationCenter` value in `humanoidConfig`.
	- `animatedParts.parts.backarm.properties.rotationCenter` is set based on the `backArmRotationCenter`+`backArmOffset` values in `humanoidConfig` (I know it's dumb, but this is how retail handles the center).
- Immediately after the animation config is loaded such as a humanoid spawning in, or updating their identity.
	- anim state `noFacialHair` will be set to `none` if the identity has an empty string for its `facialHairType`.
	- anim state `noFacialMask` will be set to `none` if the identity has an empty string for its `facialMaskType`.
	- anim state `noHair` will be set to `none` if the identity has an empty string for its `hairType`.
	- transformationGroup `frontHandPosition` will be reset and then translated by the `frontHandPosition` value in `humanoidConfig`.
	- transformationGroup `backHandPosition` will be reset and then translated by the `frontHandPosition`+`backArmOffset` values in `humanoidConfig`.
- `headNude`, `chestNude`, `legsNude`, `backNude` stats disable drawing their respective armor slot when positive, much like retail's `nude` stat which hides all of them.
- All checks for offsets in `humanoidConfig` now check a respective property in the active animator, therefore allowing the position to move around dynamically based on the animation playing, as well as the current entity scale.
	- `animatedParts.parts.anchor.properties.feetOffset` is used for calculating where to place an entity's feet when dismounting a loungeable or loading into a world after warping.
	- `animatedParts.parts.head.properties.mouthOffset` is responsible for where speech bubbles appear, where players drop items from, and when players suffocate if covered by collision.
	- The `armorOffset` property of the `head_cosmetic`, `chest_cosmetic`, `legs_cosmetic`, `back_cosmetic` parts is responsible for the position of the particle effect emitters of the respective armor slot.
	- The `rotationCenter` property of the parts `frontarm` and `backarm` is the rotation center used when setting the `frontarmrotation` and `backarmrotation` transformation groups respectively.
	- The current transformations applied to `frontarm`+`frontHandPosition` and `backarm`+`backHandPositon`, as well as the part's respective `rotationCenter` property is used for all calculations regarding offsets from that respective hand's item, such as `activeItem.handPosition(offset)` and etc.
- Engine side functions handle the setting of engine controlled animation states. As such, one should likely not touch these stateTypes with lua side functions.
	- Movement states `idle`, `duck`, `sit`, `lay`, `walk`, `run`, `jump`, `fall`, `swim`, `swimIdle`, `crawl` are set as the animation state of `bodyState` and `bodyStateHighPriority`.
	- `bodyState` and `bodyStateHighPriority` are set to be in reverse when the player is moving backwards.
	- the animation tag `bodyRunDirection` is normally set to `run` and is set to `runbackwards` when moving backwards.
	- Emote states `idle`, `blabber`, `shout`, `happy`, `sad`, `neutral`, `laugh`, `annoyed`, `oh`, `oooh`, `blink`, `wink`, `eat`, `sleep` are set as the animation state of `emoteState` and `emoteStateHighPriority`.
	- `frontarmRotationState` and `backarmRotationState` are used when the arms are in rotation mode, when `activeItem.setHoldingItem(true)`, and will also reset and set the rotation of `frontarmrotation` and `backarmrotation` based off of `activeItem.setArmAngle(angle)`.
	- `<frontarmRotationFrame>` and `<backarmRotationFrame>` tags are set based on the string input into `activeItem.setFrontArmFrame(string)` and `activeItem.setBackArmFrame(string)` respectively. They always default to `rotation`.
	- `bodyDanceState`, `frontarmDanceState`, `backarmDanceState` are set to the `dance` state when the entity is doing a "dance" that uses the respective part.
	- `<bodyDanceFrame>`, `<frontarmDanceFrame>` `<backarmDanceFrame>` are all set based on the respective dance frame.
	- Dances also may reset and set the rotation of `frontarmrotation` and `backarmrotation`.
	- If the `bodyDanceState` stateType has a state that matches the name of the dance being called, it will set that anim state rather than the `dance` state.
	- When a portrait is retrieved of an entity such as with `world.entityPortrait(entityId, mode)`, the anim state `portraitMode` will be set to the respective mode and all other engine controlled stateTypes are set to their respective `idle` (Local to the portrait).
	- One can also define an object `portraitAnimStates` in the `.species` file to set each anim state key to its string value whenever a portrait is called. This is useful for setting any potential lua controlled animation states back to idle when the engine is grabbing the portrait (Local to the potrait).
	- 'neutral' portrait modes force tags to the identity tags to the 'neutral' personality for the respective species rather than just `idle.1` (The first index of `personalities` table in the `humanoidConfig`).
	- When getting a portrait, the drawables returned are automatically scaled to fit within a 60x60 pixel area if they happen to exceed that size. This is to make sure they display nicely within a number of different UI elements if they are a larger species or the entity's scale is very large.

---

### Networked Animator
- Behavior of animation json config loading has been modified.
- In addition to sorting stateTypes by priority, those of the same priority will be sorted by key (Retail just loads them in an arbitrary order if they are the same priority).
- At `partStates.<stateType>.<state>` of an animated part, one can use a string to effectively alias one state's properties to another within the same stateType (Doing this in the retail build throws an error).
- Animations states can be set to play in reverse (Check lua callbacks).
- Drawables can be inserted at specific z layers and bound to the transforms of specific parts (Used by loungeables and player/NPC held items).
- The `processingDirectives` property of a part's `properties`/`frameProperties` will have tags applied to it, whereas retail doesn't.
- Added support for hot reloading of animation config as long as the new config's net elements would line up with the old one.

Set key `"useModifiedAnimator"` at config's root to `true` to enable further expanded behavior.
- Animation `properties`/`frameProperties` can have a table of `globalTags` to set global tags locally, therefore not causing network spam by using lua callbacks to set tags based on anim frame. It will also keep them in sync with the client viewing them.
	- `<left>` and `<right>` will be set to `front` and `back` based on animator's flip direction.
	- `<facing>` will be set to `right` or `left` based on animator's flip direction.
	- `<[stateType]Frame>` and `<[stateType]Anim>` will be set to that stateType's current frame number and string name for current anim state.

- `properties`/`frameProperties` of parts and stateTypes can apply transforms to lessen network spam of setting them lua side. It should also make the viewing experience better for clients not having any perceived delay between an animation and its parts moving, since this happens locally.
	- Both methods of applying these use the same order: Vec2F `scale` is applied first, then Vec2F `translate` is applied, then float (degrees) `rotate` is applied.
	- Parts will check if their `properties`/`frameProperties` contain an object for `transforms` and a bool `interpolated`. These transformations are then applied before the list of `transformationGroups`.
	- When `transformationGroups` are applied, it will check each `stateType` in order of priority to see if their `properties`/`frameProperties` contain an object matching its name. Then it will attempt to apply the transforms in the same manner, checking if the transformgroup is configured for interpolation rather than an anim property.

---

### Loungeable Entities
- Functions that were originally specific to vehicles were moved into the generalized loungeable entity class.
- Lounge objects can be switched to use the vehicle behavior by setting up `loungePositions` in their config, much like one would for a vehicle.
- Monsters must define `loungePositions` in their config to use them.
- `/humanoid/any/humanoidEntity.config` is used to configure lounge position data on the player and NPC entity type.
- Players, NPCs, and monsters are now loungeable entities using vehicle behavior.
- Monsters and NPCs now also properly get into lounge positions to experience all the same features that players do.
- Loungeable seats can be configured to handle the drawing of the entity lounging in it, rather than the entity itself (For better layering), by setting `handlesDrawables` on the seat's config to true.
- Loungeable seats can be configured to not be dismountable via normal interact, and will then detect the interact key as a control press.
- Loungeable seats can be configured either to suppress or not suppress tools.
- Suppressing tools now also disables NPC's contact damage and physics force regions, in addition to temporarily removing clearing tools from their hands.
- Suppressing tools now also disables monster's contact damage and physics force regions.
- Seats can be configured to suppress only certain items, types of items, or items with a certain tag. They can also whitelist the same things.
- Seats can now detect the shift control.
- lua script invocation whenever a control state changes:
	- `controlPressed(String seat, String control, float timeSince)`
	- `controlReleased(String seat, String control, float timeHeld)`
- Status effects applied by a loungeable entity to entities lounging in it will recognize the loungeable as its source.

---

### Status controller
- Status properties were changed from using a single jsonObject net element to instead be a netElementHashMap. This means that when a single value of a single property is changed, it isn't sending the entire jsonObject over the network! It only sends the individual property that was changed.
- Persistent status effects which don't exist are removed from the status controller, therefore not causing issues when mods uninstall.
- Persistent status effects can support having a source entity. This is unlike retail where the source is always the entity its running on, even if it makes sense for an external source (Like a lounge object applying a status).
- Status effects can have a list of `blockingStats`.
- Status effects can suppress tool use with a lua callback.
- Status effects have access to more lua callback tables:
	- `player`, `npc`, `monster` lua tables are accessible on their respective entity type.
	- `humanoid` is accessible on player and npc entities.
	- `parentAnimator` is identical to the `animator` callbacks, however it is for the main animator of the parent rather than its own.
	- `loungeable` is accessible on player, NPC, and monster entities.

---

### Tech controller
- Tech have access to more lua callback tables:
	- `player`, `humanoid`, `loungeable`
	- `parentAnimator` is identical to the `animator` callbacks, however it is for the main animator of the parent rather than its own.

---

### Items
- FireableItems get access to `player` and `humanoid` lua callbacks on players.
- ActiveItems get access to `player`, `humanoid`, `loungeable` lua callbacks on players.
- ActiveItems get access to `npc`, `humanoid`, `loungeable` lua callbacks on NPCs.
- `activeItem.setInventoryIcon()` can accept a table of drawables.
- `activeItem.aimAngleAndDirection()` won't cause flipping spam if the arm rotation center is in front of the entity centerline.
- Items can change in script their shortDescription and description.
- `ignoreWellFed` bool on consumable items to make them not have the `wellFed` status forcefully added to their config if they give food.

### Script Panes
- Canvases get the method `drawDrawables(JsonArray drawables, Vec2F position, [Vec2F scale])` to just directly slap drawables spat out by `world.entityPortrait` and the like.
- Canvases get click event now has `shiftHeld`, `ctrlHeld`, `altHeld` as the third, fourth and fifth args set into them.

---

### All Other Changes
- SBQ makes Starbound properly support high DPI displays.
- SBQ makes Starbound allow non-integer zoom levels:
	- The GUI has not been changed to account for this.
	- Non-integer zoom levels currently only trigger when player entity scale changes.
- `^rainbow;` text command to automatically cycle through colors for following characters.
- Json parsing supports trailing commas.
- `os.date()` added to allowed whitelist.
- Character Creation Screen has been improved:
	- Added support for modded species to use the pickers used for clothes for customization identity values instead.
	- If not defined, gender specific customization data will instead check if the respective data is defined in the species' config root, letting one not have to copy paste if a species doesn't have unique options per gender.
	- Every individual choice for identity values is stored in addition to the combined values retail uses.
	- Preserves the last customized values you had when you select a different species and then loads them back in if you select that species again.
	- Customization menu can be loaded during gameplay (Currently hardcoded to save values to a status property on players used by SBQ. Will be changed to a lua callback in the future).
- Character selection screen optimized to not reload every single character every time you scroll it.
- Patch loading has been streamlined and expanded upon:
	- Arrays of patches can contain arrays, which can contain both more arrays and objects for patch operations.
	- Arrays of patch operations can nest more arrays of patch operations infinitely, rather than the limit of only 2 levels in retail.
	- merge patch op.
	- append patch op.
	- inc patch op (adds value to value at path).
	- Put a string in the array rather than an object to set it as a reference file (Do not have files attempt circular references, it will break).
	- Use `fromRef` in patch operations rather than `value` to define a path to a value in the reference file.
- Asset sources can define a list of incompatible asset sources and mark them to not load when loading assets.
- GuiCtrl GuiAlt added to keybinds.
- `"scriptPaneMessage"` to send an entity message to a player's script pane:
	- Prepend `"scriptPaneMessage"` to `world.sendEntityMessage()`'s args. If a scriptPane doesn't receieve it, it will be processed as a normal entity message. Non-player entities just process it as a normal message.
	- Will not ever return an RPC promise.
- `player.funnyError(String funny)` for the people who decided to read through all of this, will throw a client exception to crash the player to the title screen with an arbitrary string.
- Extra GUI config values
	- bool `"dismissable"` set to false to make a GUI not close when one presses Esc.
	- float `"sourceRadius"` is used to override range from player to GUI source for keeping the GUI open. -1 makes it never dismiss by range.
- `spawnitem` command will only generate the item if it is an actual item. No more accidentally creating hundreds of Perfectly Generic Items.
- Generic items will store the ID and parameters of the item that failed to load within themselves when they convert, and will attempt to convert back into the relevant item whenever they are loaded if possible.
- Code handling damage team interactions was cleaned up.
- `Any` damage team added for hitting any team, including yourself, without needing PVP to be enabled.
- Drawable arrays in an item's `inventoryIcon` config/parameters will use the full drawable arguments (scale, rotation, etc.) rather than just position and image.
- Scripted entities will not run their update function until they have finished initializing.
- Persistent entities will no longer delete themselves if they experience a lua script error, instead just freezing in place.
	- NPCs and monsters not set to persistent will delete themselves, the same as they do in retail.
	- Vehicles and Projectiles which are not set to persistent will now properly delete themselves, as they don't in retail.
	- NPCs and Objects will use their engine-side speech bubbles to say `"Script Error!"` if they have experienced a script error.
- `universeServer.config` contains tables used for updating internal IDs if one were to change between versions of a mod.
	`updateItemType` is used for item type.
	`updateNPCType` for NPC types.
	`updateSpecies` for humanoid species.
- Interact request lua hooks across all entities are now consistent. In retail they all were inconsistent in the values sent to it. Now they all share the same values. There are some redundant values, but they're consistent now and receieve the same table:
	- EntityId `sourceId` entity Id of the interacting entity.
	- Vec2F `source` is the relative position of the object to the interacting entity.
	- Vec2F `sourcePosition` is the world position of the interacting entity.
	- Vec2F `interactPosition` is the world position of where the interacting entity was aiming when they interacted with the object.
- Objects now have an `imageKeys` parameter, which is used for setting tags in the image path for basic objects that don't use the networkedAnimator.
	- Object's tags are now applied during the place preview, unlike retail.
- Projectiles with a source entity will have their configured scale multiplied by the scale of their source entity when spawned, eg: tiny people will shoot tiny bullets, giant people will shoot giant bullets.
