1. Prologue:I know that Wyz is an incredibly busy man, and has barely managed to maintain the source of the AGS Joystick Plugin, given that, and given that I'm also implementing a version of it with SDL, it seems fitting to disengage the two things between them. A majority of the code has been written/based off code by eri0o and Wyz, so kudos go to them! The inclusion of the SDL library allows for two important things:
1) XInput recognition
2) Cross-platform
2. Instructions/Going through the functionsThe only thing u have to do, is make sure u put SDL2.dll inside ur debug and compiled folders, otherwise you'll get error messages. The dll will be provided below.
int ControllerCount ()This returns the number of gamecontrollers found, from 0 to N controllers.Controller Properties & FunctionsThe plugin uses a custom typedef which is 'Controller'. Controller is self-explanatory, and has a couple of functions and attributes.
- int ID
Returns the ID of the controller.
- int ButtonCount
Returns the number of buttons on the controller.
- int AxesCount
Returns the number of Axis on the controller.
- Open (int ID);
Opens specified controller. (0-15)
A good practice to do is put this on room_load, or game_start
Controller*gamepad;
function game_start()
{
gamepad = Controller.Open(0);
}
- void Close ();
Closes the controller, probably not really needed to be honest.
- bool Plugged ();
Returns if the controller is currently plugged or not (true / false)
- String GetName ();
This returns a string containing the name of the gamecontroller. Will return "" on error.
- int GetAxis (int axis);
Returns axis by number -32768 to 32768
if (gamepad.GetAxis(0) < -200)
{
//LEFT
}
else if (gamepad.GetAxis(0) > 200)
{
//RIGHT
}
- int GetPOV();
Returns POV value. (0-8)
if (gamepad.GetPOV() == ePOVUp)
{
cEgo.y--;
}
- bool IsButtonDown (int button);
Returns true when the specified button is currently down. (0-31)
int i=0;
String app="";
while (i < gamepad.ButtonCount+2)
{
app = app.Append(String.Format("button %d is %d[",i, gamepad.IsButtonDown(i)));
i++;
}
cEgo.SayBackground(app);
//this will parse all the buttons' states and print them on the screen.
- bool IsButtonDownOnce(int button);
Returns true when the specified button is currently down (single press). (0-31)
if (gamepad.IsButtonDownOnce(11))
{
//click on UI
}
- void Rumble(int left,int right,int duration);
Rumbles the Controller for Duration (in loops). Left and right are motors. Values go from 0 to 65535
if (gamepad.IsButtonDownOnce(11))
{
gamepad.Rumble(65535, 65535, 40);
}
v1.1-Added two new functions- void BatteryStatus();
Returns the status of the controller battery. (-1 - 5) UNKNOWN = -1, LESS THAN 5% = 0, LESS THAN 20% = 1, LESS THAN 70% = 2, 100% = 3, WIRED = 4, MAX = 5
lblstatus.Text=String.Format("BATTERY STATUS: %d",gamepad.BatteryStatus());
- int PressAnyKey();
Returns the first button the player hits on the controller, otherwise returns -1. (0-31)
while (gamepad.PressAnyKey()==-1)
{
Wait(1);
}
player.Say("Button pressed is button %d",gamepad.PressAnyKey());
3. Download linksAGSController.dll (https://github.com/Dualnames1/AGS-Controller/releases/download/1.1.0/AGSController.dll)
SDL2.dll (https://github.com/Dualnames1/AGS-Controller/releases/download/1.1.0/SDL2.dll)
*ZIP file containing both* (http://primordia-game.com/Files0/AGSController.zip)
4. SourceThe source files can be found on GITHUB (https://github.com/Dualnames1/AGS-Controller)
This is really nice Dualnames, fantastic work! Somehow does this revisited version will prevent the error: Joystick is plugged in then you save the game, unplug the joystick and try to restore the game properly fine? Or Joystick is plugged in then you save the game, export the saved game file to another computer with same ags version, unplug the joystick and try to run the game? Because in the version I have of the plugin this will crash. bool isPlugged or bool isValid doesn't seem to fix the problem.
Pretty sure, I can specifically run this test for you!
EDIT: Yep, no crashes!
Alright thanks a lot for testing this! Sorry for the dumb question, so it is AGSController.dll different from agsjoy.dll? I don't think if I just replace the plugin my script would still work.
You need to replace the functions as well. And it is different. Did a minor update for the upcoming release of AGS (which includes SDL and would cause issues)
Alright thanks for the further clarification, I look forward to attempt this plugin on a next time.
v1.1-Added two new functions- void BatteryStatus();
Returns the status of the controller battery. (-1 - 5) UNKNOWN = -1, LESS THAN 5% = 0, LESS THAN 20% = 1, LESS THAN 70% = 2, 100% = 3, WIRED = 4, MAX = 5
lblstatus.Text=String.Format("BATTERY STATUS: %d",gamepad.BatteryStatus());
- int PressAnyKey();
Returns the first button the player hits on the controller, otherwise returns -1. (0-31)
while (gamepad.PressAnyKey()==-1)
{
Wait(1);
}
player.Say("Button pressed is button %d",gamepad.PressAnyKey());
Cool, Dualnames!
I also like that you added the value after "wired" called MAX in case the user gets struck by lightning.
It's what SDL returns, im not sure why they have a 100% and a max, but here it is.
I'm getting the error there was an error loading plugin 'agscontroller.dll', it may depend on another DLL that is missing.
I have both the files in the correct locations - any ideas?
Thanks in advance.
What are the correct locations? Both must also be added on the AGS Editor directory itself.
I've placed them into the AGS directory, plus in the specific game's debug and compiled folders.
I'm getting the error message on startup of AGS.
Thanks
Ah, in this case, there's a chance the build isn't including the redistributable VC stuff - use MT flag on Visual Studio C++ compiler.
Can you try the build here? I am not sure they are correctly configured though. (I briefly read the vcxproj xml file but couldn't find mention of the MT flag... Also the build is suspectly small. It still needs the SDL.dll)
https://github.com/ericoporto/AGS-Controller/releases/tag/1.1.1
If this is the case (the above build doesn't work), I can fix later. (I don't have VS right now and managed to expire my azure connection credentials for this repo, so I can't build things right now)
Yep, I'm getting the same error as Aaron. "error loading plugin 'agscontroller.dll', it may depend on another DLL that is missing."
Quote from: Blackthorne on Sun 05/04/2020 16:44:30
Yep, I'm getting the same error as Aaron. "error loading plugin 'agscontroller.dll', it may depend on another DLL that is missing."
Was this ever resolved? I'm getting the same error. I'm using AGS 3.4.1 Patch 3.
OK the solution is to not use the SDL2.dll provided.
Use the 32-bit version from here:
https://www.libsdl.org/download-2.0.php
Am I supplying a 64bit version of SDL2.dll?
Quote from: Dualnames on Thu 17/09/2020 21:59:43
Am I supplying a 64bit version of SDL2.dll?
Yes.
$ file SDL2.dll
SDL2.dll: PE32+ executable (DLL) (console) x86-64 (stripped to external PDB), for MS Windows
Hello there! I've managed to get the plugin to work and it works fine with my controller. I am having a little trouble setting it up though. Is there any way to use these in the on_keypress function? A lot of my menu navigation code is in there and I sorta don't want to re do everything in an rep_ex function to control gamepad input(If possible that is!). Any help would be greatly appreciated.
P.S I forgot to mention, the reason I want to use it in the on_keypress function is if I hold a button down in the rep_ex function it loops repeatedly and I'm not sure if there's a way to check if the POV/dpad has only been pressed once?
I believe there is a function called IsButtonDownOnce, does that not work with pov,dpad?
If not u can set a simple check
bool LEFT=false;
bool RIGHT=false;
bool UP=false;
bool DOWN=false;
function HitNoDirection()
{
LEFT=false;
RIGHT=false;
UP=false;
DOWN=false;
}
function repeatedly_execute()
{
if (gamepad.GetAxis(0) < -200 && !LEFT)
{
HitNoDirection();
LEFT=true;
//LEFT
}
else if (gamepad.GetAxis(0) > 200 && !RIGHT)
{
HitNoDirection();
RIGHT=true;
//RIGHT
}
else if (gamepad.GetAxis(1) > 200 && !DOWN)
{
HitNoDirection();
DOWN=true;
//DOWN
}
else if (gamepad.GetAxis(1) < -200 && !UP)
{
HitNoDirection();
UP=true;
//UP
}
else
{
HitNoDirection();
}
}
Hi!
I have been trying to use the Rumble feature, but i can't seem to change the values at all. Apart from raising the duration ???
Am i doing something wrong?
All i can change is the durations length but when i try to lower it. It rumbles 4ever instead. So i've deicided to use it as a neckmassage device when my neck hurts after drawing too much pixelart, kidding (laugh)
Is it a bug or am i not understanding the concept, i've tried lowering from 40 to 30 or 35 or such. Should i lower in specific values? If so please write some that you think might work.
I want "fast" short rumbles for an impact, not these 40 game loops rumbles! (wrong)
Wished i knew what was wrong. This is the only code that works, if i change this a little either it rumbles forever. or if i raise the duration to 100 it will go for 100game loops, etc. But the other values, no
Edit: Should the gamespeed be set to 30 if i want 30 loops to work? :/ Don't want to do that, ofc.
if (!gamepad.IsButtonDownOnce(3))
{
gamepad.Rumble(65636,65535,40);
}
Hi. Plugin is not working with my Xbox One controller. Any suggestions?
I have an xbox controller and it works fine on my end, can you paste your code here.
This is an amazing idea for a plugin! Quick question: is this plugin currently only available for Windows builds of AGS games, or will it also work with MacOS / Linux runtimes?
It's not the worst thing in the world if it's Windows-only at the moment, because Wine/Proton are pretty good for AGS games, but it'd be nice to support this kind of thing natively!
https://github.com/Dualnames1/AGS-Controller/releases/tag/Current_Version
Oops! Sorry I didn't catch that. I'll definitely check this out for some of my projects!
I get this error:
[imgzoom]https://atariage.com/forums/gallery_ips/monthly_2022_01/large.1514247188_agsproblem.png.d7e31b9e664507f813073733376ce057.png[/imgzoom]
I use AGS 3.5.1
I have AGSController.dll and SDL2.dll.
The SDL2.dll has to be 32 bits and placed in the same directory.
Quote from: eri0o on Sun 16/01/2022 23:37:41
The SDL2.dll has to be 32 bits and placed in the same directory.
I don't get it since SDL2.dll is included with AGSController.dll.
Quote from: Volcan on Mon 17/01/2022 02:31:20
I don't get it since SDL2.dll is included with AGSController.dll.
Have you copied SDL2.dll to where your game.exe is?
BTW, I could not see the picture that you posted until I copied the address into browser by hand, and answered to captcha.
Quote from: Volcan on Mon 17/01/2022 02:31:20
Quote from: eri0o on Sun 16/01/2022 23:37:41
The SDL2.dll has to be 32 bits and placed in the same directory.
I don't get it since SDL2.dll is included with AGSController.dll.
Hey, I had the same problem earlier today. If you check earlier in the thread, it turns out you need to get the 32-bits version of the DLL from the LibSDL site: https://www.libsdl.org/download-2.0.php
Quote from: deadsuperhero on Mon 17/01/2022 03:30:49
Quote from: Volcan on Mon 17/01/2022 02:31:20
Quote from: eri0o on Sun 16/01/2022 23:37:41
The SDL2.dll has to be 32 bits and placed in the same directory.
I don't get it since SDL2.dll is included with AGSController.dll.
Hey, I had the same problem earlier today. If you check earlier in the thread, it turns out you need to get the 32-bits version of the DLL from the LibSDL site: https://www.libsdl.org/download-2.0.php
Thanks
It works.
Edit: Before reading any of this, note that I am using a Playstation 5 controller, which is less common on a PC (Linux) setup. It's entirely possible that different controllers read axis values differently.
Hey, so I'm currently working on adapting this to my game, but I have a couple of stumbling blocks to figure out:
So...the first one is getting the axis to work properly. In a nutshell, I'm trying to make one of my joysticks move the mouse cursor around. I have an extremely rough implementation right now:
if (gamepad.GetAxis(0) < -200 && !LEFT){ // LEFT STICK LEFT
Mouse.SetPosition(mouse.x - 1, mouse.y);
HitNoDirection();
LEFT=true;
}
else if (gamepad.GetAxis(0) > 200 && !RIGHT){ // LEFT STICK RIGHT
Mouse.SetPosition(mouse.x + 1, mouse.y);
HitNoDirection();
RIGHT=true;
}
else if (gamepad.GetAxis(1) < -200 && !UP){ // LEFT STICK UP
Mouse.SetPosition(mouse.x, mouse.y - 1);
HitNoDirection();
UP=true;
}
else if (gamepad.GetAxis(1) > 200 && !DOWN){ // LEFT STICK DOWN
Mouse.SetPosition(mouse.x, mouse.y + 1);
HitNoDirection();
DOWN=true;
}
else {
HitNoDirection();
}
Unfortunately, the cursor seems to get confused about where it's supposed to go on the screen. Up and left seem to work okay, but the game seems to get confused about down and right.
The second stumbling block is that I'm trying to treat the X button as a Left Click, and the Square button as a Right Click:
if (gamepad.IsButtonDownOnce(0)) // X Button
{
if (gWheel.Visible == true) {
Wait(1); // Have to have this, otherwise the first lines of dialogue get skipped?
Mouse.Click(eMouseLeft); // Clicking on buttons
}
else if (gInventory.Visible == true) {
Mouse.Click(eMouseLeftInv); // Interact with Inventory Items
}
else {
Mouse.Click(eMouseLeft); // Normal default Left Click, which is normally just walking to a specific point.
}
}
So...the Wheel menu buttons work fine when left clicking...but! When a message comes up on the screen, this simulated left click is incapable of dismissing them. It also can't seem to skip speech.
If you need to click, use this plugin function ClickMouse instead. Mouse.Click goes through a different click pipeline through the engine.
Quote from: deadsuperhero on Wed 19/01/2022 09:35:41
So...the Wheel menu buttons work fine when left clicking...but! When a message comes up on the screen, this simulated left click is incapable of dismissing them. It also can't seem to skip speech.
Can you clarify, where exactly do you call that code from? What do you refer to as a "message", is this a message from Display() function?
Quote from: eri0o on Wed 19/01/2022 20:43:15
If you need to click, use this plugin function ClickMouse instead. Mouse.Click goes through a different click pipeline through the engine.
I checked what the plugin does (https://github.com/Dualnames1/AGS-Controller/blob/master/AGSController/controller.cpp#L321), and it calls engine->SimulateMouseClick(), which in turn calls PluginSimulateMouseClick:
https://github.com/adventuregamestudio/ags/blob/master/Engine/plugin/agsplugin.cpp#L779
The script's Mouse.Click also calls PluginSimulateMouseClick:
https://github.com/adventuregamestudio/ags/blob/master/Engine/ac/mouse.cpp#L573
These should be identical in result. What matters is what circumstances you are calling them (e.g. on which event or callback).
Quote from: Crimson Wizard on Wed 19/01/2022 20:59:26
Quote from: deadsuperhero on Wed 19/01/2022 09:35:41
So...the Wheel menu buttons work fine when left clicking...but! When a message comes up on the screen, this simulated left click is incapable of dismissing them. It also can't seem to skip speech.
Can you clarify, where exactly do you call that code from? What do you refer to as a "message", is this a message from Display() function?
These should be identical in result. What matters is what circumstances you are calling them (e.g. on which event or callback).
Hey, no problem! This is what I'm currently doing for handling clicks normally:
if (button == eMouseLeft) {
if (gWheel.Visible == false) {
Room.ProcessClick(mouse.x, mouse.y, mouse.Mode);
}
else {
gWheel.Visible = false;
gContext.Visible = false;
}
}
For deeper context, here's my click handler in entirety:
function on_mouse_click(MouseButton button)
{
// called when a mouse button is clicked. button is either LEFT or RIGHT
if (IsGamePaused())
{
// game is paused, so do nothing (i.e. don't process mouse clicks)
if (gInventory.Visible == true) {
if (button == eMouseLeftInv) {
if (mouse.Mode == eModeLookat) {
examineItem(inventory[game.inv_activated]);
}
else if (mouse.Mode == eModeInteract) {
inventory[game.inv_activated].RunInteraction(eModeInteract);
}
else if (mouse.Mode == eModeUseinv) {
inventory[game.inv_activated].RunInteraction(eModeUseinv);
}
else if (mouse.Mode == eModePointer) {
player.ActiveInventory = inventory[game.inv_activated];
mouse.Mode = eModeUseinv;
mouse.ChangeModeHotspot(eModeUseinv, 0, 0);
}
}
}
else if (gMemories.Visible == true) {
if (button == eMouseLeftInv) {
openJournal(inventory[game.inv_activated]);
}
}
}
else if (button == eMouseLeft)
{
if (gWheel.Visible == false) {
Room.ProcessClick(mouse.x, mouse.y, mouse.Mode);
}
else {
gWheel.Visible = false;
gContext.Visible = false;
}
}
else if (button == eMouseRight){
// right-click or mouse wheel down will cycle the mouse cursor mode forwards
setContext();
setPointer();
// gWheel.SetPosition(mouse.x, mouse.y);
gWheel.Visible = true;
}
else if (button == eMouseMiddle)
{
// middle-click makes the character walk to clicked area, regardless of cursor mode
Room.ProcessClick(mouse.x, mouse.y, eModeWalkto);
}
}
To clarify - skipping speech, cutscenes, and Display() messages works just fine with both the mouse and the keyboard. But, the gamepad input just seems to ignore it.
Quote from: deadsuperhero on Thu 20/01/2022 09:20:28
Quote from: Crimson Wizard on Wed 19/01/2022 20:59:26
Can you clarify, where exactly do you call that code from?
Hey, no problem! This is what I'm currently doing for handling clicks normally:
I meant, could you tell where do you call the code that you posted above, the one with
Mouse.Click?
For example, are you calling it from repeatedly_execute, or repeatedly_execute_always, or other place?
Ah! :facepalm: I just realized that forgot to explain. The reason i ask is that:
1. The speech is a blocking action; repeatedly_execute does not run during blocking actions. repeatedly_execute_always does. So that matters.
2. Display() command is special in AGS, it's "super-blocking", because even repeatedly_execute_always does not run during it. That's of course a design oversight, but I think it was made so because engine uses Display() for special messages too that are supposed to completely freeze the game. Because of that, you won't be able to call Mouse.Click in script while Display is on screen.
The only solution to this I am aware of is to not use Display, but instead make your custom overlay or GUI for such messages.
What remains of course are these special messages that I mentioned. The only fix for them would be when the engine itself would handle gamepad.
OR, if plugin would also handle these events and called "SimulateClick" automatically, not forcing you to do that in script.
Hey, sorry for the delay - it turns out that I was calling it from repeatedly_execute! That one was 100% my bad, moving over to repeatedly_execute_always totally fixed it.
Looks like I'll have to come up with something clever for working around the Display() issue.
Hey, just digging this up again. I took a long break from doing AGS stuff, and kind of wanted to get back into making my games work with a game controller.
One area that I'm kind of stuck on involves moving the mouse with a joystick in a way that feels good.
In my current setup, the right stick on my gamepad moves the cursor around.
function trackAxis() {
if (gamepad.Plugged() == true) {
int axis0 = gamepad.GetAxis(0); // Left Stick Vertical
int axis1 = gamepad.GetAxis(1); // Left Stick Horizontal
int axis2 = gamepad.GetAxis(2); // Right Stick Vertical
int axis3 = gamepad.GetAxis(3); // Right Stick Horizontal
}
}
function checkController() {
trackAxis();
String inputName = gamepad.GetName();
if (inputName == "PS5 Controller") {
int axis2 = gamepad.GetAxis(2); // Right Stick Vertical
int axis3 = gamepad.GetAxis(3); // Right Stick Horizontal
int axisCo2 = gamepad.GetAxis(2) / 80; // 1/4th of the Room Height
int axisCo3 = gamepad.GetAxis(3) / 50; // 1/4th of the Room Width
if (axis2 < -200 && !LEFT){ // RIGHT STICK LEFT
Mouse.SetPosition(axisCo2, axisCo3);
HitNoDirection();
LEFT=true;
}
else if (axis2 > 200 && !RIGHT){ // RIGHT STICK RIGHT
Mouse.SetPosition(axisCo2, axisCo3);
HitNoDirection();
RIGHT=true;
}
else if (axis3 < -200 && !UP){ // RIGHT STICK UP
Mouse.SetPosition(axisCo2, axisCo3);
HitNoDirection();
UP=true;
}
else if (axis3 > 200 && !DOWN){ // RIGHT STICK DOWN
Mouse.SetPosition(axisCo2, axisCo3);
HitNoDirection();
DOWN=true;
}
else {
HitNoDirection();
}
}
For purposes of processing input, I'm calling my code from repeatedly_execute_always, so that the player can interact with the game even during speech and cutscenes. The problem is that the right stick has a Rubber Band effect, where the cursor returns to its position of origin after the stick is released. In other words...you can position the cursor around, but it always returns to a given spot.
I'm not completely sure on how to avoid this. My guess is that, because this is running in repeatedly_execute_always, the engine is constantly polling for mouse position based on joystick input, and updating it even when the axis values are zero?
I'm just trying to figure out how to get the cursor to stay put after I move it with the stick.
Couldn't you make the stick control the movement of the cursor rather than its absolute position directly?
Something like:
Mouse.SetPosition(mouse.x + axisCo2, mouse.y + axisCo3);
Edit: My bad, i was wrong (laugh)
I'm sorry, but it will work. The numbers will have to be scaled and bounded, but this is how controlling movement vs. controlling position is done.
Quote from: Snarky on Fri 29/04/2022 09:44:07
Couldn't you make the stick control the movement of the cursor rather than its absolute position directly?
I think you're on the right track, this does seem to be an improvement of sorts...
One thing that I've noticed with this plugin: the axis values never seem to be zero with my controller. I'm not sure if this is a bug in the plugin or my drivers, or just how controllers are in general, but my PS5 DualSense controller always registers as a non-zero number, even when the sticks aren't moving.
I'm going to keep trying to debug this on my end, it could just be that something in my setup is giving me an unnecessary headache. I just can't figure out how to represent a "joystick at rest" setting so that the game engine knows not to move the cursor any further. Right now, it just kind of slowly drifts all over the place.
You could just set it to 0 if the absolute value is less than some threshold.
deadsuperhero, this is not a bug with your controller or the plugin. There is a deadzone for controllers, for instance my xbox varies from -4000 to 4000 and jiggles around these values, so u need to do a check for not 0 but for a values within the deadzone.
Yay, I figured it out! :cheesy:
Thanks for setting me on the right path, DualNames and Snarky. <3
It turns out that I was really overthinking this a lot.
function deadzoneCheck() {
// Right Stick -- Horizontal
if (axis2 <= 2000 && axis2 >= -2000) {
dz_horiz_left=true;
}
else if (axis2 > 2000 || axis2 < -2000) {
dz_horiz_left=false;
deadZone = "False";
}
// Right Stick -- Vertical
if (axis3 <= 2000 && axis3 >= -2000) {
dz_vert_left=true;
deadZone = "True";
}
else if (axis3 > 2000 || axis3 < -2000) {
dz_vert_left=false;
deadZone = "False";
}
}
function trackAxis() {
String inputName = gamepad.GetName();
if (gamepad.Plugged() == true) {
axis0 = gamepad.GetAxis(0); // Left Stick
axis1 = gamepad.GetAxis(1); // Left Stick
axis2 = gamepad.GetAxis(2); // Right Stick
axis3 = gamepad.GetAxis(3); // Right Stick
}
if (inputName == "PS5 Controller") {
axisCo2 = gamepad.GetAxis(2) / 2000; // Rate of Horizontal Motion
axisCo3 = gamepad.GetAxis(3) / 2000; // Rate of Vertical Motion
deadzoneCheck();
// Stick Input
if (dz_horiz_left == false || dz_vert_left == false){ // RIGHT STICK
Mouse.SetPosition(mouse.x + axisCo2, mouse.y + axisCo3);
}
}
}
Checking for deadZone 100% fixed my problem, thanks for taking the time to educate me on best practices!