Introduction to Tasks and Workers - X-Hax/SADXModdingGuide GitHub Wiki

What are Tasks?

Almost everything that happens or exists in the game (menus, characters, interactable objects, bosses etc.) is set up using structures called tasks (known previously as ObjectMaster). As the game runs, it loops through a list of tasks that tell it what should happen every frame. Every time a new object is created, the game adds a task for it that remains on the task list until the object is deleted, either manually or automatically (e.g. when you leave the level where the object is). Tasks can also add or remove their own sub-tasks (child tasks), which execute together with the parent task and are removed when the parent task is removed.

Although there are several variations of the task structure, the overwhelming majority of the game's objects use the so called Elemental Task. The structure for the Elemental Task consists of the following pointers:

struct task
{
  task *next;                    // Next task on the list (reserved)
  task *last;                    // Previous task on the list (reserved)
  task *ptp;                     // Parent task (reserved)
  task *ctp;                     // Child task (reserved)
  void (__cdecl *exec)(task *);  // Main ("Executor") function (runs every frame when the game is not paused)
  void (__cdecl *disp)(task *);  // Display function (runs every frame when the game is paused)
  void (__cdecl *dest)(task *);  // Destructor function (runs once when the task is destroyed)
  OBJ_CONDITION *ocp;            // Used by SET objects
  taskwk *twp;                   // Task Worker (see below)
  motionwk *mwp;                 // Motion Worker (see below)
  forcewk *fwp;                  // Force Worker (see below)
  anywk *awp;                    // Any Worker (see below)
  void *thp;                     // Thread (reserved)
};

When a new task is created, the game allocates memory to store the structure. There is also an option to allocate memory for additional structures used to store various kinds of data for the task. For example, a boss task might need a timer, and a task that renders a model would need to store a pointer to the model and its coordinates and rotation. The structures storing this data are called workers. The game uses a variety of workers for different purposes. Here are some examples of workers used by different tasks:

Name Old name Description
taskwk EntityData1/DataPtr1 The most common worker. Used to store position, rotation, scale, timers etc.
motionwk EntityData2/DataPtr2 Stores values such as speed, acceleration, friction, angular velocity etc.
forcewk ObjUnknownA/UnknownA_ptr Stores a callback function pointer, angular velocity and X/Y/Z speed.
anywk ObjUnknownB/UnknownB_ptr A general purpose worker that stores 16 bytes of data.
bosswk EntityData1/DataPtr1 Used by bosses, can store X/Y/Z speed/acceleration and vertex welding data.
playerwk CharObjData2/DataPtr2 Stores various parameters used by playable characters.
chaoswk Stores camera and collision-related data, a texture list, a boss worker, timers etc.
colliwk CollisionInfo Stores additional collision data if the object uses shape (CCL) based collision.
eventwk Stores face morph data, shadow size and other data for characters and cutscenes.
shadowwk Stores position, rotation and size for character shadow.

Some workers can store pointers to workers of other types. There are also unique workers for some specific things - for example, Egg Hornet's jet fumes have a dedicated worker that stores texture animation data and current animation frame, Egg Viper has its own worker, Adventure Field NPCs have their workers per level etc. However, the most commonly used worker is taskwk. The structure is set up as follows:

struct taskwk
{
  char mode;                                 // Generally used to switch between "actions" (e.g. walk, attack, disappear and so on)
  char smode;                                // Same as above but the value is signed
  char id;                                   // Internal index
  char btimer;                               // 1-byte timer (0 to 255)
  __int16 flag;                              // Status flags stored in 2 bytes (may be reserved if the object has collision)
  unsigned __int16 wtimer;                   // 2-byte timer (0 to 65535)
  taskwk_counter counter;                    // 4-byte union field that can store a float, a pointer, an array of four bytes, two shorts etc.
  taskwk_timer timer;                        // Same as above but all values are signed
  taskwk_timer value;                        // Same as above
  Angle3 ang;                                // X, Y and Z rotation
  NJS_POINT3 pos;                            // X, Y and Z position
  NJS_POINT3 scl;                            // X, Y and Z scale
  colliwk *cwp;                              // Collision worker pointer
  eventwk *ewp;                              // Event worker pointer
};

Creating Tasks

To create a new task, you need to call the function CreateElementalTask like this:

task* obj = CreateElementalTask(LoadObjFlags, index, YourObjectLoadFunction);

LoadObjFlags is an 8-bit integer that specifies flags which control memory allocation for workers. If you want your object to have a certain worker type, you need to include the flag bit for that worker.

enum LoadObj : __int8
{
  LoadObj_MotionWk = 0x1,
  LoadObj_TaskWk   = 0x2,
  LoadObj_ForceWk  = 0x4,
  LoadObj_AnyWk    = 0x8,
};

So if you need your task to have taskwk and motionwk, your LoadObjFlags would be 0x3, and if you want to load all workers, it would be 0xF. For a basic interactable object you only need to set up a taskwk, so 0x2 is sufficient in most cases.

index is an integer value from 0 to 7. It is used to control the order in which the game processes tasks, since they are split in 8 linked lists. Here are some examples of how the game uses them:

Index Used by
0 "Master" objects that control levels, Eggman race AI in Tails' levels
1 "Master" level object for Perfect Chaos, skybox objects, bosses
2 Level objects
3 "Stage clear" sound effect/player action, level objects
4 Explosion effects, lens flare
5 Cutscenes, level results screen
6 Spindash trail and other effects
7 Child tasks

In general, 2 or 3 should work just fine.