Most games that aren’t some degenerate variation on solitaire are substantially more fun when they contain some kind of player-reactive challenge element. Think for a moment about the classic italian plumber game. Would it be nearly as interesting without dangerous, unaccountable turtles flying around everywhere? I don’t think so!

Much like italian plumber game, Custom Hobo is also a game, although this one is about hobos. In addition to being about tricky platforming, Custom Hobo is about hobo combat:

Glorious, meaty combat.

Hobo combat that would be stultifyingly boring if every hobo was an inert object that didn’t try to fight back:

Might as well be solitaire. Yawn.

We’re getting a bit ahead of ourselves here, though. In order to have fighting hobos, we need to know what hobos are.

No, really, what *is* a hobo?

This post and its follow-up will answer (some of) your existential questions about hobos. Strap in.

First, an ode to pieces

At Piece Factory, pieces is our religion. In Custom Hobo, every physical object that exists in the game world is CHPhysPiece, as lovingly detailed in a prior devlog. As you may recall, CHPhysPiece inherits from CHPhysicsObject, which is, essentially, a “composite piece” with special logic around it:

class CHPhysicsObject : public CHRenderTarget
{
   ... important stuff

    private:
       std::vector<std::shared_ptr<CHPhysPiece>> m_pieces;
       std::vector<std::shared_ptr<CHPhysJoint>> m_joints;
}

CHPhysicsObject is a bag of pieces and joints, and it occurs to us that our NPCs want to be made of pieces and joints too. Sounds like a CHPhysicsObject is a good fit. To wit, we subclass it:

class CHActor : public CHPhysicsObject
{
   ... important stuff ...

 //Lots of actor specific logic!
  virtual void update(float dt) override;

   ... important stuff ...

  private:
   ... important stuff ...

    /// @brief Actions implemented at the actor level that need whole-body coordination
    std::vector<CHActorAction<CHActor>> m_implementedActions; 

    // In CHPhysicsObject. Can contain CHActorPiece!
    std::vector<std::shared_ptr<CHPhysPiece>> m_pieces;
}

and voila. We have CHActor, the backbone of Custom Hobo’s dynamic gameplay elements.

Actors

CHActor is an animate physics object that is made out of Special pieces called CHActorPiece, which is a subclass of CHPhysPiece. This means it’s not only a physical “thing” in the world, we can also extend it so it knows how to implement actions:

class CHActorPiece : public CHPhysPiece
{
  ... important composite piece related doings ...

  private:

  // The actions this piece can do, and what they do when activated!
  // Data locality fanpeople might be frothing at the mouth
  // at the implications of this by this point
  std::vector<CHActorAction<CHActorPiece>> m_implementedActions; 
}

Then, we encapsulate behavior expression in CHActorAction.

Pieces What Do Stuff

CHActorAction is a bundle of callbacks and state. It doesn’t know anything about when or why CHActor might be doing the action, but it knows it has to act when told.

This is all we really know
The extent of CHActorPiece's knowledge of the world

CHActorAction expresses the “how” the action is done, if you will:

template <typename Owner>
struct CHActorAction
{
    std::function<bool(Owner* owner)> enabledWhen = [](Owner* owner) { return true; };
    std::optional<std::function<void(Owner* owner, 
        const CHActionData& desiredAction, float dt)>> onStartAction;
    std::optional<std::function<void(Owner* owner, 
        const CHActionData& desiredAction, float dt)>> onContinueAction;
    std::optional<std::function<void(Owner* owner, float dt)>> onEndAction;
    std::optional<std::function<void(Owner* owner, float dt)>> onCooledDownAction;
    std::optional<std::function<void(Owner* owner, float dt)>> onNotDoingAction;
    ...
}

Through flagrant abuse of std::function, CHActorAction lets us describe behaviors without knowing where and when the behavior might happen.

For example: in a very similar way to how your gamey gams know how to ripple on command, hobo legs know how to jump when their feet are on the ground:

CHActorAction<CHActorPiece> jump;
jump.action = CHActorActionEnum::JUMP;
jump.onStartAction = [](auto piece, const auto& actionData, float dt) 
{
    //Simple as.
    if (piece->canJump())
    {
        piece->doJump();
    }
}
Fig 5. a hobo leg in its natural environment

and some heads know, among other things, how to scream properly (a seriously underrated skill in my opinion):

CHActorAction<CHActorPiece> expressPain;
expressPain.action = CHActorActionEnum::EXPRESS_PAIN;

// Pay no attention to the mysterious `actionData` We'll explain more on that in a future post.
expressPain.onStartAction = [](auto piece, const auto& actionData, float dt) 
{
    piece->saySomething("{shaking}AAAAAhhhhh!!!!{}", 1.f);
};

head->addAction(expressPain);
Very impressive lung capacity.

If you think about it hard enough, you could model your own meatbody this way. It’s essentially a bunch of individual parts that get coordinated. Your butt knows how to sit, but not why, or where. If your brain gets lazy, you’re sittin’, that’s all I’m saying. Your legs know how to flex to impress the locals, but they don’t know if there are locals around to watch.

It all just works

With CHActorAction, we achieve a really cool separation-of-concerns that thematically matches up perfectly with Custom Hobo’s core ethos, i.e. horrible maimings. In Custom Hobo, hobos get mangled and lose limbs constantly. The CHActorAction system means, if your hobo loses a leg to a horrible explosive barrel accident, they literally lose the ability to walk with that leg:

That leg ain't expressing a CHActorAction to CHActorActionEnum::MOVE anytime soon.

Stitching the pieces together

classDiagram

    direction LR

    CHRenderTarget <|-- CHPhysicsObject
    CHPhysicsObject <|-- CHActor
    CHPhysicsObject <|-- CHPhysPiece
    CHPhysPiece <|-- CHActorPiece
    
    CHActor *-- CHActorPiece
    CHActor *--  CHActorAction
    CHPhysicsObject *-- CHPhysPiece

    CHActorPiece *-- CHActorAction

As you can see, we now have a pretty convenient way to make fairly expressive NPCs just by taping these concepts together. A hobo is a CHActor with a big bag of meaty pieces, and those pieces have CHActorAction implementations with carte blanche to make the CHActors desires become reality in the physics world.

If that hobo wants to jump, you better believe that his legs are gonna try to jump if they can. If that biomaggot wants to shoot acid in your eyes, it’s gonna happen, don’t fight it. Got an armless hobo? That hobo can’t point, but boy can they (probably) still walk. Etc.

This post solves one mystery that no one was asking about (what and how is a hobo?), and creates at least one new one - how does a hobo decide when and where to act? Where does their “control impulse” come from?

Don’t try to guess, it’s not the Chrysler building. We’ll answer these questions and more in our next actor-related post.