050 - Let there by life

January 12, 2020

In last week's journal entry I wrote about laying the foundation for the autonomous NPCs.

This week I picked up where we left off and got the autonomous-client crate in our cargo workspace sending up requests to the game server to move.

The logic is rudimentary right now - just randomly requesting to move to some area within the areas that their AreaConstrainedComp says that they're allowed to visit - but over time we'll introduce and iterate on a goal oriented action planning based system.

Seeing the snail move brought a smile to my face. It was almost like witnessing life being born.

A cool side effect of the de-coupling of the autonomous NPCs that I'm realizing is that in the future if we distribute autonomous NPC processing across machines we can deploy updates to our NPCs without needing to restart the game server.

This would be powered by a process similar to how we deploy the web-client, where we would test the new autonomous NPC client against a copy of the current production game-server static library.

This week was cut short

I didn't get as much done this week and I might've liked as I was visiting a friend over the weekend - and the weekend is usually my prime time.

I left off in the middle of normalizing some of our logic around TileBoxes to be able to re-use our definitions of regions of the game across different contexts.

This refactoring was prompted by adding an AreaConstrainedComp to Pookie.

The AreaConstrainedComp defines all of the areas that an entity is allowed to move around in.

I wanted to constrain him to his house - but his house was defined in our scenery.yml - so I would've had to duplicate that TileBox into Pookie's area constrained component.

The data structure is small, only a few fields:

pub struct TileBox {
    left_x: u32,
    bottom_y: u32,
    total_x: u8,
    total_y: u8,
}

But the problems would come whenever we wanted to move things around.

If we were to move Pookie's house we would need to move both the scenery.yml definition of his house's TileBox and then also the TileBox that was powering his AreaConstrainedComp.

Instead we're moving to a system where we have a TileBoxName enum - and all of our different configurations such as scenery locations and area constraints and any other TileBox based future configuration can all share the same infrastructure.

We'll maintain one map of TileBoxName -> PredefinedTileBox which we then deserialize and convert into a map of TileBoxName -> TileBox.

The PredefinedTileBox just allows us to define a TileBox relative to another TileBox. So that if we move one TileBox all of its other relative TileBoxes will also move.

// Here's a look at the `PredefinedTileBox`.

/// Powers being able to define the data for all of our `TileBoxName`s. We recursively process `::Relative`
/// definitions to determine the final `TileBox`.
pub enum PredefinedTileBox {
    /// Most commonly - you will place a TileBox relative to another TileBox.
    /// So a desk might be placed relative to the house it's in.
    /// A chair might be placed relative to the desk that it is slid under.
    ///
    /// When we traverse our map of `TileBoxName -> PredefinedTileBox` we convert relative boxes
    /// into TileBox.
    ///
    /// This works because predefined boxes will not be moved at runtime, so once we determine
    /// the position once it'll always be correct.
    Relative {
        /// This TileBox will be placed relative to the tile box of TileBoxName
        relative_to: TileBoxName,
        /// Translate the entire box along the X axis. Use the slide the entire box left/right.
        #[serde(default)]
        all_trans_x: i16,
        /// Translate the entire box along the Y axis. Used to slide the entire box up/down.
        #[serde(default)]
        all_trans_y: i16,
        /// Translate just the top row along the Y axis.
        /// Used to make the box taller (positiive translation) or shorter (negative translation)
        #[serde(default)]
        top_trans_y: i16,
        /// Translate just the bottom row along the Y axis.
        /// Used to make the box shorter (positive translation) or taller (negative translation)
        #[serde(default)]
        bottom_trans_y: i16,
        /// Translate just the bottom row along the X axis.
        /// Used to make the box more narrow (positive translation) or wider (negative translation)
        #[serde(default)]
        left_trans_x: i16,
        /// Translate just the bottom row along the X axis.
        /// Used to make the box more narrow (negative translation) or wider (positive translation)
        #[serde(default)]
        right_trans_x: i16,
        /// A cache of the calculated location for this scenery.
        ///
        /// When we recursively determine the absolutely positioned location of relatively positioned
        /// scenery we use this to store positions so we don't have to calculate them twice
        #[serde(skip)]
        cached_absolute_box: RefCell<Option<TileBox>>,
    },
    /// This TileBox has a location independent of all other TileBoxes. That is to say that there
    /// is nothing that will move that will cause this TileBox to move.
    Absolute(TileBox),
}

I was in the middle of these changes was my train arrived - so I'll have to pick up on Monday.

Should just be a few hours of cleanup and getting tests passing again. Already looking much more flexible.

This will be especially useful when we eventually have a world editor - as when we move things around we won't need to move all of the things that are placed relative to it. They'll all share the same underlying positioning system.

Next Week

Next week I'll be working on the ability to train hitpoints.

I'll be introducing the combat system, persisting remaining hitpoints to the database and adding a few components that will be needed for the first hitpoints training mechanic.

It'll be fun to have a discipline to train - we're starting to add more and more real gameplay.


Cya next time!

- CFN