068 - A Fine Foundation

May 24, 2020

Most of my coding time this week went to working on the underlying systems that power the Tutor of War short quest.

This was the type of workstream where I needed to build quite a bit of new functionality the first time around that I'll be able to re-use in future implementations.

In this case there was a lot of work around cutscenes and interactive dialogue and some of the other things that power exchanges with other entities in the game world.

It took me about three weeks to get the integration tests for the Tutor of War quest passing. I underestimated the amount of work that was needed.

I'm excited though, because all of that work has laid a fine foundation for future quests.

Need to work on the user interface - but the foundation for interacting with NPCs is in place! This video just shows dialogue nodes, but there are a bunch of fields in the InteractiveSequenceNode that allow you to progress through an interaction in some interesting ways.

Exporting woes

I started off the week trying to get the latest iteration of the human mesh into the game.

This was slowed down a bit by some issues I was having with exporting my rigify rig.

I eventually got things working and added some documentation on troubleshooting rigify rigs to my exporter.

The human model lacks a unique feel - so I'll be remaking it in June.

As I get more comfortable with modeling I'm starting to gain the mental capacity to begin to think about art direction. We'll see how that plays out over the coming months.

Art

I'm working on a cat model that I'll finish up this week.

Work in progress cat Started working on a cat model. Still need to model the feet and then I'll rig and texture it. Hmm... this looks like a dog. Alas.

After that I want to start adding some scenery to the world.

I'll likely continue to create and re-make things as my art skills improve and I begin to become capable of creating things that look good.

Equipping

Added support for equipping items to the game server.

I still have to add an interface for this on the client side.

I'll start with a quick and dirty interface and then improve it over time.

/// Equip an item from the player's inventory. /// /// If there is already equipment in that slot it will be de-quipped. /// /// Unless both the item and equipment are the same, in which case their quantities will be /// summed. pub fn equip_inventory_item( entity: Entity, icon: IconName, sys: &mut ClientRequestSystemData, ) -> Result<(), EquipInventoryItemError> { let eid: EID = entity.into(); let inventory = sys.inventory_comps.get_mut(entity); let inventory = inventory.ok_or(EquipInventoryItemError::NoInventoryComp { eid })?; let equipment = sys.equipment_comps.get_mut(entity); let equipment = equipment.ok_or(EquipInventoryItemError::NoEquipmentComp { eid })?; let equip_id = EquipmentId::from_inventory_item(icon) .ok_or(EquipInventoryItemError::ItemNotEquippable { icon, eid })?; inventory .get(&icon) .ok_or(EquipInventoryItemError::ItemNotInInventory { icon, eid })?; let slot = inventory.slot(icon).unwrap(); let item = inventory.remove_item(&icon).unwrap(); let previously_equipped = equipment.insert_slot_maybe_combine( equip_id.slot(), Some(Equipped::new(equip_id, item.quantity)), ); if let Some(previously_equipped) = previously_equipped { if let Some(previous_icon) = previously_equipped.equipment_id().to_inventory_item() { inventory .add_item(previous_icon, previously_equipped.quantity()) .map_err(|ie| EquipInventoryItemError::Inventory { eid, ie })?; inventory .move_to_slot_maybe_swap(&previous_icon, slot) .map_err(|ss| EquipInventoryItemError::SwapSlot { eid, ss })?; } } Ok(()) }

Logging

I was talking to a former co-worker about my plans for monitoring game server performance by storing metrics about each game tick in a Postgres column.

I'm very glad that I brought it up because he pointed me towards a much better approach to something like that - extracting metrics from logs!

I'd coincentally been beefing up the game-server's structured logging setup over the last week or two, so this suggestion was well timed and I decided to act on it.

After some hours of head scratching and learning, I now have datadog pulling logs from Cloudwatch Logs and a few dashboards set up to monitor different metrics.

Datadog dashboard Created a dashboard that I'll use to monitor the production game server's performance.

Right now the game tick interval is 600 milliseconds, so we need every game tick to always take less time to execute than that.

Eventually I'll set up alerting for any time a single tick gets too close to, say, 400 milliseconds - which will hopefully spur me to drive towards more and more performant code.

#![deny(unused_must_use)] use slog::{Key, Record, Serializer, KV}; /// Information that we log about every game tick #[derive(Debug, Clone, Copy)] pub(super) struct TickContext { /// The number of milliseconds that the tick took pub(super) duration_millis: f32, /// The number of players that were online at the end of the game tick. pub(super) end_player_count: u16, } impl KV for TickContext { fn serialize(&self, _record: &Record, serializer: &mut dyn Serializer) -> slog::Result<()> { serializer.emit_f32(Key::from("millis"), self.duration_millis)?; serializer.emit_u16(Key::from("players"), self.end_player_count)?; Ok(()) } } #[cfg(test)] mod tests { // ... snippet ... }

Other Notes and Progress

  • Introduced the landon parse command to the Landon CLI to help with processing exported data from Blender.
    • I'm not currently using this since I use landon's API instead of its CLI for the most part at this time, but it should be useful for people that are trying out the tool.

Next Week

  • Create the inventory user interface

  • Create the equipment user interface

  • Finish the cat model and start working on scenery

  • Polish Tutor of War quest

  • Implement the ability to prevent movement to certain game tiles. Right now you can walk through anything.

  • Start planning username selection


Cya next time!

- CFN