Finished work on v2 of the PCB!
- Moved the LEDs to the right side
- Fixed all of the DRC errors
- Schematic now represents the board properly
- Cleaned up routing
- Reassigned pins to make routing cleaner
To do:
- Adjust the case for new/moved components!
Finished work on v2 of the PCB!
To do:
After I got the steering working, I wanted to be able to use the G25’s shifter to change gears in the company cruiser. This required more expirimenting to figure out how the shifter is interpreted by unity’s input system.
It turns out that every position on the shifter is assigned a button, and that button is considered pressed when the shifter is in that gear. Below are the button assignements I found:
In 6 Gear Mode
I also took a look at how the player sets the cruiser’s gears, and it’s with a method called ShiftToGearAndSync(desiredGear). So when changing gears this is the method I need to call.
Getting the shifting working was pretty easy. First I created 2 new actions and bindings for the Drive and Reverse gears and bound then to the 3rd and 4th gears on the shifter. I chose those as it keep it centered and feels more natural.
Then I added some code into the same method we edited for the steering that:
So in our case, what IRL is neutral is park, forward (or 3rd) is drive and back (or 4th) is reverse.
Feels pretty epic when driving the cruiser!
The mod is basically done, but there are a few things we can still do:
Cheers!
Liam
Finished re-writing the cruiser’s steering!Not too much to say, but still a pretty big update: The cruiser’s steering wheel is now 1:1 with the physical G25 in multiple ways.
Originally the Company Cruiser was driven by holding down the A or D keys to steer. The wheel would gradually rotate in the direction you were holding and stop rotating but maintain the rotation on release. This logic obviously sucks when you want to use a sim wheel, because you want the CC’s wheel rotation to be a direct replica of the physical wheel’s rotation.
This means the logic had to be redone, therefore a rewrite of the cruiser’s GetVehicleInput() needed to be done.
Zeekerss original code for steering was this:
float num = __instance.steeringWheelTurnSpeed; __instance.steeringInput = Mathf.Clamp(__instance.steeringInput + __instance.moveInputVector.x * num * Time.deltaTime, -3f, 3f);
Modifing these lines was pretty straightforward:
num entirely as we done need the wheel’s turn speedTime.deltaTime as it’ll be directIn the end the code looked like this: __instance.steeringInput = Mathf.Clamp(__instance.moveInputVector.x * 3f, -3f, 3f);
We need to multiply by 3 as we want the max to be 3 but the input can only reach a max value of 1.
This new wheel logic worked great except one problem: The G25 would be at 90deg at it’s max but the game would show it at 225deg.
The solution to this was to adjust the anim’s clam values from +/- 1 to +/- 2.4.
Why? Because we need to remove 1.4f from the max value, and for some reason the anim needs us to add it onto 1 instead of remove ¯\_(ツ)_/¯
Going forward all that’s really left is using the G25’s shifter to change the cruiser’s gears from Park, Reverse and Drive!
Cheers,
Liam
Tried driving the company cruiser with the wheel, and a few things need to be done:
Published the input part of the mod onto Thunderstore tho to make testing easier.
I’ll make a more interesting update once I start working that out!
Cheers,
Liam
Finally finished the first and most cruicial part of the mod! The first step to this mod was to let Lethal Company’s input system receive bindings from the G25. Now this in theory should have been a fast and easy implementation. Here’s how it turned into a 4 hour journey:
I started off by writing the essential parts of the mod. This mod uses BenInEx and HarmonyLib to patch my custom code into Lethal Company, so I made the public BasicUnityPlugin class that applies all patches in the .GetExecutingAssembly(). This means that it’ll look at all the code in the built .dll and patch automatically instead of me specifying what classes I want to patch.
This is the center of attention when it comes to the input patching. This class needs to do 3 things:
By fiddling around with the input system in another project, I found the name of the wheel to be <HID::G25 Racing Wheel>. Knowing the device name, I was ready to start patching the new inputs into the input assets.
The class that we will be patching is the StartOfRound class as it handles a lot of the local player and game logic. First I found the local player through StartOfRound.Instance?.localPlayerController;, then found the player’s playerActions asset and assigned a var to that. Then I made a function that takes the InputActionAsset, finds the actions we want, such as the ‘move’ action, and applies a new binding to that action. For example, we can grab the ‘interact’ action with InputAction interact = actions.FindAction("Interact"); and give it a new binding specifically for the G25: jump.AddBinding("<HID::G25 Racing Wheel>/button7").WithInteraction("press");
Once I finished adding all the bindings to the right actions along with some null checks, I build the .dll and got to testing. Right off the bat nothing worked, but a quick change to what method we needed to patch… kind of fixed things?
Something weird was going on: the bindings I’d made to the look action were working, but none of the other were.
3 hour long story short, Zeekerss made his PlayerControllerB read from two different instances of one InputActionAsset, so the bindings and actions across the two were the same but each one was being referanced in different spots.
The camera worked right away because the line that read the camera’s input was:playerActions.Movement.Look.ReadValue<Vector2>() as opposed to all other controls read as such: InputSystem.actions.FindAction("Move").ReadValue<Vector2>();.
The InputActionAsset I was patching was the one created in the player’s Awake() method, playerActions, and not the second global one.The only reason I found for Zeekerss to make two is that the mouse was being read by the first because the look input is not one that can be rebound in the settings menu. All the controls pulled from the second asset are controls that can be rebound in settings, meaning that the second one was the player’s local input settings when the first was the default.
All that needed to be done for the G25 controls to be read for all the actions was to also patch InputSystem.actions’s asset. It took about 3 1/2 hours to figure this out, but at least it works now!
Going forward we need to:
Cheers!
Liam
Recently, and as summer’s been coming around, I’ve been playing more and more lethal company with the bros. Now I wouldn’t say we’re sweats or anything, but we’re pretty good and can get to 3000+ quotas without many resets. On thing that’s made that possible is the wonderful Company Cruiser! For anyone who’s driven the Company Cruiser before, you already know how hellish it can be. The gear changes, janky steering and funny physics make it truly an experience to manage.
I’ve been driving this thing for a while now and would like to think that I’m quite proficient, but looking around my room got me thinking:
What if I could use my Logitech steering wheel to drive the cruiser? Now of course the base game has no support for it and the input system used to drive the Cruiser is 1-0 based and not Vector based (W key accelerates at max when a pedal would be an axis), but having modded lethal before I was sure It’d be feasible. So I got cracking.
The Logitech G25 is a legacy wheel and is not supported in newer Logitech software. After much fiddling I figured out I needed to use a very old version of Logitech Gaming Software to get it to register properly on my computer.
To start I needed to learn how the wheel interacts in and gets registered by Unity, specifically Unity 2022.3.9f, the version Zeekerss used to build LC. I began by making a unity project in that version with the URP Template, as it already had a preconfigured controller using the new Input System. I also downloaded a unity project by Juandarn called SpaceVR that used the Logitech G25 to figure out how it configured the wheel in it’s input system. Here’s what I learnt:
As the goal is to be able to drive the CC with the G25, it was also time to start looking at how Zeekerss configured the Input system for LC. I did this by using an asset ripper to reverse engineer Zeekerss LC build and turn it back into a Unity Project. So now that I have LC’s source code, it turns out it’s pretty simple and all the controls under one Action Map, so in the end I’ll need to add the G25’s axis configurations to the Actions themselves. I don’t think it’ll be too hard but for that I’ll need to start on some research related to interacting with a player’s input system through an LC mod patch.
This will be quite the fun mod and journey, post a comment if you have any ideas or suggestions!
Cheers,
Liam
Major PCB Improvements
Many edits were made to the PCB, explained below:
All V2 now needs is a CAD update!
Finished Firmware and Ship!
After cleaning up the PCB design and generating gerber files I spent some time working on the firmware! I decided to use QMK for the firmware and programed support for all my hackpad’s features!
The default keys are the 4 Arrow keys, Z, and the volume knob with push to mute.
I did not expect the firmware to be as difficult to program as it was! This has been a fun project, there is still much to work on as things come together in the real world but I’m proud of where I’ve gotten up to now!
Devlog 1!
Hello world!
To commence this project I began my reading the DIY guide and following along, eventually splitting off and designing a keypad with my own variations! Below is a detailed overview of my keypad:
My keypad consists of 5 keys and a rotary encoder. The rotary encoder contains a switch that when pressed can be interpreted as it’s own keypress. I was hoping to use the rotary as a volume knob and it’s switch as a mute/unmute button. I placed them to the side to leave space for the controller as can be seen in the PCB design.
The keypad also has 5 LED’s, all strung together through the data in/outs. For the last feature, this keypad contains a programmable OLED screen on the top that can be used to display anything your imagination wants!
Moving forward I have yet to program the firmware, and I will also have to clean up the schematic as it’s quite messy.
Devlog 3!
More features on the way!
After uploading v1 and creating a readme I put time into writing new methods that will further make this project easy to use!
On Flysky controllers, the switches are lettered a-d. Instead of having to find out what channel the switches are mapped to, I designed a method that lets you pass the switch’s letter and it returns the bool value without you having to deal with the channel #.
In terms of complexity I also added two new methods for more experienced programmers using this lib:
readChannelRaw: This method returns the raw data from a specified channel
readSwitchRaw: This method returns the raw uninverted state of a switch.
Next up is writing examples and making axis mode settings!
Devlog 2!
Real world testing!
After having written the most basic functions, I’ve gone to my electronics cabinet and put my code to the test!
Using a proper FlySky i6 controller I tested my library with an Arduino Mega and was able to find and troubleshoot many issues in my library code and .ino example. Overall everything worked really well, so I am now looking towards implementing more complex methods and features to both make the lib easier to use and to tailor it to the interests of more advanced users.
Cheers!
Devlog 1!
For a long time Flysky controllers have been used in personal avionics and robotics projects, interacting with many boards.
All this time there has only been one main library used to interpret the signals from these controller’s receivers: iBusBM. It’s a great library for what it’s made for, but it’s design requires the need for extra code to be written if you want to read specific channels or switches in a clean manner.
My goal with this library is to simplify the code integration of Flysky into two main easy-to-use methods:
readAxis();
readSwitch();
Each method does exactly as their name suggests!
As this project develops I plan on adding more specific methods for more advanced use cases such as getting raw inputs or setting axis modes, but next up is real world testing on different boards!
Cheers!