custom endpoints on Custom Post Type return 404 page not found

So I’m not sure why are you getting the 404 error page, but there are two possible reasons why could that happen:

  1. Whenever you do anything that changes the permalink structures, e.g. you registered a new post type or taxonomy, or you programmatically added a custom rewrite rule or endpoint, then don’t forget to flush the rewrite rules so that WordPress recognizes the new rewrite rules and regenerate them in the database (or in certain cases/setup, WordPress might instead write all the rewrite rules to the .htaccess file).

    So if you haven’t done so, flushing the rewrite rules is easy — just visit the permalink settings page without making any changes (i.e. without having to click on the “save” button).

  2. Another possibility is that you may have an existing post type or taxonomy which is using the same query var as your custom endpoint — when you call add_rewrite_endpoint(), the query var is the 3rd parameter which defaults to the 1st parameter.

    add_rewrite_endpoint( 'review', EP_PERMALINK );
    // The above call is equivalent to:
    add_rewrite_endpoint( 'review', EP_PERMALINK, 'review' );
    // .. and both will register a *public* query var named 'review'
    

    PS: Public query var means WordPress will read it from the URL, e.g. https://example.com/my-post/?review=foo.

    So make sure that there’s no query var clash on your website. E.g. If you have a post type with review being the query var, then for your custom endpoint, define a different query var like review2..

But even after fixing the 404 error, the logic in your wpd_game_template() function doesn’t seem correct, so you should also fix that. So try with:

function wpd_game_template( $template="" ) {
    global $wp_query;

    if ( isset( $wp_query->query_vars['review'] ) ) {
        return locate_template( 'single-game-review.php' );
    }

    if ( isset( $wp_query->query_vars['related-articles'] ) ) {
        return locate_template( 'single-game-related.php' );
    }

    if ( isset( $wp_query->query_vars['screenshots'] ) ) {
        return locate_template( 'single-game-screenshots.php' );
    }

    return $template;
}

Or using get_query_var(): (works only if the query var is not empty)

function wpd_game_template( $template="" ) {
    if ( get_query_var( 'review' ) ) {
        return locate_template( 'single-game-review.php' );
    }

    if ( get_query_var( 'related-articles' ) ) {
        return locate_template( 'single-game-related.php' );
    }

    if ( get_query_var( 'screenshots' ) ) {
        return locate_template( 'single-game-screenshots.php' );
    }

    return $template;
}

I hope that helps?

I have also added a filter for query vars (don’t really know what that
means but found the code on here)

That code is basically used to make get_query_var() works when checking if a query var is set and not empty. I.e. Consider these examples:

URL: https://example.com/my-post/review/this-part
get_query_var( 'review' ) = 'this-part'

URL: https://example.com/my-post/review
get_query_var( 'review' ) = '' (empty string)

So with that code in the question, the second example above would return true.

But the thing is, when a custom query var value is set in the URL like the first example above, then get_query_var( 'review' ) would always return true.

However, you can fix that by using isset and empty in the wpse42279_filter_request() function, e.g.

if ( isset( $vars['related-articles'] ) && empty( $vars['related-articles'] ) ) {
    $vars['related-articles'] = true;
}