Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

ECS Roguelike

Discussion in 'Entity Component System' started by Sarkahn, Jan 31, 2020.

  1. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    (This project is based on the excellent Rust Roguelike Tutorial by TheBracket)

    I've restarted my roguelike from scratch with a somewhat better understanding of how the pieces should fit together - current progress:

    y1LZf3BvCc.gif

    Full source with the MIT license is up at https://github.com/sarkahn/dots-roguelike. Contributions are welcome!


    Hello. For the past few weeks I've been working on a framework for making a roguelike in Unity. Alongside it I've been making a tutorial for creating a roguelike using my framework and Unity's ECS system.

    I figure the tutorial should be of interest to anyone who wants to see how you can create an actual working game in ECS. It starts simple and each part covers a new concept, from how to manage the camera and rendering, to how to represent shared map data in ECS.

    The readme for each chapter is not a a "step-by-step" tutorial that really explains ECS - I was originally going that route and it turned out to be WAY too much work and ultimately just distracted from the ultimate point. I want to show how to make a game using ECS, not to explain how ECS works.

    Instead each chapter will give a brief overview of how the systems work for that example. At the start of chapter 1.1 I provide plenty of resources for documentation and tutorials where you can learn all about how ECS actually works as you follow along.

    I'll keep this thread updated as I add new parts. This is my first time taking a serious crack at something like this, and I'm learning as I go along too, so I'd really appreciate any feedback if people are interested in something like this. Thanks!

    Introduction
    ----------------
    1.0 - Writing to the Console: A simple example of creating and writing to a console. Nothing really ECS related.

    1.1 - ECS: Provides a brief introduction to ECS with plenty of references to learn more. Demonstrates how to use the Conversion System to create some entities, and how to create the console from code and use it to render your entities. It also shows how to handle player input to move the character around.
    View attachment 552897

    1.2 - Walking a Map: Generates a map for the player to walk around in via a "Reactive" map generator system. Covers how to handle sharing a simple representation of map data between systems, and goes over how the generator works.


    1.3 - A More Interesting Map: Makes some tweaks to the map generator to create the traditional "rooms and tunnels" style map.

    1.4 - Field of View, Interfaces, and Reactive Systems: Like the title says it covers how to create a "Field of View" system that remembers parts of the map we've already seen, and briefly covers how to use interfaces with jobs and burst and the benefits of "Reactive" systems.

    1.5 - Monsters and Refactoring: Refactors the code from the previous chapters so our systems can apply to more than just the player entity, then adds some monsters during map generation.

    1.5A - Taking a Turn: Goes over my implementation of a turn-based system in Unity's ECS, and introduces the beginnings of a combat system.
     
    Last edited: Jan 19, 2021
    Krajca, NagaChiang, Kmsxkuse and 21 others like this.
  2. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Reserved
     
    OldMage likes this.
  3. Macoron

    Macoron

    Joined:
    Mar 11, 2017
    Posts:
    31
    Hey, nice work! I also tried to implement roguelilke on ECS, you can check it here: https://github.com/Macoron/RoguelikeECS
    I ended up using hybrid ECS (easier to render tilemap). Also map represented by a native array of entities. It gives you proper way to attach new components directly on a tile (like wall component or trap component).

    Hope you find something interested for you.
     
    Deadcow_ and Sarkahn like this.
  4. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Hello! I personally would avoid the entity-per-tile approach. While it might seem intuitive at first I feel it runs counter to how the data will actually be used. Most of the time tiles will be identical to any other tile of the same type, so it makes more sense (in my case) to just store tile types and handle per instance data elsewhere.

    It should be fine for smaller maps but it could cause memory and performance issues as the map scales up - especially with the hybrid renderer which is horribly slow right now.

    Not to say your approach is wrong though, as always theres a million ways to do it. Thanks for sharing!
     
    Macoron likes this.
  5. Radu392

    Radu392

    Joined:
    Jan 6, 2016
    Posts:
    210
    He said he used it for rendering... So there's no hybrid renderer involved. And I did the same thing as him a while ago for my copy of 'They Are Billions'. I used a tilemap to render a 256x256 map which easily scaled. Tilemaps are by *far* the fastest way to render a huge map of... tiles. I even overlapped a dozen of them and it cost almost nothing either on the gpu or cpu. However, I also did not assign an entity to every single tile, merely only to the tiles that actually had a purpose other than rendering, which in my case, were about 80% of them. So unless you do work over every single one of your tiles all the time, you won't run into any performance issues or 'memory' problems.

    Of course the Tilemap has its own limits, which is why I had to do some overlapping in my case. Another problem I had was that on a huge 1kx1k tilemap, the editor was starting to slow down a lot, but *only* when editing the tilemap using the Unity editor tools. Other than those inconveniences, Tilemap is the way to go if you're serious about making a performant roguelike.

    Since you said you're learning as you go, you should at least give this method, and probably others that will pop up on this thread, a try instead of dismissing it outright based on inexperienced assumptions. Especially when you're at the beginning.
     
  6. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    @Radu392 Hello!

    You're right, I assumed when they mentioned hybrid ECS with regards to rendering they were talking about the hybrid renderer, that's my mistake. I can see looking at the code they are using Unity's tilemap to represent their map and injecting the tiles for use in ECS.

    I wasn't talking about the Tilemap in my post, like I said I had no idea they were using the Tilemap. I do agree the Tilemap is a great solution for many use cases. I personally would never use it for a roguelike since I'm not a fan of the asset creation workflow and I don't need a map editor. It's also too restrictive on what data you can pass to the shader - if I remember right you can only pass a single color. While it's possible to embed a lot of data in one color, it ultimately just doesn't fit for how I want to represent my map data.

    And for the record Tilemaps are most definitely NOT the fastest way to render a huge map of tiles. I guarantee you Graphics.DrawMeshInstancedIndirect will perform vastly better and give you much more control and flexibility with how you represent your tile data. But again, not a knocking Tilemaps in any way, they're a great tool.

    That's exactly what the hybrid renderer does. And badly. And again, I understand now they aren't using the hybrid renderer, but that is why I said that in the first place.

    I respect your opinion but I disagree. They aren't compatible with jobs in any way, forcing you to mirror your data as entities. They are wonderful if you need a map editor and you only want to run on the main thread, but in my opinion if you're "serious" about making a roguelike you're much better off writing your own rendering solution. Which is exactly what I did.

    I was not intending to "dismiss" anything. If my post came off that way, I sincerely apologize. I was simply trying to share my point of view and how I would do things differently - since it was based off a mistaken assumption in the first place perhaps that made it come off even worse.

    I appreciate the feedback (seriously), but it seems like you're making a lot of assumptions of your own. The rendering solution I wrote that is being used in my tutorials was specifically made for rendering ascii in Unity. It works well with the job system, is much simpler to set up than a Tilemap, and is very fast. While I'm always learning, I wouldn't really describe myself as being "at the beginning". Either way, again, I appreciate your input.

    Apologies to @Macoron for the mistake regarding the Hybrid renderer.
     
    Last edited: Feb 1, 2020
    Egad_McDad likes this.
  7. Macoron

    Macoron

    Joined:
    Mar 11, 2017
    Posts:
    31
    Wow, wow, easier guys. Like @Sarkahn said, there a thousand of ways to implement this.

    For instance, in my approach there are big drawback in how ECS manage adding/removing components to tile. If I understand correctly, when you add new component to the Entity, Unity reallocate it in new archetype chunk. So if a big part of map changes for some reasons, it will be a lot of copy/pasting and memory will be scatter again.

    In buffer approach you just change array value and that's it.

    Another interesting topic is how to implement actions (movement, attack, spell, etc). Because you can't use inheritance in ECS, you can't implement Action pattern on a Component level.
    I ended up creating a new Entity for each intent like moving. The big advantage is that you can add modifiers to your intent entity (like DoubleSpeedComponent). But it's add more complexity to your code.
     
    Egad_McDad and Sarkahn like this.
  8. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    I've finished part 1.3 - Makes some tweaks to the map generator to make a simple version of the good old "rooms and tunnels" style map:
    Unity_6dTQbeH3Rt.png
     
  9. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    At some point I'd like to implement some analogue of the command pattern like that, but yeah it seems like the usual problem of trying to model an oop concept in ecs. It might be just be a better fit to do do the action system it in oop, I'll have to think about that.
     
  10. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    I've finished chapter 1.4. It covers how to implement a "Field of View" system that handles remembering tiles of the map we've already seen, with a brief aside on using interfaces in Burst and the benefits of "Reactive" systems:

    qb01lAOYLo.gif
     
    Krajca, Mikael-H, Deadcow_ and 8 others like this.
  11. Exeneva

    Exeneva

    Joined:
    Dec 7, 2013
    Posts:
    432
    Is it possible to use custom sprites instead of ASCII art?
     
  12. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Technically you could just drop in a new texture on the console material and it would use that instead, but it's not built with that in mind. And you would essentially have to match up all the sprites on the texture to what the renderer expects, which is a typical code page 437 font atlas texture.

    Atm it only supports square fonts as well but I do plan to add support for non-square fonts.
     
  13. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    I've completed part 1.5 - Monsters and Refactoring. I found that as I was adding monster entities I needed to refactor some of my old systems - which previously only worked on the player - to instead be more generalized so they could apply to monster entities where it makes sense.

    RXMk7fWlVM.gif

    Next up I need to add a turn based system so my entities act in the correct order. I had no idea how you would create a turn based game in ECS so I created a complete separate prototype to test the idea. It turned out well, now I just need to figure out how to integrate that concept in my roguelike.
     
    Deadcow_, NotaNaN, Exeneva and 3 others like this.
  14. Exeneva

    Exeneva

    Joined:
    Dec 7, 2013
    Posts:
    432
    You are amazing. Will you be fleshing out the documentation for the turn-based prototype? So happy to see a turn-based ECS implementation.
     
    Sarkahn likes this.
  15. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Hahah, thanks! Yeah I plan to eventually flesh it out a bit more. Right now I'm still working out how I'm going to handle turns for the next roguelike part. I'm doing it a bit differently than I did in the prototype but I think it's more fitting for a roguelike.

    I've set up a typical "energy system" like in Dwarf Fortress where all actor speed gets added to their energy in a loop until someone has enough energy to act. Its promising so far, just need to flesh it out a bit more then simplify it enough to be tutorializable.
     
    Mikael-H likes this.
  16. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    Oh wow, I've come across your RLTK for Unity repository this week and I didn't realise you were also working through the same tutorial I've been doing. I've also been following the tutorial for the last few weeks and I've made some good progress. I was busy last night adding a better "turn structure" that will also allow my Player & Monsters to be able to attack one another. There's a lot of code that I'm introducing now that will very likely change the direction I'll be taking this in, but I'm very excited for what I've been able to achieve. Admittedly I am lazy and using a Tilemap for rendering, but it was more a choice supported by wanting to get up and running as soon as possible.

    I'm hoping I'll be able to finish damage tonight, but I also know that I might be delving a little too deep into pointers for some of my solutions. I'm amazed at how alike our two projects still look, but I'm sure we'll be going in very different directions.I'm aiming to at least get to showing basic UI by the time 7DRL rolls by, but I'm hoping to at least have some more complex map generation done as well. Best of luck with the project! If you're interested in seeing my horrible attempt at this feel free to browse the link below. It's not nearly as well documented as yours, but if you want to read code you're more than welcome to do so!

    https://github.com/LazyGameDevZA/RogueDOTS
     
    pal_trefall and Sarkahn like this.
  17. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Hahah, I figured there had to be a few more people out there working on roguelikes in Unity now that we have a better alternative to the game object model.

    I'm curious if you think you'll continue using the tilemap as you move forward? For me the asset creation is just too annoying and I want to be able to switch to project tiny as soon as possible once it has better support for dynamic meshes. Though I imagine they'll have their own tilemap at some point too.

    Also looking forward to seeing how you end up handling turns and combat. It's an interesting problem since it pretty much defines how every action in your game will be performed.

    Good luck with 7DRL!
     
  18. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    I'm most likely going to drop using Tilemap in the future. It currently serves the purposes of what I need and performance is decent enough that I won't consider it the bottleneck just yet. Regarding asset creation I do all of that programmatically. I have a font spritesheet that I've sliced up in such a way that using the tilemap requires very little upfront effort. You have mentioned that the shader support is very limited, but the way I got around that was to just have multiple tilemap layers, one for the foreground and one for the background. There's also a tilemap for the map itself, but I'm considering just removing it and merging everything into one.

    If you're curious my RenderWorldSystem linked below contains all of my rendering code.

    https://github.com/LazyGameDevZA/Ro...eDOTS.Terminal.Rendering/RenderWorldSystem.cs
     
    Sarkahn likes this.
  19. Exeneva

    Exeneva

    Joined:
    Dec 7, 2013
    Posts:
    432
  20. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Hey, thanks for the interest! Been very busy at my day job the past couple weeks so I haven't had a lot of time/energy to work on this. I just finished the third (and I think final) iteration of the turn/energy system. Aside from my work it took a while because it turned out to not really fit "nicely" into the typical ECS workflow, so the code is a bit awkward but the results are good. I made a post about it here:

    https://forum.unity.com/threads/simulating-a-lot-of-entities-in-ecs-in-an-interactive-world.835504/

    I was concerned with how backwards the whole thing felt but after getting some feedback from people who are a lot smarter than I am it feels like what I ended up with is more or less on the right track. I just need to clean things up a bit and write the actual tutorial. May be a bit yet but it's coming.
     
    Deadcow_ likes this.
  21. Deadcow_

    Deadcow_

    Joined:
    Mar 13, 2014
    Posts:
    130
    I believe that you don't need to use several tilemaps, you may just set tiles on different Z positions, that'll be your layers. You also may use this to render semi transparent tiles over all tilemap and clear them up dynamically to represent field of view for the player. I also tried to change tiles colour to represent FOW, works well too.

    I also discovered that tilemaps have really nice performance, but I actually came up with "9 Tilemaps" solution, this way you need to repaint only dynamic stuff and not the whole map when player is moving
     
  22. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    I will be reworking my solution very soon to either be a manual texture rendered on a quad or reducing the tilemap layers down to 2. Part of this is because I'll be introducing a structure that describes the console and then render from that instead of using the world state directly.
     
  23. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
  24. pal_trefall

    pal_trefall

    Joined:
    Feb 5, 2019
    Posts:
    78
    I find this code very nice and clean. Well done! One detail is it looks like you will consume a turn for the player even if she bumps into a wall? Might be more fair to the player if we only consume a turn when she performs a valid action.
     
    Sarkahn likes this.
  25. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Thanks a lot! For the help and the compliment, hahah.

    I do have it set up so bumping a wall doesnt use an action. In the
    TurnPlayerSystem
    ,
    MapUtility.CellIsUnblocked
    checks against the state map buffer, which accounts for both entities and walls. If the cell is unblocked we move, if its blocked we check for a monster and act, otherwise nothing happens.

    The movement code is a bit messy from needing to shove it in directly with turn processing and having to share some of the logic between the monsters and player. I'd like to find a cleaner way to handle it.
     
  26. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,653
    Good job (or IJob?) ;)
     
    Mikael-H and Sarkahn like this.
  27. pal_trefall

    pal_trefall

    Joined:
    Feb 5, 2019
    Posts:
    78
    Ah, I misread your code. You always do energy -= cost; but cost is 0 by default. So all good then.
     
    Sarkahn likes this.
  28. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    This is a potential design decision that I feel can be left up to the developer. With roguelikes it's handy to have a "skip" turn and sometimes it's just simpler to use a bump into as a skip a wall rather than dedicate a completely separate input for waiting.
     
  29. Deadcow_

    Deadcow_

    Joined:
    Mar 13, 2014
    Posts:
    130
    Guys... more? :D
     
  30. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    If you mean for ascii roguelikes to play - nethack, brogue, dcss and tome are the ones I played the most. All fantastic games in their own way.

    While I'm here I guess I should say - I haven't completely abandoned this. 2020 has been a pretty rough year for me. I've been trying to get back into programming stuff, but it's tough.

    A few weeks ago I tried porting this over to Project Tiny. It worked out pretty well, you can even play it in the browser. I pretty much remade everything from scratch. I have a lot more experience with ECS now so it went pretty smoothly, I even changed the terminal renderer to be completely integrated with ECS and it works quite well.

    I hit an awful bug that's killed progress for now. Once this bug gets fixed and I can start making progress again I'll start thinking about the whole tutorial part of it.
     
  31. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    So I've been working on this some more lately, since the Tiny version is dead in the water until this bug gets fixed I went back to a normal Unity project for now. I made some good progress - I now have ui, logging system, monsters with fov and pathfinding, combat, and inventory. It's almost looking like a real game!

    y1LZf3BvCc.gif
     
  32. LazyGameDevZA

    LazyGameDevZA

    Joined:
    Nov 10, 2016
    Posts:
    143
    This is looking gooooood! I need to get back to mine again sometime.
     
    Sarkahn likes this.
  33. pal_trefall

    pal_trefall

    Joined:
    Feb 5, 2019
    Posts:
    78
    Nice! Have you updated your github with this recent progress? I might have a closer look at this for 7DRL usage, if you intend to open source it under a useful license.
     
    Sarkahn likes this.
  34. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Nope. I was planning to eventually overwrite the original "RLTK Roguelike" repo with this (and retain the MIT license) and probably rename it, but atm this version is in a private repo. It's in a rough state right now - I'm in the middle of a bunch of terminal work, figuring out how I want to do "particle effects" in the terminal.

    You bring up a good point about 7DRL though, I guess it's a good time to try and clean it up and put it out there for public consumption. I'll see what I can do over the next couple weeks.
     
    pal_trefall likes this.
  35. pal_trefall

    pal_trefall

    Joined:
    Feb 5, 2019
    Posts:
    78
    Yeah, that would be good timing, otherwise I'd just wait until next year's 7DRL to look at it again. A fun challenge to use DOTS for 7DRL, but writing everything from scratch just wouldn't be realistic.
     
  36. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Okay, repo has been completely updated to the current version, fully open source and MIT licensed:

    https://github.com/sarkahn/dots-roguelike

    Unlike my previous go around I didn't put much concern toward making this "beginner friendly" so if you're not familiar with Unity's ECS it might be a bit rough to parse. I added a small blurb in the readme on how to get started with the ECS terminal at least.
     
    pal_trefall and NagaChiang like this.
  37. pal_trefall

    pal_trefall

    Joined:
    Feb 5, 2019
    Posts:
    78
    I played around with this code a little bit today.

    It would be nice if it was simple to change default background/foreground color in one place from the editor (not touch code to change the game's palette). Would also be nice if the background color of a tile like an actor or item could be set to default background color when I don't want to override the default background color.

    It would be nice to set wall and floor color + glyphs in editor too, without having to touch code.

    It looks like items sometimes spawn in walls.

    I really like how easy it is to add new items and actors. The code is very readable and easy to modify, so good job! And yeah, I'm obviously just scratching the surface so far. A lot of work went into this, I can tell.
     
    Last edited: Jan 19, 2021
    Sarkahn likes this.
  38. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Great idea! I think making an editor representation of a tile that can be plugged into authoring components should cover both those requests, I opened an issue for that and the item bug. Thanks!
     
    pal_trefall likes this.
  39. pal_trefall

    pal_trefall

    Joined:
    Feb 5, 2019
    Posts:
    78
    Is it currently possible to use the keyboard for targeting spells btw? I was only able to use the mouse for that, which for a Roguelike purist feels a bit odd. If it's not currently supported I might just look at adding that feature during 7DRL.
     
  40. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Nope, the targeting is only set up for the mouse right now.
     
  41. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Finished with my first pass at ecs particles:
    rAQ9DcEPZ7.gif

    I ended up going for an "SOA" approach, so the colors, positions, velocity, acceleration, etc, are all dynamic buffers that live on the emitter entity. I figured AOS would have been simpler to write but with all separate buffers systems can isolate only the data they care about in their queries. It was pretty painful to set up at first but it should perform better than the alternative, as far as I understand anyways.

    Particle systems are pretty simple and intuitive to write. This one sets the initial speed of new particles (separate systems set directions). I use a buffer of indices to track "new" particles - it gets populated at the start of the frame and cleared at the end, so systems in the middle can handle new particles however they want and there's no branching:

    Code (CSharp):
    1.     [UpdateInGroup(typeof(InitializationSystemGroup))]
    2.     [UpdateAfter(typeof(Particle2DInitializationSystem))]
    3.     public class Particle2DRandomInitialSpeedSystem : SystemBase
    4.     {
    5.         protected override void OnUpdate()
    6.         {
    7.             Random rand = new Random((uint)UnityEngine.Random.Range(1, int.MaxValue));
    8.             Entities.ForEach((
    9.                 ref DynamicBuffer<Particle2DVelocity> velBuffer,
    10.                 in DynamicBuffer<Particle2DInitializedIndex> initIndexBuffer,
    11.                 in Particle2DRandomInitialSpeed speed) =>
    12.             {
    13.                 var indices = initIndexBuffer.AsNativeArray();
    14.                 var velArr = velBuffer.AsNativeArray();
    15.  
    16.                 for(int ii = 0; ii < indices.Length; ++ii)
    17.                 {
    18.                     int i = indices[ii];
    19.  
    20.                     velArr[i] = math.normalize(velArr[i]) *
    21.                         rand.NextFloat(speed.min, speed.max);
    22.                 }
    23.             }).ScheduleParallel();
    24.         }
    25.     }
    This is my first time writing something like this so I had to piece it all together from random stuff I found online. It's a naive implementation but it seems to work well - aside from an annoying out of range bug during particle cleanup. It's very fast and nice and flashy which is all I wanted.

    The particles framework itself is pretty much self-contained - only rendering is tied to my terminals. I wrote it for 2D but it should be pretty trivial to 3D-ify it if needed.

    I also tried to use authoring scripts to make it easy to tweak emitters in the editor like Unity's built in particles, but it's kinda lame since you can't see the results until you hit play. I'd like to set up a "preview" somehow so you can see results live, but I don't know if that's possible with ECS. Not being able to tweak entity values during play mode continues to be a bummer.
     
  42. pal_trefall

    pal_trefall

    Joined:
    Feb 5, 2019
    Posts:
    78
    Very exciting stuff man! I can't wait to take this for a spin come 7DRL ;-)
     
    Sarkahn likes this.