Jump to content
  • Advertisement

Search the Community

Showing results for tags 'Tabletop Games'.

The search index is currently processing. Current results may not be complete.


More search options

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Categories

  • Audio
    • Music and Sound FX
  • Business
    • Business and Law
    • Career Development
    • Production and Management
  • Game Design
    • Game Design and Theory
    • Writing for Games
    • UX for Games
  • Industry
    • Interviews
    • Event Coverage
  • Programming
    • Artificial Intelligence
    • General and Gameplay Programming
    • Graphics and GPU Programming
    • Engines and Middleware
    • Math and Physics
    • Networking and Multiplayer
  • Visual Arts
  • Archive

Categories

  • Audio
  • Visual Arts
  • Programming
  • Writing

Categories

  • Game Dev Loadout
  • Game Dev Unchained

Categories

  • Game Developers Conference
    • GDC 2017
    • GDC 2018
  • Power-Up Digital Games Conference
    • PDGC I: Words of Wisdom
    • PDGC II: The Devs Strike Back
    • PDGC III: Syntax Error

Forums

  • Audio
    • Music and Sound FX
  • Business
    • Games Career Development
    • Production and Management
    • Games Business and Law
  • Game Design
    • Game Design and Theory
    • Writing for Games
  • Programming
    • Artificial Intelligence
    • Engines and Middleware
    • General and Gameplay Programming
    • Graphics and GPU Programming
    • Math and Physics
    • Networking and Multiplayer
  • Visual Arts
    • 2D and 3D Art
    • Art Critique and Feedback
  • Community
    • GameDev Challenges
    • GDNet+ Member Forum
    • GDNet Lounge
    • GDNet Comments, Suggestions, and Ideas
    • Coding Horrors
    • Your Announcements
    • Hobby Project Classifieds
    • Indie Showcase
    • Article Writing
  • Affiliates
    • NeHe Productions
    • AngelCode
  • Topical
    • Virtual and Augmented Reality
    • News
  • Workshops
    • C# Workshop
    • CPP Workshop
    • Freehand Drawing Workshop
    • Hands-On Interactive Game Development
    • SICP Workshop
    • XNA 4.0 Workshop
  • Archive
    • Topical
    • Affiliates
    • Contests
    • Technical
  • GameDev Challenges's Topics
  • For Beginners's Forum
  • Unreal Engine Users's Unreal Engine Group Forum
  • Unity Developers's Forum
  • Unity Developers's Asset Share

Calendars

  • Community Calendar
  • Games Industry Events
  • Game Jams
  • GameDev Challenges's Schedule

Blogs

There are no results to display.

There are no results to display.

Product Groups

  • Advertisements
  • GameDev Gear

Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


About Me


Website


Role


Twitter


Github


Twitch


Steam

Found 26 results

  1. 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. Links Twitter Post: https://twitter.com/Eck314/status/1162755432592683008
  2. 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 Notice/Disclaimer 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: http://www.warehouse23.com/products/car-wars-deluxe-edition
  3. 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. Setup 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. 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. Notice/Disclaimer 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 http://www.warehouse23.com/products/car-wars-deluxe-edition 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. Links Twitter Post: https://twitter.com/Eck314/status/1147966512818458625
  6. Foreword Learning to make content for games is a journey that never ends, but trying to break in for the first time is a difficult process. When I started several years ago, I came across too many theoretical articles on content creation for games and too few applied examples. To this end, I've put together this brief article on elegant design, but decided to do so from the vantage point of an applied (theoretical) example. Disclaimer: at the time of writing this article the suggested card effect had never been released by the developers, but by the time of its release a very similar effect was added, possibly proving the legitimacy of the process. Enjoy! On Elegance The title of this article states that "Elegant" Content will be designed here. So, what exactly IS Elegance in design? To answer this question I find no better alternative than to quote Mark Rosewater, Head Designer for the Magic the Gathering team over at Wizards of the Coast: In the words of Rosewater: How big should a piece of text be if you want it to be elegant? The answer is as big as it needs to be - and not a word more. Elegance requires taking a holistic view of writing. Every word, every sentence, every paragraph is a piece of a larger puzzle. It's not enough to understand the impact of a single element. Elegance requires simplicity. Simplicity requires a single purpose of thought. This means that elegance starts before you write a single word. A good sculptor must know his image before he picks up his chisel. There are many ways for you to explain an idea. The most elegant one though is not through definition but by example. By connecting your idea to one already known by the reader, you're leaving the work of teaching to someone in the past. Education is hard. Comparison is easy. A common barrier to elegance is the belief that only one way will work. Often a writer is unable to abandon a beloved piece of prose even when evidence demonstrates otherwise. If something doesn't add to the larger sense of the piece, you have to learn to let it go. But remember: "If I had more time, I would have written a shorter letter." - Marcus T. Cicero Choosing our Game I was just as lost as you are. I mean, for the most part, I thought I understood what the above meant until I actually started to implement it and figured these were all pretty words... The truth is that these words are perfectly accurate, but without a proper example to ground them into a process they're meaningless to a beginner -- so let's change that shall we? Now that we have a rough understanding of what it means to design elegantly (but have yet to see it applied to truly understand what it really means), we need to pick a framework (a game) to which we will apply this knowledge. For this example, I chose the relatively modern board game/miniature game "Star Wars X-Wing Miniatures". If you are not familiar with the product, I would recommend a quick read of the core principles of the game. They can be found on the game's website, or I could just give you the link now couldn't I? Now, I'll assume you have not read through that documentation and cover the basics. It's important to note that the more you know about the core game, the more you'll understand this article, but that understanding of the game is not mandatory to understand how elegance will be applied to design as a whole, though some of its subtleties might be lost on you. The Framework - Core Mechanics / Components X-Wing Miniatures, at its core, is a game of ships moving, attacking, and getting destroyed. Each ship comes with stock capabilities ("stats": attack, defense, life/shields), specific maneuvers it can do (moves) and specific actions it can perform (boost, focus, barrel roll, etc.) Each ship also comes with a set of customizable "slots". These slots can be filled with upgrade cards before the game starts, which effectively constitutes the player's list. For example, the Millenium Falcon allows the player to bring 2 "CREW" upgrades along, so you could bring Chewbacca and Luke aboard Han's favored craft to recreate some of the greatest Star Wars moments if you wanted. Upgrades come in various types, and each ship fields a unique combination of them: Astromech: Much like the famed R2D2, these are droids you can attach to some crafts to add some special abilities Salvaged Astromech: Similar to the Astromechs, but faction-specific to the Scum and Villainy (think Boba Fett & Friends) Torpedoes: Expendable ordnance. Fire and forget! Missiles: Pretty much the same as torpedoes, they're just "different" Cannon: Repeated-use secondary weapons. They tend to pack a punch. Turret: Repeated-use secondary weapons. These tend to fire "out of arc" (360 degrees) Bomb: Expendable ordnance, dropped from your rear, and only trigger when someone moves over them, or at the end of a combat round. Crew: Having them on your ship grants abilities. System: Having this equiped to your ship grants abilities (it's just like a crew or astromech in a different package). Modification: Once again, grants special abilities. Modifications are special in that every ship can field one. Illicit: Tend to be "single-use" abilities