Jump to content
  • Advertisement


  • Content Count

  • Joined

  • Last visited

  • Days Won


Blog Entries posted by Eck

  1. Eck
    I'm Chris Eck, and I'm the tools developer at HBS for the Battletech project. I've recently been given permission to write up articles about some of the things I work on which I hope to post on a semi regular basis. Feel free to ask questions about these posts or give me suggestions for future topics. However please note, I am unable to answer any questions about new/unconfirmed features.
    I've been tackling Unity 2018 upgrade issues as they come my way, and I've been chugging along with my data driven enumeration refactors. I've done four or five now so I'm starting to gain a deeper understanding of the process. I find all references to the hard coded enum and identify where any code decisions are being made. Then I create data fields to drive that logic instead. After I have all those bits defined, I create the table/columns in the database and then populate it.
    Creating the C# classes and functions that interface with the database is not quite copy/paste grunt work. There needs to be a class with the fields, and functions to select, insert, and update those rows in the database. But the fields for the different enumerations are all different. The work isn't difficult, but it's time consuming and a bit fiddly. When I started writing this final set I told myself, "Self... Writing this code is boring and repetitive... There has to be a better way..."

    Enter Code Generation
    I had several completed examples so I the kinks already worked out. I used all the AmmoCategory classes and functions and just copied them into a text file. I then replaced anything that had AmmoCategory specific items with some keys that I could find and replace. I ended up with a template file that looked like this:
    public class %ClassName% { %FieldList%} #region %TableName% public static List<%ClassName%> SelectAll%PluralTableName%(this MetadataDatabase mdd) { string query = "SELECT %TableAlias%.* FROM %TableName% as %TableAlias%"; List<%ClassName%> %LowerTableName%List = mdd.Query<%ClassName%>(query).ToList(); return %LowerTableName%List; } public static %ClassName% Select%TableName%ByID(this MetadataDatabase mdd, int %LowerTableName%ID) { %ClassName% %LowerTableName%Row = mdd.Query<%ClassName%>( "SELECT * FROM %TableName% WHERE %TableName%ID=@%TableName%ID", new { %TableName%ID = %LowerTableName%ID }) .FirstOrDefault(); return %LowerTableName%Row; } public static %ClassName% Insert%TableName%Value(this MetadataDatabase mdd, %TableName%Value %LowerTableName%) { mdd.Execute("INSERT INTO %TableName%" + " (%InsertFieldList%)" + " values(%InsertValueList%)", new { %InsertAnonymousTypeList% }); %ClassName% %LowerTableName%Row = Select%TableName%ByID(mdd, %LowerTableName%.ID); return %LowerTableName%Row; } public static %ClassName% Update%TableName%Value(this MetadataDatabase mdd, %TableName%Value %LowerTableName%) { mdd.Execute("UPDATE %TableName% SET" + " %UpdateFieldList%" + " WHERE %UpdateWhereFieldList%", new { %InsertAnonymousTypeList% }); %ClassName% %LowerTableName%Row = Select%TableName%ByID(mdd, %LowerTableName%.ID); return %LowerTableName%Row; } public static %ClassName% InsertOrUpdate%TableName%Value(this MetadataDatabase mdd, %TableName%Value %LowerTableName%) { InsertOrUpdateEnumValue(mdd, %LowerTableName%); %ClassName% %LowerTableName%Row = Select%TableName%ByID(mdd, %LowerTableName%.ID); if (%LowerTableName%Row == null) { %LowerTableName%Row = Insert%TableName%Value(mdd, %LowerTableName%); } else { %LowerTableName%Row = Update%TableName%Value(mdd, %LowerTableName%); } return %LowerTableName%Row; } #endregion // %TableName% Then I defined my WeaponCategory table in the database (that's the new one I was working on). The table has the various column names and their data types defined and Sqlite has a PRAGMA command that can pull that information for you. Once I have that data, I can start with my substitution.
    Here's what the code generating function looks like
    /// <summary> /// Given a table name, pull that table's info from the database and generate code for /// the DTO, Select, Insert, and Update functions. /// </summary> /// <param name="tableName">The name of the table to generate code for</param> /// <param name="pluralTableName">The pluralized name. I didn't feel like writing code to figure out when to drop the y and ad ies. :P</param> /// <returns>The generated code for the DTO, Select, Insert, and Update functions.</returns> public static string GenerateCode(string tableName, string pluralTableName) { MetadataDatabase mdd = MetadataDatabase.Instance; string lowerTableName = char.ToLower(tableName[0]) + tableName.Substring(1); string className = string.Format("{0}_MDD", tableName); string lowerClassName = string.Format("{0}_MDD", lowerTableName); List<SqliteTableInfo> columnInfoList = mdd.GetTableInfo(tableName); string tableAlias = ""; for (int i = 0; i < tableName.Length; ++i) { char c = tableName[i]; if (char.IsUpper(c)) tableAlias += char.ToLower(c); } // Load up our template file. string generatedCode = File.ReadAllText("SqliteCodeGen\\CodeGenTemplate.txt"); StringBuilder fieldList = new StringBuilder(); StringBuilder insertFieldList = new StringBuilder(); StringBuilder insertValueList = new StringBuilder(); StringBuilder insertAnonymousTypeList = new StringBuilder(); StringBuilder updateFieldList = new StringBuilder(); StringBuilder updateWhereFieldList = new StringBuilder(); // Loop through each of our column names and build the data that we need for (int i = 0; i < columnInfoList.Count; i++) { SqliteTableInfo columnInfo = columnInfoList[i]; fieldList.AppendLine(BuildFieldLine(columnInfo.name, columnInfo.type)); insertFieldList.Append(BuildInsertField(columnInfo)); insertValueList.Append(BuildInsertValue(columnInfo)); insertAnonymousTypeList.Append(BuildInsertAnonymousType(columnInfo, lowerTableName)); if (columnInfo.pk > 0) updateWhereFieldList.Append(BuildUpdateWhereField(columnInfo)); else updateFieldList.Append(BuildUpdateField(columnInfo)); } // Remove the extra ", " from each of these lists. insertFieldList.Remove(insertFieldList.Length - 2, 2); insertValueList.Remove(insertValueList.Length - 2, 2); insertAnonymousTypeList.Remove(insertAnonymousTypeList.Length - 2, 2); updateFieldList.Remove(updateFieldList.Length - 2, 2); // Remove the extra "AND " from this list updateWhereFieldList.Remove(updateWhereFieldList.Length - 4, 4); generatedCode = generatedCode.Replace("%TableName%", tableName); generatedCode = generatedCode.Replace("%PluralTableName%", pluralTableName); generatedCode = generatedCode.Replace("%TableAlias%", tableAlias); generatedCode = generatedCode.Replace("%LowerTableName%", lowerTableName); generatedCode = generatedCode.Replace("%ClassName%", className); generatedCode = generatedCode.Replace("%LowerClassName%", lowerClassName); generatedCode = generatedCode.Replace("%FieldList%", fieldList.ToString()); generatedCode = generatedCode.Replace("%InsertFieldList%", insertFieldList.ToString()); generatedCode = generatedCode.Replace("%InsertValueList%", insertValueList.ToString()); generatedCode = generatedCode.Replace("%InsertAnonymousTypeList%", insertAnonymousTypeList.ToString()); generatedCode = generatedCode.Replace("%UpdateFieldList%", updateFieldList.ToString()); generatedCode = generatedCode.Replace("%UpdateWhereFieldList%", updateWhereFieldList.ToString()); // If you want to write out a file instead of use the copy/paste window, uncomment this line. //File.WriteAllText(string.Format("SqliteCodeGen\\{0}.cs", tableName), generatedCode); return generatedCode; } public static string BuildFieldLine(string fieldName, string fieldTypeText) { string fieldType = "string"; // INTEGER can mean a few different things in SQLITE if (fieldTypeText == "INTEGER") { // We're identifying bools by convention. See if this starts with one of the common bool words. if (IsBool(fieldName)) fieldType = "bool"; else if (fieldName.EndsWith("ID")) fieldType = "long"; else fieldType = "int"; } else if (fieldTypeText == "REAL") fieldType = "float"; else if (fieldTypeText == "TEXT") fieldType = "string"; else Debug.LogError(string.Format("Unsupported Field Type [{0}] - defaulting to string", fieldType)); string fieldLine = string.Format("\tpublic {0} {1} {{ get; private set;}}", fieldType, fieldName); return fieldLine; } private static bool IsBool(string fieldName) { if (IsBoolWord(fieldName, "Can")) return true; if (IsBoolWord(fieldName, "Force")) return true; if (IsBoolWord(fieldName, "Has")) return true; if (IsBoolWord(fieldName, "Is")) return true; if (IsBoolWord(fieldName, "Include")) return true; if (IsBoolWord(fieldName, "Should")) return true; if (IsBoolWord(fieldName, "Use")) return true; if (IsBoolWord(fieldName, "Uses")) return true; return false; } public static bool IsBoolWord(string fieldName, string boolWord) { if (fieldName.StartsWith(boolWord) && fieldName.Length > boolWord.Length && char.IsUpper(fieldName[boolWord.Length])) return true; else return false; } private static string BuildInsertField(SqliteTableInfo columnInfo) { return string.Format("{0}, ", columnInfo.name); } private static string BuildInsertValue(SqliteTableInfo columnInfo) { return string.Format("@{0}, ", columnInfo.name); } private static string BuildInsertAnonymousType(SqliteTableInfo columnInfo, string lowerTableName) { string annonymousType = string.Format("{0}={1}.{0}, ", columnInfo.name, lowerTableName); return annonymousType; } private static string BuildUpdateWhereField(SqliteTableInfo columnInfo) { return string.Format("{0}=@{0} AND ", columnInfo.name); } private static string BuildUpdateField(SqliteTableInfo columnInfo) { return string.Format("{0}=@{0}, ", columnInfo.name); } Since Sqlite only has a few data types INTEGER, REAL, TEXT, BLOB - it doesn't have types for bool. In the database, we use integer based fields for those. On the c# side though, I want to use bools. I identify which fields are bools by naming convention. If it starts with something like "Is" or "Has" and the next letter is capital, then I call it a bool. It may not be perfect, but it's easily fixed if I misidentify a field.
    Running it against this table
    CREATE TABLE "WeaponCategory" ( "WeaponCategoryID" INTEGER NOT NULL UNIQUE, "IsBallistic" INTEGER NOT NULL, "IsMissile" INTEGER NOT NULL, "IsEnergy" INTEGER NOT NULL, "IsSupport" INTEGER NOT NULL, "IsMelee" INTEGER NOT NULL, "CanUseInMelee" INTEGER NOT NULL, "IsAffectedByEvasive" INTEGER NOT NULL, "ForceLightHitReact" INTEGER NOT NULL, "DamageReductionMultiplierStat" TEXT NOT NULL, "ToBeHitStat" TEXT NOT NULL, "DesignMaskString" TEXT NOT NULL, "TurretDamageMultiplier" REAL NOT NULL, "VehicleDamageMultiplier" REAL NOT NULL, "MinHorizontalAngle" REAL NOT NULL, "MaxHorizontalAngle" REAL NOT NULL, "MinVerticalAngle" REAL NOT NULL, "MaxVerticalAngle" REAL NOT NULL, "UIColorRef" TEXT NOT NULL, "FallbackUIColor" TEXT, "Icon" TEXT NOT NULL, "HardpointPrefabText" TEXT NOT NULL, "UseHardpointPrefabTextAsSuffix" INTEGER NOT NULL, PRIMARY KEY("WeaponCategoryID") ); yields the following code.
    #region WeaponCategory public static List<WeaponCategory_MDD> SelectAllWeaponCategories(this MetadataDatabase mdd) { string query = "SELECT wc.* FROM WeaponCategory as wc"; List<WeaponCategory_MDD> weaponCategoryList = mdd.Query<WeaponCategory_MDD>(query).ToList(); return weaponCategoryList; } public static WeaponCategory_MDD SelectWeaponCategoryByID(this MetadataDatabase mdd, int weaponCategoryID) { WeaponCategory_MDD weaponCategoryRow = mdd.Query<WeaponCategory_MDD>( "SELECT * FROM WeaponCategory WHERE WeaponCategoryID=@WeaponCategoryID", new { WeaponCategoryID = weaponCategoryID }) .FirstOrDefault(); return weaponCategoryRow; } public static WeaponCategory_MDD InsertWeaponCategoryValue(this MetadataDatabase mdd, WeaponCategoryValue weaponCategory) { mdd.Execute("INSERT INTO WeaponCategory" + " (WeaponCategoryID, IsBallistic, IsMissile, IsEnergy, IsSupport, IsMelee, CanUseInMelee, IsAffectedByEvasive, ForceLightHitReact, DamageReductionMultiplierStat, ToBeHitStat, DesignMaskString, TurretDamageMultiplier, VehicleDamageMultiplier, MinHorizontalAngle, MaxHorizontalAngle, MinVerticalAngle, MaxVerticalAngle, UIColorRef, FallbackUIColor, Icon, HardpointPrefabText, UseHardpointPrefabTextAsSuffix)" + " values(@WeaponCategoryID, @IsBallistic, @IsMissile, @IsEnergy, @IsSupport, @IsMelee, @CanUseInMelee, @IsAffectedByEvasive, @ForceLightHitReact, @DamageReductionMultiplierStat, @ToBeHitStat, @DesignMaskString, @TurretDamageMultiplier, @VehicleDamageMultiplier, @MinHorizontalAngle, @MaxHorizontalAngle, @MinVerticalAngle, @MaxVerticalAngle, @UIColorRef, @FallbackUIColor, @Icon, @HardpointPrefabText, @UseHardpointPrefabTextAsSuffix)", new { WeaponCategoryID = weaponCategory.WeaponCategoryID, IsBallistic = weaponCategory.IsBallistic, IsMissile = weaponCategory.IsMissile, IsEnergy = weaponCategory.IsEnergy, IsSupport = weaponCategory.IsSupport, IsMelee = weaponCategory.IsMelee, CanUseInMelee = weaponCategory.CanUseInMelee, IsAffectedByEvasive = weaponCategory.IsAffectedByEvasive, ForceLightHitReact = weaponCategory.ForceLightHitReact, DamageReductionMultiplierStat = weaponCategory.DamageReductionMultiplierStat, ToBeHitStat = weaponCategory.ToBeHitStat, DesignMaskString = weaponCategory.DesignMaskString, TurretDamageMultiplier = weaponCategory.TurretDamageMultiplier, VehicleDamageMultiplier = weaponCategory.VehicleDamageMultiplier, MinHorizontalAngle = weaponCategory.MinHorizontalAngle, MaxHorizontalAngle = weaponCategory.MaxHorizontalAngle, MinVerticalAngle = weaponCategory.MinVerticalAngle, MaxVerticalAngle = weaponCategory.MaxVerticalAngle, UIColorRef = weaponCategory.UIColorRef, FallbackUIColor = weaponCategory.FallbackUIColor, Icon = weaponCategory.Icon, HardpointPrefabText = weaponCategory.HardpointPrefabText, UseHardpointPrefabTextAsSuffix = weaponCategory.UseHardpointPrefabTextAsSuffix, }); WeaponCategory_MDD weaponCategoryRow = SelectWeaponCategoryByID(mdd, weaponCategory.ID); return weaponCategoryRow; } public static WeaponCategory_MDD UpdateWeaponCategoryValue(this MetadataDatabase mdd, WeaponCategoryValue weaponCategory) { mdd.Execute("UPDATE WeaponCategory SET" + " IsBallistic=@IsBallistic, IsMissile=@IsMissile, IsEnergy=@IsEnergy, IsSupport=@IsSupport, IsMelee=@IsMelee, CanUseInMelee=@CanUseInMelee, IsAffectedByEvasive=@IsAffectedByEvasive, ForceLightHitReact=@ForceLightHitReact, DamageReductionMultiplierStat=@DamageReductionMultiplierStat, ToBeHitStat=@ToBeHitStat, DesignMaskString=@DesignMaskString, TurretDamageMultiplier=@TurretDamageMultiplier, VehicleDamageMultiplier=@VehicleDamageMultiplier, MinHorizontalAngle=@MinHorizontalAngle, MaxHorizontalAngle=@MaxHorizontalAngle, MinVerticalAngle=@MinVerticalAngle, MaxVerticalAngle=@MaxVerticalAngle, UIColorRef=@UIColorRef, FallbackUIColor=@FallbackUIColor, Icon=@Icon, HardpointPrefabText=@HardpointPrefabText, UseHardpointPrefabTextAsSuffix=@UseHardpointPrefabTextAsSuffix" + " WHERE WeaponCategoryID=@WeaponCategoryID ", new { WeaponCategoryID = weaponCategory.WeaponCategoryID, IsBallistic = weaponCategory.IsBallistic, IsMissile = weaponCategory.IsMissile, IsEnergy = weaponCategory.IsEnergy, IsSupport = weaponCategory.IsSupport, IsMelee = weaponCategory.IsMelee, CanUseInMelee = weaponCategory.CanUseInMelee, IsAffectedByEvasive = weaponCategory.IsAffectedByEvasive, ForceLightHitReact = weaponCategory.ForceLightHitReact, DamageReductionMultiplierStat = weaponCategory.DamageReductionMultiplierStat, ToBeHitStat = weaponCategory.ToBeHitStat, DesignMaskString = weaponCategory.DesignMaskString, TurretDamageMultiplier = weaponCategory.TurretDamageMultiplier, VehicleDamageMultiplier = weaponCategory.VehicleDamageMultiplier, MinHorizontalAngle = weaponCategory.MinHorizontalAngle, MaxHorizontalAngle = weaponCategory.MaxHorizontalAngle, MinVerticalAngle = weaponCategory.MinVerticalAngle, MaxVerticalAngle = weaponCategory.MaxVerticalAngle, UIColorRef = weaponCategory.UIColorRef, FallbackUIColor = weaponCategory.FallbackUIColor, Icon = weaponCategory.Icon, HardpointPrefabText = weaponCategory.HardpointPrefabText, UseHardpointPrefabTextAsSuffix = weaponCategory.UseHardpointPrefabTextAsSuffix, }); WeaponCategory_MDD weaponCategoryRow = SelectWeaponCategoryByID(mdd, weaponCategory.ID); return weaponCategoryRow; } public static WeaponCategory_MDD InsertOrUpdateWeaponCategoryValue(this MetadataDatabase mdd, WeaponCategoryValue weaponCategory) { InsertOrUpdateEnumValue(mdd, weaponCategory); WeaponCategory_MDD weaponCategoryRow = SelectWeaponCategoryByID(mdd, weaponCategory.ID); if (weaponCategoryRow == null) { weaponCategoryRow = InsertWeaponCategoryValue(mdd, weaponCategory); } else { weaponCategoryRow = UpdateWeaponCategoryValue(mdd, weaponCategory); } return weaponCategoryRow; } #endregion // WeaponCategory  
    That's quite a bit of boilerplate code. And not only is generating the code way faster than I could do it by hand, I can count on it being bug free. Or if there is ever a bug in it, I can fix it in my code generation and rerun it against my table entities to fix the same problem everywhere.
    I had been putting this off for a while. Any time I was making a new table for the database, time pressures were always high. It was faster to do the one-off table by hand than to do the code-gen system (in the immediate short term). Now that I finally have some breathing room I was able to put this system in place. It didn't take very long and it will probably pay for the time investment after I do one more of these enumerations.
    Personal Project Update - Car Wars
    A friend of mine invited me to participate in his company's game jam. They all use Unreal so I've been putting my free time into running through Unreal tutorials. I don't want to be a boat anchor on his team. >.< So Car Wars is going to be postponed probably a couple of weeks while I participate in that.
    Tips From your Uncle Eck
    Keep an eye out for code generation opportunities. If you're writing the same type of code over and over and Generics aren't quite the right fit, consider writing some code generation. It probably doesn't take as long as you might think and you'll save yourself time in the long run.

    Twitter Post: https://twitter.com/Eck314/status/1162755432592683008
  2. Eck
    It's been a while since my last developer journal. Mainly because I've been doing lots of things besides development. I've been playing lots of video games: Borderlands 2, Battletech, Master of Orion 2, and Blood Bowl 2. I've been doing lots of Blood Bowl and Twilight Imperium stuff including writing after action reports, twitch streams, and podcasts. I've also been completing some home improvement projects, learning German, working out, and just working. 
    In short, I haven't made Car Wars a priority and so it doesn't get any of my time. Even though I've been productive in other "useful" things, my goof-off time hasn't really been a conscious decision and so I feel a little bit down from slacking off. This is something I've talked about in previous journal posts. It's okay to goof off so long as you prompt yourself, "Hey self. Instead of playing Blood Bowl, I could be working on my side project... Am I cool with goofing off and messing around instead of making progress?" If you ask yourself that and the answer is yes, goof off with a clear conscious. Otherwise you'll binge several hours of a video game and before you know it, that's one more day without any progress.
    Anyway, I actually HAVE made some progress. It's just not as much as I'd like. Most of this was working when I posted the last Car Wars journal. I worked out a few design decisions and fixed a few bugs. I also spent quite a bit of time cleaning up my object pooler, commenting the heck out of it and making the code asset worthy. I still need to do a few things like record a demo video, and create some store page assets for it before I can publish it on the Unity asset store, so expect that in a week or two.
    Here's a video for what I have so far:
    I have no skills in modelling, animating, or basically anything creative. So I franken-bashed a bunch of free assets together including different animations, a model, and making my own animation controller. I think it looks pretty cool for the prototype stage.
    I create the move order ghosts by instantiating a copy of the pedestrian through my object pooler, and make them translucent by turning the alpha of the materials down. To get the highlight effect, I just change the material color to yellow. 
    // A couple of cached references to the renderers protected MeshRenderer tokenMeshRenderer = null; public MeshRenderer TokenMeshRenderer { get { if (tokenMeshRenderer == null) tokenMeshRenderer = GetComponentInChildren<MeshRenderer>(); return tokenMeshRenderer; } } protected SkinnedMeshRenderer modelMeshRenderer = null; public SkinnedMeshRenderer ModelMeshRenderer { get { if (modelMeshRenderer == null) modelMeshRenderer = GetComponentInChildren<SkinnedMeshRenderer>(); return modelMeshRenderer; } } private void SetTransparency(float transparency) { Color color = TokenMeshRenderer.material.color; color.a = transparency; TokenMeshRenderer.material.color = color; color = ModelMeshRenderer.material.color; color.a = transparency; ModelMeshRenderer.material.color = color; } public void SetColor(Color newColor) { Color color = TokenMeshRenderer.material.color; newColor.a = color.a; TokenMeshRenderer.material.color = newColor; color = ModelMeshRenderer.material.color; ModelMeshRenderer.material.color = newColor; }  
    So now I have the very basics of turn-based mechanics worked out. I was lost in the weeds for a few days as I thought about all the different possibilities of how to tackle this problem. Do I let everyone plan everything and then execute in the correct sequence? What about collisions? How about allowing units to plan future phases out? What about keeping information from the other player hidden. How about when two cars are moving really fast and they're nearby each other? Should I present a timeline and allow the user to scrub through it for their planned movement and their opponent's approximated movement? ... and so on.
    Eventually I came to the conclusion that I need to just pick a direction and go. So I decided on something a bit simpler for now. Units will move in initiative order, and complete their movement for the phase before the next unit is allowed to go. This keeps the problem space much simpler and lets me work the kinks out of a turn-based mechanics system. As I code this simpler version, I'll try to make design decisions that consider some of the other problems I identified. Hopefully, that will make future refactors and features easier.
    Tips from your Uncle Eck
    If you don't make your game dev project a priority, then you won't make any progress on it.
    When you get overwhelmed by a complex design, take a step back and try to focus on a subset of the problem. Then expand on that as more of the problem gets solved. 
    A decent solution now is far better than a perfect solution that never happens.
    Charity Fund Raising
    I'm helping raise money through Extra Life which is a charity that helps sick kids. Give a little something if you can. And share the links below if you have time. Thanks!
    My Extra Life page - https://www.extra-life.org/index.cfm?fuseaction=donorDrive.participant&participantID=367649 
    Twitter Post for sharing
    Facebook Post for sharing
    Car Wars is a registered trademark of Steve Jackson Games, and the Car Wars logo is copyrighted by Steve Jackson Games. All rights are reserved by SJ Games. This logo is used here in accordance with the SJ Games online policy. 
    Computer Games based on SJ properties are prohibited so I'll never be able to release this project to the public (not even for free). It's just a fun personal project for myself and the most I'll be able to share is my experiences while working on it. In my pipe dream I'll get this into a cool enough state that SJ Games contacts me to publish the game. But what's more likely is a cease and desist letter. We'll just have to see how things go. Here's hoping. 
    If you're interested in legit Car Wars products, I recommend Warehouse 23:
  3. Eck
    Again we had a 6-player game planned but our 6th player canceled a few days before Game Day. One of these days we'll get a 6-player game going, but today was not the day. We did random races for everyone but the newest player who we gave Federation of Sol. These were the races:

    From left to right
    Alexander - Universities of Jol'Nar (purple)
    Mike - Naalu Collective (yellow) - can't see him but he's the guy sitting behind Alex
    Dom - Federation of Sol (blue)
    Devin - Nekro Virus (black)
    Eck (and his beard) - Embers of Muaat (red)
    We used the TI4 balanced map generator here: https://ti4-map-generator.derekpeterson.ca/ using the 5 player "notch" style. Here's what it rolled up:

    Mentak (#2 on the map) changed out to be Nekro Virus the night before.
    I showed it to everyone and they thought it was fine. I felt like I had a pretty sweet setup with that 5 resource system right next door, but it was my ONLY system and since I was Embers of Muaat, I didn't mind taking it. The person I was worried about the most was Naalu (#9 on the map), but I was under the impression he was a veteran player. With him running Naalu, I didn't mind a tougher start for him. He wanted to offer Alex and me a trade good for our notched starting position. I turned it down. The Embers of Muaat are a proud people and do not need your charity! Jol'nar happily took the trade good. Later on I found out Mike was a newer player so felt kinda bad for his start.
     I set everything up the night before so we'd be ready to hit the ground running, first thing. TI 4 is such a grand game.

    Round 0

    Round 1

    Normally round 1 is only marginally interesting so I'd summarise and skip the picture. But this is what happened. The Nekro virus chose Warfare and expanded to two systems adjacent to Mecatol to secure a point. Embers of Muaat became neighbors to Jol'Nar to trade. And Federation of Sol researched Gravity drive and took one of the only good systems in range of Naalu. Dom (Federation of Sol) was kind of new and I explained how aggressive a move that was, but he and Mike (Naalu) said them fighting was a common theme in their games. I made sure to reiterate that the goal is victory points. I actually started out with Contstruction, so I could have a decent star dock a bit closer to the action.
    Round 2

    I took Construction for the second time in a row to solidify the borders everyone was politely agreeing on. I also traded my War Sun promissory note to Jol'Nar in exchange for his research agreement and Hyper Metabolism to help with my Command Counter addiction. Since I had been trading so much money with Jol'Nar and he was able to expand  so quickly to his planets he was able to put start a substantial and mobile fleet. Federation of Sol Consolidated his holdings on the Naalu border and pushed towards Mecatol Rex. At this point Naalu was feeling the pressure and realized how resource starved he was. Also, Naalu and Jol'Nar kept taking Leadership and Technology so Nekro was a little token starved and so was I.

    Round 3

    You'll notice a lot of black plastic disappeared in this shot. Nekro took Mecatol Rex this round. I had given him my Cease Fire earlier for something and I wanted it back. After upgrading my 4 PDS adjacent to Mecatol I activated the system and asked for my Cease Fire back. He declined and the fight did not go his way. This did two things. It left the airspace of that 4 PDS system open and so Jol'Nar was able to fly through that and claim another tech specialty planet from Nekro and blow up the recently built space dock. That double whammy really started the downfall of the Nekro for the rest of the game. People didn't feel too bad at this point since Nekro was at 4 points and the rest of us were at 0-2.
    I was so engrossed in my side of the board I only vaguely remember some of the Naalu/Sol agression that went on. I remember there was some betrayal and Naalu was reeling from the fights. He had a couple of fleets but couldn't strike into Sol territory without being very exposed. At that point he pleaded for the benevolent Embers of Muat for aid. In exchange, he would give me one trade good every time he got refreshed. So I gave him the blueprints for my Peace Spheres (War Sun is an extremist propaganda term used by the space media). I even helped fund his first one later on. He roleplayed up the whole vassal thing a few times during the game which was cool.
    During the Agenda phase I also pushed the vote to give Naalu the Prophecy of Ixth giving him +1 to his fighter rolls. A few people even put riders on it, and Nekro picked up Spec Ops 2 so everyone was somewhat appeased (and I was sitting on Mecatol with a decent fleet). Muahaha or... Muu a a a aat!

    Round 4

    This was another tense round. Look at that purple fleet! He looked me square in the eye and said, "It's not for you." And I had a choice to make. I had a decent shot at taking that fleet out right then, but I'd probably have to give up Mecatol to do it. I pride myself on not being the first to backstab so I rolled the dice and trusted him. At this point I think I picked up Cruiser two and solidified my Mecatol position. If anyone came at me, it was going to hurt. Naalu was able to finally bloody Federations of Sol's nose and was back in the game. Everyone started scoring victory points on tech/planet objectives now.
    Round 5

    At the start of the round I decided to make my move and I chose Imperial. I knew I wouldn't be able to hold everything, but the game was going to be a glorious fireball of awesomeness. Everyone was already deciding who was going to try and engage me, and then the Federation of Sol player played his Imperial Arbiter title to take Imperial away from me. And just like that, the table went back to a tense-mostly peace.
    Naalu started moving in on Jol'Nar's territory but didn't attack anything yet. Still there was some implied pressure from his crystal fighter II's with a +1. Definitely not insignificant.
    With Mecatol rex, a few influence planets, and Hyper Metabolism I was gaining 7 command counters a round. I kept pumping more and more into fleet. At this point I think I was at 7 or 8.  The stage 2 that got flipped last round was own 6 planets with the same trait. A nigh impossible task... But with nearly all my Cruiser II's on the board that meant I could strike out anywhere I needed to. I stalled until everyone had passed to make my move. I even gave away my support for the throne for my Naalu (my loyal vassal) to retreat away from the hex he was originally retreating to. Then I flew out from mecatol rex and snagged up the last two planets I needed. 

    Round 6

    Jol'Nar kept his word. The fleet was actually for destroying Nekro's flagship for a secret objective. Naalu struck at Jol'Nar, Jol'Nar fought back. At one point Jol'Nar had a decent fleet go after Naalu's Homeworld but the stacks of fighters there held off the space nerds and their -1 combat rolls.
    Federation of Sol destroyed my sacrificial cruisers to get back his planets I snagged at the end of last round and he was prepared to assault Mecatol next round. At the end of this round, everyone but Nekro was at 8 or 9 Victory Points and it was going to come down to initiative order. Next round. 
    Round 7
    Jol'Nar had the Naalu Promissory Note so he was going to go first. Nekro was speaker so he got to choose the first card. If he chose imperial, that effectively meant Jol'Nar won. If he chose something else, that meant I'd choose imperial. Devin is a great guy and he didn't want to king-make either way. Jol'nar gave him a way out and said don't worry about it, he had it under control. I looked through my action cards cause I knew what was coming. I didn't have a sabotage...
    I chose Imperial so I could score this round. He played Public Disgrace which means I have to put it back and pick a different card... I asked if anyone wanted to play a Sabotage? Naalu... my glorious vassal locks eyes with me. I tell him, I'll give him my support for the throne so he can be tied for second place... He plays a Sabotage and the table goes wild. 
    I tell Alex he can have the victory but he laughs and says nah. Then he hands me the Gift of Prescience so no one else can steal the victory out from under me. This was one of those rare (extremely rare) instances where being honorable and kind actually clinched the victory instead of throwing the victory away.
    TI 4 can be an amazing story generator. In the early years we were all emerging from our home systems while the Nekro Virus empire threatened the galaxy. We united against that galactic crisis (Sorry Devin!) and entered an intense cold war period where everyone was building massive fleets including several war suns. After several minor conflicts, it came down to the two super powers on the board racing for the custodian's approval. In the end, Jol'Nar, Naalu, and Muaat chose to work together and ushered in a new age of peace. I picture Sol reluctantly agreeing to abide by the council's decision, but claiming as much territory as they can in the upcoming peace accords.
    Good game everyone!
    Previous Game: https://www.gamedev.net/blogs/entry/2265456-twilight-imperium-4th-edition-game-2/
    Next Game: Stay tuned!

  4. Eck
    Game 2 was supposed to be a 6-player game, choose whatever race you wanted, balanced map. That dropped to a 5-player game when one of the players couldn't make it. Then a 5th player bailed last minute and his phone didn't send the message. It's worth noting that he felt terrible about this and apologized multiple times. So instead of having the map setup and ready to rock and roll right at game time. We started 30 minutes late and had to build the map the old fashioned way. I think it's a testament to the streamlined improvements that we were still able to finish in a reasonable 8.5 hours (including pizza phase). Here's what happened...
    Race selection method - choose whatever you want. Speaker - roll randomly and Sardakk N'orr got it.
    Devin - Mentak (green) - Playing Mentak again. Damn pirates Dalton - Federation of Sol (blue) - New player - came super prepared and even had notes for early strategies after listening to Space Cats and Peace Turtles. Javi - Letnev (purple) - New player Eck - Sardakk N'orr (black/me) - I won the last game, so for this game I was going to go for more role-playish fun. I planned to go combat focused and support others who did the same. The 5th player - no show was supposed to play Embers of Muat. I was hoping to trade the War Sun tech with him, and I'd have payed silly prices to make that happen. Ah well... Some other time!
    Instead we randomly built the map, and I didn't take a clean pic of it because I was focused on analyzing the new board. Sorry about that!
    One thing to keep in mind for a 4-player game is every strategy card gets picked so every secondary action is possible on your turn. This is super useful for counting on secondaries as part of your grand plans. I only mention this cause I took too long to write up this after action report, so I don't recall all my strategy card picks.
    Round 1 - I told the table my idea of me wanting to take the game a little less seriously. I'd still try to win but my goal was to be a warlike bug race. Everyone else was welcome (and encouraged) to play their best. They were game so the first proclamation I made was - whoever wins the first combat, gets my support for the throne! The table snickered and agreed. Other than that, it was a pretty standard early turn, Sardaak N'orr (black/me) got Warfare and expanded towards Letnev (purple) for some early trading partners. Everyone else expanded a bit.
    0 - Mentak (green/Devin) 0 - Federation of Sol (blue/Dalton) 1 - Letnev (purple/Javi) 0 - Sardakk N'orr (black/Eck/me) Round 1 end
    Round 2 - Federation of Sol (blue) was poised to take Mecatol this turn and chose Imperial. The rest of the table agreed this was a bad idea (once Sol gets on Mecatol, it's hell to kick him off) so Mentak (green) blocked him with a couple of cruisers. Blue took our shared border planet without even talking about it. Though I respected the move as a combat-focused-space-bug, I planned my counter strike for this transgression.
    1 - Mentak (green/Devin) 0 - Federation of Sol (blue/Dalton) 2 - Letnev (purple/Javi) 0 - Sardakk N'orr (black/Eck/me) Round 2 end
    Someone offered me a stick of gum... It made me laugh pretty hard.

    Round 3 - This was an exciting turn. Lots of combat so the space-bugs were pleased! The Federation (blue) kicked those pesky Mentak (green) pirates off of Mecatol. A bug of my word, I gave him my support for the throne. Letnev (purple) lost two ground forces trying to take a border world I said he could have. Blue had blocked the only access into his undefended world... That is, it was the only way if you were unwilling to risk the Gravity Rift... Sardakk N'orr (black/me)  hurled a carrier past the gravity rift successfully to take the Federation's (blue's) richest world. For those that are unfamiliar with the rule, every ship you send out of or through a Gravity Rift gets a +1 to their speed. However on a 1d10 roll of 1-3, the ship is destroyed. It could have been the Federation's undefended homeworld, but Blue was a new player and I didn't want to be THAT mean.
    3 - Mentak (green/Devin) 4 - Federation of Sol (blue/Dalton) 2 - Letnev (purple/Javi) 1 - Sardakk N'orr (black/Eck/me)  Gravity Rift Snipe
    Round 3 end
    Round 4 - I forgot to take a picture cause so much stuff was going on. And it's been too long  to remember ALL the details. Sardakk N'orr (black/me) managed to kick the Federation (blue) off of Mecatol through a combination of action cards, bombardment, and a ton of ground forces. I got back, gave up, and got back my Support for the Throne. I think it wound up in Mentak's (green's) hands. This time I think it was for destroying someone's Dreadnaught?
    Sardakk N'orr (black/me) had a massive fleet on and around Mecatol Rex when the agenda Ixthian Artifact came up. I had lots of influence but definitely not enough to win the vote. I put down 19 against, other people put 24 for. Then I played the action card to get +5 votes. Since I was the speaker, I broke ties... We rolled the die anyway as a what-if and it came up 5. WHEW!
    Mentak (green) snagged a poorly defended Federation (blue) world and blew up a space dock. And people also started building up their fleets.<score guess>
    6 - Mentak (green/Devin) 5 - Federation of Sol (blue/Dalton) 4 - Letnev (purple/Javi) 3 - Sardakk N'orr (black/Eck/me) <pic missing>
    Round 5 - Lots of fleet massing! Sardakk N'orr (black/me) had nearly ALL their plastic out on the board at one point. More massive battles, Mentak (green) jumped ahead to 8 during the round, but I used the Silence of Space action card to sneak through the overly defended wormhole and snipe his homeworld. Now Mentak(green) was in a weird position. To get his homeworld back he'd have to attack me, which would lose his support for the throne...
    8- Mentak (green/Devin) 6 - Federation of Sol (blue/Dalton) 6 - Letnev (purple/Javi) 5 - Sardakk N'orr (black/Eck/me) Round 5 end
    Round 6 - Sadakk N'orr (black/me) wound up playing Diplomacy on Mentak(green's) home system, meaning he couldn't take it back this round. He still scored a secret objective though which brought him up to 9. People tried desperately to kick Sardakk N'orr (black/me) off of Mecatol, but just couldn't do it. There was too much plastic on the board.
    9 - Mentak (green/Devin) 7 - Federation of Sol (blue/Dalton) 6 - Letnev (purple/Javi) 7 - Sardakk N'orr (black/Eck/me) Round 6 end
    Round 7 - With a point for mecatol, and a 2 point tech objective, Sardaak N'orr(black/me) was able to clinch the victory. Had I been unable to score mid-turn, Mentak (green) would have won without owning his home system by scoring his last secret objective. The picture of the board was pretty much the same, since my first action ended the game.
    Final score
    9 - Mentak (green/Devin) 7 - Federation of Sol (blue/Dalton) 7 - Letnev (purple/Javi) 10 - Sardakk N'orr (black/Eck/me) VICTORY!!!  
    Final thoughts
    This was a long and crazy ride. I think I wound up giving my support for the throne away about 4 times? I really shouldn't have won this game, but the second half of the game lined up perfectly as far as agendas and objectives were concerned. Other people just couldn't score those big 2 point objectives and I barely managed to eek out a victory. I am now 2 for 2 in my Twilight Imperium 4th games! Wish me luck for the next game.
    Other Notes
    Here was our cool 5 player map setup. This took quite a while to balance and even though we didn't actually use it, I thought others might be interested in a balanced Twilight Imperium 5 player map. The red hexes are impassible, and the 2 trade goods were a suggestion online. I'm not sure the trade goods are  necessary though. Special thanks to my daughter for helping me tweak the board.

    Prev Game: https://www.gamedev.net/blogs/entry/2265302-twilight-imperium-4th-edition-game-1/
    Next Game: https://www.gamedev.net/blogs/entry/2267551-twilight-imperium-4th-edition-game-3/

  5. Eck
    For this short holiday week, there wasn't really much to talk about on the Battletech front. I'm fixing minor/safe bugs and waiting on QA to run my Dynamic enum through the ringer. But on the personal project front, I do have an announcement. I started working on a computerized port of Car Wars during my free time over the last couple of weeks and I'll be sharing my progress as I have interesting things to show. 
    Car Wars is a registered trademark of Steve Jackson Games, and the Car Wars logo is copyrighted by Steve Jackson Games. All rights are reserved by SJ Games. This logo is used here in accordance with the SJ Games online policy. 
    Computer Games based on SJ properties are prohibited so I'll never be able to release this project to the public (not even for free). It's just a fun personal project for myself and the most I'll be able to share is my experiences while working on it. In my pipe dream I'll get this into a cool enough state that SJ Games contacts me to publish the game. But what's more likely is a cease and desist letter. We'll just have to see how things go. Here's hoping. 
    If you're interested in legit Car Wars products, I recommend Warehouse 23. It's what the Car Wars site links to and it seems a bit cheaper than DriveThru RPG
    Why Car Wars?
    Steve Jackson's Car Wars was a game I fell in love with back in High School in the late 90's. I bought the deluxe edition and picked up any supplements I found at game stores and used book stores. It can be a little on the fiddly side - each turn is 1 second that is broken down into 5 phases. You move paper tokens around on a map an inch at a time performing maneuvers that affect how in-control your driver is. I love this kind of fiddly complexity but it isn't for everyone.

    I was in college by the time I found anyone willing to play Car Wars and it wasn't nearly often enough for me. Twenty-five years later and I only have between five and ten played games to my name. I figure, the only way I'm going to get to play enough Car Wars to really scratch that itch is if I code the game myself so I can play vs. the AI.
    I've been working on a Car Wars port off and on throughout the years. XNA, OpenGL, DirectX, etc. I never was skilled or dedicated enough to see the project through to the end. I'm hoping that's different now.  

    This time, I'm going with Unity. There are still plenty of skills that I lack: modeling, animation, sound engineering, etc, (mainly content creation skills). But with the Unity Asset Store there are free and paid for assets that I'll be able to use without having to resort to "programmer art". Plus I've only mentioned this project in a couple of places and I already have an offer from a die-hard Car Wars fan to help out with some 3d Modeling and Animation. 
    I guess we'll have to see how far I get with this attempt. I really just want to play some Car Wars.
    Early Goals/Mission Statement
    In my past attempts, I focused on the math and weird game mechanics for a typical Car Wars game. Getting a car token to move around on the screen, dropping a mine counter, detecting collisions, etc. This time, I want to focus on polish and all the last minute crap you have to do in order to transform your shitty prototype into an actual game. I also want to prioritize finishing a feature before starting a new one. I'm not sure if other developers are like this but here's what happens to me. In professional projects, my coding method works something like this - 1. Think about the problem 2. code something that works (hacky is okay) 3. refactor the hacky code into decent code (hacky rarely okay) 4. test the heck out of it 5. call the feature done and move on. 
    In personal projects, I tend to execute steps 1 and 2, then skip straight to 5 so I can work on something else that's cool and exciting. Eventually my code becomes a mountain of technical debt and doing anything in the project becomes a nightmare. As the code base becomes more and more painful to work in, motivation becomes less and less until eventually I stop working on the project altogether. Not this time!
    With these things in mind, I started my project with the Main Menu screen. It's not sexy but it's where every game starts and it's a simple problem to solve. It took longer to find the assets than to throw the code together. After I got the quit button working and some free music playing for the main menu, I realized I needed to give credit to the composer and provide a link to their asset. That meant a credits screen. Here's what I have so far.
    Writing the credits screen, I used it as an excuse to code up a simple object pooler. Each line of text is its own text control that spawns in at the bottom and moves itself to the top of the screen and turns itself off. The object pooler reuses those items. It's overkill for such few items, but it's something I know I'll use in the future when spawning bullets and other high count items. 
    Future Plans
    What's the first thing that pops into your head when I say Car Wars? A gang of motorcycles swarming a big rig on a desolate stretch of highway? A half dozen vehicles shooting the heck out of each other in an arena battle? Two rival duelists starting a grudge in the middle of town? Well too bad. The first thing I'll be concentrating on is Pedestrians. 
    Why pedestrians? For their basic mechanics, they are relatively simple. This will let me focus on the turn-based game state and let me get that working in a clean way. Once I get the basics down, there are tons of special case rules that I can dive into. Entering parked vehicles, picking up and moving obstacles, jumping onto moving vehicles, etc. 
    Next time I'll be showing some pedestrians planning/exectuing their turn-based movement.
    Personal Update
    This weekend the family and I went camping at Green Lakes. The place was beautiful. We went hiking on a few trails, swam in the lake, and even rented kayaks. Of course with camping came grilling out and having smores so it was a good time. Expand the spoiler for some camping pics.
    Twitter Post: https://twitter.com/Eck314/status/1147966512818458625
  6. Eck
    I'm Chris Eck, and I'm the tools developer at HBS for the Battletech project. I've recently been given permission to write up articles about some of the things I work on which I hope to post on a semi regular basis. Feel free to ask questions about these posts or give me suggestions for future topics. However please note, I am unable to answer any questions about new/unconfirmed features.
    We shipped Urban Warfare and the launch was relatively smooth. There were only a few critical bugs we needed to patch out and we did so in a timely manner. After that, the team focused on the upgrade to Unity 2018. The day after most of the team was upgraded and many of the kinks were worked out... Unity released a new 2018 version. >.< Still there's lots of cool features and editor performance improvements that I'm looking forward to.
    Data Driven Enumerations
    With the team focused on the Unity upgrade process, things were settled down enough for me to finally cram my Enum replacement into the project. It's something I've been wanting to do since last year. I was hoping to get it in during the 1.4 release in December, but some DataManager optimization refactors took priority and caused some stability issues which made testing my code impossible.
    Why replace enums? Aren't they a good thing? - At first glance, it does seem like enums are great. It's a simple type you can pass around and it gives your code clarity over using an integer or string. Plus it's built into the language so it takes very little effort to implement. In very simple cases, I think they still have their use. But the second you start tying logic to individual values, I think you should strongly consider a data driven approach.
    I decided to tackle ContractType as my first enum in our code base to replace. It is probably the most complex enum in our game: it touches the Sim Game, the Combat Game, Content Creation, Serialization, and several other systems. If my Dynamic Enum can replace this guy, then it's safe to use it for everything else in our system.
    1. Easy to use in Unity - In Unity enums are drawn as drop down controls and I don't want to lose that.
    2. Support int or string as key - I want to be able to use either of these types. Integers if performance is a big deal or strings to keep things human readable.
    3. Don't break save games - There needs to be some kind of upgrade path for loading old saves.
    4. One place to edit the data - There were at least a dozen places you had to go to add a new Contract Type. I want exactly one place. And I want it in a format that a developer (specifically me) doesn't have to get involved.
    Getting Unity to draw my data like a drop down was pretty easy. I found an excellent code sample for something similar here: https://gist.github.com/ProGM/9cb9ae1f7c8c2a4bd3873e4df14a6687 So I created my own DynamicEnum attribute and wrote a PropertyDrawer for it. And that knocked out the first two requirements. Here's what the code looks like:
    To make sure old save games were supported, I created a new constant for ContractType called INVALID_DEFAULT. Any place that stored the old ContractType now got a new ContractTypeID field which defaults to INVALID_DEFAULT_ID. Since this is a new field, old saves wouldn't have it yet. After deserializing these classes, we just check to see if the new field is the INVALID_DEFAULT_ID and if it is, we convert the old Enum value to the integer ID. Any new content created, would serialize correct values while old save games get upgraded to the new way of storing it. I also protected the ID and wrote a property to make sure we were always pulling the correct data.
    [DynamicEnum(ContractTypeEnumeration.TypeName)] // This is protected so people are forced to use the property. protected int supportedContractTypeID = ContractTypeEnumeration.INVALID_DEFAULT_ID; private ContractType_MDD supportedContractTypeRow = null; public ContractType_MDD SupportedContractTypeRow { get { // If we're loading from an old version, convert the old value to the new value. if (supportedContractTypeID == ContractTypeEnumeration.INVALID_DEFAULT_ID) { supportedContractTypeID = (int)supportedContractType; } // If the cached row is null or out of date, pull the correct one from the database. if (supportedContractTypeRow == null || supportedContractTypeRow.ContractTypeID != supportedContractTypeID) { supportedContractTypeRow = MetadataDatabase.Instance.GetContractTypeByContrctTypeID(supportedContractTypeID); } return supportedContractTypeRow; } } Number four took the longest even if it wasn't the toughest. The concept is simple. Create a class that represents one line item in the original enumeration like ContractType.SimpleBattle. As you identify different code decisions based on individual enumeration values, create fields to store that data. My class cor ContractType wound up looking like this:
    public class ContractTypeValue : EnumValue { public ContractTypeValue() : base() { } public ContractTypeValue(EnumValue_MDD enumValueRow, ContractType_MDD contractTypeRow) : base (enumValueRow) { Version = (int)contractTypeRow.Version; IsSinglePlayerProcedural = contractTypeRow.IsSinglePlayerProcedural; IsStory = contractTypeRow.IsStory; IsRestoration = contractTypeRow.IsRestoration; CustomMusic = contractTypeRow.CustomMusic; IsMultiplayer = contractTypeRow.IsMultiplayer; UsesFury = contractTypeRow.UsesFury; ContractRewardMultiplier = contractTypeRow.ContractRewardMultiplier; Illustration = contractTypeRow.Illustration; Icon = contractTypeRow.Icon; } public int Version { get; private set; } public bool IsSinglePlayerProcedural { get; private set; } public bool IsStory { get; private set; } public bool IsRestoration { get; private set; } public string CustomMusic { get; private set; } public bool IsMultiplayer { get; private set; } public bool UsesFury { get; private set; } public float ContractRewardMultiplier { get; private set; } public string Illustration { get; private set; } public string Icon { get; private set; } public bool IsStoryOrRestoration { get { return IsStory || IsRestoration; } } } // Old code looks like this bool usesFury = (Combat.EncounterLayerData.SupportedContractType == ContractType.ArenaSkirmish); // New code looks like this bool useFury = Combat.EncounterLayerData.SupportedContractTypeRow.UsesFury; // It's subtle, but makes a big difference. Before we had one hardcoded type that used fury // to change our mind about that we'd have to touch several places in the code where this // fact was coded (UI, game logic, resource loading, etc). If we wanted to add a new contract // type that used fury we'd have to add that with code. With the new system, we just flip // a bit from false to true and now that ContractType uses fury instead. I now have a single json file that contains a list of all the contract types and their associated metadata. Instead of having decisions hard-coded to individual enumeration values, we drive our decisions off the data. This gives us exactly one spot to edit the data and it means designers (and modders too!) can add a new ContractType without getting engineers involved. 
    Before, this was a process that took a while to get right because we would forget ALL the places that ContractType values were being used to drive logic. All in all this refactor touched 88 files and had dozens of different places with hardcoded logic. A big change like this reduces complexity while increasing flexibility which are two big steps in the right direction.
    Personal Project Update - Car Wars
    I've wanted a computerized version of Car Wars since high school (which was a few years ago now). Since then, I've started my own version several times but never really made it anywhere of consequence. I have the itch to start it again so I've been working out some decisions on paper and chipping away at things the last few weeks. There's not much to show off yet, but I wanted to announce it as a self-motivational thing. Now that YOU know I'm working on it, I have to KEEP working on it. Otherwise, I'll be letting you down.
    Tips From your Uncle Eck
    Be careful with your use of enums, they can be a code smell that something isn't quite right. If you find yourself hardcoding logic to specific enum values, you should definitely consider switching to a data driven system. That way when people change their minds, they can just change the data in a file instead of having to change code and cut a new build. Plus it makes modding your game that much easier.

    Twitter Post: https://twitter.com/Eck314/status/1145358891141738501
  7. Eck
    We shipped Urban Warfare last week (the new Battletech DLC) along with the 1.6 free update. Things have calmed down enough that I only worked on some simple bugfixes which isn't really much to write about. But I did have an interesting conversation. Kiva asked me if I could talk to a friend of hers and answer a few questions about becoming a tools developer. This isn't the first time I've been asked this question so I thought I would turn our e-mail chain into an article. 
    This turned into a pretty long post so I'll do a quick summary. When you're learning to be a game developer you have a choice to make every time you sit down at your computer. You take the blue pill - you play some video games, cruise the internet, and wake up the next day no closer to your goal. You take the red pill - put in some effort, learn some new skills, work hard, and achieve your dream.
    Another thing to say is there isn't a single "best" formula for how to get into the games industry. All I can say is that this worked for me.
    1. Learn Unity 2. Make learning games https://www.gamedev.net/articles/programming/general-and-gameplay-programming/your-first-step-to-game-development-starts-here-r2976  3. Write a dev journal about your experiences/plans https://www.gamedev.net/ 4. Start working on your game / applying in the industry  
    About Riley
    Riley has been programming for a while and dabbling in game development. After watching some GDC Vault videos, they became more interested in becoming a game developer - specifically a tools programmer. They were polite and appreciative of me taking time to talk with them. Riley asked me for some good resources to better understand how tools development works and for advice on looking for jobs. They also asked about what kind of work I did on Battletech and other games.
    Eck's First Email
    Hello Riley,
    Let me tell you a little bit about myself first. Originally I was a regular developer working on business applications. I started out with C++ and then moved over to C# and SQL as the technology shifted that way. I dabbled with game development as a hobby since college keeping a few projects spinning but never really did anything major. I started working on my own engine and saving up money so I could quit my job and make a serious attempt at game development. After about a year, I participated in a game jam and saw what amazing things people were doing with Unity and I was just floored. I really should have used something like Unity or Unreal instead of working on my own engine.

    Then I quit my job and switched to Unity. I started writing a weekly Developer Journal about the things I was working on here: https://www.gamedev.net/blogs/blog/1922-ecks-journal-still-flying/ - This helped me stay motivated with my learning efforts, and also served as a portfolio to show potential employers I had serious passion, and it would also serve as a form of marketing for my game if I remained an indie developer. The indie dev plan was the way I was leaning towards but then I saw Harebrained was hiring for Battletech. My wife made me apply even though I knew I wouldn't get the job. Long story short, I did get the job even though I had 0 professional game dev experience and didn't live in Seattle. So I gave away most of my stuff, packed up my family, moved across the country, and have been working there ever since.
    A tool developer's job is to make everyone else's job possible, easier, and faster. It's about creating easy to use "tools" like a map editor, or a mech editor. It's about making those tools intuitive, fast, and automating repetitive tasks. It's about identifying the pain points that people are dealing with and eliminating them. The best advice I can give you on how to learn to be a tools developer is to make games on your own and try to drive them with data. You'll quickly become aware of the annoying parts of game development. Figure out how to automate those annoying tasks where possible or reduce it to as few clicks as you can.
    I also recommend starting a developer journal. It's motivational - especially if you end your posts with what you plan to accomplish next week. It makes you feel like you've committed to something and other people will be disappointed in you if you don't. And then a few years later, it's nice to have a historical record of your time. You said you were interested in seeing what work I did. I started doing the developer journal thing before I got my job so you can see exactly what I was working on leading up to that. Lately, I've been writing up journal entries about the kinds of stuff I've been working on for Battletech. So click the link above and read all about it. Feel free to ask any questions on the game dev site and I'll be happy to answer them.
    Finding a game development job is super hard. And even when you do find one, there's a decent chance that job could suck. There are plenty of sweat shops out there that just squeeze every hour they can out of employees and then boot them after the game ships. One thing I found out working at HBS is that I'm happy just being a programmer. If this wasn't Battletech, I probably would have gone back to Business Application development to make way more money so I could retire sooner. But HBS is an amazing company to work at and I'm working on an IP that was a HUGE part of my teen/college years so I'm super happy. #LivingTheDream Do your research on the companies, apply lots of places, and prepare yourself mentally for quite a bit of rejection.
    I hope this helps Riley. Good Luck!
    - Eck
    P.S. - Watch Collateral (Jamie Fox, Tom Cruise). It's the movie that really lit a fire under my ass to go follow my dream.
    Riley's Response
    Hello Eck,
    Firstly, thank you so much for responding, it really means a huge amount!
    Unity is an amazing piece of technology indeed and some of the incredible work I see on twitter is honestly amazing. However I've been rather weary of it for a while, due to mostly varying issues I've heard about it. Though I should just get on with some tutorials and learn things! C# / C++ / SQL is a good set of techs to learn as you allude to later on, business applications are big money! My main language is C# / PHP!
    How far did you get with the work on your own engine? I've attempted that a few times and it is a rather big challenge especially when you are brand new to it all. I have just noticed in your first blog post you were using XNA / MonoGame, it's my current go to as well (well FNA) but it's a good toolset.
    It is rather awesome your wife and family were so supportive of your move when you did get the job, thought I can imagine that it was a big move and stepping into an unknown. It's also awesome that you did end up getting the job even with 0 professional game dev experience. That is a massive thing that does end up worrying me when looking to apply "At least 2 games shipped", "At least 5 years in the industry" etc. Though Harebrained sounds like it must rather be an awesome company. 
    I shall have to give that a go, but literally everything you described is what I absolutely adore doing, creating systems and tools, to make peoples lives easier, automate things. In almost all my jobs, I end up building tools of some form to help others out, in one job I build a dashboard to monitor SSL certificates alongside website up time and in another, I built a whole framework to help developers write importers that were fail safe and powerful in the reporting side of things, so literally anyone could find out what went wrong.
    Any suggestions for sites to use for creating a journal? Would be interesting to see if it does help, cause I struggle massively with motivation and keeping going at projects though having any form of record of things I've done / am doing is a really nice thing to be able to look back on and see how / where you have grown. I shall be giving your blog a read over the next few nights and will definitely drop a few questions into the comment section, will be nice to talk more about how you have done things etc.
    The industry does seem like a few fast turn over area and yeah, I don't have the energy left in me for a sweatshop kinda company, we've all read the horror stories. I'm really glad to hear that you do love it so much at HBS and you feel so happy there, it's always nice when you do find places like that. Lots of research to be done and oh I've had my fair share of No's (including being ignored by a somewhat indie games company I looked up to, which sucked, but oh well).
    Thank you again, seriously this is honestly a massive help to me and I appreciate you taking time to read and respond to my emails, also Kiva, thank you also
    Hope you both have a lovely day!
    Kind regards
    P.S. I shall have to go watch it indeed, thank you for the suggestion
    Eck's Response
    This email could sound a little cranky/confrontational. Tone is lost in text so please keep that in mind. At worst it should be received as "blunt" which is a quality I've been known to have. I'm just trying to help.     Being wary of Unity: Originally I was in the camp of people that thought people using Unity were "cheating" and weren't real developers. You were only a REAL game developer if you wrote your own engine. That's the main reason I worked on my own engine. And quite frankly - that's a stupid camp to be in. Instead of working on low level stuff that's been done a million times by a million developers for nearly 2 years, I could have actually been working on a game and possibly released it. 
    I'm not sure what issues you've heard about Unity but they are definitely able to be dealt with. I mean look at Battletech and Shadowrun. Both of those were made with Unity. If the engine can handle a professional studio's ability to produce games for 7-8 years, then it can definitely handle a one-man indie's dive into game development. Stop using that as an excuse and start going through the tutorials. Then start making games - simple ones. Specifically, make clones of other simple games. For a road map of what games to make and why - give this article a read. I can't recommend it enough: https://www.gamedev.net/articles/programming/general-and-gameplay-programming/your-first-step-to-game-development-starts-here-r2976    It explains what you need to make and why. If you're thinking to yourself "Pfft - Pong. I'm a better developer than that. I want to make a REAL game." - you're wrong. I don't care how awesome you are. Until you actually start making games, you're ignorant of at least SOME of the problems. So suck it up, and crank it out. - "you don't know what you don't know"   How far did I get with my engine: I worked on it off and on as a side project for quite a while - starting, stopping, scraping, restarting. But then I got serious on it for about 1.5 years. I focused on 2D stuff and had an object hierarchy complete with translation, rotation, and scale. I got some post processing glow effects, sounds effects, music, input management, particle systems, etc. Basically I got to the point where I could make a simple game. I entered a week long game jam where the theme was "The toys are alive". Here's a tech demo of my entry.      I was super pleased with what I was able to produce, but that game jam was a wakeup call. I saw all the crazy awesome things other people were doing with Unity and I kicked myself for not learning it sooner. Since C# is your main language, then I strongly recommend jumping into Unity yourself. Worse comes to worst, you further your C# skills a bit more. So if game development doesn't work out, the skills still translate into a well paid career.    Your experience so far: Making tools in business software and making tools in games is pretty much the same thing (and that's how I sold it in my interview).  Writing an importer for customer data instead of weapon data has all the same problems. It just sounds less cool to talk about. If you have the skills and can show the passion for game development - at least some companies will take a look at you. Being able to prove that you can walk the walk is what those experience requirements are all about. If you're looking for a good site to start a blog on, I highly recommend https://www.gamedev.net/. It's an awesome community of game developers of all experiences. From the 0 xp newbie who wants to make an MMO to the elite pros who have written low level graphic card drivers. There are forums to ask questions, and articles that explain tons of topics. They also have occasional game jams or challenges which is a great way to learn in a group of supportive people. Just recently they did a side-scrolling shooter challenge which was fun.   Motivation: Motivation is something many people struggle with. It's so easy to stay in your comfort zone, play video games all day, and keep those dopamine sensors fed. You have to CHOOSE to learn a new skill. And it's extra difficult for programmers because the same device we use to be game developers is the same tool we use for recreation. Every time you sit down to work... WORK. Every time you sit down to play - ask yourself if you've worked enough today (or this week). If the answer is no, then you have a choice to make. You can choose to be responsible and work. Or you can choose to goof off and play a game. Choosing to goof off every once in a while is okay. But if you ONLY choose to goof off, you're never going to reach your goal. That's just how that works.    As I have gotten older, I realized that time is THE MOST VALUABLE ASSET you have. You have a finite amount of time in this life. You are going to leave this world one day. Make your peace with that and accomplish what you want before you do.   Thanks: You're welcome.   Summary:  1. Learn Unity 2. Make learning games https://www.gamedev.net/articles/programming/general-and-gameplay-programming/your-first-step-to-game-development-starts-here-r2976  3. Write a dev journal about your experiences/plans https://www.gamedev.net/ 4. Start working on your game
  8. Eck
    I heard about the GameDev challenge quite a while ago but I wasn't sure if I'd have enough time to complete an entry. This weekend I decided to give it a go. I was planning on just working on it in secret and submitting it if I succeeded... But apparently some gamedev people read my twitter feed so the cat's out of the bag now. It's due on June 1st so I didn't give myself much time to complete it. Here's hoping I can complete it in time since @Rutin found out my secret on twitter. >.<
    I made decent progress on Sunday and Monday. It's definitely not a game yet but the basics are starting to come together. 

    I have a simple menu, Move Patterns, Shoot Patterns, Enemies, and Collision working. I still need to do Enemy Waves, Levels, SoundEffects, Music, Powerups, and polish. Sounds like a lot but I think I have the tough problems solved, and I'm using a bunch of assets from a Humble Bundle so it should come together pretty quickly.
    Scriptable Objects
    I try to data drive my programs and I was planning on doing it with XML again. However, Unity made some improvements to ScriptableObjects over the last few versions so I thought I'd give Unity's them a try for this simple project. I really hate the name ScriptableObject... The name gave me a mental block which made it harder to learn the system. I wish they would have called it DataObject or something similar. It's just a data file, but the name ScriptableObject threw me for a loop. Learn more about them here: https://learn.unity.com/tutorial/live-sessions-on-scripting#5c7f8528edbc2a002053b629 
    I have a simple MovePattern class which is one segment of a MoveSequence. It has a duration, a direction, and a speed. A sequence is made up of a list of MovePatterns and lets you specify whether its play once or a loop. Here is what it looks like:

    I have similar data structures for Shooting, and enemy spawning. An Enemy is made up of a sprite, a move pattern, and a shoot pattern. We just reference these ScriptableObjects(data files) the same way we'd point to a prefab on disk. When I release the game, I'll put my code in a zip or git repo where anyone can download and mess with it.
    I like how easy it is to use the ScriptableObjects to throw something together. You can use the editor to create new data files and configure them in the inspector. However the end user won't be able to edit these things without jumping through a few hoops. If it was just an xml or json file in the StreamingAssets folder the end user could easily just edit those files and mod your game. For real applications I'll probably stick to actual data files instead of ScriptableObjects but for some quick and dirty stuff like a game jam, this was pretty slick.
  9. Eck
    I'm Chris Eck, and I'm the tools developer at HBS for the Battletech project. I've recently been given permission to write up articles about some of the things I work on which I hope to post on a semi regular basis. Feel free to ask questions about these posts or give me suggestions for future topics. However, please note I am unable to answer any questions about new/unconfirmed features.
    The last few weeks I've been in an unofficial bug fixing contest. A bug fixing contest is a lot like a pie eating contest. The reward for fixing a bug is you get more bugs to fix. >.< I cleaned up some performance fallout, worked on updating region labels, and put some finishing touches on drop pods. Of those, drop pods are the coolest so let's talk about that.
    Drop Pods
    The Urban environment is a lot more cramped than the open terrain maps of old. Tall skyscrapers sometimes prevent a dropship from coming in to drop off mechs. The dropships are so large that the wings will clip right through the buildings in the flyby animations and that just won't do.
    Someone modeled a bomb like drop pod. Someone else modeled the open drop pod. Will worked up a VFX and designed a ParticleSystem that had the flaming Drop Pod slamming into the ground and then kicking up a huge cloud of debris (and built a separate one for each biome). Rob worked on new sound effects. And I wired it all up for the designers and spawn it during the game. 

    First, I had to add a new SpawnMethodType so that designers could specify when a lance should spawn with this new animation. When Drop Pods is selected, they need to plummet from the sky during spawn. I instantiate the prefab at the UnitSpawnPoint location and the VFX plays out. Here is the particle system in action.

    ParticleSystems don't have a method for communicating events so I had to watch the VFX and write down when different things happened (when the pod hits the ground, when a cloud is big enough to hide the mech teleporting in, when the effect was over). After I had numbers in constants for different events, I wrote a Coroutine to wait the appropriate amount of time. A coroutine is like a function that has the ability to pause execution and return control to Unity but then to continue where it left off on the following frame. 
    My first test had all the pods landing at the exact same time. I didn't like that so I introduced another constant that would put a delay between each one. I still wasn't satisfied so I added a random delay on top of that. Now there won't be a regular pattern and if two lances spawn drop pods at the same time there will be some variance instead of their units landing in the exact same pattern in the exact same timing. It's very jarring when it happens.
    private const float dropPodImpact = 1f; private const float dropPodSpawnDelay = dropPodImpact + 2f; public IEnumerator StartDropPodAnimation(float initialDelay, ParticleSystem dropPodVfxPrefab, GameObject dropPodLandedPrefab, Action unitDropPodAnimationComplete, int sequenceGUID) {     // Only do work if we actually have a unit to spawn.     if (HasUnitToSpawn)     {         // Wait a random amount of time before starting         float delay = Random.Range(.5f, 1.75f) + initialDelay;         yield return new WaitForSeconds(delay);         // Play the sound effect         WwiseManager.PostEvent(AudioEventList_play.play_dropPod_projectile, WwiseManager.GlobalAudioObject);         // And start the VFX         if (dropPodVfxPrefab != null)         {             ParticleSystem instance = Instantiate(dropPodVfxPrefab, transform);             instance.transform.position = hexPosition;             instance.Play();         }         else         {             LogError("Null drop pod animation for this biome.");         }         // Wait until the drop pod hits, play the sounds, and kill whoever is standing in the spot         yield return new WaitForSeconds(dropPodImpact);         WwiseManager.PostEvent(AudioEventList_play.play_dropPod_impact, WwiseManager.GlobalAudioObject);         yield return ApplyDropPodDamageToSquashedUnits(sequenceGUID);         // Wait a bit more and teleport the units in and spawn the landed drop pod.         yield return new WaitForSeconds(dropPodSpawnDelay);         TeleportUnitToSpawnPoint(dropPodLandedPrefab);         // Wait a couple more seconds for the drop pod vfx to finish playing then we can say we're done.         yield return new WaitForSeconds(2f);     }     unitDropPodAnimationComplete(); } If you aren't familiar with how Coroutines work this might look a little weird with all these yield returns. Basically you're yielding control back to the caller and will continue doing work on future frames. Here's the Coroutine documentation if you're interested in learning more:   https://docs.unity3d.com/Manual/Coroutines.html
    While testing, another thing that I noticed is that we bunch our spawn points up but the physical drop pods are pretty big and they overlap each other like so.

    To give the designers some in editor indication I added some code to the UnitSpawnPoint "Gizmo" (or in editor widget). Lookup Unity's OnDrawGizmos for more information. It gives you ways to draw things in the editor's scene view.

    After stringing everything together, here's what it looks like. (in my super ugly all-flat test level). Also I'm in the editor so pay no attention to the lag spike. *jedi hand wave*
    There are definitely some Rule of Cool physics going on here. Any person inside the cockpit of that drop pod animation would be turned into strawberry jam, and the mech would be a mass of twisted metal and myomer. Drop Pods are supposed to split up in the atmosphere and then jump jets and parachutes are supposed to let the mech drift safely down. I brought this up as a concern, but I also said I wouldn't change anything after seeing the drop pod animations in the game. B)
    New Hatchetman Variant
    It was announced that we were releasing a few variants and one of those is a brand new Hatchetman variant. I was tasked with stating it out so I did some research but most of the variants are all way past the 3025 era. I saw the HCT-5K and that sounded pretty interesting so I designed an earlier prototype version the HCT-3K. There was some lore debate on whether or not a Kurita variant would be this far "south" in the periphery so after the forum goers read about the 3X consideration they demanded that we change it. In lore the X stands for experimental. But for me it stands for 3 Ecks (Me, my wife, and my daughter). I can't wait for this to ship because I can finally point at something concrete in the game and said I MADE THAT!  Also, there's a small chance it will get added to Battletech cannon so it's yet another cool thing about #livingthedream.
    Blood Bowl Update
    In more casual news, I've been streaming my Wood Elves and finally won a couple of games. It's vastly different from my typical bashy-ork play style but I feel like I'm learning. The league that I joined TRBBL (Totally Relaxed Blood Bowl League) has a cool bunch of laid back coaches in it. Season 3 will be starting soon and I plan to stream those matches when I play them (roughly every other week). Lately I've been streaming sporadically about once a week. Feel free to come hang out when I do: https://www.twitch.tv/eck314 or you can check out the matches that I upload on Youtube: https://www.youtube.com/watch?v=VQaN12XU5Wg&list=PL2M43bS2cfSMNvtzWDR0DxuQgh0Ky7yrA
    If you're interested in playing Blood Bowl 2, there's a big steam sale for all Games Workshop related products. I think Standard Edition costs like $5 and Legendary Edition is around $15 which comes with all the teams. It's a good time to pick it up. https://store.steampowered.com/sub/192166/ 
    Previous Journal: https://www.gamedev.net/blogs/entry/2267220-battletech-developer-journal-06/ Next Journal: Stay tuned! Twitter Post: https://twitter.com/Eck314/status/1130221812179185664
  10. Eck
    I'm Chris Eck, and I'm the tools developer at HBS for the Battletech project. I've recently been given permission to write up articles about some of the things I work on which I hope to post on a semi regular basis. Feel free to ask questions about these posts or give me suggestions for future topics. However, please note I am unable to answer any questions about new/unconfirmed features.
    Last week I saw my current task list at the lowest it has ever been... Two issues! My manager asked me if we should start pulling in some of my 1.7 tasks back in and I said let's see how next week goes... I'm glad we did because now I'm back up to 12 issues. >.< There's still plenty to do to get Urban Warfare/1.6 out the door so I'll be busy until the last possible moment I'm sure. In the recent livestream, Connor said that he found out the reward for completing all your tasks... is more tasks. It's like a pie eating contest.
    Exploding buildings!
    Now that we've demoed some of the new features, I can talk about some interesting things I did a while ago. The designers wanted building destruction to be able to affect the battlefield a little more dynamically than just removing a big obstruction. The feature pitched was to paint down some kind of design mask as the building was destroyed. Sure. We can do that.
    We had exploding buildings before (it's not a video game until you have exploding barrels) and any time a building died, we would paint down a destroyed building design mask that acted similarly to trees. Adapting this system was relatively easy. I added a new class to Obstructions that would detail what happens when the building gets destroyed.
    public class ExplodeBuildingSettings {     public bool explode = false;     public int damage = 50;     public int heatDamage = 0;     public int stabilityDamage = 0;     public int radius = 50; public string paintTerrainVFXName = "";     public ArtilleryVFXType artilleryVFXType = ArtilleryVFXType.Explosion;     public TerrainMaskFlags paintTerrainMask = TerrainMaskFlags.None;     [EnumFlags]     public PaintOverTerrainMaskFlags removeTerrainMask = PaintOverTerrainMaskFlags.Road | PaintOverTerrainMaskFlags.DestroyedBuilding; } So if the building is set to explode, it can inflict a configured amount of damage, heat, or stability to every unit that is in the configured radius. The artilleryVFXType is the VFX that gets played (like an explosion or electricity field). It can also paint a new design mask down like a coolant puddle that helps cool off your mechs, or an electricity field that makes it harder to be hit, or even a forest... Even if it doesn't make sense.
    Since the UI can only display one design mask, we had to have a priority system for design masks. Roads and destroyed buildings are pretty high up on the list so as part of the exploding building step we remove those design masks to paint our dynamic stuff down.
    When I first started testing this feature, I turned on exploding buildings by default... Here's what happened:
    More Blood Bowl
    Since I played the tournament last week I got the itch to start playing more. Cyanide's Blood Bowl II is an almost direct port from the table-top, and I absolutely love it. But Online play can be pretty darn toxic at times. Kent (Zinak) told me about this cool, laid back league with a pretty clear mission statement of "Don't be a dick." and so I signed up for their Season 3 event. Here's the discord link if anyone else is interested: https://discord.gg/YxHUpf
    I decided to stick with my orcs and got to play one preseason game vs Shaoz's Ogres. I streamed the preseason match on twitch and uploaded it to you tube here:
    Grubsnik wound up scoring and I got a doubles skill up and I decided on Dodge.
    Sorry for the shorter article. I've been putting in a bunch of late nights for Urban Warfare, plus it's Mother's Day weekend. Hopefully the exploding buildings make up for it. I'm sure you understand.  
    Previous Journal: https://www.gamedev.net/blogs/entry/2267185-battletech-developer-journal-05/ Next Journal: https://www.gamedev.net/blogs/entry/2267249-battletech-developer-journal-07/ Twitter Post: https://twitter.com/Eck314/status/1127601842228166656
  11. Eck
    I'm Chris Eck, and I'm the tools developer at HBS for the Battletech project. I've recently been given permission to write up articles about some of the things I work on which I hope to post on a semi regular basis. Feel free to ask questions about these posts or give me suggestions for future topics. However, please note I am unable to answer any questions about new/unconfirmed features.
    This week was even more performance work as we were gearing up for a cool live stream demo and we were hoping to show off some new Urban Warfare maps. The performance strike team made some huge gains in various areas and I think the stream went really well - I still need to watch it because I was on the road heading to a Blood Bowl tournament at the time.
    In addition to performance, I was helping other people with various tasks: UI, Serialization, code reviews, etc. But one awesome thing that I did was solve a bug that has been plaguing us for about two years. Often times the longer it takes to track a bug down the stupider or easier the problem is to fix. At the 2-4 hour mark it's usually something like a typo or capitalization issue. 
    The bug that wouldn't die
    So we've been having this issue where maps would just unpublish themselves seemingly at random and for no good reason. The problem has been happening for YEARS. And Friday I FINALLY found the problem.
    Many times in programming - the longer it takes to find the problem - the stupider the problem is. This was no different. We build all our procedural encounters in a test_encounters Scene and make our prefabs there. Well we don't want to publish those test encounters so we make sure to exclude them from the build in that scene. This value is stored on the Encounter Layer itself so when changes to the prefab get applied it would update the prefab value. Any prefab instance that didn't manually override the prefab value would then unpublish itself in the next build.
    One of the biggest problems was I could never see it happen on my machine. I'd have the designers check in these changes so I could try and identify what was happening. But at that point, the designer had already changed the publish value on the prefab instance so it always worked correctly when I was looking at it... GAH!
    After finding the cause of that issue I got SUPER jazzed. I love solving problems and when it's a persistent one like this it just feels so gooooood. It was a great way to end the work week.
    More Encounter logic
    Let's talk a little bit about the classes and scene hierarchy of Encounter Logic. At the top level I have an EncounterGameLogic. This is the base class for any object that the Encounter Logic operates on and the important bits are: a Name, an EncounterObjectGUID (unique id), some State Management (active/inactive/ignored) and some corresponding lifecyce functions (OnEnterActive, OnExitActive, etc)
    The topmost object in the Scene is the EncounterLayerParent. There is exactly one of these in each Map and it holds a list of Encounter Layers. As an example we might have three different battle encounters, an escort encounter, an attack base, and a defend base encounter all implemented on the same map. The Encounter Layer Parent manages which Encounter Layer is active since only one of those can be active at a given time. 
    In the case of procedural missions (Battle, Escort, Attack Base, etc) these are all created as prefabs. To implement a new Encounter Layer on the map, a designer just has to drag the prefab into the scene, position all the spawners, regions, encounter boundaries, etc. Play test it a few times, mark it for publishing and then it'll be incuded in the next build. When the Combat Mission loads, it tells the Encounter Layer Parent which Encounter Layer to activate. 
    The Encounter Layer contains a list of Encounter Chunks. A Chunk is a collection of Encounter Objects and sometimes prewired logic that I talked about in my Previous Journal Entry. EncounterChunkGameLogic inherits from EncounerObjectGameLogic and its only real job is passing along its Activation/Deactivation signals to its children. Chunks that have actual logic in them derive a child class and expose some configuration to the designer so they can tweak different things about them.
    Underneath chunks are where the actual Encounter Objects live. Things like Regions, Objectives, Dialogue, Lance Spawners, Routes, etc. Some of those like Regions, Routes, and Lance Spawners have further child objects underneath them (Region Points, Route Points, Unit Spawn Points). Each of these objects contains the Encounter Logic for its purpose and inherits from base classes where appropriate. There's an Objective base class that handles pretty much everything except for WHAT the objective is tracking: methods to CheckForSuccess, Failure, firing off messages when it succeeds or fails, populating the UI, etc. And everything overrides its own validation function that report helpful errors: Objectives need to be configured appropriately, Encounter Objects need to be within the boundaries,  etc. The object Hierarchy looks like the image to the right. 
    So most encounter objects exist as children of Chunks under the Encounter Layer. Obstructions are a special case though. They just exist in the level. Since we want things like rocks, foundations, and targetable buildings to exist for every Encounter Layer of a given map, they can't exist underneath the Encounter Layer. Otherwise we'd have to have copies of all the obstructions for each Encounter Layer and it would make creating prefabs difficult.
    Hopefully that was a decent overview of the Encounter Layer structure. I'll save Map Data and Contract Application for another day. 

    Blood Bowl Tournament
    I started to write up some notes about my Blood Bowl tournament experience, but it got big enough to be its own blog entry. If you're interested you can see how Grubsnik's Greenskins fared here. I will say that I had a blast and I finally got to meet my friend Kent in real life. (Publishing Blood Bowl journal soon)


    Tips from your Uncle Eck
    The longer you spend attacking a problem and something just DOESN'T make sense, start checking the simple stuff. Maybe it's a typo or casing issue, maybe you forgot to implement a function, or maybe it's just not plugged in.

    Previous Journal: https://www.gamedev.net/blogs/entry/2267151-battletech-developer-journal-04/ Next Journal: https://www.gamedev.net/blogs/entry/2267220-battletech-developer-journal-06/ Twitter Post: https://twitter.com/Eck314/status/1125147585524772872
  12. Eck
    This weekend I drove down to Pennsylvania for a pretty big Blood Bowl tournament. This is the biggest Blood Bowl event I've been to with 58 coaches ready to do battle on the pitch. My friend Kent told me about the tourney and it was a good opportunity to finally meet him in person. 

    I ran Grubsnik's Greenskins (my orcs) since that was my most painted team and we made the decision to go last minute. Normally you number your players differently so you can tell them apart. However someone said the number 7 was lucky and all the orks were fighting over who got to wear that number. Rather than start with some injured players after what was sure to be a massive brawl, the coach decided to give everyone the number 7 and just have different colored jerseys to tell them apart. This lead to some amazing marketing cause then fans could support the team in the color of their choice!

    Here's my roster: Grubsnik's Greenskins
    Game 1
    My first matchup was vs Clyde/King Tut playing as Khemri. He had four strength 5 players so he out-strengthed me.  But I had better armour and agility on my side. Plus Bomber Dribblesnot was able to do some serious work tossing bombs at the low agility players and knocking them over. A mummy finally had enough and put Dribblesnot on his back. It was a tough match up but my armour held out and I was able to secure a 1-0 victory after knocking out a few of his low armour skeletons.

    Game 2
    My second match was vs Jay/Some Kind of Eagles and he had a beautifully painted Human team. He is also a former Gen Con Blood Bowl champion. A friend of his found out I was going to be at the tourney and told him to track me down to say hi. I gave him my HBS business card and he took a couple pictures as proof of the completed task. I think the friend's name was Val? If so... Hi Val! If not, ping me with your name so I can say hi appropriately.
    I prevented him from scoring in his first half, knocked out four guys and was 1 pip away from a counter score. During the second half I managed to march the ball down field but time was running short. Right after the 10 minutes remaining mark I decided to run it in even though the ball was completely secure. I just wanted to make sure time wasn't called before I officially ran it in.
    I wound up accidentally throwing the win away though because my opponent said, "Let's hurry up and setup so I have a chance to equalize." I thought he meant just throw a bunch of blocks and earn a few points that way so I said sure. I quickly setup my guys leaving the sidelines completely open. He passes it up the field and we manage to squeeze two turns out giving him the chance to score. I try to stop him but my orcs are too far out of position to stop him. GAH! >.<

    Game 3
    My third game was vs. Jacob/What the Frog playing as Slaan - bunch of speedy leaping jerks and it looks like I forgot to take pictures. Playing defense vs them was extremely tough. I started wearing them down in the first half, but even still, they managed to leap right over my boyz and score during their possession in 4 turns. However, I almost managed a counter score but couldn't quite pull it off. I did manage to put about half his team in the KO and Injury box though.
    For the second drive, I was able to score pretty quickly and nearly cleared the pitch of his players. He was down to only 3 people on the field and I still had a full roster. I was expecting an easy time of it but I forgot he had Long Bomb. My orcs were unable to pickup the ball down in his field and he managed to stand up, snatch the ball, dodge out, and throw a hail marry pass down to the opposite side of the field!!! 
    I wound up having to blitz the guy a very particular way to even have a chance of scoring. I succeed and the ball scatters into the exact correct spot. Then my blitzer scoops up the ball and hands it off to my thrower. He runs up the field and makes a go-for-it roll to pass to my goblin. The pass is accurate and the goblin has to use his Catch skill reroll but manages to snag it. Then he runs it in for a touchdown. Victory 2-1! Someone made the comment - I hate it when elves dress up like orks.
    Overall, I placed 6th out of 58 and I'm pretty proud of the outcome. There are quite a few high-tier players that make the trek to Four Diamonds. Plus we wound up raising overr $6500 to help fight childhood cancer. And I finally got to meet my buddy Kent in real life. It was an awesome day.

    Here's a link to the tourney page: https://www.facebook.com/The4DiamondsCup/
    And here's a link to the Battletech Game Developer journal I wrote this week: https://www.gamedev.net/blogs/entry/2267185-battletech-developer-journal-05/ 
  13. Eck
    I'm Chris Eck, and I'm the tools developer at HBS for the Battletech project. I've recently been given permission to write up articles about some of the things I work on which I hope to post on a semi regular basis. Feel free to ask questions about these posts or give me suggestions for future topics. However, please note I am unable to answer any questions about new/unconfirmed features.
    As a tools developer, my heavy workload is usually at the beginning of new versions. Laying all the groundwork for things to come. Now we're reaching the end of Urban Warfare (due out June 4th if you haven't heard). So I only have a few tasks left in my queue and none of them are critical for Urban Warfare. That leaves me in the lovely position of having time to tackle performance issues. I hate tackling performance issues. Especially in other people's code... And performance issues aren't sexy, so I'll also be talking about Encounter Logic and how that works too.
    When tackling performance issues - the absolute first thing you do is MEASURE THE SLOWNESS! I can not stress this enough. I've seen it so many times where a developer (myself included) is "pretty sure the slowness is here" then they spend hours optimizing the wrong thing. Their efforts make a function taking half a millisecond run twice as fast and are surprised they don't notice anything when they retest. A performance profiler will tell you much more accurately where the problem is. 
    I was assigned the task to start looking at Pathfinding and Line of Sight calculations during unit movement. In Urban environments especially, this can be a big drag. So the first thing I did was... hook up the profiler.

    Running with the profiler attached makes things even worse. So don't look at these results and say OMG IT'S TOO SLOW! IT'LL NEVER WORK!!  Not only does the game have to run, but now it has to log information about every single function it calls and how much time each one takes. But since it's doing that everywhere, the profiler is still a good indicator for where the actual problem is. In the attached image, you can see the callstack of the running code and metrics about how long things took. Total is how much of that frame was used by this function and everything else it calls. Self is how much of that work was from this function.  
    So in the highlighted node, we see that about 23.9% of the time is spent in PathNodeGrid.FindBlockerBetween() and 6.6% was because of what goes on inside that function leaving 17.3% to the children. The next line MapTerrainDataCell.get_height() takes 12.7% of the total time. This was a red flag to me. The height of a given cell should be lightning fast.
    After some digging, it looks like the height of each cell is dynamically calculated by first seeing if there are any buildings in the hex. Getting the tallest one. Getting its height, and then comparing that to the height of the terrain. And then returning whichever one is bigger. It's doing this 12,351 times in this particular snapshot too. That is quite a bit of work for something that doesn't change very often. 
    Instead of dynamically calculating the height, we can cache that value. Then update the height of cells when things change (like when a building gets destroyed). That basically eliminates the MapTerrainDataCell.get_height() row and all its children. Accessing a raw float is way faster than a property. There are a few tradeoffs though. It uses a bit more memory to cache that value. Obstructions now need to update their related cells when they get destroyed. And map load times will increase by a few milliseconds so it can build the cache. All in all, I think that's worth it. 
    public float cachedHeight = 0f; // This used to be a public property. I changed it to private so nobody could access it. // That made refactoring easy/peasy cause the compiler told me wherever it was being used. // Then I replaced accesses to height with cachedHeight. private float height { get { if ( MapEncounterLayerDataCell.HasBuilding) return Mathf.Max(MapEncounterLayerDataCell.GetBuildingHeightUnsafe(), terrainHeight); else return terrainHeight; } } // This function updates the cached values by using the old/slow properties. It // gets called during the initial map load for every cell and then again if // buildings get destroyed. Other than that, a cell's height shouldn't change // so it will be safe to use the cached values. public void UpdateCachedValues() { cachedHeight = height; cachedSteepness = steepness; } There's definitely still more to do, but I just fixed about 12% of the slowness right then.
    Encounter Logic
    So the combat game is made up of all the rules of movement, shooting, melee, heat, stability, etc. It details out what you and the enemy can do. The Encounter Logic sits next to that and controls things like the states of objectives, whether or not a region is turned on, which units to spawn, what dialogue is displayed, etc. Here's how it works.
    Encounter Logic (aka map logic) exists in one of two places: either in the Encounter Layer itself, or inside of C# coded "chunks". Let's look at the Encounter Layer first here's the first bit of logic for a Simple Battle contract.

    Each bit of logic (aka trigger) is made up of a message that it listens to. A conditional that must evaluate to true in order to fire the remainder of the logic. If that passes, then it transmits a signal to a list of 0 or more Encounter Objects (almost always Activate). And a list of 0 or more "Results" or commands that execute. This can be anything like tell a dropship to liftoff, play some dialogue, issue some orders to the AI. 
    Name - Friendly name to tell the designer what this trigger is going to do. I think the name Ambush Warning is why sometimes Darius sounds strategically inept. Ambush Warning could mean warn of an impending ambush or it could mean warning we are being ambushed right now. Because of this sometimes Darius says something like: Heads up commander there may be additional hostiles in the area... You mean like the ones shooting me?!?! Thanks Darius!
    Only Trigger Once - checked - So once this trigger fires, it doesn't fire again. OnVisibilityAcquiredBlip - This is the message it listens to. The designers are allowed to listen to any message in the game though some might not make sense (like OnAppShutdown). Any time there is an acquired blip, we'll test the conditional to see if it's true.
    We have a GenericCompoundConditional which is a way for designers to link up multiple conditionals. This one is configured so that ALL the conditionals in the list must be true. The first is an ObjectInvolvedConditional configured to look at units tagged with ambush_units. The second is to make sure the DestroyAmbushers objective is not in progress yet. 
    If both of those things are true, we fire the logic of this trigger. We activate the DestroyAmbushers objective, then we fire off the Results which starts some dialogue and queues an audio event to signal that the ambushers have arrived.
    Being able to conditionally listen to different messages and fire off different results lets the designers do pretty much whatever they want. If there's something new they want to check against, I can code up a new Conditional. If there's something new they want to do, I can code up a new kind of Result. 
    Another way encounter logic can exist is through custom written C# "chunks". Chunks are collections of related objects and logic bundled up into a prefab so designers can drop them into scenes, configure a couple of things and test it out. Let's take a look at the Dropship Extraction chunk. We usually make these after it becomes obvious that we're going to use it on more than one Encounter Layer.

    In the Heirarchy on the left, you can see what's in this chunk. A dropship landing spot, a region, an objective to call the dropship, and an objective to load the dropship. In the middle you can see my horrendously ugly test scene along with the highlighted contents of the chunk. In the inspector on the right you can see some bits of configuration for the chunk itself. Spawn Lances When Landed will actually trigger the lances identified by RequiredTagsOnLances. Take Off Immediately will extract the units as soon as the last unit enters the region instead of waiting for the end of the round. Extract via Dropship says whether or not to actually use the dropship (or just disappear near a building or tunnel). This is a feature I just added otherwise I'd have called this chunk the Extraction Chunk instead of Dropship Extraction Chunk. The individual objectives are then configured to the specific lance that's trying to get extracted. Maybe it's for the player units, or maybe it's for an allied lance.
    Inside the chunk it listens to Objective Succeeded messages and checks things in much the same way as the encounter logic. Instead of a drag and drop graphical interface, it just uses C#.
    private void OnObjectiveSucceeded(MessageCenterMessage message) { ObjectiveSucceeded objectiveSucceeded = message as ObjectiveSucceeded; DropshipLandingSpotGameLogic dropshipLandingSpot = dropshipLandingSpotRef.GetEncounterObject(Combat.ItemRegistry); DropshipGameLogic dropship = dropshipLandingSpot.GetDropship(); // If the CallDropship objective succeeded. if (objectiveSucceeded.ObjectiveGuid == callDropshipObjectiveRef.EncounterObjectGuid) { // and the dropship is off screen. if (dropship.currentAnimationState == DropshipAnimationState.OffScreen && extractViaDropship) { dropshipLandingSpot.LandDropship(); } } // If the LoadDropship objective succeeded. if (objectiveSucceeded.ObjectiveGuid == loadDropshipObjectiveRef.EncounterObjectGuid) { DespawnUnits(); if (dropship.currentAnimationState == DropshipAnimationState.Landed && extractViaDropship) { dropshipLandingSpot.TakeoffDropship(); } } } Whew. This entry kind of got away from me and there's still tons more to cover with the Encounter Logic. Map/Encounterlayer relationship, Encounter Objects, Map Data, Applying Contract Data. That will have to wait for another day.
    CWolf's Cool Mod
    CWolf is a modder that ripped all my code apart, asked me all kinds of questions and built a really cool mod. It's called Mission Control and it allows other modders to build content that modifies existing contracts. Tthings like randomizing the spawn points, adding in new lances (sometimes friendly), and adding hooks for dialogue. Check it out!
    Tip from your Uncle Eck
    When tackling any performance issue ALWAYS HOOK UP A PROFILER FIRST. Otherwise you'll spend time speeding up the wrong thing and it'll still be too slow. 
    Previous Journal: https://www.gamedev.net/blogs/entry/2267126-battletech-developer-journal-03/ Next Journal: https://www.gamedev.net/blogs/entry/2267185-battletech-developer-journal-05/ Twitter Post: https://twitter.com/Eck314/status/1122581487012937728
  14. Eck
    I'm Chris Eck, and I'm the tools developer at HBS for the Battletech project. I've recently been given permission to write up articles about some of the things I work on which I hope to post on a semi regular basis. Feel free to ask questions about these posts or give me suggestions for future topics. However, please note I am unable to answer any questions about new/unconfirmed features.
    A few weeks ago we started a more stringent code review process where everyone is supposed to put changes on a feature branch and then get those changes code reviewed and tested before merging it back in. For most devs I imagine that's between 1-2 feature branches a week. But my task list is a bunch of small, unrelated changes all over the system so that was 1-3 feature branches per day. 
    Needless to say, everyone has been super busy working on Urban Warfare (and things are looking awesome by the way) so I had a big backlog of these. That couple with a Jira/Confluence hiccup meant I spent most of last week implementing code review feedback and merging my code back in. But I still worked on a couple of interesting things and I have 3 years of working on Battletech to pull from. So if I don't write one of these Journals it's cause I'm being lazy.
    New Region Types
    Until recently, we only had a few region types: Positive(Gold), Negative(Red), and Hidden defined by a RegionType enumeration (enum) in code. In the early days we felt like this would be enough to differentiate and identify the different region types. But after we shipped, there was still a bit of confusion about particular regions. Like the Escort Destination region in Capture Escort - is the player supposed to stand there? Or the Ambush Convoy mission has a Negative escape region for the enemy. The purpose is to prevent the enemy from getting there, but standing there is actually a good thing.
    To clear up this confusion we decided to implement new region types, and expose more data to the user like varying regions by color and then adding region labels. At first I was just going to add more values to the RegionType enum, but as I worked through it, I became more aware of the hard coded switch statements tied to its label and color. Bleh.
    Even though it was more work right now, I refactored RegionType to be a new RegionDef data file. For each region type there will exist one of these RegionDef files. There, we can specify what colors, the labels, and descriptions of the regions.
    // Original enumeration enum RegionDisplayType { Hidden = 0, Positive = 1, Negative = 2, } // Sample of some hardcoded colors tied to different region types public Color GetRegionColor(RegionDisplayType regionDisplayType) { Color regionColor; switch (regionType) { case RegionDisplayType.Hidden: default: regionColor = Color.clear; break; case RegionDisplayType.Positive: regionColor = UIManager.Instance.UIColorRefs.GetUIColor(UIColor.Gold); break; case RegionDisplayType.Negative: regionColor = UIManager.Instance.UIColorRefs.GetUIColor(UIColor.Red); break; } return regionColor; } // New RegionDef files get one file per Region Type. { "Description" : { "Id" : "regionDef_EvacZone", "Name" : "Evac Zone", "Details" : "When active, move all of your units into this zone to evacuate your units.", "Icon" : "" }, "FutureColorHex" : "#F79B2680", "ActiveColorHex" : "#F79B26FF", "FutureLabel" : "Future Evac Zone", "ActiveLabel" : "Evac Zone" } Now the only code I have to write is letting the designers choose which region they want and drive the choices off of the files in the RegionDef folder. All those hardcoded switch statements get converted over to just using the appropriate bits of data like selectedRegionType.ActiveLabel. And now, any time a designer wants a new region type, they can add it themselves by just creating a new Region Def file instead of pestering me.
    Since we're going to be showing region labels now, that's some new text in the game. New text in the game means new translations to be made. The developer who normally handles Localization tasks was busy doing other things so after a quick 15 minute discussion on how things worked, he sent me on my way. 
    First I needed to write up a RegionDefStringsCollector class. Since I'm working with a json file, it's pretty simple. I inherit from a Json String Collector base class, tell it my ResourceType (RegionDef) and just override my Collect method. Then there's a Localization class that has a list of all the Collectors. I add an entry to my new class and then the next time the strings get collected for localization, all my new data will get collected.
    public class RegionDefStringsCollector : JSONDataStringCollectorBase { // Tell the JSON collector what type we are public RegionDefStringsCollector() : base(BattleTechResourceType.RegionDef) { } // Collect our strings into the locTable protected override void Collect(string jsonText, EditorLocTable locTable, string source) { RegionDef def = new RegionDef(); try { def.FromJSON(jsonText); if (def.Description != null) { Util.CollectDescription(locTable, def.Description, source+".Description"); } Util.AddToLocTable(locTable, def.ActiveLabel, source + ".ActiveLabel"); Util.AddToLocTable(locTable, def.FutureLabel, source + ".FutureLabel"); } catch (System.Exception e) { Debug.LogError("Error reading " + source + " " + e); return; } } }  
    Work Bench
    And this weekend I was productive at home too. I ordered a bunch of lumber for some projects around the house and knocked out my work bench. Somebody said look at all those tools in the blog post image that he doesn't use. Well here's proof that I do!

    Tips from your Uncle Eck
    Be careful with your use of enums, they can be a code smell that something isn't quite right. If you find yourself hardcoding logic to specific enum values, you should definitely consider switching to a data driven system. That way when people change their minds, they can just change the data in a file instead of having to change code and cut a new build. Plus it makes modding your game that much easier.
    Previous Journal: https://www.gamedev.net/blogs/entry/2267089-battletech-developer-journal-02/ Next Journal: https://www.gamedev.net/blogs/entry/2267151-battletech-developer-journal-04/ Twitter Post: https://twitter.com/Eck314/status/1118878585736003585  
  15. Eck
    I'm Chris Eck, and I'm tools developer at HBS for the Battletech project. I've recently been given permission to write up articles about some of the things I work on which I hope to post on a semi regular basis. Feel free to ask questions about these posts or give me suggestions for future topics. However, please note I am unable to answer any questions about new/unconfirmed features.   What's a tools developer?
    I don't work directly on the game, instead I work on the tools that help make the game. My "customers" are the other game developers in the studio, and my goal is to make their lives easier, increase their productivity, or enable them to do new and exciting things. The main areas I'm responsible for are the Maps and Encounter logic and the tools that Designers use to build encounters, contracts, events, and flashpoints. 
    I really enjoy the Tools Developer position as a job. It's literally a different problem to solve every day. I also like interacting directly with the people who use what I code, which is something most game developers don't get the chance to do.
    For example, here are a few of the things I worked on last week:

    Vertical Dropship Animation Style
    In Urban Warfare environments, there are quite a few buildings scattered around (I hope that didn't spoil the surprise for anyone...) Our normal Leopard Dropship flyby animation comes in at a bit of an angle. In Urban maps, the wings would occasionally clip through a nearby building so I needed to give designers the ability to configure that behavior.
    I added the configuration bit to the Lance so when units are spawning via dropship, designers can choose the animation style. One of our animators worked up some new animations, and our tech artist wired them up into an AnimatorOverrideController.

    The AnimatorOverrideController is a Unity component that does just what it says. The original AnimatorController contains all the logic  for transitioning between various animation states based on animation variables set by the code. The  OverrideController basically maps any calls to the original animation over to the new vertical set.

    Notice how we didn't override leopard_moveCoreGroundIdle? That's because nothing special needs to happen here. When the dropship has landed on the ground, it's just sitting there so there's no need to change the animation. Sittin' still is just sittin' still. When a particular override is unset, the original animation is used.
    Design Mask System for Urban Environments
    Urban maps are quite a bit different than the normal maps we've shipped in the past. For one, the ground isn't a Unity Terrain object. Instead, it's a bunch of GroundPlate "Obstructions" (an Obstruction is anything that can block movement, line of sight, or have units stand on - so big rocks, buildings, foundations, etc.). For normal terrain maps, Designers would paint down DesignMasks directly on the terrain. So they might paint a swath of trees down next to a little patch of rough terrain, and paint a road through them both. However, no No visible terrain meant designers couldn't paint down any design masks. 

    We came up with the idea to create these MaterialSlots on the GroundPlates. Pictured here is a blank GroundPlate with four MaterialSlots. When the designer selects the Trees designer mask, it paints that section of the GroundPlate with a nice looking grass texture. As part of the map export process, a DesignMask camera takes a snapshot of all the different Materials (one for each DesignMask (Trees, Water, Roads, etc) and saves png's out for the game to read in later.
    In the combat game, we use those PNG's to determine which design masks units are standing in, and will impart different game effects based on that. This was already working since we did a similar export process for Terrain based maps. 
    One more thing I had to do though was add a special design mask for ground plates. Normally when a building is sitting inside a forest and a mech jumps on top of it, you wouldn't want the design mask of what's painted on the ground. The mech isn't "in trees" it's on a building. But in the case of GroundPlates - I DO want to use the underlying design mask of the "terrain". So in the GroundPlate BuildingDef I set it to UseTerrain. So whenever a unit is standing on a ground plate, it checks the terrain to get the correct design mask.
    Combat Dialogue Sequences
    One problem we were having was when multiple dialogues could fire get fired off at the same time, the order was non-deterministic. It just depended on who registered to listen for that message first. Sometimes it was okay, but sometimes was a bit jarring depending on how the dialogue was written. So we made Combat Dialogue Sequences
    Instead of individual dialogues firing off onesy-twosy style in whatever order they happen to be registered, the DialogueSequence says we'll fire DialogueA then DialogueB then DialogueC. It also has some smarts in it to skip over dialogue that has been turned off by the contract. Say DialogueB is in a ControlledByContractChunk that's turned off. The sequence will play DialogueA and then DialogueC.

    This ensured a specific order that Designers and Contract writers could count on and also cleaned up some weird CameraFocus issues that were occurring.
    I decided to also support playing DialogueSequences just in case. So we could have something like DialogueA - DialogueSequenceB (Dialogue B1, B2) - DialogueC. I didn't make the editor component as pretty as I could, but there's . There's a slot for a Dialogue and a slot for a Sequence for each DialogueItem. You're only supposed to set 1 and there's a validation error if you try to set both. Maybe one day I'll go back and pretty it up, but probably not.
    Dynamic Structure Points for Buildings
    One of the designers was worried about buildings having a static amount of structure points. Give the building too many and it takes forever to beat it down with lighter mechs in the early game. Give them too few and heavier mechs can take down a building too easily when you're trying to defend it in the later game. So we came up with the idea to adjust the amount of structure points based on the difficulty of the contract.
    In CombatGameConstants, I defined a structure to hold a row of building hit points per difficulty. The designer then tags specific buildings in the level with scaled_small, scaled_medium, or scaled_large. And when the combat game loads up, the starting structure points of these buildings are overriden appropriately.

    That's all for now
    So there was a glimpse of a few issues that I tackled last week. If you found this interesting or have any questions, let me know in the comments below. Or via the twitter post below.
    Until next time,
    - Eck
    Next Journal: https://www.gamedev.net/blogs/entry/2267089-battletech-developer-journal-02/ Tweet: https://twitter.com/Eck314/status/1114663250094886918  
  16. Eck
    I'm Chris Eck, and I'm the tools developer at HBS for the Battletech project. I've recently been given permission to write up articles about some of the things I work on which I hope to post on a semi regular basis. Feel free to ask questions about these posts or give me suggestions for future topics. However, please note I am unable to answer any questions about new/unconfirmed features.
    I'm going to try and maintain a weekly pace with these developer journals, but we'll see how long that lasts. Last week was mostly cool new features I can't talk about or bug fixes that aren't super interesting. Luckily JustinCase wanted me to talk a little more about the editors that are included with BattleTech so this week we're going to talk about the ContractParser and its simple Markup files.
    Contract Parser
    First, some history... When we started writing contracts in Battletech, they were in straight json format, but Kiva (the Lead Game Designer on Battletech) and the rest of the designers felt it was a bit too cumbersome to edit by hand. To change this, Kiva wrote up a few PHP scripts that would take some proprietary markup and turn that into json by overriding the default template for given contracts. This worked just fine for a while. However, she quickly became a bottleneck as demands on her time grew throughout the project. The tool was only setup on her machine and she was the only one that could add features to it. Eventually this bubbled up the priority list and I was asked to replace the php scripts with a tool of my own.
    Enter the Contract Parser. It's a simple C# command line app that takes a directory or filename as a command line argument, and then it parses all the contract text files into json files. The way it works is pretty simple. It loads up the ContractTemplate json for that specific ContractType (SimpleBattle, CaptureBase, etc) and then loads up the specified designer's txt files and overrides individual bits of data (Objective text, Combat Dialogues, Units, etc). Then it just saves out the json to the appropriate file name.
    The biggest problem I had to tackle was how to make a CommandLine application with the data I needed from the BattleTech codebase - without including ALL the code. The contract parser doesn't care about the models, the animations, the terrain, the sound effects, etc. But our code base didn't have clean lines of separation between modules. As I started including just the bits I needed, more and more dependencies started to creep in.
    The ContractOverride needed access to the EncounterLayer so it could apply itself. The EncounterLayer needed access to EncounterObject, Chunks, Objectives, and Serialization libraries. Some of those wound up connecting into the SimGame and things just exploded out from there. I spent days refactoring classes so I could use them in my standalone application. I removed the dependencies that I could and then used some conditional compiling and mock classes for anything I couldn't easily separate out. It was decidedly not fun. Eventually I managed to pull that data out into a separate DLL that I could compile against and now all three editors share a link to that file.
    How does it work?
    The code parses each file line by line and acts like a simple state machine. Line type is identified by a name in square brackets. For simple items it only needs one line like Name, ContractType, and Salvage. But some items are more complex like Dialogue Content which need other lines to detail out the rest of the data Color, cast, emote, audio.
    Here's a sample pulled from SimpleBattle_AFavorToRegret.txt:
    // Dialogue [Dialogue]Dialogue_MissionStart [Content]Enemies detected in the area, Commander. [Color]1|1|1|1 [Cast]castDef_DariusDefault [Emote]Default [Audio]NONE [Content]Intel reports that {TEAM_TAR.FactionDef.ShortName} sent a pretty serious lance of 'Mechs. [Color]1|1|1|1 [Cast]castDef_DariusDefault [Emote]Default [Audio]NONE [Content]Also keep an eye out for harassing units from the locals. [Color]1|1|1|1 [Cast]castDef_DariusDefault [Emote]Default [Audio]NONE Comments and blank lines are skipped. The [Dialogue] node specifies which dialogue to edit by name. Now the parser is in Dialogue mode so it can only parse Content nodes. It sees a content node so starts parsing it. Now it's in Content mode and can handle Color/Cast/Emote/Audio. After that, the parser sees a Content node and can't handle that in Content Mode so it pops back out into Dialogue mode. Dialogue mode DOES know how to handle Content nodes so it parses another one. Eventually the end of the file is reached or it runs into a node it can't parse so it gets back into the root level nodes and tries to parse that. If the root level can't parse the node then it's a validation error and it reports the problem in the file and which line number.
    After the contract parser runs, it shows a summary of what it processed and calls out each of the validation errors. It shows what file, what line, tells you what the problem is, and even includes the original line so you can hopefully see the problem before opening up the file. Here's what some errors might look like:

    And here's a link to some more detailed documentation about the ContractParser markup. It's a little bit out of date but it still has some useful information in it. https://docs.google.com/document/d/1ePfeUph7HIGNCVZxDXDJ0fZVEiqXQLGhdPwIsgdeLdM/edit?usp=sharing
    The base game Designer Text files, Contract Json files, and Contract Parser are all included with the game. If you browse to local files on steam you can open the "BattleTech_data\StreamingAssets" folder. "design\contracts" holds the Designer Text Files. "data\contracts" holds the Contract Json. And "editors" holds the ContractParser, EventEditor, and FlashpointEditor. There are some helper *.bat files there with instructions in them if you edit the file. Basically you change StreamingAssetsSource to just StreamingAssets and that should work for the built game.
    Flashpoint Editor
    The Flashpoint Editor already has some pretty amazing documentation available. Amechwarrior has been puzzling it out and writing up his findings in a Paradox forum post. If you're at all interested in creating your own flashpoints, head over to his post and be sure to tell him what an awesome job he did https://forum.paradoxplaza.com/forum/index.php?threads/flashpoint-authors-guide.1153590/
    Hopefully this inspires a few people to take a crack at writing their own contracts. I look forward to seeing what you create.
    - Eck
    If there's a topic you want me to cover about a feature that's already shipped, or if you have any questions for me, feel free to ask.
    Tips from your Uncle Eck
    Keep your data classes really dumb. Only use them for data or you may find yourself unravelling the code base just to get at what you need.
    Make your error messages as informative as possible. Say what's wrong and where it happened. It seems simple, but I've seen too many bad error messages in my day.
    Previous Journal: https://www.gamedev.net/blogs/entry/2267040-battletech-developer-journal-01/ Next Journal: https://www.gamedev.net/blogs/entry/2267126-battletech-developer-journal-03/ Tweet: https://twitter.com/Eck314/status/1117515256962723841  
  17. Eck
    I finally got a chance to play Twilight Imperium 4th edition so I figured I'd do a write up. We had 5 players and it was a blast. 
    For race selection, we used this website about a week before the game http://www.mygurps.com/TwilightImperium.html and selected 3 choices per player. Here's what it rolled up for us (underlined is what we chose):
    Yellow (Eck (me) ) may choose from The Clan of Saar, The Naalu Collective, or The Nekro Virus. Green (Devin) may choose from The Barony of Letnev, The Mentak Coalition, or The Winnu. Blue (Matt) may choose from The Federation of Sol, The Yssaril Tribes, or The Xxcha Kingdom. Black (Eric) may choose from The Ghosts of Creuss, The L1Z1X Mindnet, or Sardakk N'orr. Blue (Alex) may choose from The Yin Brotherhood, The Emirates of Hacan, or The Embers of Muaat. To save time, I setup the map before hand since I was hosting. I went with a 5-player symmetrical design and tried to balance out tech specialties, resources, influence, and planet types. Here's what I came up with. See the future pictures for how that red section gets essentially "cut-out" for a 5 player game.
    Map setup
    With a mostly balanced map, we rolled to see who would get first pick of starting locations. And the last person to choose was awarded the Speaker token. One other house rule we played with was the Speaker would get to pick the 6th Strategy Card. The secondary ability on the 6th card would get triggered after the Speaker activated his Strategy Card.
    Round 1 start
    And then we started playing.  It was a pretty standard set of early turns where people moved out and claimed a few systems. The only thing of special note was the Mentak (Green) chose Warfare but instead of claiming more of his central pie slice, he instead claimed the contested planet between him and the Hacaan (Purple). This led to immediate border friction and posturing between the two races. Green was there first, but Purple felt pinned in behind the Gravity Rift (black hole). 
    I feel like the Naalu (yellow) are late bloomers so I was quick to make friends with my more combat focused neighbors. Here's what we looked like after Round 1.
    Round 1 complete
    I made a deal with the L1z1x (black) player to allow me to claim the green tech specialty planet and move out of the system so he could have the bigger resource value world. The green tech specialty would allow me to get my racial tech Neuroglaive faster and make it so I could hold my own versus all those dreadnaughts. We also exchanged Ceasefires.
    I couldn't quite take Mecatol this round so I moved adjacent to claim that victory point and set me up for next turn. I made sure to get Sol's (blue's) permission before taking that world and gave him our border planet to solidify the peace. 
    I also traded my racial promissory note to the Mentak which allowed him to move first in the next round. Giving him a slight edge with the coming conflict of the Hacaan's (purple's) fleets. And I bought a sabotage card from the Hacaan which helped fund the war efforts. Publicly trading for a sabotage was great since it made people less likely to play action cards against me.
    Round 2 Complete
    Round 3 was very tense. L1z1x (black) chose Imperial, but his dreads could only move 1 (at the start). I delayed my move as long as I could so he was low on tactical counters. Then when he upgraded his dreads, I activated Mecatol Rex. At this point he stated multiple times that if I did that, he would roll through my territory. But this was the same turn I got Neurogalives. I told him I was no longer afraid of his fleets, Mecatol Rex was mine, but I would not strike first. He was still low on counters so he couldn't really do anything but stew this turn.
    While this was going on, the Hacaan (purple) got pinned in by Mentak (green) and Sol (blue). The space cats started massing a big fleet and told the Mentak (green) player he was coming for him. Blue claimed a few more planets and a couple of points.
    Round 3 complete
    L1z1x (black) started an arms race with my peaceful Naalu (yellow), threatening me the entire time. Not much was exchanged besides words however. Black was still token starved so he couldn't attack me without crippling his own position thanks to Neurogalive.
    Big things were happening on the other side of the board. The Hacaan (purple) used an action card to connect Alpha and Beta wormholes to threaten the Mentak's (green's) homeworld. In response, the Mentak moved their speed 3 cruiser fleets through the Beta wormhole and took the Hacaan's homeworld.
    My daughter was wandering in and out of the game all day. She was in the room around this time. After the game she asked me if green attacked purple with his cruisers because she saw he could sneak through... #ParentingWin
    Blue started moving his fleets towards Mecatol but I made some sweet deals just to get his ceasefire.
    Round 4 complete
    The Mentak (green) counter attacked the Hacan (purple), but the space cats played skilled retreat. Then they retook their homeworld. The Federation of Sol (blue) started moving towards Hacan (purple). L1z1x (black) built up a ton of PDS and upgraded them meaning I couldn't go crazy in his backyard. 
    The glorious Naalu (yellow/me) made a series of plays over this round to gain 5 points. 
    Imperial - held mecatol +1 Imperial - (Public Objective) Held 6 non-home system planets +1 Secret (Action phase) - Win a space combat versus a player with the most points +1 Secret (Status Phase) - Own two faction technologies +1 Public (Status Phase) - Own two unit upgrade technologies +1 So I went from 3 points to 8 points in one round. I still had the Speaker token so I'd get first choice of strategy card, and as the Naalu I would get to act first no matter which Strategy Card I chose. Nobody could take Mecatol from me this round since I had waited so late in the turn to make my move this turn. 
    I knew there were action cards or agendas that might be able to mess me up, but the only thing that popped up was Seeds of an Empire. Voting on that either the first player would gain a point, or the last player would gain a point. I was scared for a moment because I thought first player might lose a point. 
    Round 5 complete
    We didn't bother playing out any of round 6 because I was going to choose imperial, score a point for Mecatol, and score a point for one of the objectives that they couldn't take from me.
    Victory Naalu (yellow/me)!
    Final score:
    The Naluu Collective - 10 The L1z1x Mindnet - 6 The Mentak Coalition - 6 The Federation of Sol - 4 The Emirates of Hacan - 3  
    Final Thoughts:
    Fourth edition is much more streamlined than 3rd. The 5 player game took 8 hours including a break in the middle for pizza. Being the Naalu and going from 3 to 8 in one turn and then winning the first action of the next turn was really impressive, but it also felt a little bit unfair. Hanging out at 3 points, people didn't feel I was a big threat so they never felt the need to stop dealing with me diplomatically. But I was also on Mecatol Rex for the entire game and nobody attacked me there once. So I don't feel too bad about winning. I liked our 6th card house rule, and I also liked the 5 player wedge cut out. Most of the others didn't like it however, because they felt like it took away too many planets. I don't think they realize that with a 6th player we'd have someone else occupying space AND we'd also have to deal with 5 blank tiles so there would be EVEN FEWER worlds.
    I know people had fun though because they're already asking me when I'm hosting another round. Maybe we'll play the 14 point game soon.

    Next Game: https://www.gamedev.net/blogs/entry/2265456-twilight-imperium-4th-edition-game-2/
  18. Eck
    I wrote this nearly a year ago, but didn't get around to publishing it. Now that more info is coming out about the game, I think it's a fine time to share the epic struggle between Lazaraus and Talon.
    Original Post:
    It's been over a year since I posted last so let me catch up my Game Dev buddies. You already know that I landed my dream job at Harebrained Schemes working on Battletech as a Unity/C# Tools developer. The job has been amazing and my co-workers are awesome. I look forward to going into work every morning and since then I've gotten to do even more cool things:

    Go to Gen Con and show off the super pre-alpha. Talk to fans about the game. Sign people's Battletech rulebooks, swag, and posters like I'm a rockstar. Play table-top Battletech with oversized minis on twitch in a live-action show where damage is recorded by taking hammers, dremmels, and soldering irons to the minis. (Check out Death From Above on Hyper RPG) Help work on and shape a game that was such a big part of my teen/college life. Win the Ultimate Multiplayer Battletech Trophy for the first season at HBS. Anyway, back to the post at hand... Connor is a coworker of mine and one of the main DFA stars (Talon). There's a pretty big audience for the show and interest was expressed in getting to watch Connor and I battle it out in multiplayer. I wasn't setup to record the video so instead I took screenshots of us playing so I could write up this play by play post.
    We were fighting a Battle (20 Million C-Bills) fight on Big Loch. There's an island in the center with some trees, hills, and bigger cover surrounded by a body of water. I took a Jenner, Trebuchet, Hunchback, and Kintaro. Connor took a Commando, Centurion, Kintaro, and Jagermech (LRM variant).

    Round 2: We start out on opposite sides of the island. That beacon off in the distance marks where his units spawn. I start by moving my Trebuchet to the left and my Kintaro to the right. If he charges up the middle, he'll have to choose his facings carefully. I send my speedy Jenner up the middle to scout things out, and my Hunchback also takes the straightest route since he's the slowest unit I have.

    Round 4: Connor was too clever to go charging up the middle. Now my Trebuchet is a bit out of position, but it can still bring its LRMs to bear through indirect fire. You can see the results by the slight damage to armor, and minor amount of stability damage on his Centurion. I probably should have taken an action shot, but I was concentrating so hard on the tactics of the match. You can also see my Kintaro is out of position all by himself. Luckily I was able to use the initiative track and that cover to keep him fresh for a round or two.

    Round 7: There's only so much dancing around cover I can do and my Kintaro takes a pounding. I try to finish off the Commando and give the Centurion a tempting back shot. It's risky but my Kintaro's front armor is pretty wasted.

    Round 7: My Kintaro fails to take out the commando and I'm a little worried at this point. My hunchback finally gets close enough to threaten the Jagermech next turn.

    Still Round 7: Instead of shooting at the Centurion, I sprint my Jenner up to shoot at the Commando. It's on the edge of death now but still alive. Lucky for me Connor's Centurion missed an 85% AC/10 shot to the Kintaro's rear. He puts his back to the wall thinking he's safe...

    Round 8: In round 7, I made sure all my units were done before Connor's. That guarantees that I get to go first in round 8. I use my Jenner to finally finish off the Commando. Even though he only had 1 leg, he still had his large laser. Not only that, he could of still meleed one of my units to knock him down or just use his activation to manipulate the initiative track. Active units are always a threat.

    Round 8: My Kintaro somehow manages to stay alive even though it gets knocked prone. You can see here I have a pretty even spread. My right side is the least damaged so I try to offer that in future rounds. My Jenner is pretty exposed in the water, but he'll have to decide between taking a shot on my light or finish off the Kintaro.

    Still Round 8: I didn't take a screen shot, but Connor finished off my Kintaro and I take out his fresh Centurion with my Trebuchet. It was able to move just far enough up to get a rear shot on his Centurion. There were rocks in the way but my Jenner spots for him which allows me to indirect fire for some awesome back shots. Then my Hunchback slams an AC/20 into the center torso of the Jagermech.

    Round 9: Connor turns the Jagermech's back to my Hunchback. It was probably the right move since his front CT was gone, but the rear armor wasn't enough to save him. The AC/20 lands home in the rear CT. His Kintaro blows away my Jenner, but goes up a ton of heat to do it. He tries to use the rock as cover.

    Round 10: After the Kintaro moves, my Hunchback climbs the hill and manages to crit the SRM ammo. He only has a medium laser left so Connor calls the match.
    It was a great fight and I barely won. When I over-committed my Kintaro early in the fight I thought Connor was going to give me my first defeat. My luck held out though and the damage clustering favored me (both giving and receiving). Connor also meant to take a pilot with Sensor Lock but misclicked during the setup.
    I didn't talk much about the pilot skills, but they play a big part. My Kintaro had a pilot that gives him evasive (normally only granted by sprinting and not firing). That's one reason he could soak so much damned fire. My Jenner had Sensor Lock which allowed him to clear evasive and grant line of sight to a unit. My Trebuchet had master tactician which let him act one initiative phase sooner and exploit the sensor locking Jenner. My hunchback had Bulwark which gives you defensive bonuses for staying still. I had to move him every turn so it never came up.
    I don't remember everything Connor had but I do remember he had Master Tactician in his Kintaro... That thing being able to act during the light phase is freaking ridiculous.
    Anyway this was the first time the mighty Talon and Lazarus clashed on the field of battle and it was glorious. I can't wait for everyone to experience the fun and excitement of a multiplayer match.
    - Eck
  19. Eck
    I caught wind of the Missile Command challenge earlier in the month and figured I'd knock it out during Christmas break. I was planning on coding more on my Car Wars prototype but the challenge took up most of my programming time. Sleeping late, hanging out with my family, and playing video games took up the rest of my free time. All in all, it was a great way to slide into the new year.
    I spent some effort keeping the code clean (mostly) and even did a couple of hours worth of commenting and code reorganization before zipping up the package. Many sample projects are quick and dirty affairs so aren't really a good learning resource for new programmers. Hopefully my project isn't too bad to look at.  

    What went right?
    Switching to Unity a few years ago. This was definitely the right call for me. It helped me land my dream job and allows me to focus on making games rather than coding EVERYTHING from the ground up. 
    KISS (Keep It Simple Stupid) - I started thinking about doing my own take on Missile Command but decided on doing just a simple clone. The known and limited scope let me keep the project clean and kept development time down to something reasonable. Playing the original at http://my.ign.com/atari/missile-command was a big help for identifying features I wanted.
    Getting the pixel perfect look of old-school graphics was a little bit tricky, but thanks to a well written article I was able to get sharp edges on my pixels. https://blogs.unity3d.com/2015/06/19/pixel-perfect-2d/ I had done some research on this earlier so I knew this would be a problem I'd have to solve. Or rather see how someone else solved it and implement that. 
    Using free sounds from freesound.org was a good use of time. There are only 4 sound effects in the game and it only took me an hour or two to find what I felt were the right ones.
    What went ok?
    Making UI's in Unity doesn't come naturally to me yet. I just want some simple elements laid out on the screen. Sometimes it goes pretty quickly, other times I'm checking and unchecking checkboxes, dragging stuff around in the heirarchy, and basically banging around on it until it works. 
    I got the minimal core features of Missile Command, but not all of it. You don't really think about all the details until you start making them. I'm missing cruise missiles, bombers, and the splitting of warheads.
    Dragging the different sprites into position on the screen was manual and fiddly. There's probably a better way to do this, but it didn't take too long.
    You can't shoot through explosions, which makes the game a little more challenging. And you can blow up your own cities if you shoot defense warheads too close to them. It was easy enough to fix, but I left it in there.
    What went wrong?
    I spent a ton of time getting the pixel perfect stuff right. Playtesting in editor, it was set to Maximize on Play. There wasn't quite enough room to see the full screen so the scale was at 0.97 making the display 97% what it should be and thus, blurred all my sharp edges. I didn't see that setting though... >.< I pulled my hair out trying to see the problem in my math, and even downloaded a free pixel perfect camera which was STILL showing blurry stuff. Finally, I built the game and ran it outside the editor and saw things were just fine. There's also an import setting on sprites for Compression that I cleared to None.  I'm not sure if this second step was necessary. I've been bit by the Editor not being 100% accurate to final game play and wished I would have tried that sooner.

    I had trouble with the scoring screen. I wanted to put the missiles up on the score line like Missile Command does, but ran into trouble with game sized assets and canvas sized UI elements. After 45 minutes or so I said screw it, and just put a count of the missiles and cities up. 
    I didn't data drive the game as much as I wanted. The data is in the code itself so users can't mod the game without downloading the source code. I'm also only using one set of colors for the level. If I put any more time into this, I'll probably tackle this issue next, and then worry about the features I missed out on.
    Final thoughts
    Working on this project makes me appreciate how awesome the programmers of old really were.  With the tools I have (Unity, Visual Studio, etc.) it took me a couple of weekends. And even then I didn't recreate all the features of the original. I'm including a link to the zipped up project in case anyone wants to see the source code to play around with it. Hopefully someone finds it useful. If so, let me know. 
    EcksMissileCommand.zip - Executable if you want to play.
    EcksMissileCommand_Source.zip - The project if you want to mess around with it.
    Sound Credits
    https://freesound.org/people/sharesynth/sounds/344506/ - Explosion.wav
    https://freesound.org/people/sharesynth/sounds/344525/ - LevelStartAlarm.wav
    LevelStartAlarm_Edit.wav - I used Audacity to edit the above sound. I took the first six beeps then faded them to silence.
    https://freesound.org/people/Robinhood76/sounds/273332/ - MissileLaunch.wav
    https://freesound.org/people/sharesynth/sounds/341250/ - ScoreCount.wav
  20. Eck
    BattleTech is going to have a Random Event System during the single player campaign. Basically as time ticks by, there's a random chance for these events to popup during the time between combat missions. You're given a situation and a handful of options to choose from. Some options may not be available based on previous events, contracts, or other game play. Based on the chosen option, a result set is chosen at random (weighted) and the results of that are applied. 
    I recently delivered the event editor for our game so the designers could crank out events at a faster pace. Originally, it was hand edited json. Then it was a simple text format that Kiva wrote a python parser for to convert that text into json. Now it's a WinForms app with a gui, validation, and other utilities built in. The designers have been using it for a while now and so far they're really happy with it. Here's a screenshot of the main editor and one of its subforms.

    Event System Data Model
    Events - Have a Title, Description, Image, Scope, a list of Requirements, and a list of Options. The Scope tells us which main object we're going to be dealing with (typically Company or Mechwarrior). The requirements say what must be met in order for this event to be pulled. And the options are the choices the player makes when they see this event.
    Option - An option is a choice a player makes. It has some text, a list of Requirements, and a list of potential Result Sets. Choosing this option randomly selects one of the result sets.
    Result Set - Is what gets applied as an outcome to the event. It has a description, a Weight, and a list of Results. The Weight influences the randomness of the outcome. If we have two result sets and one has a weight of 75 and the other a weight of 25, then the first Result Set has a 75% chance to be chosen.
    Result -This contains all the data that happens as a result of the event.  Added Tags, Removed Tags, Stat Modifications, Forced Events, Actions, flags for a Temporary Result, and its Duration. This is how the game world gets modified. You can add a tag to the Company Honorable, Remove a cowardly tag from a mech warrior, give a star league era Gauss Rifle, or force an event into the queue for later. 
    Requirements - Requirements have a Scope, a list of Required Tags, a list of Excluded Tags, and a list of Comparisons. The scope tells us what we're looking for. Any Required tags must be on the object. Any excluded tags most NOT be on the object. And any Comparisons must be met. For example: Scope: MechWarrior, Required Tags: Marik_Origin, Excluded Tags: Cowardly, Comparison: Injuries > 0 - would look for a wounded Marik pilot who is not a coward. 
    TagSet - A list of strings attached to various items in our game (maps, encounters, contracts, company, mechwarriors, mechs, etc.) It's a simple concept, but very powerful. Many things are setup with an initial set of tags, but we also add tags to your Company, and your various mechwarriors. These are then querried on by events and other systems in our game to make the world feel more alive.
    Data Driven Content
    I'm a big proponent of using data to drive applications (game or not) because of all the benefits it provides. The major benefit here is being able to add content to your game without access to the source code. When done correctly, it opens the doors wide for a modding community to take your game into new directions and it can really add some longevity to a title.
    The event editor will be included in the final version of our game and I'm excited to see what events they'll come up with.  
    - Edited to talk about what the event system actually is so people don't get confused about "event programming". 
  21. Eck
    Hey everyone - long time no post. Sorry about that. I landed my dream job over a year ago and have been pretty busy since then. I'm working at Harebrained Schemes on the new turn-based Battletech game. The last one was over 20 years ago! Come check it out at:
    Today I'm going to talk a little bit about a metadata database (That's really fun to say btw) and how you might use it in your projects.

    What is a metadata database?
    Let's start with a couple of definitions. Metadata is just data about other data. And a database is just a structured way of storing and accessing data. So... a metadata database is a structured way of storing and accessing data about other data... Is your mind blown yet?
    Why use a metadata database?
    I'm going to look at our units for this example. We have a bunch of units with lots of data stored in separate files. Our unit metadata contains things like the filename, the type of unit: mech, vehicle, turret - and tags that describe the kind of unit that it is: medium, sniper, jump_capable. Instead of hard coding our unit spawn points to a specific unit, we configure the spawn point to ask for a unit with the tags "mech" and "medium" to change things up a bit. Without a metadata database, we'd have to load up every single unitdef into memory, and then loop through all of the data to build a list of the units that match, then select a random entry. With a MDDB, we can write a query that returns the list of units that qualify without having to load every single file and load the unit that we need to spawn.
    Also, once you put this type of data in a database, you can start writing sql queries to easily get at information. How many maps implement a particular encounter? How many contracts are written for the Escort contract type. Are there any events that can't be triggered because they rely on tags that are never awarded.
    Some Library Options
    We're using C# and Unity for our project, and these are the main tools we're using for our SQL needs.
    http://www.sqlite.org/ - Free Sql engine http://www.mono-project.com/docs/database-access/providers/sqlite/ - Free library for interfacing with SQLite http://sqlitebrowser.org/ - Free visual tool for editing your database schema and writing queries. Some TagSet Query Code
    Here's some C# I wrote that builds a dynamic bit of sql based on the number of required tags and excluded tags. It wasn't trivial so I figured I'd share it. One requirement I'm not completely happy with is that the tags you ask for have to be in the database. At the start of the function I insert the ones that weren't present. It made the sql a little bit cleaner to look at and get right.  For our needs it's not a big deal because we'll only be asking for tags that we care about, but if an end user is typing in random stuff you'll want a slightly different approach.
    Also I made one change to the Schema since after the graphic was made. I dropped the TagID and just use Name as the primary key. TagSetTag then drops its TagID column and gets a TagName column to point to Tag. I recommend that datamodel instead of the one shown, but our fans made the graphic for the data model and I didn't have a way of updating it easily. The code presented matches the old way.
    public static List<TagSet_MDD> GetTagSetWithRequiredTags(this MetadataDatabase mdd, TagSetType tagSetType, TagSet requiredTags, TagSet excludedTags) {     // Get the tag rows for the specified tags so we can get their database ids.     List<Tag_MDD> requiredTagRows = GetOrCreateTagsInTagSet(mdd, requiredTags);     List<Tag_MDD> excludedTagRows = GetOrCreateTagsInTagSet(mdd, excludedTags);     // Here's what the query will look like for 2 requried tags and 1 excluded tag     /*     select         ts.*     from TagSet as ts         -- Required Tags         inner join TagSetTag as tst0             on ts.TagSetID = rtst0.TagSetID         inner join TagSetTag as tst1             on ts.TagSetID = rtst1.TagSetID         -- Excluded tags         left join TagSetTag as etst0             on ts.TagSetID = etst0.TagSetID             and etst0.TagID='tag id 3'     where         rtst0.TagID = 'tag id 1'         and rtst1.TagID = 'tag id 2'         and etst0.TagID is null     */     string queryText = "SELECT ts.* FROM TagSet ts ";     string joinText = string.Empty;     string whereClause = string.Format("WHERE ts.TagSetTypeId = {0} ", (int)tagSetType);     // Join to one instance of TagSetTag for each required tag.     for (int i = 0; i < requiredTagRows.Count; ++i)     {         joinText += string.Format(GTSWRT_RequiredInnerJoinFormat, i);         whereClause += string.Format(GTSWRT_RequiredWhereClauseFormat, i, requiredTagRows[i].TagID);     }     // Join to one instance of TagSetTag for each excluded tag     for (int i = 0; i < excludedTagRows.Count; ++i)     {         joinText += string.Format(GTSWRT_ExcludedLeftJoinFormat, i, excludedTagRows[i].TagID);         whereClause += string.Format(GTSWRT_ExcludedWhereClauseFormat, i);     }     // Assemble the query text and return the results.     queryText = queryText + joinText + whereClause;     List<TagSet_MDD> tagSetList = mdd.Query<TagSet_MDD>(queryText).ToList();     return tagSetList; }  
  22. Eck
    Lots of updates but not enough time to do them justice. Here's a quick bulletpoint list.

    Sold my house back in Tennessee. That went very smoothly and it went to a good family. \o/
    Daughter switched schools again. She's liking it, and the math is finally a challenge.
    Still working hard at work. I've made several terrain tools, worked on some cool Inverse Kinematics stuff, and now I'm working on the level design tool for Battletech. I plan to share a little of what I'm doing and write up some techniques, but there's just no time!
    I'm guest starring on a live Battletech RPG show on twitch. It's on HyperRPG and called Death From Above. There's an RP session where the story unfolds and a tabletop session where we play on a custom board with oversized miniatures and inflict actual damage to the mechs as they take hits. That's on Fridays at 6:30pm PST. Next Friday is my last session for a bit.
    And I'm captaining a team for a boardgame fundraising event at Mox Boarding House. It's called the Gauntlet and it's raising money for YouthCare. If you donate, not only do you help homeless youths, you also give my team power ups for the board game tournament. Plus checkout my sweet incentives! :D It's on Sunday May 15th, [s]so hurry and donate before it's too late![/s] It's too late to help my team, but you can still feel free to give money to YouthCare.


  23. Eck
    Personal Update:
    Who has two thumbs and just accepted a gig at Harebrained Schemes working on the new BATTLETECH game?!?!?

    This guy!

    I submitted my resume and portfolio hoping against hope that they would choose me... AND THEY DID! I'm now the new Tools Engineer for the project.

    Two days after they said I'd need to wait for a couple of weeks to interview the other candidates, they sent me an offer letter. (I wonder if there's a funny story there) I thought I'd have more time to prepare!!! O.O Now I need to move my family across the country from Tennessee to Washington state in the next week or two. We're scrambling to pack-up all the stuff we plan to keep and giving away the rest to friends and family members. This is a huge life change, but my family is behind me 100%!

    Why is it my dream job? Well, being a for-reals game developer has been one of my goals for a while now. I was prepared to do it on my own, but then I heard Harebrained Schemes were hiring a few developers for the new BATTLETECH Kickstarter project. I've been playing BTech and nearly all of it's variations for 30 years now so I'm a big fan of the franchise. Take a look at my collection if you don't believe me: Eck's Battletech Stuff. So for me, this isn't just A game development job. This is THE game development job.

    I've been focusing my efforts over the last several years to give myself a chance to become a game developer on my own. I'm happy I did, because without this preparation, I don't think I would have even been considered. And if I wasn't financially secure, I couldn't have accepted the offer in the first place. It's expensive to move across the country!

    I'm super stoked!!!

    It hasn't been all sunshine and happiness though. Dreams come at a price. I am saying goodbye to some awesome friends I've had since college. They're happy for me landing my dream job, but they're also very sad to see me go. I've already had a few tearful goodbyes and there's probably still a few more of those in the future. I've hosted Game Day every other week for nearly 15 years now... We probably won't sit at the same physical game table again. I'll keep in touch digitally, but it's not the same. Maybe after things settle down, we'll get some virtual tabletop action going.

    Development Update:
    I spent some time working on content generation tools for quests. I was just shy of finishing that since I was offered the job and since then I've been doing home repairs, packing, and donating stuff. I do still plan to finish the game, but A Voxel Adventure is on hold for the near future. I'm planning on focusing on content generation tools so my wife can develop some quests, monsters, and gear for the game while I work on being a badass code ninja at work.

    Tip from your Uncle Eck:
    If you're a software developer, you probably have the luxury of being well paid. So put in a little effort to get financially secure. You never know when you might need that extra money.

    Apply for your dream job even if you "know" they will say no... Cause they might say YES!
  24. Eck
    Sorry for the lack of updates. As it turns out, living the dream is hard work. I'll make up for it with a really big post instead.

    Personal Update:
    So, we rented an apartment really close to work and my daughter's school. Car shopping has been terrible. The first car we went to go look at stank. And I'm not talking old-car smell. I thought I was in the Seinfeld episode with the stinky car. The next three we looked at also had a smell and a fourth had a rattling engine sound. Luckily my mom wanted to get a new car so she's selling me hers. That should get shipped to us sometime this week.

    Our apartment is starting to shape up after weeks of it being jam-packed full of boxes. We bought a ton of furniture from IKEA and most of our stuff now has a place. It's still a little bit crowded so I'll be giving away some more of my stuff. We FINALLY got everything straightened out with Comcast and have our computers connected to the interwebs. It's been about three weeks since I've played anything, but with the unpacking nearing its end and the car due in soon, I should have some free time soon.

    Home stress levels have been much higher than I thought they would be. We had a good family cry about missing our friends, family, and home a week ago. Normally, I don't have to deal with those pesky things called feelings, but this was a significant enough event to register in my robot heart. We're all feeling much better now though. Stupid feelings.

    Oh, and last week my daughter and I went to space camp which was awesome! My mom bought us tickets when we were still in Tennessee. What should have been 4 hours worth of driving turned into over 11 hours of flight and 2 hours of driving. (Still worth it!) On the way down, we spent the night at my mom's and I got to see a couple of my gaming buddies which was fun too. I'll post a link to my Facebook album when I get around to uploading my pics.

    Development Update:

    Week 1
    Work has been a little bit stressful, pretty hard, and very cool. I had a computer waiting for me on the first day which was nice. At my last job, a guy waited over a month for his computer... And yes, he was a programmer. >.
    Anyway, they threw me in the deep end but so far I'm doing fine. I fixed a few bugs, and added some features to a couple of 3rd party terrain tools we're exploring: Gaia Stamps, Relief Terrain Pack, and Terrain Composer. The main thing I worked on this week was writing a New Map tool for Mike Hines the Technical Art Director. Basically it copies a source scene in Unity, does some automatic setup for new levels, and fixes all the references in the new scene. If you've ever tried copying a complex scene in Unity, it can do some weird things.

    Harebrained Schemes helped launch a new twitch channel named HyperRPG. The launch event was a 48 hour charity stream and they needed warm bodies to fill Sunday 5am-12pm so I volunteered. They just told me we'd be playing board games and talking, but I felt like I was in a fruity-oaty bar commercial. But I got to slap a guy at least. Internet personality Goobers said that if anyone donated $200 someone on the show could slap him. He hadn't seen all of Firefly so I tagged him pretty good.


    They have some interesting content with a MechWarrior campaign where they RP on Tuesday and play on a custom board with large scale mechs that they mangle as they're damaged on Friday. There's also a Shadowrun campaign and other nerdy goodness there. Check it out.

    Week 2-3
    Mike came up with a cool way to use Terrain Composer to switch between painting Art and Game Design features so I worked on a tool to automate switching between those modes. I also worked on extracting the different game design masks that get painted down. For example, a level designer paints down an area of forest and I'll save out the mask to a PNG that can be read by the game logic. Then as mechs move through the forest areas, it slows them down and affects their targeting numbers.

    General Coolness:
    Being a Battletech fan boy, it's been pretty awesome in general. I get to hear the story writers talk about different characters and paths of the story. I get to see the concept art for different characters, buildings, and environments take shape. I get to play the prototype and feel how the game mechanics work out. Not only that, I get to help guide the process.

    The team is awesome too. They're nice, smart, and fun to be around. It very much feels like a family. Most teams I've been on in the past there's been some less than stellar talent or even some dead weight on the team. But everyone I've been exposed to has proven themselves to be very useful and highly skilled. Plus, the leadership of the company is honest, forthcoming, and they're just great guys.

    And to top it all off, when Jordan Weisman comes into the office, he sits at the end of my row.

    I can't believe how lucky I was to get this job.
    It's a game development job
    For a game that I love
    For an out of state company
    And me without any professional game development experience
    Without knowing anyone in the biz
    And it turns out the job rocks
    And the team rocks
    And even the new city rocks.

    It's ridiculous. Thank you God. I'm not sure what I've done to deserve this, but I sure do appreciate it.

    Tip from your Uncle Eck:
    Living the dream isn't all sunshine and rainbows. It's hard work, it requires sacrifices, and making the transition can be more than a little stressful. It's definitely not for everyone.

    Lucky for me I didn't throw away my amazing life for a shitty job that I just thought I wanted. Now... to find a new gaming group.

  25. Eck
    Personal Update:

    So right now, I'm flying across the country with my immediate family. We each have about 3 days worth of clothes, the Chromebook, our phones, and that's it. We gave away or donated lots of our stuff, all of our furniture, and even our cars. The remaining stuff, we crammed into the POD and finally finished loading it Monday evening. They picked it up Tuesday and started trucking it across the country. It takes an estimated 10 days to get there so it will be the 27th at the earliest before we have access to our stuff. That includes our computers! >.

    Tuesday and Wednesday was all home repair, cleaning, and last minute tasks. It was really sad saying goodbye to my mega awesome Game Room. It was so empty. My family, friends, and I made a ton of good memories there. It was also especially profound to me when I was looking at my empty keychain. No house - no car - and most of my possessions are in my garage waiting for the donation crew to come pick them up. I'm saying goodbye to the life I've known for nearly 20 years. O.O

    TODO - Pic of empty game room and key ring.

    My wife has been looking at houses/apartments online and we'll be checking some out later in the week.

    Development Update:

    Since one of the first tools I'll be working on is a ramp tool for the level editors, I dove into learning about Unity Terrain. (The computers were the last things to get loaded ) Terrain geometry in unity is handled behind the scenes by a 2d heightmap. Each pixel on the heightmap corresponds to an area in the terrain and you can change the form of the terrain by modifying this data. Unity terrain also has the data for trees, grass, and wind zones baked in.

    After my research, I decided to get a simple terrain effect working. I wanted to level out a circular platform where the user clicked. The first thing I got working was converting mouse coordinates over to terrain coordinates.

    TODO - Conversion details, and code samples.

    Once I had the proper terrain coordinates, modifying the terrain was a simple matter of using GetHeights to pull a list of nearby terrain cells, modifying a circular pattern to the appropriate height, and then using SetHeights to set the data.

    I'm writing this entry on the plane so I'll come back and share some code samples after I find a place and get my computers back online.

    I can solve the trivial case of orthogonal oriented ramps, but I need some help for arbitrarily oriented ramps. I understand how to draw a pixelated "line" of terrain on the map using something like a Bresenham line drawing algorithm, and lerping the height to create the ramp. But that would only be one cell (pixel) thick. I thought about "walking" along the path and drawing perpendicular lines of the appropriate height, but I'm worried it will generate holes in the integer terrain coordinates. Maybe not though.

    I could build an orthogonal ramp, and then rotate it appropriately with some matrix math, but that assumes the ramp will be straight. If some level editor wants a ramp to curve around the terrain of a hill, the solutions I'm thinking of won't work. I'll need to allow them to drop a couple of control points and somehow trace that arbitrary path to build the width of the ramp. I need to talk to the artists to see what they want and talk to the lead developer to bounce my ideas off of him.

    Next up, I worked my way through some more editor tutorials. One in particular showed me how to allow the user to place control points for lines, curves, and splines, so that's going to be useful for sure.

    Sorry for such a long post. It's a long flight! My first official day is Monday Feb 22nd. My next post probably won't be until the 28th, but I'll be sure to tell you all about it.

    Unity Gotchas:

    GetHeight returns the height of the terrain in height adjusted units. If you have a 1200 scaled height terrain, it would return a value between 0 and 1200. But GetHeights and SetHeights return and expect float values from 0 to 1.

    Unity stores the heightmap as a 2d texture. Changes to the heightmap modify that texture permanently and the changes aren't "undone" between runs. So if you're hoping to deform the terrain during a game, know that you'll need to keep a copy of the original to restore the terrain between each run.

    Tip from your uncle Eck:

    I'm a little low on relevant wisdom to share, so I'll give some more life advice. Don't give people two options when one of them is wrong. This is especially true for ladies and their husbands. Ladies, we want to make you happy! Just tell us what you want! :)
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!