Jump to content
  • entries
  • comments
  • views

Battletech Event Editor / Data Driven Gameplay



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". 


Recommended Comments

Nice!  Mildly surprised to see WinForms.  Not a bad thing, as I always loved WinForms for it's simplicity, even if it's uglier to look at than a WPF  or XAML UWP app.

I am really curious about the Event system used in-game.  I've never been completely happy with any of my event systems inside Unity.

Share this comment

Link to comment

Time was a factor and WinForms is what I knew from the olden days. By the time WPF became a thing, I was still on a legacy ASP.Net project for a big client that couldn't switch technologies. I never got around to picking it up. 

So far the event system is pretty cool. It's still coming online, but I'm really liking what I see. :)

- Eck

Share this comment

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
  • Advertisement
  • Blog Entries

  • Similar Content

    • By CommanderLake
      I've been experimenting with my own n-body simulation for some time and I recently discovered how to optimize it for efficient multithreading and vectorization with the Intel compiler. It did exactly the same thing after making it multithreaded and scaled very well on my ancient i7 3820 (4.3GHz). Then I changed the interleaved xy coordinates to separate arrays for x and y to eliminate the strided loads to improve AVX scaling and copy the coordinates to an interleaved array for OpenTK to render as points. Now the physics is all wrong, the points form clumps that interact with each other but they are unusually dense and accelerate faster than they decelerate causing the clumps to randomly fly off into the distance and after several seconds I get a NaN where 2 points somehow occupy exactly the same x and y float coordinates. This is the C++ DLL:
      #include "PPC.h" #include <thread> static const float G = 0.0000001F; const int count = 4096; __declspec(align(64)) float pointsx[count]; __declspec(align(64)) float pointsy[count]; void SetData(float* x, float* y){ memcpy(pointsx, x, count * sizeof(float)); memcpy(pointsy, y, count * sizeof(float)); } void Compute(float* points, float* velx, float* vely, long pcount, float aspect, float zoom) { #pragma omp parallel for for (auto i = 0; i < count; ++i) { auto forcex = 0.0F; auto forcey = 0.0F; for (auto j = 0; j < count; ++j) { if(j == i)continue; const auto distx = pointsx[i] - pointsx[j]; const auto disty = pointsy[i] - pointsy[j]; //if(px != px) continue; //most efficient way to avoid a NaN failure const auto force = G / (distx * distx + disty * disty); forcex += distx * force; forcey += disty * force; } pointsx[i] += velx[i] -= forcex; pointsy[i] += vely[i] -= forcey; if (zoom != 1) { points[i * 2] = pointsx[i] * zoom / aspect; points[i * 2 + 1] = pointsy[i] * zoom; } else { points[i * 2] = pointsx[i] / aspect; points[i * 2 + 1] = pointsy[i]; } /*points[i * 2] = pointsx[i]; points[i * 2 + 1] = pointsy[i];*/ } } This is the relevant part of the C# OpenTK GameWindow:
      private void PhysicsLoop(){ while(true){ if(stop){ for(var i = 0; i < pcount; ++i) { velx[i] = vely[i] = 0F; } } if(reset){ reset = false; var r = new Random(); for(var i = 0; i < Startcount; ++i){ do{ pointsx[i] = (float)(r.NextDouble()*2.0F - 1.0F); pointsy[i] = (float)(r.NextDouble()*2.0F - 1.0F); } while(pointsx[i]*pointsx[i] + pointsy[i]*pointsy[i] > 1.0F); velx[i] = vely[i] = 0.0F; } NativeMethods.SetData(pointsx, pointsy); pcount = Startcount; buffersize = (IntPtr)(pcount*8); } are.WaitOne(); NativeMethods.Compute(points0, velx, vely, pcount, aspect, zoom); var pointstemp = points0; points0 = points1; points1 = pointstemp; are1.Set(); } } protected override void OnRenderFrame(FrameEventArgs e){ GL.Clear(ClearBufferMask.ColorBufferBit); GL.EnableVertexAttribArray(0); GL.BindBuffer(BufferTarget.ArrayBuffer, vbo); mre1.Wait(); are1.WaitOne(); GL.BufferData(BufferTarget.ArrayBuffer, buffersize, points1, BufferUsageHint.StaticDraw); are.Set(); GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0); GL.DrawArrays(PrimitiveType.Points, 0, pcount); GL.DisableVertexAttribArray(0); SwapBuffers(); } These are the array declarations:
      private const int Startcount = 4096; private readonly float[] pointsx = new float[Startcount]; private readonly float[] pointsy = new float[Startcount]; private float[] points0 = new float[Startcount*2]; private float[] points1 = new float[Startcount*2]; private readonly float[] velx = new float[Startcount]; private readonly float[] vely = new float[Startcount];  
      Edit 0: It seems that adding 3 zeros to G increases the accuracy of the simulation but I'm at a loss as to why its different without interleaved coordinates. Edit 1: I somehow achieved an 8.3x performance increase with AVX over scalar with the new code above!
    • By OConquestGame
      Hello there!
      I’m the creator and producer of an upcoming visual novel / video game. 
      My team and I are looking for artists (character and background), writers (experienced in writing relatable characters and witty dialogue), and programmers (familiar with unity and creating mini games). 
      Our team is a group of close friends looking to break the mold of the traditional visual novel and create something new and positive. This game will be highly promoted and be a great portfolio piece. Rates are negotiable!
      If you are interested please contact/message us today! OConQuestGame@gmail.com
    • By Gas Lantern Games

      I have spent the last year and a half developing a game in my spare time in Unity! I am releasing it soon on Steam. Ant Empire is a strategic remake of some older games. It is influenced by games such as Ant Empire and Civilization.

      I am currently doing a kickstarter to help fund an AI before launch.

      I have attached some images (tried some gifs but they were too large) to show the current stage of Ant Empire, which is nearly completed.

    • By MarkNefedov
      So, initially I was planning to create a base class, and some inherited classes like weapon/armour/etc, and each class will have an enum that specifies its type, and everything was going ok until I hit "usable items".
      I ended up with creating UsableItem class, and tons of inherited classes, like Drink/Apple/SuperApple/MagickPotato/Potion/Landmine/(whatever that player can use) each with unique behaviour. I planned to store items in the SQLite database, but I discovered that there are not many ways of creating variables(pointers) with type determined at runtime (that preferably get their stats/model/icon/etc from DB). So, I think that I need to use some variation of the Factory pattern, but I have no idea how I should implement it for this particular case (giant switch/case 😂 ).
      It would be really nice if you guys can give me some advice on how I should manage this kind of problem or maybe how I should redesign the inventory.
      Inventory storage is an array of pointers. I'm working with CryEngine V, so RTTI can't be used.
      Example code:
      namespace Inventory { enum ItemType { Static, Building, Usable, Weapon, Armour }; class InventoryItem { public: virtual ~InventoryItem() = default; virtual ItemType GetType() = 0; virtual string GetName() = 0; virtual string GetIcon() = 0; virtual void Destroy() { //TODO: Notify inventory storage delete this; } }; class UsableItem : public InventoryItem { public: struct Usage { int Index; string Use_Name; }; virtual CryMT::vector<Usage> GetUsages() = 0; virtual void UseItem(int usage) = 0; }; class TestItem : public UsableItem { int Counter =0; ItemType GetType() override { return ItemType::Usable; } string GetName() override { return "TestItem"; } string GetIcon() override { return "NULL"; } CryMT::vector<Usage> GetUsages() override { CryMT::vector<Usage> Usages; Usages.push_back(Usage{1, "Dec"}); Usages.push_back(Usage{2,"Inc"}); Usages.push_back(Usage{3,"Show"}); return Usages; } void UseItem(int usage) override { CryMT::vector<Usage> uses = GetUsages(); switch (usage) { case 0: for (int i =0; i<uses.size(); i++) { CryLog(uses[i].Use_Name); } break; case 1: Counter--; CryLog("Dec"); CryLog("%d", Counter); break; case 2: Counter++; CryLog("Inc"); CryLog("%d", Counter); break; case 3: CryLog("%d", Counter); break; default: CryLog("WRONG INDEX"); break; } } }; }  
    • By Effekseer
      Effekseer Project develops "Effekseer," which is visual software for creating open source games; on September 13,
      I released "Effekseer 1.4," which is the latest major version release. 
      Effekseer is a tool to create various visual effects used in games and others.
      With Effekseer, you can easily create various visual effects such as explosion, light emission, and particle simply by specifying different parameters.
      Effekseer's effect creation tool works on Windows and macOS.
      The created visual effects can be viewed on Windows, macOS, Linux, iOS, Android and other environments with DirectX, OpenGL and so on.
      In addition, there are plugins / libraries for game engines such as Unity and UnrealEngine4 to view visual effects.
      Effekseer 1.4 is an updated version of Effekseer 1.3 released in November 2017.
      This update contains the following changes:
      The renewal of UI. Support the tool for macOS. Addition of a function to read FBX with animation. Addition of parameters to protect collied effects and objects. Addition of parameters for easier control of the effects. In addtion I improve plugins/libraries for Unity, UnrealEngine4 and Cocos2d-x.
      Besides that, more than 40 new sample effects have been added and many bugs have been fixed.
      Effekseer 1.4 is available on the project website.
      The license for the software is the MIT license.

      Sample Effects.
      Tool Demo

      View full story

Important Information

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

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!