Skip to content

Implementing Objects

MarkL edited this page Feb 25, 2014 · 10 revisions

A guide to implementing objects.

Prerequisites

This guide assumes that graphics (animations) already exist for the object, as currently the tools and guidelines do not exist for implementing new animations. It is also assumed that you have a copy of the AnimationViewer to view and find animations.

Creating the Lua descriptor file

The first thing which needs to be done is to create a Lua file which contains details of the object. Create a new Lua file in the CorsixTH\Lua\objects, and paste in the following minimal template. Convention dictates that filenames be lowercase with underscores to separate words (for example, my_new_object.lua rather than My New Object.Lua).

--[[ Copyright (c) <year> <your name>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. --]]

local object = {}

return object

The license declaration is not a technical requirement, but it is required if you want to commit the object to the CorsixTH code repository. The return object line must be the last line of the file, and the local object = {} line must be the first (apart from the license declaration), so anything you add to this file must be inserted between these two lines.

Basic object properties

There are some fairly simple properties which must be specified for all objects. These properties as set as fields on the object table.

Property Data type Example How to choose the correct value
id string "my_new_object" This should be a string which uniquely identifies the object. By convention, this value is the same as the filename, but without the .lua extension.
thob number 42 If the object was present in the original Theme Hospital, then take the value from the table on the [wiki page. If the object was not present in the original, then choose a unique integer between 1 and 255 inclusive which is either marked as unused on THObjects, or not listed at all.
name localised string _S(2, 43) Find the line --self:dumpStrings"debug-strings.txt" in CorsixTH\Lua\app.lua, uncomment the line (by removing the two hyphens at the start of the line), and then run CorsixTH. This will create a file called debug-strings.txt along-side CorsixTH.exe. Open this text file, and find the correct name in section 2. Commonly, the correct identifier for objects will be one greater than the THOB value.
ticks boolean false Start by setting this value to false. After you've implemented the idle_animations property, come back and change this value to true if any of the idle animations contain multiple frames.
build_cost number 1000 Take the correct value from the table on the wiki page, or run the original Theme Hospital and find the cost, or decide on a cost which seems reasonable.

After implementing these properties, the Lua file should be looking something like the following (after the license declaration):

local object = {}
object.id = "my_new_object"
object.thob = 42
object.name = _S(2, 43)
object.ticks = false
object.build_cost = 1000
return object

Simple animation properties

The simple animations for an object are the build preview animation and the idle animations.

The build preview animation (a numeric property called build_preview_animation) is the graphic used in the furnish corridor and/or purchase items dialog. This animation is aligned differently to normal animations. For example, for the GP's cabinet, animation #80 shows the cabinet aligned to a tile, whereas the build preview animation for the GP's cabinet is #5054, in which the cabinet is not aligned to a tile. The THObjects page contains build preview animation numbers for most objects. If it doesn't list a value, then you'll have to look through every animation to find one of the object with the correct off-tile alignment.

Idle animations are slightly more complex, as there can be up to four idle animations (one for each compass direction). As stated on the CoordinateSystems page, compass directions translate into the isometric viewpoint as in the following image:

directions

The property for idle animations is a table called idle_animations containing between one and four numeric properties called north, east, south, and/or west. To determine the values for these properties, look through every animation and find animations where the object is not being used, and the animation is tile-aligned. By convention, if there is only one idle animation, then this is called north, even if looks more like another direction. Remember that if any of the idle animations are actually animated (i.e. have more than one frame), then object.ticks must be set to true, or only the first frame will be shown ingame.

After implementing these properties, the relevant section of the Lua file should be looking something like:

object.build_preview_animation = 906
object.idle_animations = {
  north = 174,
  east  = 176,
  south = 170,
  west  = 172,
}

Orientations

An orientation table provides information on how an object behaves when facing in a particular direction. The number of different directions you can have an object facing ingame is the same as the number of orientation tables. The terms "orientation" and "orientation table" are used interchangeably from here on.

Like idle animations, there can be between one and four orientations for each object. Slightly non-intuitively, there can be more orientations than there are idle animations, and the orientations don't need to be in the same directions as the idle animations. As outlined in the following table, an orientation will try and use the animations in the same direction, and if they are not present, then it will mirror (flip left and right sides) the animations of another direction:

Orientation Preferred Animations Backup Animations
north north west, mirrored
east east south, mirrored
south south east, mirrored
west west north, mirrored

A common pattern for objects with only one idle animation is to specify that animation as both the north and south animation, and then provide north and east orientations. This will cause the north orientation to use the animation normally, and the east orientation to use the animation mirrored.

The only compulsory property on an orientation is footprint. This is a list of tiles which the object occupies. In a footprint, tile {0,0} is the origin tile of the animation, which in the AnimationViewer is the tile with a blue outline. The following image gives some examples of footprint tile co-ordinates:

footprint_coords

There are two different kinds of footprint tile:

Name Code Flags required to place object Flags set when object placed
Normal {x, y} buildable and passable not buildable, not passable
Passable {x, y, only_passable = true} passable not buildable

Additionally, two more flags are available:

Name Example code Effect
invisible {x, y, only_passable = true, invisible = true} The tile is still a part of the footprint, but the tile it is on is not highlighted to the player when placing the object.
optional {x, y, optional = true} When many tiles in the same footprint has this flag set only one of said tiles' criteria for placement need to be met to be able to place the object.

A footprint is then a list of footprint tiles, for example:

object.orientations = {
  north = {
    footprint = { {-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {0, 0, only_passable = true}, {1, 0} },
  }
}

After footprint, there are a number of optional properties on an orientation table:

Property Data type Default value Description
use_position table or string {0, 0} The footprint tile which a humanoid must be standing on to begin using the object. The string "passable" can be specified instead of a position, which causes the first passable footprint tile to be used. The specified tile should be a passable footprint tile adjacent to a normal footprint tile.
use_position_secondary table nil If the object can be used by two humanoids simultaneously, then the footprint tile which the second humanoid must be standing on to begin using the object.
use_animate_from_use_position boolean false Should be set to false (the default) if the origin of the usage animations is the same as the origin of the idle animations (i.e. see animations 82 and 90), and set to true if the origin of the usage animations corresponds to use_position on the idle animations (i.e. see animations 80 and 88).
finish_use_position table use_position The footprint tile which a humanoid finishes on after using the object. If this is the same tile which they started on, then this property need not be specified.
finish_use_position_secondary table use_position_secondary If the object can be used by two humanoids simultaneously, then the footprint tile which the second humanoid finishes on after using the object. If this is the same tile which they started on, then this property need not be specified.
finish_use_orientation_secondary string nil If the object can be used by two humanoids simultaneously, then the orientation the secondary user should be looking at the end of object usage. Can be "north", "east", "south" or "west". This only needs to be specified if the secondary user needs to be visible again before end of animation and the desired orientation is different from his starting orientation.
animation_offset table {0, 0} The number of pixels (not tiles) to offset the object by.
added_animation_offset_while_in_use table {0, 0} The additional number of pixels to offset the object by when it is in use.
render_attach_position table {0, 0} The footprint tile which the object should be attached to for rendering purposes. Failure to set this value correctly will result in graphical glitches. To split the rendering of the object over several tiles (which is less efficient than doing it all on one tile, but often useful), specify a list of tiles (e.g. {{0, 0}, {0, 1}}) for this setting. See the Rendering wiki page for full details.
early_list boolean false Should be set to true if the rectangle formed by the render attach tile and the normal footprint tiles to the north and west of the render attach tile is wider than it is long (for example, animation 2124 when mirrored). Failure to set this value correctly will result in graphical glitches. See the Rendering wiki page for full details.
early_list_while_in_use boolean early_list Should be set to true if the rectangle formed by the in-use render attach tile and the normal footprint tiles and in-use passable tiles to the north and west of the in-use render attach tile is wider than it is long (for example, animation 3700 when mirrored). Failure to set this value correctly will result in graphical glitches. See the Rendering wiki page for full details.

The completed orientations part of the object should look at least slightly similar to this:

object.orientations = {
  north = {
    footprint = { {0, 0}, {0, 1, only_passable = true} },
    use_position = "passable",
    use_animate_from_use_position = true,
  },
  east = {
    footprint = { {0, 0}, {1, 0, only_passable = true} },
    early_list_while_in_use = true,
    use_position = "passable",
    use_animate_from_use_position = true,
  },
}

Making the object purchasable

There are two ways to make the object purchasable, corresponding to the two different ways in which an object can be purchased.

To allow the object to be placed in the corridor, and hence purchasable from the furnish corridor dialog (which can be useful for debugging), set the corridor_object property on the object to any number. The order in which objects appear in the furnish corridor object list is controlled by the value of the corridor_object property - smaller values go at the top of the list, larger values toward the bottom.

To allow the object to be placed in a room, and hence purchasable during room creation, the object.id property must be inserted into the room's objects_needed or objects_additional list.

Making the object usable

To allow the object to be used via the standard use_object or multi_use_object actions, there are some additional properties which must be set on the object. If the object is used via a different action, then disregard this section.

If the object is used by a single humanoid at a time - and hence is used via use_object - then the usage_animations property must be specified for the object. usage_animations is a multi-level table of animation numbers organised by direction, then phase, then user. The directions must correspond to the directions of the idle animations. The phases can be any of:

  1. begin_use
  2. begin_use_2
  3. begin_use_3
  4. in_use (this phase can, if required, repeat multiple times)
  5. finish_use
  6. finish_use_2
  7. finish_use_3

The user is one of the humanoid class names from CorsixTH\Lua\humanoid.lua. The completed usage_animations property might then look like:

object.usage_animations = {
  north = {
    begin_use = {
      ["Standard Male Patient"] =   96,
      ["Chewbacca Patient"    ] = 3744,
    },
    in_use = {
      ["Standard Male Patient"] =  146,
      ["Chewbacca Patient"    ] = 3760,
    },
    finish_use = {
      ["Standard Male Patient"] =  216,
      ["Chewbacca Patient"    ] = 3752,
    },
  },
  east = {
    begin_use = {
      ["Standard Male Patient"] =   98,
      ["Chewbacca Patient"    ] = 3746,
    },
    in_use = {
      ["Standard Male Patient"] =  148,
      ["Chewbacca Patient"    ] = 3762,
    },
    finish_use = {
      ["Standard Male Patient"] =  218,
      ["Chewbacca Patient"    ] = 3754,
    },
  },
}

It is worth noting that instead of a single animation number, a table containing a list of animation numbers can be given instead. In this case, a single animation from said list is chosen at random.

If the object is used by two humanoids simultaneously (typically by a staff member and a patient) - and hence is used via multi_use_object - then the multi_usage_animations property must be specified for the object. multi_usage_animations is a multi-level table of animation numbers organised by user pair, then direction, then phase. The user pair is a string consisting of two humanoid class names separated by a hyphen (for example "Standard Male Patient - Doctor", which makes the patient the primary user, and the doctor the secondary user). The directions must correspond to the directions of the idle animations. The phases can be any of:

  1. begin_use
  2. begin_use_2
  3. begin_use_3
  4. in_use (this phase can, if required, repeat multiple times)
  5. finish_use
  6. finish_use_2

If you don't have eagle eyes, then the difference between these phases and the phases for usage_animations, is the absence of the finish_use_3 phase.

Normally, the animation will include both humanoids, however there are some cases where one animation includes the object and the primary user, and there is a separate animation for the secondary user. In this case, along-side the phases can go a table called secondary, which contains phase / animation number pairs for the secondary user. In extreme cases, some of the usage animations will include both humanoids, and some will contain only one. In these cases, just specify secondary animations for the phases which require them.

The completed multi_usage_animations property might then look like:

object.multi_usage_animations = {
  ["Stripped Male Patient - Doctor"] = {
    north = {
      begin_use   = 1018,
      in_use      =  652,
      finish_use  = 3282,
      secondary = {
        in_use     =  656,
      },
    },
    south = {
      begin_use   = 1018,
      in_use      =  652,
      finish_use  = 3282,
      secondary = {
        in_use  = 656,
      },
    },
  },
  ["Stripped Female Patient - Doctor"] = {
    north = {
      begin_use  = 3008,
      in_use     = 2840,
      finish_use = 4556,
      secondary = {
        in_use     =  656,
      },
    },
    south = {
      begin_use  = 3008,
      in_use     = 2840,
      finish_use = 4556,
      secondary = {
        in_use = 656,
      },
    },
  },
}