ValveSoftware / csgo-osx-linux

Counter-Strike: Global Offensive
http://counter-strike.net
774 stars 69 forks source link

[CS2] can not single tap any rifles #3521

Open rooiratel opened 10 months ago

rooiratel commented 10 months ago

Your system information

Please describe your issue in as much detail as possible:

Whenever I use a rifle in cs2, whenever I single click the mouse, the rifle fires a burst of 2-4 rounds instead of single firing.

This only happens in CS2. It doesn't happen with any other application that I use, and using the exact same hardware, I did not have this issue with CS:GO when it was still available.

I am not the only one with this issue:

https://www.reddit.com/r/cs2/comments/17chdgi/cant_consistently_single_tap_random_double_shots/

https://www.reddit.com/r/cs2/comments/17bn07j/why_does_my_gun_randomly_shoot_twice_when_i/

Steps for reproducing this issue:

  1. Launch cs2
  2. Join a game (even bot mach is fine)
  3. Buy any rifle
  4. Single click the mouse (Observe that it fires 2-4 shot bursts instead of single shots)
lamprosxd commented 10 months ago

Im also having this problem

limitedAtonement commented 10 months ago

Did you try running bind del "+attack; -attack" in your console? (Replace "del" with your "fire" key, maybe "mouse1".)

rooiratel commented 10 months ago

@limitedAtonement Why would this be necessary? Shouldn't a single click just fire a single round by default?

limitedAtonement commented 10 months ago

I don't know. If your finger is sluggish, that wouldn't work (probably not your problem). If the above DOES work, it suggests a problem with your finger or mouse or something. If the above DOESN'T work, it suggests a problem with the game or game configuration.

rooiratel commented 10 months ago

Okay I just tried it. It fixed the single click firing multiple shots, but now I can't hold down the fire key and spray. It only fires single shots.

But again to my original question, why would any kind of config change be necessary? With the exact same hardware it worked fine on CS:GO, so it should work fine on CS2. It can't be a hardware issue. It is a bug in CS2.

centra5 commented 10 months ago

It's absolutely a bug in cs2. I got my friend who didn't have the problem on his machine to try shooting on my PC and he instantly noticed the problem. I then tried using his machine and shooting felt fine. We switched around mice, and no matter what hardware we used, it would always occasionally double shoot on my PC while we had no problems consistently single tapping on his machine. Resetting configs didn't help and this was also tested with a fresh install of the game.

I can't believe we're this far into cs2's launch and the gunplay is this broken. It's not a hardware issue or skill issue, and the people who assume it is because the bug isn't affecting them are actively sabotaging any chance of the bug being fixed. The bug is very real and is a complete showstopper for a competitive first person shooter like CS2.

limitedAtonement commented 10 months ago

But again to my original question, why would any kind of config change be necessary?

It should NOT be necessary, but thank you for conducting the test. I set backspace to attack1 and delete to the single-tap command I gave above, but this should absolutely not be necessary. Sorry, I have no more help to give! It's good to hear that centra5 can at least reproduce.

centra5 commented 9 months ago

I found a workaround. Turn off steam cloud and then delete (steam install)/userdata/(steamid3)/730, then open the game, reconfigure, and tapping should work again. Turns out that steam cloud was loading some ancient config file from csgo or something in my case.

RusJJ commented 9 months ago

Can confirm it's happening for a lot of ppl... You really need to be fast to click it for like 1ms.

rooiratel commented 8 months ago

Over the last few updates it has improved a lot. I'd say that less than 5% of the shots now burst fire on a single click.

I am happy to close this for myself, but if other people are still experiencing it I'll leave it open. @RusJJ @lamprosxd are you guys happy for me to close this issue?

rooiratel commented 8 months ago

Actually I take that back. Since this morning , it's just as bad as before. Pretty unplayable.

RusJJ commented 8 months ago

Didnt see your message. It's fine for me. Never happening now, i can easily fire 1 bullet.

ricardow0 commented 7 months ago

Still happening.

centra5 commented 7 months ago

Still happening.

Agreed, although I know it contradicts what I said earlier. I think I could tap again because the bug might be correlated with frame times or something, and I was on an empty server. Sorry for the misinfo - on the bright side, I don't think valve reads these anyway except for kiask.

I expect that someone will have to post proof of this happening on r*ddit before it's ever fixed.

PatrykMajchrzakDev commented 6 months ago

I had the same problem. In my case, it was a program for a game called Warframe. Maybe try to disable some external software that might be interfering with CS2

centra5 commented 5 months ago

This issue is real and it seems to be related to framerate. To demonstrate, I bound an unused keyboard button to run this script, which sends a click down and click up event to the window in focus.

#!/bin/bash
CLICK_TIME=0.09
xdotool mousedown 1
sleep $CLICK_TIME
xdotool mouseup 1
notify-send "clicked for about $CLICK_TIME"

Running a script for something that needs precise timing is not ideal, but the clicks were generally within a few milliseconds of each other, which was all that was needed. The script also displays a nice notification so that you can see when it's triggered. To prove the accuracy of the script I used a small SDL program to log how long the mouse button was held down for when triggering clicks:

// clang++ ./sdl_test.cc -o sdl_test -lSDL2 -std=c++20 -Wall
//
// click in the window to see how long your mouse was depressed for.

#include <iostream>
#include <optional>

#include <stdint.h>
#include <SDL2/SDL.h>

int main() {
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        return 1;
    }
    SDL_Window* const window = SDL_CreateWindow(
            "SDL Window",
            SDL_WINDOWPOS_UNDEFINED,
            SDL_WINDOWPOS_UNDEFINED,
            640,
            480,
            0);
    if (window == nullptr) {
        return 1;
    }

    std::optional<std::uint32_t> last_down;

    for (SDL_Event event;;) {
        if (!SDL_WaitEvent(&event)) {
            break;
        }
        switch (event.type) {
        case SDL_QUIT:
            return 0;
        case SDL_MOUSEBUTTONDOWN:
        case SDL_MOUSEBUTTONUP:
            const SDL_MouseButtonEvent& click = event.button;
            if (click.button != SDL_BUTTON_LEFT) {
                continue;
            }
            if (click.type == SDL_MOUSEBUTTONDOWN) {
                last_down = click.timestamp;
            } else {
                const std::uint32_t diff = click.timestamp - last_down.value_or(click.timestamp);
                std::cout << "Clicked for " << diff << "ms\n";
            }
        }
    }
}

This brings us to the proof video, where I demonstrate the autoclicker and then use it in-game, first at 144 frames per second and then again at 30 frames per second.

https://github.com/ValveSoftware/csgo-osx-linux/assets/106570755/d313aca0-bc70-47ad-bb5d-537586c80288

It works fine at 144fps but the rifle double-taps at 30fps. Keep in mind the click time is only ~92ms (>100ms is when the AK should start double-tapping) so the game is wrongly interpreting click timing by at least 10% at 30fps. I suspect the reason this happens at higher (average) framerates is due to micro-stuttering.

limitedAtonement commented 5 months ago

Great work! One small thing I would change about your xdotool script:

#!/bin/bash
CLICK_TIME=0.09
START_TIME=$(date +%s%N);
xdotool mousedown 1
sleep $CLICK_TIME
xdotool mouseup 1
END_TIME=$(date +%s%N);
ELAPSED_TIME=$(( (END_TIME - START_TIME) / 1000000 ));
notify-send "clicked for no more than $ELAPSED_TIME ms"

When sending the notify, don't say how long you PLANNED to click, measure the time it took to click.

rooiratel commented 5 months ago

Haven't been here in a while. Update from my end: the problem does not occur nearly as often as it used to, but it is still definitely there.

The m4 seems to behave almost always correctly.

With the AK I get the double shot maybe 30% of the time.

Looking at @centra5 's awesome post, I would not have thought it would have anything to do with the frame-rate. I will also note that I too have a 144hz screen.

I am using Sway as my window manager, so I'm not sure how well I'd be able to port the script over to Wayland, but I'll give it a shot when I have some time.

limitedAtonement commented 4 months ago

@rooiratel Porting should be okay because I think cs2 will run as a X.org window even in wayland?

nJ3ahxac commented 2 months ago

I made a similar tool to what was provided by @centra5. This one does not generate click events, rather it just logs the duration of key-presses sent to CS2 via X11.

// clang++ ./cs2_input.cc -o cs2_input -lX11 -std=c++20

#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include <algorithm>
#include <cstring>
#include <iostream>
#include <optional>
#include <string>
#include <vector>

template <typename T>
static std::vector<T> get_property(Display *const display, const Window window,
                                   const Atom prop_type,
                                   const std::string &list_name) {
    const Atom prop = XInternAtom(display, list_name.c_str(), False);

    std::vector<T> result;
    constexpr auto max_ret_bytes = 1024 * 1024;

    Atom ret_type;
    int ret_format = 0;
    std::size_t ret_count = 0, ret_bytes_after = 0;
    unsigned char *ret_prop;

    if (XGetWindowProperty(display, window, prop, 0, (max_ret_bytes) / 4, False,
                           prop_type, &ret_type, &ret_format, &ret_count,
                           &ret_bytes_after, &ret_prop) != Success) {
        throw std::runtime_error("get_property: XGetWindowProperty fail for " +
                                 list_name);
    }

    result.resize(ret_count);
    std::memcpy(result.data(), ret_prop, result.size() * sizeof(result[0]));
    XFree(ret_prop);
    return result;
}

static std::string get_window_name(Display *const display,
                                   const Window window) {
    // didnt bother with other one since even wmctrl complains about bad ret
    // format also this needs to handle utf8 better
    const auto prop_type = XInternAtom(display, "UTF8_STRING", False);
    const auto vec =
        get_property<char>(display, window, prop_type, "_NET_WM_NAME");
    return std::string{std::begin(vec), std::end(vec)};
}

static std::vector<Window> get_client_list(Display *const display) {
    return get_property<Window>(display, DefaultRootWindow(display), XA_WINDOW,
                                "_NET_CLIENT_LIST");
}

// search the _NET_CLIENT_LIST for a window with the name argument.
static std::optional<Window> find_window_named(Display *const display,
                                               const std::string_view &name) {
    const std::vector<Window> clients = get_client_list(display);
    if (const auto it = std::find_if(std::begin(clients), std::end(clients),
                                     [&](const Window window) {
                                         return get_window_name(display,
                                                                window) == name;
                                     });
        it != std::end(clients)) {
        return *it;
    }
    return std::nullopt;
}

int main(const int argc [[maybe_unused]],
         const char *const argv [[maybe_unused]][]) {
    Display *display = XOpenDisplay(NULL);
    if (display == nullptr) {
        std::cerr << "X11 failed to open display\n";
        return 1;
    }

    std::optional<Window> cs2 = find_window_named(display, "Counter-Strike 2");
    if (cs2 == std::nullopt) {
        std::cerr << "Failed to find cs2 window\n";
        return 1;
    }

    XSelectInput(display, *cs2, ExposureMask | KeyPressMask | KeyReleaseMask);

    std::cout << "Listening to CS2 Events for 'q' key\n";
    std::optional<unsigned long> press = std::nullopt;
    while (true) {
        XEvent event{};
        XNextEvent(display, &event);

        char key[32]{};
        KeySym keysym{};
        XComposeStatus compose{};

        if (const int len =
                XLookupString(&event.xkey, key, sizeof(key), &keysym, &compose);
            len <= 0 || std::string{key} != "q") {
            continue;
        }

        const auto &time = event.xbutton.time;
        if (event.type == KeyPress) {
            press.emplace(time);
            continue;
        }

        if (!press.has_value()) {
            continue;
        }

        const auto diff = time - *press;
        std::cout << "key press time: " << diff << "ms\n";
        std::cout << "    Correct AK47 Shot Count: " << (diff / 100) + 1
                  << '\n';
        press.reset();
    }

    XCloseDisplay(display);
}

X11 is ugly and has a quirk where it's difficult to cleanly grab mouse events from an application already consuming them, however keyboard events are OK. As a workaround we bind +attack to 'q' and treat that as if it was our left mouse button for this test.

unbind "q"
bind "q" "+attack"

Running this in the background when single tapping rifles reveals the same behaviour described by @centra5. Rifles misbehave sometimes, especially at lower frame rates, occasionally firing more or less bullets than what they should according to the events provided by X11.

I think the script from @centra5 is more useful for testing - this just confirms that the provided evidence is not botched by the script/xdotool running slower than expected. By capturing exactly what X11 sees and sends to CS2, we know for sure that cs2 is mishandling input.

Frankly it's embarrassing that something as fundamental as tapping a rifle is still broken almost a year after release and greatly contributes to the unpolished feel described by many others.

limitedAtonement commented 2 months ago

@nJ3ahxac Excellent research, and it does provide an excellent additional datum. Before, we knew that sending X events appropriately demonstrated the problem, and your research shows that X events properly sent to CS2 are also being mishandled! For what it's worth, this is not a problem for me. I'm running arch linux. Currently the game doesn't work at all (X hands with 100% cpu usage while CS2, and even other games are running, so probably a graphics drivers problem), but that problem has only existed for a month or so.

thomasweiland93 commented 3 weeks ago

Still no update on this issue :(