Using Toilets

The devblog comes early this week because of Thanksgiving. Tell your family that you’re thankful for it.


Using Objects

There are many objects that a colonist uses throughout his life: beds, toilets, lockers, tables, chairs, toilets, etc.

When a colonist wants to use a toilet, we look for an available toilet (within walking distance) and then the colonist walks to the toilet and urinates.

That’s the high level overview.

The internal implementation is a bit more complicated.

(Last week I wrote a post about access positions, and how a colonist figures out where to go when using an object. You can read it here).

More Complicated Toilet

I skipped a bunch of steps that are involved in “using” a toilet.

Here’s what a colonist actually has to do when using a toilet (still from a high level).

Walk to toilet, sit down, face the front, close the door, urinate, open the door, stand up, walk to exit.

Colonist unabashedly urinating for a devblog reader

Colonist unabashedly urinating for a devblog reader

Absence of Toilets

What should happen when a colonist needs to urinate, but no toilet is available?

Some possible answers might be:

  1. Colonist waits to urinate until a toilet is available (he’ll wait forever)

  2. Colonist pees his pants (maybe after waiting for a bit)

  3. Colonist urinates somewhere else

We strive to have a complex world in which one object can be used a thousand different ways. So of course we’re going to make the colonist pee somewhere else.

That poses a problem, though, if the colonist isn’t looking for a toilet, what is he looking for?

Object Purpose

In solving the urination crisis it became obvious that colonists almost always have a reason for doing something, and that multiple objects can be considered valid for each reason.

When a colonist needs to urinate, he’s not looking for a toilet. He’s looking for a “urination” object.

Some valid urination objects might be:

  1. Toilet

  2. Floor

  3. Bed

  4. Potted Plant

  5. Swimming Pool

Priority

A colonist should pee in a toilet before he pees on the floor, and he should probably pee in a potted plant before he pees in a bed.

So we gave priorities to each object.

Internally, it’s sort of simple. The colonist just looks for each object in the specified order, if no object is found, he looks for the next object.

So a colonist will first look for a toilet. If none are found, he looks for a potted plant. If no potted plants are found, he looks for a floor. If no floors are found, the AI state fails.

Personality

The objects that a colonist considers valid can change throughout a colonist’s life. It would be lame if every colonist always used the same objects with the exact same priority.

A fancy-pants, high status colonist might refuse to ever sleep on the floor. A colonist that becomes scared of toilets might always pee on the floor. A colonist who is really hungry might eat something off the floor.

Non-colonist entities might need to “sleep”, “eat”, and “urinate” as well.

A cleaner robot might associate “eating” with a recharge station and “urinating” with an oil changing unit.

This also opens up the possibility of cleaner robots malfunctioning and sleeping in a colonist’s bed or other weird behavior.

Injecting AI Actions

At a high level, using a toilet or potted plant for urination is relatively simple:

  1. Find object

  2. Go to object

  3. Use object

Unfortunately, the actual “using” of a potted plant is somewhat different from a toilet (you have to close a toilet door, for example).

To solve this, I allow objects to inject AI actions into a colonist when they use an object. Basically, objects can completely control a colonist once the colonist “uses” them.

This allows us to create entirely new objects, with entirely custom “uses”, without modifying any existing code.

Example Injection

Here’s the code for “using” a bed.

(I removed a parameter cast in order to simplify the example, but besides that it’s literally copy and pasted from the BedComponent).

The 3 things that happen when you use a bed are:

  1. Mount bed (get into bed)

  2. Sleep, until not tired (recheck every frame)

  3. Dismount bed (get out of bed)

 
void OnObjectUsed(AgentEventParameter parameter)
{
    LoadedState newState = new LoadedState("GeneratedBedState");
    newState.allActions = new ListOfObjects<LoadedAction>(5);
 
    newState.allActions[0] = new LoadedAction(AIActionType.MountClaimedObject, 1, -1);
    newState.allActions[1] = new LoadedAction(AIActionType.Sleep, 3, 2);
    newState.allActions[2] = new LoadedAction(AIActionType.WaitForFrame, 1, 1);
    newState.allActions[3] = new LoadedAction(AIActionType.DismountClaimedObject, 4, 4);
    newState.allActions[4] = new LoadedAction(AIActionType.Success, -1, -1);
 
    generatedState.AppendState(newState);
}
 

AIActionTypes are the simplest “verb” that a colonist can perform. Common actions that we use are:

  • Drop Off Items

  • Pickup Items

  • Rotate

  • Go Somewhere

  • Mount / Dismount

  • Wait

Queuing State

The only other code that’s required is the code that actually tells the colonist to find a bed. (For simplicity, I removed the parameters for state priority and category)

 
PassedData passedData = new PassedData(PassedDataType.AIReason, AIReason.Sleep);
QueuedState queuedState = new QueuedState(StateType.UseObjectForReason, passedData);
 
colonist.CallEvent(EventName.OnStateQueued, new OnStateQueuedParameter(queuedState));
 

The important part to focus on is the first line.

This is where I’m passing in that the AIReason is “Sleep”. The colonist would use a toilet if I passed in “Urinate” instead of “Sleep”.

In the second line I’m creating a new state of “UseObjectForReason”. This state is used wherever a colonist needs to use something.

(I say “colonist” because it’s simpler. In reality, any GameObject Agent could use anything)

Multiple Uses of Same Object

Some objects can be used for multiple purposes.

A chair can be used for sleeping, sitting, and eating.

This poses a minor issue, because a colonist needs to do something slightly different when eating in a chair instead of sleeping in a chair.

To solve this we have to tell every object what its valid “use reason” are and what to do when a colonist uses the object for a specific reason.

So when a chair is used for sleeping we play the colonist sleeping animation. When a chair is used for eating we make the colonist eat. In both cases we make the colonist visually sit in the chair.


Claimable Objects

We don’t want multiple colonists to use the same toilet at the same time. We also don’t want 3 colonists waiting to use the same toilet when other toilets are available.

To solve this issue, colonists “claim” objects the moment that they decide to use them. This prevents any other colonist from using that same object.

The objects become unclaimed once the colonist is finished using them (they’re also unclaimed if the colonist fails to use the toilet, for any reason).

Objects can allow multiple claimers. The Food Counter allows this, so that that up to 5 colonists can wait in line at the same counter.

Minor Issue

A claimed toilet basically doesn’t exist at all to a colonist.

This can pose a problem if all toilets are currently being walked towards, because they’ll all be claimed (even though they all appear to be available). Any additional colonists who needs to urinate will think that no toilets exist and might pee on the floor.

We haven’t implemented a solution to this, but we would have to do something where a colonist can distinguish between an object existing and an object being claimed.

Personally, I don’t think this is necessary. It’s much better to simply make a colonist pee on the floor only when they really need to pee. This would provide the colonist with several opportunities to use a toilet before the floor is ever used.

Conditional Claimable Objects

Objects can conditionally be considered valid by individual colonists.

This is frequently used when a colonist is looking for food.

Meatballs

When a colonist is hungry he might be looking for either “bread” or “meatballs”.

There could be 3 objects with food. All of them are “food objects”, but only the objects with “bread” or “meatballs” should be considered valid.

To solve this, we pass in the food (items) that the colonist is looking for. Each food object checks its own internal container to figure out if it has whatever the colonist wants. (We then have to reserve the items so that another colonist doesn’t take them). Any objects without enough bread or meatballs are consider invalid

Personality, Again

The validity check is performed in both the colonist and the target object. So the colonist can also conditionally consider objects valid. (in the previous example, the food object was determining if it should be valid, the colonist wasn’t making the decision)

A colonist could consider any bed with the color red to be invalid. You could prevent a specific colonist from ever using a specific toilet. Or you could have a fancy colonist that only uses high quality furniture.

Other Examples

I mainly talked about urination, but there are all sorts of Reason-Object relationships

Sleeping

  1. Bed

  2. Couch

  3. Chair

  4. Toilet (because it’s funny)

  5. Floor

Bed.png

Eating

  1. Chair, next to table

  2. Table, standing

  3. Chair, not next to table

  4. Toilet (because it’s still funny)

  5. Floor

Table.png

Finding Food

  1. Food Counter

  2. Locker, mine

  3. Fridge

  4. Vending machine (we don’t have those yet)

Storing Personal Objects

  1. Locker

  2. Shelf

  3. Table

Locker.png

Is That It?

I think that’s about all there is for “using” objects.

To summarize:

  1. Colonists use an object for a “reason”

  2. Reasons are converted into specific objects

  3. Specific object is found and used

Thanks for reading, everyone.

Have a nice Thanskgiving, if you’re celebrating. Otherwise, have a nice Thursday.

-Tyler