diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9289ab9..c7e47ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,12 @@ on: tag_name: description: "Tag for the release" required: true - default: "v1.2.0" + default: "v1.0.0" + prerelease: + description: "Mark as pre-release" + required: false + default: "false" + type: boolean permissions: contents: write @@ -31,18 +36,23 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt + - name: Install PyMeow + run: | + curl -LO https://github.com/qb-0/pyMeow/releases/download/1.73.42/pyMeow-1.73.42.zip + pip install pyMeow-1.73.42.zip + - name: Install PyInstaller run: pip install pyinstaller - name: Package Application run: | - pyinstaller --noconfirm --onefile --windowed --icon "src\img\icon.ico" --name "CS2.Triggerbot" --version-file "version.txt" --add-data "classes;classes/" --add-data "gui;gui/" --add-data "src/img/*;src/img" --add-data "src/fonts/*;src/fonts" --add-data "src/*;src" "main.py" + pyinstaller --noconfirm --onefile --windowed --icon "src\img\icon.ico" --name "VioletWing" --version-file "version.txt" --add-data "classes;classes/" --add-data "gui;gui/" --add-data "src/img/*;src/img" --add-data "src/fonts/*;src/fonts" --add-data "src/*;src" "main.py" - name: Upload Build Artifact uses: actions/upload-artifact@v4 with: - name: CS2_Triggerbot - path: dist/CS2.Triggerbot.exe + name: VioletWing + path: dist/VioletWing.exe release: needs: build @@ -55,15 +65,15 @@ jobs: - name: Download Build Artifact uses: actions/download-artifact@v4 with: - name: CS2_Triggerbot + name: VioletWing path: ./artifact-download - name: Verify Downloaded Artifact run: | echo "Listing artifact-download directory:" ls -la ./artifact-download - if [ ! -f ./artifact-download/CS2.Triggerbot.exe ]; then - echo "Error: File CS2.Triggerbot.exe not found in artifact-download directory!" + if [ ! -f ./artifact-download/VioletWing.exe ]; then + echo "Error: File VioletWing.exe not found in artifact-download directory!" exit 1 fi @@ -73,21 +83,22 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.event.inputs.tag_name }} - name: CS2 TriggerBot - Release ${{ github.event.inputs.tag_name }} + name: VioletWing - ${{ github.event.inputs.prerelease == 'true' && 'Pre-release' || 'Release' }} ${{ github.event.inputs.tag_name }} body_path: ./ChangeLog.md files: | - ./artifact-download/CS2.Triggerbot.exe + ./artifact-download/VioletWing.exe + prerelease: ${{ github.event.inputs.prerelease }} - name: Send Telegram Notification if: success() run: | curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \ -d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \ - -d text="🎉 New Release: CS2 TriggerBot ${{ github.event.inputs.tag_name }} has been published! Check it out: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.event.inputs.tag_name }}" + -d text="🎉 New ${{ github.event.inputs.prerelease == 'true' && 'Pre-release' || 'Release' }}: VioletWing ${{ github.event.inputs.tag_name }} has been published! Check it out: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.event.inputs.tag_name }}" - name: Send Telegram Notification on Failure if: failure() run: | curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \ -d chat_id=${{ secrets.TELEGRAM_ADMIN_CHAT_ID }} \ - -d text="🚨 Release Failed: CS2 TriggerBot ${{ github.event.inputs.tag_name }}. Check the workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + -d text="🚨 ${{ github.event.inputs.prerelease == 'true' && 'Pre-release' || 'Release' }} Failed: VioletWing ${{ github.event.inputs.tag_name }}. Check the workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a503e35..e8c2ec0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to CS2 TriggerBot +# Contributing to VioletWing -Thank you for your interest in contributing to the CS2 TriggerBot project. This guide will assist you from setting up the development environment to submitting pull requests. +Thank you for your interest in contributing to the VioletWing project. This guide outlines the process for setting up your development environment, contributing code, and submitting changes. ## Table of Contents @@ -16,34 +16,34 @@ Thank you for your interest in contributing to the CS2 TriggerBot project. This ## Code of Conduct -By participating in this project, you agree to abide by the [Code of Conduct](CODE_OF_CONDUCT.md). Please be respectful, considerate, and open to constructive feedback. +By participating, you agree to follow the [Code of Conduct](CODE_OF_CONDUCT.md). Be respectful, inclusive, and open to constructive feedback. ## Getting Started -1. **Fork the Repository**: Create a personal fork of the repository on GitHub. -2. **Clone Your Fork**: Clone your fork to your local environment: +1. **Fork the Repository**: Create a personal fork on GitHub. +2. **Clone Your Fork**: Clone your fork locally: ```bash - git clone https://github.com/Jesewe/cs2-triggerbot.git - cd cs2-triggerbot + git clone https://github.com/Jesewe/VioletWing.git + cd VioletWing ``` -3. **Add Upstream Remote**: Stay updated with the original repository: +3. **Add Upstream Remote**: Sync with the original repository: ```bash - git remote add upstream https://github.com/Jesewe/cs2-triggerbot.git + git remote add upstream https://github.com/Jesewe/VioletWing.git ``` ## Development Setup -1. **Install Python**: Ensure you have Python version **≥ 3.8** and **< 3.12.5** installed. +1. **Install Python**: Use Python **≥ 3.8** and **< 3.12.5**. 2. **Install Dependencies**: Install required packages: ```bash pip install -r requirements.txt ``` -3. **Run the Application**: Launch the application for testing and development: +3. **Run the Application**: Test the application: ```bash python main.py @@ -51,68 +51,95 @@ By participating in this project, you agree to abide by the [Code of Conduct](CO ### Testing Changes -- Execute the application to verify your modifications. -- Review logs at `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\tb_logs.log` for debugging. +- Run the application to validate changes. +- Check logs at `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\vw_logs.log` or `vw_detailed_logs.log` for debugging. +- Test in Counter-Strike 2 (casual or offline modes) to ensure TriggerBot, Overlay, Bunnyhop, and NoFlash features work as expected. ## Coding Standards -Adhere to the following guidelines for consistency and readability: +Follow these guidelines for consistent, high-quality code: -- **PEP 8**: Follow the [PEP 8 style guide](https://www.python.org/dev/peps/pep-0008/). -- **Naming**: Use clear, descriptive names for variables, functions, and classes. -- **Error Handling**: Handle exceptions gracefully and log errors appropriately. -- **Documentation**: Comment complex or critical sections to explain intent. -- **Modularity**: Decompose large functions into smaller, reusable units. +- **PEP 8**: Adhere to the [PEP 8 style guide](https://www.python.org/dev/peps/pep-0008/). +- **Naming**: Use descriptive, meaningful names for variables, functions, and classes (e.g., `trigger_key` instead of `tk`). +- **Error Handling**: Implement robust exception handling and log errors using the `Logger` class. +- **Documentation**: Add docstrings for functions and comments for complex logic. +- **Modularity**: Break down large functions into smaller, reusable components. ### GUI Development -- **customtkinter**: Use `customtkinter` for any enhancements or changes to the graphical interface. +- **Customtkinter**: Use `customtkinter` for GUI enhancements. +- **Consistency**: Match the existing UI theme (colors, fonts: Chivo, Gambetta) and layout (e.g., scrollable frames, card designs). +- **Responsiveness**: Ensure GUI elements adapt to different window sizes. + +### Feature-Specific Guidelines + +- **TriggerBot**: Ensure compatibility with offset updates from [cs2-dumper](https://github.com/a2x/cs2-dumper). +- **Overlay (ESP)**: Test visual elements (bounding boxes, snaplines, minimap) in various game resolutions. +- **Bunnyhop**: Verify smooth movement automation across different surfaces. +- **NoFlash**: Confirm flashbang mitigation without affecting game performance. ### Logging -- **Format**: Use the `Logger` class format for log entries. -- **Levels**: Apply appropriate log levels (`INFO`, `WARNING`, `ERROR`) based on severity. +- **Format**: Follow the `Logger` class format for log entries. +- **Levels**: Use appropriate levels (`INFO`, `WARNING`, `ERROR`) based on context. +- **Location**: Logs are saved to `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\`. ## Submitting Issues -Before submitting a new issue: +Before opening an issue: 1. Search existing issues to avoid duplicates. -2. Provide a clear problem description, steps to reproduce, expected behavior, and relevant logs or screenshots. +2. Include: + - A clear description of the problem or bug. + - Steps to reproduce. + - Expected vs. actual behavior. + - Screenshots or log excerpts (from `vw_logs.log` or `vw_detailed_logs.log`). + +Use the [Issues tab](https://github.com/Jesewe/VioletWing/issues) and select the appropriate template (e.g., Bug Report). ## Pull Request Process -1. **Create a Branch**: Use a descriptive branch name: +1. **Create a Branch**: Use a descriptive name: ```bash - git checkout -b feature/your-feature + git checkout -b feature/add-new-feature ``` -2. **Commit Changes**: Ensure each commit message is concise and descriptive: +2. **Commit Changes**: Write clear, concise commit messages: ```bash - git commit -m "Brief description of changes" + git commit -m "Add bunnyhop timing adjustment in settings" ``` 3. **Push to Fork**: ```bash - git push origin feature/your-feature + git push origin feature/add-new-feature ``` -4. **Open a Pull Request**: On GitHub, submit a PR to the main repository, including: +4. **Open a Pull Request**: Submit a PR to the main repository, including: - - Purpose of your changes. - - Potential impact on existing functionality. - - Testing steps and results. + - Purpose of the changes (e.g., “Adds NoFlash toggle to General Settings”). + - Impact on existing features (e.g., “No changes to TriggerBot”). + - Testing details (e.g., “Tested in CS2 casual mode, 1920x1080”). + - Screenshots for GUI changes. ### Review Process -- **Code Review**: PRs are reviewed for quality, functionality, and adherence to standards. -- **Feedback**: Address requested changes promptly. +- PRs are reviewed for code quality, functionality, and adherence to standards. +- Respond to feedback promptly and make requested changes. +- PRs may require rebasing if the upstream repository updates: + + ```bash + git fetch upstream + git rebase upstream/main + git push --force + ``` ## Feature Requests and Feedback -For new ideas or improvements, open an issue labeled **Feature Request** in the [Issues tab](https://github.com/Jesewe/cs2-triggerbot/issues). +Submit new ideas or improvements via the [Issues tab](https://github.com/Jesewe/VioletWing/issues) with the **Feature Request** label. Include: -We appreciate your contributions to CS2 TriggerBot and your efforts in improving this project! +- Description of the feature (e.g., “Add customizable snapline colors for Overlay”). +- Use case or benefit. +- Any mockups or examples (optional). diff --git a/ChangeLog.md b/ChangeLog.md index 24e4881..36f0e1a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,18 +1,15 @@ ### Summary -- **A complete rework of all GUI elements**. -- Reworking the logic of binding the triggerbot to the side mouse buttons. -- Moved memory functions into a **MemoryManager** class. -- Moved offset initialization to a **Utility** class. -- Updated trigger_bot.py to use the new **MemoryManager** and **Utility** classes. -- Improved styling. -- Improved logging system. -- Fixed logging system in **"Application Logs"** tab. -- Fixed a bug displaying TriggerBot status in the **"DashBoard"** tab. -- Added Line Limiting Logic. -- Added **"Supporters"** tab. -- Added **"Auto-Update"** Logic. -- Added fonts: **Chivo** and **Gambetta**. -- Redesigned the **"Supporters"** tab. +- Renamed all instances of **"CS2 TriggerBot"** to **"VioletWing"** in the codebase, build scripts, and documentation. +- Added new classes for bunnyhop, ESP, and noflash features. +- Implemented new GUI tabs for overlay and trigger settings. +- Integrated new settings into the configuration manager. +- Improved the general settings tab with new options. +- Improved configuration updates. +- Refactored code for better organization and readability. +- Removed unused image files. +- Threads optimization. +- Fixed a bug with the **"Start Client"** and **"Stop Client"** buttons. +- Fixed a bug where `None` values were added to detailed logging. -![Downloads](https://img.shields.io/github/downloads/Jesewe/cs2-triggerbot/v1.2.5/total?style=for-the-badge&logo=github&color=D5006D) ![Platforms](https://img.shields.io/badge/platform-Windows-blue?style=for-the-badge&color=D5006D) ![License](https://img.shields.io/github/license/jesewe/cs2-triggerbot?style=for-the-badge&color=D5006D) +![Downloads](https://img.shields.io/github/downloads/Jesewe/VioletWing/v1.2.5.1/total?style=for-the-badge&logo=github&color=D5006D) ![Platforms](https://img.shields.io/badge/platform-Windows-blue?style=for-the-badge&color=D5006D) ![License](https://img.shields.io/github/license/jesewe/cs2-triggerbot?style=for-the-badge&color=D5006D) diff --git a/README.md b/README.md index f288f9b..06b36ab 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,16 @@
- CS2 TriggerBot -

CS2 TriggerBot

-

Your ultimate aiming assistant for Counter-Strike 2

+ VioletWing +

VioletWing

+

Your ultimate assistant for Counter-Strike 2

-[![Downloads](https://img.shields.io/github/downloads/jesewe/cs2-triggerbot/total?style=for-the-badge&logo=github&color=D5006D)](https://github.com/Jesewe/cs2-triggerbot/releases) -[![Latest Release](https://img.shields.io/github/v/release/jesewe/cs2-triggerbot?style=for-the-badge&logo=github&color=D5006D)](https://github.com/Jesewe/cs2-triggerbot/releases/latest/) -[![License](https://img.shields.io/github/license/jesewe/cs2-triggerbot?style=for-the-badge&color=D5006D)](LICENSE) -[![Boosty](https://img.shields.io/badge/Support%20on-Boosty-orange?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABx0lEQVQ4T2NkoBAwUqifgXH7Hz7++X/sXz1yF8Rz9V6QCBhYMBFAEgnk6sDkNf/XlDPf/2nqgM7iXvb57+3V+c1sbmF9AvHPnz/ff39lQUTtPzj0YPvj79u1Pnjx58P///v2rf/75u08cOhR3+vPnT///+OP79+/d3TiBtHR1d/fXv35+8ffv2tc+fP5xAiPu7N0YmJiZs/f37/3/1/5BgYGBgYGevn27evfuHYNzc3P79OmTz5w5I0BcXFw//vjjc+7cOT179mx2GoQxgqYO/vn0zMzOjv78/nT59+oSpqam5e/bsmXfv3pVVVVUjlUdf369ZOTk/Px8YOrq6hoyMjIpKSkxZs2axZIlS9y5cyc1NTUuXLhgbm7u9u3b9evXr5WVlXn69GkFBQW+efNmRUVFLVu2zOrqKhwcHLp48aIqKiq6e/duMTExMXDgQHBwcFD9+/c3b968I0eOHGzatEkJCQlp0qQJjY2Nt956KXDhwoVFixY1dOjQoTfeeIPq6uqWnZ2dCgsLU1FR0QULFkxKSkq+fv1q3759x44dO2zYsIGioqJLly7Vv39/ZWVlRUVF5+vTp8fPz2xYsWqK+vP/78+QqFQrZs2bLCwsLZ2dlRqVQ5ZcqUbN++vZUrV+Tn5xcuXKj8+fPVr18//v7778yYMSMbNmyQkpIiJSUFjY2NDh06tPz8/ISEhCgqKtHLlyiVLlqT8/Py8efOmadOmOT09fWzatEnnzp1TKBRlZ2fXrl3r1q1bYWFhQUxMzPKysr279//zpw5U+3bt7e+vr5hYWFOnz+vRYsWWrNmjWvXrjUnT57k5OSUlpZGc3Oz69evNzMzUvn17Tpw4UauXL2dmZpKTk7V9+/a2bNmihYUFxcXFGz16dC5fvjxt27aVkpJihw8fDgYGBnJzc2XKlCFjxozh4eFdu3YtNjY2nTp1ymq1WlFRUfr06VOXLl1UqVTi5eXFvXv3+/fvX5s2bcqSJUs0btyYLVu2dOTIEYODg7Vu3Zrbtm1DhgzRq1cvnTt3jqSkJO3Zs0cVFhbq1KnDyMgYLVq0sNixY/Lz82PYsGEaP36c8ePHmTNnDpWVlXn48GG1atWqmTNnioqKkiNHjtjZ2dn69euZmZnR1NSU7du3V3Nzs9asWSNOnDiR3NxcBwcH66xXkP89rZhLhAAAAAElFTkSuQmCC)](https://boosty.to/jesewe) +[![Downloads](https://img.shields.io/github/downloads/jesewe/VioletWing/total?style=for-the-badge&logo=github&color=D5006D)](https://github.com/Jesewe/VioletWing/releases) +[![Latest Release](https://img.shields.io/github/v/release/jesewe/VioletWing?style=for-the-badge&logo=github&color=D5006D)](https://github.com/Jesewe/VioletWing/releases/latest/) +[![License](https://img.shields.io/github/license/jesewe/VioletWing?style=for-the-badge&color=D5006D)](LICENSE) +[![Boosty](https://img.shields.io/badge/Support%20on-Boosty-orange?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABx0lEQVQ4T2NkoBAwUqifgXH7Hz7++X/sXz1yF8Rz9V6QCBhYMBFAEgnk6sDkNf/XlDPf/2nqgM7iXvb57+3V+c1sbmF9AvHPnz/ff39lQUTtPzj0YPvj79u1Pnjx58P///v2rf/75u08cOhR3+vPnT///+OP79+/d3TiBtHR1d/fXv35+8ffv2tc+fP5xAiPu7N0YmJiZs/f37/3/1/5BgYGBgYGevn27evfuHYNzc3P79OmTz5w5I0BcXFw//vjjc+7cOT179mx2GoQxgqYO/vn0zMzOjv78/nT59+oSpqam5e/bsmXfv3pVVVVUjlUdf369ZOTk/Px8YOrq6hoyMjIpKSkxZs2axZIlS9y5cyc1NTUuXLhgbm7u9u3b9evXr5WVlXn69GkFBQW+efNmRUVFLVu2zOrqKhwcHLp48aIqKiq6e/duMTExMXDgQHBwcFD9+/c3b968I0eOHGzatEkJCQlp0qQJjY2Nt956KXDhwoVFixY1dOjQoTfeeIPq6uqWnZ2dCgsLU1FR0QULFkxKSkq+fv1q3759x44dO2zYsIGioqJLly7Vv39/ZWVlRUVF5+vTp8fPz2xYsWqK+vP/78+QqFQrZs2bLCwsLZ2dnRqVQ5ZcqUbN++vZUrV+Tn5xcuXKj8+fPVr18//v7778yYMSMbNmyQkpIiJSUFjY2NDh06tPz8/ISEhCgqKtHLlyiVLlqT8/Pzy8efOmadOmOT09fWzatEnnzp1TKBRlZ2fXrl3r1q1bYWFhQUxMzPKysr279//zpw5U+3bt7e+vr5hYWFOnz+vRYsWWrNmjWvXrjUnT57k5OSUlpZGc3Oz69evNzMzUvn17Tpw4UauXL2dmZpKTk7V9+/a2bNmihYUFxcXFGz16dC5fvjxt27aVkpJihw8fDgYGBnJzc2XKlCFjxozh4eFdu3YtNjY2nTp1ymq1WlFRUfr06VOXLl1UqVTi5eXFvXv3+/fvX5s2bcqSJUs0btyYLVu2dOTIEYODg7Vu3Zrbtm1DhgzRq1cvnTt3jqSkJO3Zs0cVFhbq1KnDyMgYLVq0sNixY/Lz82PYsGEaP36c8ePHmTNnDpWVlXn48GG1atWqmTNnioqKkiNHjtjZ2dn69euZmZnR1NSU7du3V3Nzs9asWSNOnDiR3NxcBwcH66xXkP89rZhLhAAAAAElFTkSuQmCC)](https://boosty.to/jesewe/donate) FeaturesInstallationUsage • -ConfigurationTroubleshootingContributing @@ -21,39 +20,47 @@ # Overview -CS2 TriggerBot is an automated tool designed for Counter-Strike 2 that assists with precise aiming by automatically triggering a mouse click when an enemy is detected in the player's crosshairs. The tool features a graphical user interface (GUI) for easy configuration. +VioletWing is an automated tool designed for Counter-Strike 2 that enhances gameplay with features like precise aiming, visual overlays, and movement automation. It includes a graphical user interface (GUI) for easy configuration. ## Features -- **Automatic Trigger**: Fires when an enemy is detected under your crosshair. -- **Configurable Trigger Key**: Set a keyboard key (e.g., `x`, `c`) or mouse button (`mouse4`, `mouse5`) via the GUI or `config.json`. -- **Toggle Mode**: Activate the bot with a single key press instead of holding (configurable in the GUI). -- **Configurable Delays**: Adjust minimum (`ShotDelayMin`), maximum (`ShotDelayMax`), and post-shot (`PostShotDelay`) delays for natural shooting behavior. -- **Attack Teammates Option**: Toggle friendly fire via a checkbox in the GUI or `config.json`. -- **Automatic Offset Fetching**: Retrieves the latest offsets and client data from remote sources on startup. +- **TriggerBot**: + - Automatically fires when an enemy is under your crosshair. + - Configurable trigger key (e.g., `x`, `c`, `mouse4`, `mouse5`) via GUI or `config.json`. + - Toggle mode for single-key activation. + - Adjustable delays (`ShotDelayMin`, `ShotDelayMax`, `PostShotDelay`) for natural shooting. + - Option to attack teammates. +- **Overlay (ESP)**: + - Displays enemy bounding boxes, snaplines, health numbers, nicknames, and a minimap. + - Customizable colors, line thickness, and minimap size. +- **Bunnyhop**: + - Automates bunny hopping for continuous jumping and speed maintenance. +- **NoFlash**: + - Reduces or eliminates flashbang effects for uninterrupted visibility. +- **Automatic Offset Fetching**: Retrieves latest offsets from remote sources on startup. - **Graphical User Interface (GUI)**: - - **Dashboard**: Monitor bot status, offset updates, and version info. - - **Settings Tab**: Configure trigger settings and delays with real-time validation. - - **Logs Tab**: View real-time logs from `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\tb_logs.log`. - - **FAQ Tab**: Access answers to common questions. - - **Supporters Tab**: View a list of contributors and supporters who help the project. -- **Dynamic Config Updates**: Detects and applies changes to `config.json` without restarting, thanks to `file_watcher.py`. -- **Share/Import Settings**: Export settings as a compressed code or import from others via the GUI. -- **Reset to Defaults**: Restore default settings with one click in the GUI. -- **Update Checker**: Alerts you to new versions via GitHub releases. -- **Logging**: Detailed logs saved to `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\tb_logs.log` and a detailed version at `tb_detailed_logs.log`. + - **Dashboard**: Shows bot status, offset updates, and version info. + - **General Settings**: Toggle TriggerBot, Overlay, Bunnyhop, and NoFlash. + - **Trigger Settings**: Configure trigger key, delays, and toggle mode. + - **Overlay Settings**: Adjust ESP features and appearance. + - **Logs Tab**: View real-time logs from `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\vw_logs.log`. + - **FAQ Tab**: Answers to common questions about all features. + - **Supporters Tab**: Lists contributors and supporters. +- **Dynamic Config Updates**: Applies `config.json` changes without restarting via `file_watcher.py`. +- **Update Checker**: Notifies of new versions via GitHub releases. +- **Logging**: Saves logs to `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\vw_logs.log` and `vw_detailed_logs.log`. ## Installation -You can install the trigger bot by cloning the repository or by downloading a pre-built executable file from the releases. +Install by cloning the repository or downloading a pre-built executable from releases. ### Option 1: Clone the Repository 1. **Clone the Repository:** ```bash - git clone https://github.com/Jesewe/cs2-triggerbot.git - cd cs2-triggerbot + git clone https://github.com/Jesewe/VioletWing.git + cd VioletWing ``` 2. **Install Dependencies:** @@ -62,81 +69,53 @@ You can install the trigger bot by cloning the repository or by downloading a pr pip install -r requirements.txt ``` + **PyMeow Module:** + PyMeow is essential for rendering the overlay. + Download the latest `pyMeow*.zip` from the [PyMeow GitHub Releases page](https://github.com/qb-0/pyMeow/releases) and install it: + + ```bash + pip install pyMeow*.zip + ``` + 3. **Run the Script:** + ```bash python main.py ``` ### Option 2: Download Pre-Built Executable -Alternatively, download the ready-to-use executable from the [Releases](https://github.com/jesewe/cs2-triggerbot/releases) page. Download the latest version and run the executable directly. - -**Note:** This project requires Python version >= 3.8 and < 3.12.5. - -## Configuration - -The `config.json` file is automatically generated in the directory `%LOCALAPPDATA%\Requests\ItsJesewe\` on the first run. You can modify the `TriggerKey` in this file or via the GUI. - -Example `config.json`: - -```json -{ - "Settings": { - "TriggerKey": "x", - "ToggleMode": false, - "ShotDelayMin": 0.01, - "ShotDelayMax": 0.03, - "AttackOnTeammates": false, - "PostShotDelay": 0.1 - } -} -``` - -### Configuration Options - -- **TriggerKey**: The key or mouse button (`x`, `mouse4`, or `mouse5`) to activate the bot. -- **ToggleMode**: `true` enables toggle mode; `false` requires holding the key. -- **ShotDelayMin** and **ShotDelayMax**: Control the delay between shots to simulate natural behavior. -- **PostShotDelay**: Delay after each shot (seconds). -- **AttackOnTeammates**: `true` to enable friendly fire. - -**GUI Configuration:** Use the **Settings** tab to modify these values interactively. Changes are saved instantly and applied dynamically if `config.json` is edited externally. - -## Usage +Download the latest executable from the [Releases](https://github.com/Jesewe/VioletWing/releases) page and run it directly. -1. **Launch Counter-Strike 2:** Ensure the game is running. -2. **Start the Bot:** Run `main.py` or the executable, then click **Start Bot** in the Dashboard tab. -3. **Configure Settings:** Adjust trigger key, delays, and other options in the **Settings** tab. -4. **Monitor Activity:** Check the **Logs** tab for real-time updates or the **Dashboard** for status. -5. **Toggle the Bot:** Use the configured trigger key to activate/deactivate (toggle mode) or hold to fire (hold mode). -6. **Advanced Features:** - - **Share/Import:** Export/import settings via the Settings tab. - - **FAQ:** Refer to the FAQ tab for help. +**Note:** Requires Python >= 3.8 and < 3.12.5. ## Troubleshooting -- **Failed to Fetch Offsets:** Ensure you have an active internet connection and that the source URLs are accessible. -- **Errors with Offsets after Game Update:** After a Counter-Strike 2 game update, there may be issues with offsets, which can result in errors. Offsets are sourced from [https://github.com/a2x/cs2-dumper](https://github.com/a2x/cs2-dumper) and are not updated by the author of TriggerBot. Please wait for updated offsets to be released by the cs2-dumper repository. -- **Could Not Open `cs2.exe`:** Make sure the game is running and that you have the necessary permissions. -- **Unexpected Errors:** Check the log file located in the log directory for more details. -- **Issues with Importing Settings:** Ensure the imported config.json file is valid and matches the expected format. +- **Failed to Fetch Offsets**: Verify internet connection and source URL accessibility. +- **Offset Errors Post-Update**: Wait for updated offsets from [https://github.com/a2x/cs2-dumper](https://github.com/a2x/cs2-dumper). +- **Could Not Open `cs2.exe`**: Ensure the game is running with necessary permissions. +- **Overlay Not Displaying**: Check Overlay settings and game mode (windowed/borderless). +- **Bunnyhop Inconsistent**: Verify focus on game window and settings. +- **NoFlash Not Working**: Confirm offsets are updated and feature is enabled. +- **Unexpected Errors**: Review logs in `%LOCALAPPDATA%\Requests\ItsJesewe\crashes\`. +- **Invalid Config Import**: Ensure `config.json` format is correct. ## Contributing -Contributions are welcome! Please open an issue or submit a pull request on the [GitHub repository](https://github.com/Jesewe/cs2-triggerbot). +Contributions are welcome! Open an issue or submit a pull request on the [GitHub repository](https://github.com/Jesewe/VioletWing). ## Support the Developer -If you find CS2 TriggerBot helpful and want to support its continued development, consider donating through Boosty. Your support helps maintain and improve this tool. +Support continued development by donating via Boosty: -- [Support on Boosty](https://boosty.to/jesewe) +- [Support on Boosty](https://boosty.to/jesewe/donate) -Thank you for your generosity! +Thank you for your support! ## Disclaimer -This script is for educational purposes only. Using cheats or hacks in online games is against the terms of service of most games and can result in bans or other penalties. Use this script at your own risk. +This tool is for educational purposes only. Using automation tools in online games violates terms of service and may result in bans. Use at your own risk. ## License -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. +Licensed under the MIT License. See the [LICENSE](LICENSE) file for details. diff --git a/SECURITY.md b/SECURITY.md index f46b50d..fd1fe23 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Supported Versions -The "CS2 TriggerBot" project is an open-source tool designed to assist with aiming in Counter-Strike 2. As of the latest update, the project is actively maintained, and users are encouraged to use the latest version available on the [GitHub repository](https://github.com/Jesewe/cs2-triggerbot). +The "VioletWing" project is an open-source tool designed to assist with aiming in Counter-Strike 2. As of the latest update, the project is actively maintained, and users are encouraged to use the latest version available on the [GitHub repository](https://github.com/Jesewe/VioletWing). ## Reporting a Vulnerability @@ -22,7 +22,7 @@ The maintainer will acknowledge receipt of your report within 5 business days an - **Use at Your Own Risk**: This tool is intended for educational purposes only. Using cheats or automation tools in online games like Counter-Strike 2 may violate the game's terms of service and can result in penalties, including bans. Users should be aware of these risks and use the tool responsibly. -- **Malware Risks**: Always download the tool from the official [GitHub repository](https://github.com/Jesewe/cs2-triggerbot) to avoid malicious versions. Be cautious of executables from untrusted sources, as they may contain malware or backdoors. +- **Malware Risks**: Always download the tool from the official [GitHub repository](https://github.com/Jesewe/VioletWing) to avoid malicious versions. Be cautious of executables from untrusted sources, as they may contain malware or backdoors. - **Code Review**: Users are encouraged to review the source code before running the tool to ensure it behaves as expected and does not contain any malicious code. @@ -36,4 +36,4 @@ By using this tool, you agree to comply with all relevant laws and game terms of --- -For more information, refer to the [README.md](https://github.com/Jesewe/cs2-triggerbot/blob/main/README.md) and [LICENSE](https://github.com/Jesewe/cs2-triggerbot/blob/main/LICENSE) files in the project's repository. +For more information, refer to the [README.md](https://github.com/Jesewe/VioletWing/blob/main/README.md) and [LICENSE](https://github.com/Jesewe/VioletWing/blob/main/LICENSE) files in the project's repository. diff --git a/classes/bunnyhop.py b/classes/bunnyhop.py new file mode 100644 index 0000000..a4ad148 --- /dev/null +++ b/classes/bunnyhop.py @@ -0,0 +1,101 @@ +import threading +import time +import ctypes +from typing import Optional + +from classes.config_manager import ConfigManager +from classes.memory_manager import MemoryManager +from classes.logger import Logger +from classes.utility import Utility + +# Initialize the logger for consistent logging +logger = Logger.get_logger() +# Define the main loop sleep time for reduced CPU usage +MAIN_LOOP_SLEEP = 0.05 +# Constants for bunnyhop +FORCE_JUMP_ACTIVE = 65537 +FORCE_JUMP_INACTIVE = 256 +KEY_SPACE = 0x20 + +class CS2Bunnyhop: + """Manages the Bunnyhop functionality for Counter-Strike 2.""" + def __init__(self, memory_manager: MemoryManager) -> None: + """ + Initialize the Bunnyhop with a shared MemoryManager instance. + """ + # Load the configuration settings + self.config = ConfigManager.load_config() + self.memory_manager = memory_manager + self.is_running = False + self.stop_event = threading.Event() + self.force_jump_address: Optional[int] = None + + def load_configuration(self): + """Load and apply configuration settings.""" + self.bunnyhop_enabled = self.config.get("General", {}).get("Bunnyhop", False) + + def update_config(self, config): + """Update the configuration settings.""" + self.config = config + self.load_configuration() + logger.debug("Bunnyhop configuration updated.") + + def initialize_force_jump(self) -> bool: + """Initialize the force jump address.""" + if self.memory_manager.dwForceJump is None: + logger.error("dwForceJump offset not initialized.") + return False + try: + self.force_jump_address = self.memory_manager.client_base + self.memory_manager.dwForceJump + return True + except Exception as e: + logger.error(f"Error setting force jump address: {e}") + return False + + def perform_jump(self, is_jumping: bool) -> bool: + """Perform a single jump action.""" + try: + if not is_jumping: + self.memory_manager.write_int(self.force_jump_address, FORCE_JUMP_ACTIVE) + return True + else: + self.memory_manager.write_int(self.force_jump_address, FORCE_JUMP_INACTIVE) + return False + except Exception as e: + logger.error(f"Error performing jump: {e}") + return is_jumping + + def start(self) -> None: + """Start the Bunnyhop.""" + if not self.initialize_force_jump(): + logger.error("Failed to initialize force jump address.") + return + + self.is_running = True + + is_game_active = Utility.is_game_active + sleep = time.sleep + is_jumping = False + + while not self.stop_event.is_set(): + try: + if not is_game_active(): + sleep(MAIN_LOOP_SLEEP) + continue + + if ctypes.windll.user32.GetAsyncKeyState(KEY_SPACE) & 0x8000: + is_jumping = self.perform_jump(is_jumping) + + sleep(MAIN_LOOP_SLEEP) + except KeyboardInterrupt: + logger.debug("Bunnyhop stopped by user.") + self.stop() + except Exception as e: + logger.error(f"Unexpected error in main loop: {e}", exc_info=True) + sleep(MAIN_LOOP_SLEEP) + + def stop(self) -> None: + """Stop the Bunnyhop and clean up resources.""" + self.is_running = False + self.stop_event.set() + logger.debug("Bunnyhop stopped.") \ No newline at end of file diff --git a/classes/config_manager.py b/classes/config_manager.py index 2a453fe..b82f7ea 100644 --- a/classes/config_manager.py +++ b/classes/config_manager.py @@ -1,5 +1,6 @@ -import os, zlib, base64, orjson +import os, orjson from pathlib import Path +from pyMeow import get_color, fade_color from classes.logger import Logger @@ -13,7 +14,7 @@ class ConfigManager: with caching for efficiency and default configuration management. """ # Application version - VERSION = "v1.2.5" + VERSION = "v1.2.5.1" # Directory where the update files are stored UPDATE_DIRECTORY = os.path.expanduser(r'~\AppData\Local\Requests\ItsJesewe\Update') # Directory where the configuration file is stored @@ -21,15 +22,36 @@ class ConfigManager: # Full path to the configuration file CONFIG_FILE = Path(CONFIG_DIRECTORY) / 'config.json' - # Default configuration settings + # Default configuration settings with General, Trigger, and Overlay categories DEFAULT_CONFIG = { - "Settings": { - "TriggerKey": "x", # Default trigger key - "ToggleMode": False, # Whether to use toggle mode for the trigger - "ShotDelayMin": 0.01, # Minimum delay between shots in seconds - "ShotDelayMax": 0.03, # Maximum delay between shots in seconds - "AttackOnTeammates": False, # Whether to attack teammates - "PostShotDelay": 0.1 # Delay after each shot + "General": { + "Trigger": False, # Enable or disable the trigger feature + "Overlay": False, # Enable or disable the overlay feature + "Bunnyhop": False, # Enable or disable the bunnyhop feature + "Noflash": False # Enable or disable the noflash feature + }, + "Trigger": { + "TriggerKey": "x", # Key to activate the trigger + "ToggleMode": False, # Enable toggle mode for the trigger + "ShotDelayMin": 0.01, # Minimum delay between shots + "ShotDelayMax": 0.03, # Maximum delay between shots + "AttackOnTeammates": False, # Allow attacking teammates + "PostShotDelay": 0.1 # Delay after shooting before the next action + }, + "Overlay": { + "enable_box": True, # Enable or disable the bounding box overlay + "draw_snaplines": True, # Enable or disable snaplines in the overlay + "snaplines_color_hex": "#FFFFFF", # Color of the snaplines in hexadecimal format + "box_line_thickness": 1.0, # Thickness of the bounding box lines + "box_color_hex": "#FFA500", # Color of the bounding box in hexadecimal format + "text_color_hex": "#FFFFFF", # Color of the text in the overlay + "draw_health_numbers": True, # Enable or disable health numbers in the overlay + "use_transliteration": False, # Use transliteration for names in the overlay + "draw_nicknames": True, # Enable or disable drawing nicknames in the overlay + "draw_teammates": True, # Enable or disable drawing teammates in the overlay + "teammate_color_hex": "#00FFFF", # Color for teammates in the overlay + "enable_minimap": False, # Enable or disable the minimap overlay + "minimap_size": 200 # Size of the minimap in pixels } } @@ -50,7 +72,6 @@ def load_config(cls): # Ensure the configuration directory exists. os.makedirs(cls.CONFIG_DIRECTORY, exist_ok=True) - # Create the configuration file with default settings if it doesn't exist. if not Path(cls.CONFIG_FILE).exists(): logger.info("config.json not found at %s, creating a default configuration.", cls.CONFIG_FILE) cls.save_config(cls.DEFAULT_CONFIG, log_info=False) @@ -103,4 +124,26 @@ def save_config(cls, config: dict, log_info: bool = True): if log_info: logger.info("Saved configuration to %s.", cls.CONFIG_FILE) except IOError as e: - logger.exception("Failed to save configuration: %s", e) \ No newline at end of file + logger.exception("Failed to save configuration: %s", e) + +COLOR_CHOICES = { + "Orange": "#FFA500", + "Red": "#FF0000", + "Green": "#00FF00", + "Blue": "#0000FF", + "White": "#FFFFFF", + "Black": "#000000", + "Cyan": "#00FFFF", + "Yellow": "#FFFF00" +} + +class Colors: + orange = get_color("orange") + black = get_color("black") + cyan = get_color("cyan") + white = get_color("white") + grey = fade_color(get_color("#242625"), 0.7) + red = get_color("red") + green = get_color("green") + blue = get_color("blue") + yellow = get_color("yellow") \ No newline at end of file diff --git a/classes/esp.py b/classes/esp.py new file mode 100644 index 0000000..cb961d1 --- /dev/null +++ b/classes/esp.py @@ -0,0 +1,388 @@ +import threading +import time +import pyMeow as overlay +import keyboard +from typing import Iterator, Optional, Dict + +from pynput.keyboard import Listener as KeyboardListener +from classes.config_manager import ConfigManager, Colors, COLOR_CHOICES +from classes.memory_manager import MemoryManager +from classes.logger import Logger +from classes.utility import Utility + +# Initialize the logger for consistent logging +logger = Logger.get_logger() +# Define the main loop sleep time for reduced CPU usage +MAIN_LOOP_SLEEP = 0.05 +# Target FPS for the overlay rendering +TARGET_FPS = 60 +# Number of entities to iterate over +ENTITY_COUNT = 64 +# Size of each entity entry in memory +ENTITY_ENTRY_SIZE = 120 + +class Entity: + """Represents a game entity with cached data for efficient access.""" + def __init__(self, controller_ptr: int, pawn_ptr: int, memory_manager: MemoryManager) -> None: + self.controller_ptr = controller_ptr + self.pawn_ptr = pawn_ptr + self.memory_manager = memory_manager + self.pos2d: Optional[Dict[str, float]] = None + self.head_pos2d: Optional[Dict[str, float]] = None + self._cached_data = None + self._last_update = 0 + + def _update_cache(self) -> None: + """Update cached data with a time interval.""" + current_time = time.time() + if current_time - self._last_update >= 0.1: + try: + self._cached_data = { + "name": self.memory_manager.read_string(self.controller_ptr + self.memory_manager.m_iszPlayerName), + "health": self.memory_manager.read_int(self.pawn_ptr + self.memory_manager.m_iHealth), + "team": self.memory_manager.read_int(self.pawn_ptr + self.memory_manager.m_iTeamNum), + "pos": self.memory_manager.read_vec3(self.pawn_ptr + self.memory_manager.m_vOldOrigin), + "dormant": bool(self.memory_manager.read_int(self.pawn_ptr + self.memory_manager.m_bDormant)) + } + self._last_update = current_time + except Exception as e: + logger.error(f"Failed to update cache for entity: {e}") + self._cached_data = None + + @property + def name(self) -> str: + """Get the entity's name, optionally transliterated.""" + self._update_cache() + if self._cached_data and self._cached_data.get("name"): + return Utility.transliterate(self._cached_data["name"]) if self.memory_manager.config["Overlay"]["use_transliteration"] else self._cached_data["name"] + return "" + + @property + def health(self) -> int: + """Get the entity's health.""" + self._update_cache() + return self._cached_data["health"] if self._cached_data else 0 + + @property + def team(self) -> int: + """Get the entity's team number.""" + self._update_cache() + return self._cached_data["team"] if self._cached_data else -1 + + @property + def pos(self) -> Dict[str, float]: + """Get the entity's 3D position.""" + self._update_cache() + return self._cached_data["pos"] if self._cached_data else {"x": 0.0, "y": 0.0, "z": 0.0} + + @property + def dormant(self) -> bool: + """Check if the entity is dormant.""" + self._update_cache() + return self._cached_data["dormant"] if self._cached_data else True + + def bone_pos(self, bone: int) -> Dict[str, float]: + """Get the 3D position of a specific bone.""" + try: + game_scene = self.memory_manager.read_longlong(self.pawn_ptr + self.memory_manager.m_pGameSceneNode) + bone_array_ptr = self.memory_manager.read_longlong(game_scene + self.memory_manager.m_pBoneArray) + return self.memory_manager.read_vec3(bone_array_ptr + bone * 32) + except Exception as e: + logger.error(f"Failed to get bone position for bone {bone}: {e}") + return {"x": 0.0, "y": 0.0, "z": 0.0} + + @staticmethod + def validate_screen_position(pos: Dict[str, float]) -> bool: + """Validate if a screen position is within bounds.""" + screen_width = overlay.get_screen_width() + screen_height = overlay.get_screen_height() + return 0 <= pos["x"] <= screen_width and 0 <= pos["y"] <= screen_height + + def world_to_screen(self, view_matrix: list) -> bool: + """Convert world coordinates to screen coordinates.""" + try: + pos2d = overlay.world_to_screen(view_matrix, self.pos, 1) + head2d = overlay.world_to_screen(view_matrix, self.bone_pos(6), 1) + if not self.validate_screen_position(pos2d) or not self.validate_screen_position(head2d): + return False + self.pos2d = pos2d + self.head_pos2d = head2d + return True + except Exception as e: + return False + +class CS2Overlay: + """Manages the ESP overlay for Counter-Strike 2.""" + def __init__(self, memory_manager: MemoryManager) -> None: + """ + Initialize the Overlay with a shared MemoryManager instance. + """ + self.config = ConfigManager.load_config() + self.memory_manager = memory_manager + self.is_running = False + self.stop_event = threading.Event() + self.local_team = None + self.load_configuration() + + def load_configuration(self) -> None: + """Load and apply configuration settings.""" + settings = self.config['Overlay'] + self.enable_box = settings['enable_box'] + self.draw_snaplines = settings['draw_snaplines'] + self.snaplines_color_hex = settings['snaplines_color_hex'] + self.box_line_thickness = settings['box_line_thickness'] + self.box_color_hex = settings['box_color_hex'] + self.text_color_hex = settings['text_color_hex'] + self.draw_health_numbers = settings['draw_health_numbers'] + self.use_transliteration = settings['use_transliteration'] + self.draw_nicknames = settings['draw_nicknames'] + self.draw_teammates = settings['draw_teammates'] + self.teammate_color_hex = settings['teammate_color_hex'] + self.enable_minimap = settings['enable_minimap'] + self.minimap_size = settings['minimap_size'] + # Default minimap position + self.minimap_position = "top_left" # Options: top_left, top_right, bottom_left, bottom_right + + def update_config(self, config: dict) -> None: + """Update the configuration settings.""" + self.config = config + self.load_configuration() + logger.debug("Overlay configuration updated.") + + def iterate_entities(self) -> Iterator[Entity]: + """Iterate over game entities and yield Entity objects.""" + try: + ent_list_ptr = self.memory_manager.read_longlong(self.memory_manager.client_dll_base + self.memory_manager.dwEntityList) + local_controller_ptr = self.memory_manager.read_longlong(self.memory_manager.client_dll_base + self.memory_manager.dwLocalPlayerController) + except Exception as e: + logger.error(f"Error reading entity list or local controller pointer: {e}") + return iter([]) + + for i in range(1, ENTITY_COUNT + 1): + try: + list_index = (i & 0x7FFF) >> 9 + entity_index = i & 0x1FF + entry_ptr = self.memory_manager.read_longlong(ent_list_ptr + (8 * list_index) + 16) + if not entry_ptr: + continue + + controller_ptr = self.memory_manager.read_longlong(entry_ptr + ENTITY_ENTRY_SIZE * entity_index) + if not controller_ptr or controller_ptr == local_controller_ptr: + continue + + controller_pawn_ptr = self.memory_manager.read_longlong(controller_ptr + self.memory_manager.m_hPlayerPawn) + if not controller_pawn_ptr: + continue + + list_entry_ptr = self.memory_manager.read_longlong(ent_list_ptr + 8 * ((controller_pawn_ptr & 0x7FFF) >> 9) + 16) + if not list_entry_ptr: + continue + + pawn_ptr = self.memory_manager.read_longlong(list_entry_ptr + ENTITY_ENTRY_SIZE * (controller_pawn_ptr & 0x1FF)) + if not pawn_ptr: + continue + except Exception as e: + logger.error(f"Error iterating entity {i}: {e}") + continue + + yield Entity(controller_ptr, pawn_ptr, self.memory_manager) + + def draw_entity(self, entity: Entity, view_matrix: list, is_teammate: bool = False) -> None: + """Render the ESP overlay for a given entity.""" + try: + if not entity.world_to_screen(view_matrix): + return + if entity.health <= 0 or entity.dormant: + return + + head_y = entity.head_pos2d["y"] + pos_y = entity.pos2d["y"] + box_height = pos_y - head_y + box_width = box_height / 2 + half_width = box_width / 2 + + outline_color = overlay.get_color(self.teammate_color_hex if is_teammate else self.box_color_hex) + text_color = overlay.get_color(self.text_color_hex) + + if self.draw_snaplines: + screen_width = overlay.get_screen_width() + screen_height = overlay.get_screen_height() + overlay.draw_line( + screen_width / 2, + screen_height / 2, + entity.head_pos2d["x"], + entity.head_pos2d["y"], + overlay.get_color(self.snaplines_color_hex), + 2 + ) + + if self.enable_box: + overlay.draw_rectangle( + entity.head_pos2d["x"] - half_width, + entity.head_pos2d["y"] - half_width / 2, + box_width, + box_height + half_width / 2, + Colors.grey + ) + overlay.draw_rectangle_lines( + entity.head_pos2d["x"] - half_width, + entity.head_pos2d["y"] - half_width / 2, + box_width, + box_height + half_width / 2, + outline_color, + self.box_line_thickness + ) + + if self.draw_nicknames: + nickname = entity.name + nickname_font_size = 11 + nickname_width = overlay.measure_text(nickname, nickname_font_size) + overlay.draw_text( + nickname, + entity.head_pos2d["x"] - nickname_width // 2, + entity.head_pos2d["y"] - half_width / 2 - 15, + nickname_font_size, + text_color + ) + + bar_width = 4 + bar_margin = 2 + bar_x = entity.head_pos2d["x"] - half_width - bar_width - bar_margin + bar_y = entity.head_pos2d["y"] - half_width / 2 + bar_height = box_height + half_width / 2 + overlay.draw_rectangle( + bar_x, + bar_y, + bar_width, + bar_height, + overlay.get_color("black") + ) + health_percent = max(0, min(entity.health, 100)) + fill_height = (health_percent / 100.0) * bar_height + if health_percent <= 20: + fill_color = overlay.get_color("red") + elif health_percent <= 50: + fill_color = overlay.get_color("yellow") + else: + fill_color = overlay.get_color("green") + fill_y = bar_y + (bar_height - fill_height) + overlay.draw_rectangle( + bar_x, + fill_y, + bar_width, + fill_height, + fill_color + ) + if self.draw_health_numbers: + health_text = f"{entity.health}" + overlay.draw_text( + health_text, + int(bar_x - 25), + int(bar_y + bar_height / 2 - 5), + 10, + text_color + ) + except Exception as e: + logger.error(f"Error drawing entity: {e}") + + def draw_minimap(self, entities: list[Entity], view_matrix: list) -> None: + """Render the minimap overlay.""" + if not self.enable_minimap: + return + + map_min = {"x": -4000, "y": -4000} + map_max = {"x": 4000, "y": 4000} + map_size = {"x": map_max["x"] - map_min["x"], "y": map_max["y"] - map_min["y"]} + + minimap_size = self.minimap_size + screen_width = overlay.get_screen_width() + screen_height = overlay.get_screen_height() + + positions = { + "top_left": (10, 10), + "top_right": (screen_width - minimap_size - 10, 10), + "bottom_left": (10, screen_height - minimap_size - 10), + "bottom_right": (screen_width - minimap_size - 10, screen_height - minimap_size - 10) + } + minimap_x, minimap_y = positions[self.minimap_position] + + overlay.draw_rectangle(minimap_x, minimap_y, minimap_size, minimap_size, Colors.grey) + overlay.draw_rectangle_lines(minimap_x, minimap_y, minimap_size, minimap_size, Colors.black, 2) + + for entity in entities: + if entity.health <= 0 or entity.dormant: + continue + pos = entity.pos + map_x = ((pos["x"] - map_min["x"]) / map_size["x"]) * minimap_size + minimap_x + map_y = ((map_max["y"] - pos["y"]) / map_size["y"]) * minimap_size + minimap_y + color = Colors.cyan if entity.team == self.local_team else Colors.red + overlay.draw_circle(map_x, map_y, 3, color) + + def start(self) -> None: + """Start the Overlay.""" + self.is_running = True + self.stop_event.clear() + + try: + overlay.overlay_init("Counter-Strike 2", fps=TARGET_FPS) + except Exception as e: + logger.error(f"Overlay initialization error: {e}") + self.is_running = False + return + + frame_time = 1.0 / TARGET_FPS + is_game_active = Utility.is_game_active + sleep = time.sleep + + while not self.stop_event.is_set(): + try: + # Check if game window is active + if not is_game_active(): + sleep(MAIN_LOOP_SLEEP) + continue + + start_time = time.time() + view_matrix = self.memory_manager.read_floats(self.memory_manager.client_dll_base + self.memory_manager.dwViewMatrix, 16) + + local_controller_ptr = self.memory_manager.read_longlong(self.memory_manager.client_dll_base + self.memory_manager.dwLocalPlayerController) + if local_controller_ptr: + local_pawn_handle = self.memory_manager.read_longlong(local_controller_ptr + self.memory_manager.m_hPlayerPawn) + local_pawn_ptr = self.memory_manager.read_longlong(self.memory_manager.client_dll_base + self.memory_manager.dwLocalPlayerPawn) + self.local_team = self.memory_manager.read_int(local_pawn_ptr + self.memory_manager.m_iTeamNum) + + entities = list(self.iterate_entities()) + if overlay.overlay_loop(): + overlay.begin_drawing() + overlay.draw_fps(0, 0) + self.draw_minimap(entities, view_matrix) + for entity in entities: + is_teammate = False + if self.local_team is not None and entity.team == self.local_team: + if not self.draw_teammates: + continue + is_teammate = True + self.draw_entity(entity, view_matrix, is_teammate) + overlay.end_drawing() + + elapsed_time = time.time() - start_time + if elapsed_time < frame_time: + sleep(frame_time - elapsed_time) + except KeyboardInterrupt: + logger.debug("Overlay stopped by user.") + self.stop() + except Exception as e: + logger.error(f"Unexpected error in main loop: {e}", exc_info=True) + sleep(MAIN_LOOP_SLEEP) + + overlay.overlay_close() + logger.debug("Overlay loop ended.") + + def stop(self) -> None: + """Stop the Overlay and clean up resources.""" + self.is_running = False + self.stop_event.set() + time.sleep(0.1) + try: + logger.debug("Overlay stopped.") + except Exception as e: + logger.error(f"Error stopping Overlay: {e}") \ No newline at end of file diff --git a/classes/file_watcher.py b/classes/file_watcher.py index a62f68f..1dc5e29 100644 --- a/classes/file_watcher.py +++ b/classes/file_watcher.py @@ -1,4 +1,5 @@ -import os, threading +import os +import threading from watchdog.events import FileSystemEventHandler from classes.config_manager import ConfigManager @@ -10,14 +11,18 @@ class ConfigFileChangeHandler(FileSystemEventHandler): """ A file system event handler for monitoring changes to the configuration file. - Automatically updates the bot's configuration when the configuration file is modified. + Automatically updates the configuration for all features when the config file is modified. """ - def __init__(self, bot, debounce_interval=1.0): + def __init__(self, main_window, debounce_interval=1.0): """ - Initializes the file change handler with a reference to the bot instance. + Initializes the file change handler with a reference to the main window instance. Caches the configuration file path for efficiency. + + Args: + main_window: Instance of MainWindow managing all features. + debounce_interval: Time in seconds to debounce file change events. """ - self.bot = bot + self.main_window = main_window self.debounce_interval = debounce_interval self.debounce_timer = None self.config_path = ConfigManager.CONFIG_FILE @@ -31,12 +36,18 @@ def on_modified(self, event): self.debounce_timer.start() def reload_config(self): - """Reloads the configuration file and updates the bot's configuration.""" + """Reloads the configuration file and updates all feature configurations.""" try: # Reload the updated configuration file new_config = ConfigManager.load_config() - # Update the bot's configuration with the new settings - self.bot.update_config(new_config) + + # Update configurations for all features + self.main_window.triggerbot.config = new_config + self.main_window.overlay.config = new_config + self.main_window.bunnyhop.config = new_config + self.main_window.noflash.config = new_config + + # Update UI in the main thread to reflect new configuration + self.main_window.root.after(0, self.main_window.update_ui_from_config) except Exception as e: - # Log an error if the configuration update fails - logger.exception("Failed to reload configuration from %s", self.config_path) \ No newline at end of file + logger.exception("Failed to reload configuration from %s: %s", self.config_path, e) \ No newline at end of file diff --git a/classes/logger.py b/classes/logger.py index 04d1b89..456b292 100644 --- a/classes/logger.py +++ b/classes/logger.py @@ -1,17 +1,18 @@ import os import logging +import traceback class Logger: """ A class to handle logging for the application. - It sets up logging to a file, a detailed log file, and the console. + It sets up logging to a file, a detailed log file, and the console with enhanced error details. """ # Define the directory where logs will be stored. LOG_DIRECTORY = os.path.expanduser(r'~\AppData\Local\Requests\ItsJesewe\crashes') # Define the full path for the log file within the LOG_DIRECTORY. - LOG_FILE = os.path.join(LOG_DIRECTORY, 'tb_logs.log') + LOG_FILE = os.path.join(LOG_DIRECTORY, 'vw_logs.log') # Define the full path for the detailed log file within the LOG_DIRECTORY. - DETAILED_LOG_FILE = os.path.join(LOG_DIRECTORY, 'tb_detailed_logs.log') + DETAILED_LOG_FILE = os.path.join(LOG_DIRECTORY, 'vw_detailed_logs.log') # Cache for the logger instance. _logger = None @@ -48,7 +49,7 @@ def setup_logging(): datefmt='%Y-%m-%d %H:%M:%S' ) - # File handler for tb_logs.log with error handling + # File handler for vw_logs.log with error handling try: file_handler = logging.FileHandler(Logger.LOG_FILE, mode='w', encoding='utf-8') file_handler.setLevel(logging.INFO) @@ -63,9 +64,9 @@ def setup_logging(): stream_handler.setFormatter(standard_formatter) root_logger.addHandler(stream_handler) - # Detailed formatter and handler + # Detailed formatter with enhanced error context detailed_formatter = logging.Formatter( - fmt='[%(asctime)s.%(msecs)03d %(levelname)-8s] {%(name)s:%(module)s:%(funcName)s:%(lineno)d} [PID:%(process)d TID:%(thread)d]: %(message)s', + fmt='[%(asctime)s.%(msecs)03d %(levelname)-8s] {%(name)s:%(module)s:%(funcName)s:%(lineno)d} [PID:%(process)d TID:%(thread)d] %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) try: @@ -82,11 +83,17 @@ def setup_logging(): @staticmethod def get_logger(): + """Returns the cached logger instance, creating it if necessary.""" if Logger._logger is None: Logger._logger = logging.getLogger(__name__) return Logger._logger @staticmethod - def log_exception(exc: Exception): + def log_exception(exc: Exception, context: str = None): + """Logs an exception with detailed information including stack trace and optional context.""" logger_instance = Logger.get_logger() - logger_instance.error("An exception occurred", exc_info=True) \ No newline at end of file + # Format the stack trace and exception details + exc_details = f"\nException Type: {type(exc).__name__}\nException Message: {str(exc)}\nStack Trace:\n{''.join(traceback.format_tb(exc.__traceback__))}" + if context: + exc_details = f"Context: {context}\n{exc_details}" + logger_instance.error(f"An exception occurred: {exc_details}", extra={'exc_info': ''}) \ No newline at end of file diff --git a/classes/memory_manager.py b/classes/memory_manager.py index 88fd53c..27465cf 100644 --- a/classes/memory_manager.py +++ b/classes/memory_manager.py @@ -1,5 +1,7 @@ import pymem import pymem.process +import struct + from classes.logger import Logger from classes.utility import Utility @@ -7,16 +9,31 @@ logger = Logger.get_logger() class MemoryManager: - def __init__(self, offsets: dict, client_data: dict) -> None: - self.offsets, self.client_data = offsets, client_data - self.pm, self.client_base = None, None + def __init__(self, offsets: dict, client_data: dict, buttons_data: dict) -> None: + """Initialize MemoryManager with offsets and client data.""" + self.offsets = offsets + self.client_data = client_data + self.buttons_data = buttons_data + self.pm = None + self.client_base = None self.ent_list = None # Cache for entity list pointer - # Offset attributes will be set by load_offsets + self.config = None # Configuration cache + # Offset attributes self.dwEntityList = None self.dwLocalPlayerPawn = None + self.dwLocalPlayerController = None + self.dwViewMatrix = None self.m_iHealth = None self.m_iTeamNum = None self.m_iIDEntIndex = None + self.m_iszPlayerName = None + self.m_vOldOrigin = None + self.m_pGameSceneNode = None + self.m_bDormant = None + self.m_hPlayerPawn = None + self.m_flFlashDuration = None + self.m_pBoneArray = None + self.dwForceJump = None def initialize(self) -> bool: """ @@ -30,7 +47,7 @@ def initialize(self) -> bool: self.load_offsets() if self.dwEntityList is None: # Ensure offsets were loaded successfully return False - self.ent_list = self.pm.read_longlong(self.client_base + self.dwEntityList) + self.ent_list = self.read_longlong(self.client_base + self.dwEntityList) return True def initialize_pymem(self) -> bool: @@ -38,7 +55,7 @@ def initialize_pymem(self) -> bool: try: # Attempt to attach to the cs2.exe process self.pm = pymem.Pymem("cs2.exe") - logger.info("Successfully attached to cs2.exe process.") + logger.debug("Successfully attached to cs2.exe process.") return True except pymem.exception.ProcessNotFound: # Log an error if the process is not found @@ -55,7 +72,7 @@ def get_client_module(self) -> bool: # Attempt to retrieve the client.dll module client_module = pymem.process.module_from_name(self.pm.process_handle, "client.dll") self.client_base = client_module.lpBaseOfDll - logger.info("client.dll module found and base address retrieved.") + logger.debug("client.dll module found and base address retrieved.") return True except pymem.exception.ModuleNotFoundError: # Log an error if the module is not found @@ -68,14 +85,23 @@ def get_client_module(self) -> bool: def load_offsets(self) -> None: """Load memory offsets from Utility.extract_offsets.""" - extracted = Utility.extract_offsets(self.offsets, self.client_data) + extracted = Utility.extract_offsets(self.offsets, self.client_data, self.buttons_data) if extracted: self.dwEntityList = extracted["dwEntityList"] self.dwLocalPlayerPawn = extracted["dwLocalPlayerPawn"] + self.dwLocalPlayerController = extracted["dwLocalPlayerController"] + self.dwViewMatrix = extracted["dwViewMatrix"] + self.dwForceJump = extracted["dwForceJump"] self.m_iHealth = extracted["m_iHealth"] self.m_iTeamNum = extracted["m_iTeamNum"] self.m_iIDEntIndex = extracted["m_iIDEntIndex"] - logger.info("Offsets initialized successfully.") + self.m_iszPlayerName = extracted["m_iszPlayerName"] + self.m_vOldOrigin = extracted["m_vOldOrigin"] + self.m_pGameSceneNode = extracted["m_pGameSceneNode"] + self.m_bDormant = extracted["m_bDormant"] + self.m_hPlayerPawn = extracted["m_hPlayerPawn"] + self.m_flFlashDuration = extracted["m_flFlashDuration"] + self.m_pBoneArray = extracted["m_pBoneArray"] else: logger.error("Failed to initialize offsets from extracted data.") @@ -84,9 +110,9 @@ def get_entity(self, index: int): try: # Use cached entity list pointer list_offset = 0x8 * (index >> 9) - ent_entry = self.pm.read_longlong(self.ent_list + list_offset + 0x10) + ent_entry = self.read_longlong(self.ent_list + list_offset + 0x10) entity_offset = 120 * (index & 0x1FF) - return self.pm.read_longlong(ent_entry + entity_offset) + return self.read_longlong(ent_entry + entity_offset) except Exception as e: logger.error(f"Error reading entity: {e}") return None @@ -95,16 +121,16 @@ def get_fire_logic_data(self) -> dict | None: """Retrieve data necessary for firing logic.""" try: # Read the local player and entity ID - player = self.pm.read_longlong(self.client_base + self.dwLocalPlayerPawn) - entity_id = self.pm.read_int(player + self.m_iIDEntIndex) + player = self.read_longlong(self.client_base + self.dwLocalPlayerPawn) + entity_id = self.read_int(player + self.m_iIDEntIndex) if entity_id > 0: # Retrieve the entity, team, and health entity = self.get_entity(entity_id) if entity: - entity_team = self.pm.read_int(entity + self.m_iTeamNum) - player_team = self.pm.read_int(player + self.m_iTeamNum) - entity_health = self.pm.read_int(entity + self.m_iHealth) + entity_team = self.read_int(entity + self.m_iTeamNum) + player_team = self.read_int(player + self.m_iTeamNum) + entity_health = self.read_int(entity + self.m_iHealth) return { "entity_team": entity_team, "player_team": player_team, @@ -117,4 +143,80 @@ def get_fire_logic_data(self) -> dict | None: logger.error("Game was updated, new offsets are required. Please wait for the offsets update.") else: logger.error(f"Error in fire logic: {e}") - return None \ No newline at end of file + return None + + def write_float(self, address: int, value: float) -> None: + """Write a float to memory.""" + try: + self.pm.write_float(address, value) + logger.debug(f"Wrote float {value} to address {hex(address)}") + except Exception as e: + logger.error(f"Failed to write float at address {hex(address)}: {e}") + raise + + def write_int(self, address: int, value: int) -> None: + """Write an integer to memory.""" + try: + self.pm.write_int(address, value) + logger.debug(f"Wrote int {value} to address {hex(address)}") + except Exception as e: + logger.error(f"Failed to write int at address {hex(address)}: {e}") + raise + + def read_vec3(self, address: int) -> dict | None: + """ + Reads a 3D vector (three floats) from memory at the specified address. + """ + try: + return { + "x": self.pm.read_float(address), + "y": self.pm.read_float(address + 4), + "z": self.pm.read_float(address + 8) + } + except Exception as e: + logger.error(f"Failed to read vec3 at address {hex(address)}: {e}") + return {"x": 0.0, "y": 0.0, "z": 0.0} + + def read_string(self, address: int, max_length: int = 256) -> str: + """ + Reads a null-terminated string from memory at the specified address. + """ + try: + data = self.pm.read_bytes(address, max_length) + string_data = data.split(b'\x00')[0] + return string_data.decode('utf-8', errors='replace') + except Exception as e: + logger.error(f"Failed to read string at address {hex(address)}: {e}") + return "" + + def read_floats(self, address: int, count: int) -> list[float]: + """ + Reads an array of `count` floats from memory. + """ + try: + data = self.pm.read_bytes(address, count * 4) + return list(struct.unpack(f'{count}f', data)) + except Exception as e: + logger.error(f"Failed to read {count} floats at address {hex(address)}: {e}") + return [] + + def read_int(self, address: int) -> int: + """Read an integer from memory.""" + try: + return self.pm.read_int(address) + except Exception as e: + logger.error(f"Failed to read int at address {hex(address)}: {e}") + return 0 + + def read_longlong(self, address: int) -> int: + """Read a long long from memory.""" + try: + return self.pm.read_longlong(address) + except Exception as e: + logger.error(f"Failed to read longlong at address {hex(address)}: {e}") + return 0 + + @property + def client_dll_base(self) -> int: + """Get the base address of client.dll.""" + return self.client_base \ No newline at end of file diff --git a/classes/noflash.py b/classes/noflash.py new file mode 100644 index 0000000..03e7533 --- /dev/null +++ b/classes/noflash.py @@ -0,0 +1,85 @@ +import threading +import time +from typing import Optional + +from classes.config_manager import ConfigManager +from classes.memory_manager import MemoryManager +from classes.logger import Logger +from classes.utility import Utility + +# Initialize the logger for consistent logging +logger = Logger.get_logger() +# Define the main loop sleep time for NoFlash +NOFLASH_LOOP_SLEEP = 0.1 + +class CS2NoFlash: + """Manages the NoFlash functionality for Counter-Strike 2.""" + def __init__(self, memory_manager: MemoryManager) -> None: + """ + Initialize the NoFlash with a shared MemoryManager instance. + """ + # Load the configuration settings + self.config = ConfigManager.load_config() + self.memory_manager = memory_manager + self.is_running = False + self.stop_event = threading.Event() + self.local_player_address: Optional[int] = None + + def update_config(self, config): + """Update the configuration settings.""" + self.config = config + logger.debug("NoFlash configuration updated.") + + def initialize_local_player(self) -> bool: + """Initialize the local player address.""" + if self.memory_manager.dwLocalPlayerPawn is None or self.memory_manager.m_flFlashDuration is None: + logger.error("dwLocalPlayerPawn or m_flFlashDuration offset not initialized.") + return False + try: + self.local_player_address = self.memory_manager.client_base + self.memory_manager.dwLocalPlayerPawn + return True + except Exception as e: + logger.error(f"Error setting local player address: {e}") + return False + + def disable_flash(self) -> None: + """Set the flash duration to 0.0.""" + try: + player_position = self.memory_manager.read_longlong(self.local_player_address) + if player_position: + self.memory_manager.write_float(player_position + self.memory_manager.m_flFlashDuration, 0.0) + except Exception as e: + logger.error(f"Error disabling flash: {e}") + + def start(self) -> None: + """Start the NoFlash.""" + if not self.initialize_local_player(): + logger.error("Failed to initialize local player address.") + return + + self.is_running = True + + is_game_active = Utility.is_game_active + sleep = time.sleep + + while not self.stop_event.is_set(): + try: + if not is_game_active(): + sleep(NOFLASH_LOOP_SLEEP) + continue + + self.disable_flash() + sleep(NOFLASH_LOOP_SLEEP) + except KeyboardInterrupt: + logger.debug("NoFlash stopped by user.") + self.stop() + except Exception as e: + logger.error(f"Unexpected error in main loop: {e}", exc_info=True) + sleep(NOFLASH_LOOP_SLEEP) + + def stop(self) -> None: + """Stop the NoFlash and clean up resources.""" + self.is_running = False + self.stop_event.set() + + logger.debug("NoFlash stopped.") \ No newline at end of file diff --git a/classes/trigger_bot.py b/classes/trigger_bot.py index 4b97e3f..1b424d3 100644 --- a/classes/trigger_bot.py +++ b/classes/trigger_bot.py @@ -16,13 +16,13 @@ MAIN_LOOP_SLEEP = 0.05 class CS2TriggerBot: - def __init__(self, offsets: dict, client_data: dict) -> None: + def __init__(self, memory_manager: MemoryManager) -> None: """ - Initialize the TriggerBot with offsets, configuration, and client data. + Initialize the TriggerBot with a shared MemoryManager instance. """ # Load the configuration settings self.config = ConfigManager.load_config() - self.memory_manager = MemoryManager(offsets, client_data) + self.memory_manager = memory_manager self.is_running, self.stop_event = False, threading.Event() self.trigger_active = False self.toggle_state = False @@ -39,7 +39,7 @@ def __init__(self, offsets: dict, client_data: dict) -> None: def load_configuration(self) -> None: """Load and apply configuration settings.""" - settings = self.config['Settings'] + settings = self.config['Trigger'] self.trigger_key = settings['TriggerKey'] self.toggle_mode = settings['ToggleMode'] self.shot_delay_min = settings['ShotDelayMin'] @@ -61,6 +61,7 @@ def update_config(self, config): """Update the configuration settings.""" self.config = config self.load_configuration() + logger.debug("TriggerBot configuration updated.") def play_toggle_sound(self, state: bool) -> None: """Play a sound when the toggle key is pressed.""" @@ -116,11 +117,8 @@ def should_trigger(self, entity_team: int, player_team: int, entity_health: int) def start(self) -> None: """Start the TriggerBot.""" - if not self.memory_manager.initialize(): - return # Set the running flag to True and log that the TriggerBot has started self.is_running = True - logger.info("TriggerBot started.") # Define local variables for utility functions is_game_active = Utility.is_game_active @@ -156,23 +154,21 @@ def start(self) -> None: sleep(MAIN_LOOP_SLEEP) except KeyboardInterrupt: - logger.info("TriggerBot stopped by user.") + logger.debug("TriggerBot stopped by user.") self.stop() except Exception as e: logger.error(f"Unexpected error in main loop: {e}", exc_info=True) def stop(self) -> None: - """ - Stops the TriggerBot and cleans up resources. - Also stops the keyboard and mouse listeners. - """ - # Set the running flag to False and signal the stop event + """Stops the TriggerBot and cleans up resources.""" self.is_running = False self.stop_event.set() - # Stop the keyboard and mouse listeners - if self.keyboard_listener.running: - self.keyboard_listener.stop() - if self.mouse_listener.running: - self.mouse_listener.stop() - # Log that the TriggerBot has stopped - logger.info(f"TriggerBot stopped.") \ No newline at end of file + time.sleep(0.1) + try: + if self.keyboard_listener.running: + self.keyboard_listener.stop() + if self.mouse_listener.running: + self.mouse_listener.stop() + logger.debug(f"TriggerBot stopped.") + except Exception as e: + logger.error(f"Error stopping TriggerBot: {e}") \ No newline at end of file diff --git a/classes/utility.py b/classes/utility.py index a10db4d..538b6b2 100644 --- a/classes/utility.py +++ b/classes/utility.py @@ -8,6 +8,7 @@ from packaging import version from dateutil.parser import parse as parse_date +from classes.config_manager import COLOR_CHOICES from classes.logger import Logger # Initialize the logger for consistent logging @@ -35,6 +36,12 @@ def fetch_offsets(): ) response_client = requests.get(client_dll_url) + buttons_url = os.getenv( + 'BUTTONS_URL', + 'https://raw.githubusercontent.com/a2x/cs2-dumper/refs/heads/main/output/buttons.json' + ) + response_buttons = requests.get(buttons_url) + if response_offset.status_code != 200: logger.error("Failed to fetch offsets: offsets.json request failed.") return None, None @@ -42,11 +49,16 @@ def fetch_offsets(): if response_client.status_code != 200: logger.error("Failed to fetch offsets: client_dll.json request failed.") return None, None + + if response_buttons.status_code != 200: + logger.error("Failed to fetch buttons: buttons.json request failed.") + return None, None try: offset = orjson.loads(response_offset.content) client = orjson.loads(response_client.content) - return offset, client + buttons = orjson.loads(response_buttons.content) + return offset, client, buttons except orjson.JSONDecodeError as e: logger.error(f"Failed to decode JSON response: {e}") return None, None @@ -60,20 +72,20 @@ def fetch_offsets(): @staticmethod def check_for_updates(current_version): - """Checks GitHub for the latest version and returns the download URL of 'CS2.Triggerbot.exe' if an update is available.""" + """Checks GitHub for the latest version and returns the download URL of 'VioletWing.exe' if an update is available.""" try: - response = requests.get("https://api.github.com/repos/Jesewe/cs2-triggerbot/releases/latest") + response = requests.get("https://api.github.com/repos/Jesewe/VioletWing/releases/latest") response.raise_for_status() data = orjson.loads(response.content) latest_version = data.get("tag_name") if version.parse(latest_version) > version.parse(current_version): for asset in data.get("assets", []): - if asset.get("name") == "CS2.Triggerbot.exe": + if asset.get("name") == "VioletWing.exe": download_url = asset.get("browser_download_url") if download_url: logger.info(f"New version available: {latest_version}.") return download_url - logger.warning("No 'CS2.Triggerbot.exe' found in the latest release assets.") + logger.warning("No 'VioletWing.exe' found in the latest release assets.") return None logger.info("No new updates available.") return None @@ -107,24 +119,93 @@ def is_game_running(): return any(proc.info['name'] == 'cs2.exe' for proc in psutil.process_iter(attrs=['name'])) @staticmethod - def extract_offsets(offsets: dict, client_data: dict) -> dict | None: - """Load memory offsets.""" + def extract_offsets(offsets: dict, client_data: dict, buttons_data: dict) -> dict | None: + """Load memory offsets for game functionality.""" try: client = offsets["client.dll"] dwEntityList = client["dwEntityList"] dwLocalPlayerPawn = client["dwLocalPlayerPawn"] + dwLocalPlayerController = client["dwLocalPlayerController"] + dwViewMatrix = client["dwViewMatrix"] + dwForceJump = buttons_data["client.dll"]["jump"] classes = client_data["client.dll"]["classes"] m_iHealth = classes["C_BaseEntity"]["fields"]["m_iHealth"] m_iTeamNum = classes["C_BaseEntity"]["fields"]["m_iTeamNum"] m_iIDEntIndex = classes["C_CSPlayerPawnBase"]["fields"]["m_iIDEntIndex"] + m_iszPlayerName = classes["CBasePlayerController"]["fields"]["m_iszPlayerName"] + m_vOldOrigin = classes["C_BasePlayerPawn"]["fields"]["m_vOldOrigin"] + m_pGameSceneNode = classes["C_BaseEntity"]["fields"]["m_pGameSceneNode"] + m_bDormant = classes["CGameSceneNode"]["fields"]["m_bDormant"] + m_hPlayerPawn = classes["CCSPlayerController"]["fields"]["m_hPlayerPawn"] + m_flFlashDuration = classes["C_CSPlayerPawnBase"]["fields"]["m_flFlashDuration"] + m_pBoneArray = 496 # Default value + return { "dwEntityList": dwEntityList, "dwLocalPlayerPawn": dwLocalPlayerPawn, + "dwLocalPlayerController": dwLocalPlayerController, + "dwViewMatrix": dwViewMatrix, + "dwForceJump": dwForceJump, "m_iHealth": m_iHealth, "m_iTeamNum": m_iTeamNum, - "m_iIDEntIndex": m_iIDEntIndex + "m_iIDEntIndex": m_iIDEntIndex, + "m_iszPlayerName": m_iszPlayerName, + "m_vOldOrigin": m_vOldOrigin, + "m_pGameSceneNode": m_pGameSceneNode, + "m_bDormant": m_bDormant, + "m_hPlayerPawn": m_hPlayerPawn, + "m_flFlashDuration": m_flFlashDuration, + "m_pBoneArray": m_pBoneArray } except KeyError as e: logger.error(f"Offset initialization error: Missing key {e}") - return None \ No newline at end of file + return None + + @staticmethod + def get_color_name_from_hex(hex_color: str) -> str: + """Get color name from hex value.""" + for name, hex_code in COLOR_CHOICES.items(): + if hex_code == hex_color: + return name + return "Black" + + @staticmethod + def transliterate(text: str) -> str: + """Converts Cyrillic characters in the given text to their Latin equivalents.""" + mapping = { + 'А': 'A', 'а': 'a', + 'Б': 'B', 'б': 'b', + 'В': 'V', 'в': 'v', + 'Г': 'G', 'г': 'g', + 'Д': 'D', 'д': 'd', + 'Е': 'E', 'е': 'e', + 'Ё': 'Yo', 'ё': 'yo', + 'Ж': 'Zh', 'ж': 'zh', + 'З': 'Z', 'з': 'z', + 'И': 'I', 'и': 'i', + 'Й': 'I', 'й': 'i', + 'К': 'K', 'к': 'k', + 'Л': 'L', 'л': 'l', + 'М': 'M', 'м': 'm', + 'Н': 'N', 'н': 'n', + 'О': 'O', 'о': 'o', + 'П': 'P', 'п': 'p', + 'Р': 'R', 'р': 'r', + 'С': 'S', 'с': 's', + 'Т': 'T', 'т': 't', + 'У': 'U', 'у': 'u', + 'Ф': 'F', 'ф': 'f', + 'Х': 'Kh', 'х': 'kh', + 'Ц': 'Ts', 'ц': 'ts', + 'Ч': 'Ch', 'ч': 'ch', + 'Ш': 'Sh', 'ш': 'sh', + 'Щ': 'Shch', 'щ': 'shch', + 'Ъ': '', 'ъ': '', + 'Ы': 'Y', 'ы': 'y', + 'Ь': '', 'ь': '', + 'Э': 'E', 'э': 'e', + 'Ю': 'Yu', 'ю': 'yu', + 'Я': 'Ya', 'я': 'ya' + } + return "".join(mapping.get(char, char) for char in text) \ No newline at end of file diff --git a/gui/faq_tab.py b/gui/faq_tab.py index 65b1ad9..19dd396 100644 --- a/gui/faq_tab.py +++ b/gui/faq_tab.py @@ -24,7 +24,7 @@ def populate_faq(main_window, frame): # Subtitle providing context ctk.CTkLabel( title_frame, - text="Find answers to common questions about TriggerBot usage and configuration", + text="Find answers to common questions about TriggerBot, Overlay, Bunnyhop, and NoFlash usage and configuration", font=("Gambetta", 16), text_color=("#6b7280", "#9ca3af") ).pack(anchor="w", pady=(8, 0)) @@ -32,14 +32,21 @@ def populate_faq(main_window, frame): # List of FAQ items faqs = [ ("What is a TriggerBot?", "A TriggerBot automatically shoots when your crosshair is positioned over an enemy player, providing enhanced reaction times in competitive gameplay. It works by detecting enemy pixels and triggering mouse clicks."), + ("What does the Overlay (ESP) feature do?", "The Overlay (ESP) feature displays visual information on the game screen, such as enemy bounding boxes, snaplines, health numbers, nicknames, and a minimap, helping you track opponents and teammates effectively."), + ("What is the Bunnyhop feature?", "The Bunnyhop feature automates the process of bunny hopping in Counter-Strike 2, allowing continuous jumping to maintain speed and improve movement control without manual input."), + ("What is the NoFlash feature?", "The NoFlash feature reduces or eliminates the effect of flashbangs in Counter-Strike 2, ensuring you maintain visibility and can continue playing effectively even when flashed."), ("Is this tool safe to use?", "This tool is provided for educational and research purposes only. Using automation tools in online games may violate terms of service and could result in account penalties. Always check game rules before use."), - ("How do I configure the trigger key?", "Navigate to Settings and enter your preferred key in the Trigger Key field. You can use keyboard keys (e.g., 'x', 'c', 'v') or mouse buttons (e.g., 'mouse4' for mouse button 4, 'mouse5' for mouse button 5)."), - ("What are the delay settings for?", "Delay settings give the bot a more natural feel by adding timing differences. You can set minimum and maximum delays to randomize how quickly it shoots. Plus, the Post Shot Delay adds a short pause after each shot, making it seem like a real person is reacting."), - ("Can I use this on FACEIT or ESEA?", "No, you can't use automation tools on anti-cheat platforms like FACEIT, ESEA, or VAC-secured servers. Doing so will get you a permanent ban. Stick to casual servers or practice offline instead."), - ("How do I update the offsets?", "When you start the app, it automatically gets the latest offsets from the server. You can see when it was last updated on the dashboard. If you want to refresh it manually, just go to Settings."), - ("Why isn't the bot triggering?", "Here are some usual problems: the trigger key might be set wrong, the game window isn't focused, you might not see the enemy in your crosshair, or your game settings might have changed. Take a look at your Settings to make sure everything's set up right."), - ("What should I do if the app crashes?", "First, try restarting the app. If it's still crashing, make sure you have the latest version, check that your system meets the requirements, and see if any antivirus is blocking the app."), - ("Is there a hotkey to toggle the bot on/off?", "Yes, you can set a toggle hotkey in Settings. This allows you to quickly enable/disable the triggerbot during gameplay without alt-tabbing to the application.") + ("How do I configure the trigger key?", "Navigate to the Trigger Settings tab and enter your preferred key in the Trigger Key field. You can use keyboard keys (e.g., 'x', 'c', 'v') or mouse buttons (e.g., 'mouse4' for mouse button 4, 'mouse5' for mouse button 5)."), + ("What are the delay settings for?", "Delay settings in the Trigger Settings tab give the TriggerBot a more natural feel by adding timing differences. You can set minimum and maximum delays to randomize how quickly it shoots, and the Post Shot Delay adds a pause after each shot to mimic human reaction times."), + ("How do I customize the Overlay (ESP) settings?", "In the Overlay Settings tab, you can enable or disable features like bounding boxes, snaplines, health numbers, nicknames, and the minimap. You can also adjust colors, line thickness, and minimap size to suit your preferences."), + ("Can I use these features on FACEIT or ESEA?", "No, automation tools like TriggerBot, Overlay, Bunnyhop, or NoFlash are not allowed on anti-cheat platforms like FACEIT, ESEA, or VAC-secured servers. Using them will likely result in a permanent ban. Stick to casual servers or offline practice."), + ("How do I update the offsets?", "The app automatically fetches the latest offsets from the server on startup. You can check the last update time on the Dashboard."), + ("Why isn't the TriggerBot triggering?", "Common issues include: incorrect trigger key configuration, the game window not being focused, the crosshair not being on an enemy, or changed game settings. Verify your settings in the Trigger Settings tab."), + ("Why isn't the Overlay (ESP) displaying?", "Ensure the Overlay feature is enabled in General Settings and that the game is running. Check the Overlay Settings tab to confirm visibility options (e.g., bounding boxes, snaplines) are enabled. Also, verify that your game is in a compatible mode (e.g., windowed or borderless)."), + ("Why doesn't Bunnyhop work consistently?", "Bunnyhop may fail if the game window is not focused, the Bunnyhop feature is disabled in General Settings, or your timing settings interfere. Check the General Settings tab and ensure consistent key inputs."), + ("Why is NoFlash not working?", "Ensure NoFlash is enabled in General Settings. It may not work if the game’s anti-cheat detects memory modifications or if the offsets are outdated. Restart the app to refresh offsets and verify game compatibility."), + ("What should I do if the app crashes?", "Try restarting the app, ensuring you have the latest version, and checking system requirements. Verify that no antivirus is blocking the app. Check the Logs tab for error details."), + ("Is there a hotkey to toggle features on/off?", "Yes, you can set a toggle hotkey for the TriggerBot in the Trigger Settings tab. Other features like Overlay, Bunnyhop, and NoFlash can be toggled via General Settings, but they don’t have individual hotkeys.") ] # Create FAQ cards diff --git a/gui/general_settings_tab.py b/gui/general_settings_tab.py index af5704a..3fbd2ab 100644 --- a/gui/general_settings_tab.py +++ b/gui/general_settings_tab.py @@ -1,213 +1,46 @@ import customtkinter as ctk -def populate_settings(main_window, frame): - """Populate the settings frame with configuration options.""" +def populate_general_settings(main_window, frame): + """ + Populates the General Settings tab with UI elements for configuring main application features. + All changes are saved in real-time to the configuration. + """ # Create a scrollable container for settings settings = ctk.CTkScrollableFrame( frame, fg_color="transparent" ) settings.pack(fill="both", expand=True, padx=40, pady=40) - + # Frame for page title and subtitle title_frame = ctk.CTkFrame(settings, fg_color="transparent") title_frame.pack(fill="x", pady=(0, 40)) - + # Settings title with an icon title_label = ctk.CTkLabel( title_frame, - text="⚙️ Settings", + text="⚙️ General Settings", font=("Chivo", 36, "bold"), text_color=("#1f2937", "#ffffff"), anchor="w" ) title_label.pack(side="left") - + # Subtitle providing context subtitle_label = ctk.CTkLabel( title_frame, - text="Configure your CS2 bot preferences", + text="Configure main application features", font=("Gambetta", 16), text_color=("#64748b", "#94a3b8"), anchor="w" ) subtitle_label.pack(side="left", padx=(20, 0), pady=(10, 0)) - - # Create sections for trigger and timing settings - create_trigger_config_section(main_window, settings) - create_timing_settings_section(main_window, settings) - - # Frame for action buttons - actions_frame = ctk.CTkFrame( - settings, - corner_radius=20, - fg_color=("#ffffff", "#1a1b23"), - border_width=2, - border_color=("#e2e8f0", "#2d3748") - ) - actions_frame.pack(fill="x", pady=(40, 0)) - - # Content frame within actions section - actions_content = ctk.CTkFrame(actions_frame, fg_color="transparent") - actions_content.pack(fill="x", padx=40, pady=40) - - # Header for configuration management - header_frame = ctk.CTkFrame(actions_content, fg_color="transparent") - header_frame.pack(fill="x", pady=(0, 30)) - - # Title for configuration management section - ctk.CTkLabel( - header_frame, - text="💾 Configuration Management", - font=("Chivo", 24, "bold"), - text_color=("#1f2937", "#ffffff"), - anchor="w" - ).pack(side="left") - - # Description of configuration options - ctk.CTkLabel( - header_frame, - text="Save, reset, or manage your configuration", - font=("Gambetta", 14), - text_color=("#64748b", "#94a3b8"), - anchor="e" - ).pack(side="right") - - # Frame for action buttons - buttons_frame = ctk.CTkFrame(actions_content, fg_color="transparent") - buttons_frame.pack(fill="x") - - # Frame for primary action buttons (save and reset) - primary_frame = ctk.CTkFrame(buttons_frame, fg_color="transparent") - primary_frame.pack(side="left") - - # Save settings button - save_btn = ctk.CTkButton( - primary_frame, - text="💾 Save Settings", - command=main_window.save_settings, - width=160, - height=50, - corner_radius=16, - fg_color=("#22c55e", "#16a34a"), - hover_color=("#16a34a", "#15803d"), - font=("Chivo", 16, "bold"), - border_width=2, - border_color=("#16a34a", "#15803d"), - anchor="center" - ) - save_btn.pack(side="left", padx=(0, 15)) - - # Reset to defaults button - reset_btn = ctk.CTkButton( - primary_frame, - text="🔄 Reset Defaults", - command=main_window.reset_to_defaults, - width=160, - height=50, - corner_radius=16, - fg_color=("#6b7280", "#4b5563"), - hover_color=("#4b5563", "#374151"), - font=("Chivo", 16, "bold"), - border_width=2, - border_color=("#4b5563", "#374151"), - anchor="center" - ) - reset_btn.pack(side="left") - - # Frame for secondary action buttons (open config and share/import) - secondary_frame = ctk.CTkFrame(buttons_frame, fg_color="transparent") - secondary_frame.pack(side="right") - - # Open config directory button - config_btn = ctk.CTkButton( - secondary_frame, - text="📁 Open Config", - command=main_window.open_config_directory, - width=140, - height=50, - corner_radius=16, - fg_color=("#3b82f6", "#2563eb"), - hover_color=("#2563eb", "#1d4ed8"), - font=("Chivo", 16, "bold"), - border_width=2, - border_color=("#2563eb", "#1d4ed8"), - anchor="center" - ) - config_btn.pack(side="left", padx=(0, 15)) - - # Share/import settings button - import_btn = ctk.CTkButton( - secondary_frame, - text="📤 Share/Import", - command=main_window.show_share_import_dialog, - width=140, - height=50, - corner_radius=16, - fg_color=("#8b5cf6", "#7c3aed"), - hover_color=("#7c3aed", "#6d28d9"), - font=("Chivo", 16, "bold"), - border_width=2, - border_color=("#7c3aed", "#6d28d9"), - anchor="center" - ) - import_btn.pack(side="left") -def create_trigger_config_section(main_window, parent): - """Create trigger configuration section with related settings.""" - # Section frame with modern styling - section = ctk.CTkFrame( - parent, - corner_radius=20, - fg_color=("#ffffff", "#1a1b23"), - border_width=2, - border_color=("#e2e8f0", "#2d3748") - ) - section.pack(fill="x", pady=(0, 30)) - - # Header frame for section title and description - header = ctk.CTkFrame(section, fg_color="transparent") - header.pack(fill="x", padx=40, pady=(40, 30)) - - # Section title with icon - ctk.CTkLabel( - header, - text="🎯 Trigger Configuration", - font=("Chivo", 24, "bold"), - text_color=("#1f2937", "#ffffff"), - anchor="w" - ).pack(side="left") - - # Description of section purpose - ctk.CTkLabel( - header, - text="Control how the trigger responds", - font=("Gambetta", 14), - text_color=("#64748b", "#94a3b8"), - anchor="e" - ).pack(side="right") - - # List of settings for trigger configuration - settings_list = [ - ("Trigger Key", "entry", "trigger_key", "Key to activate trigger (e.g., 'x' or 'mouse4' for mouse button 4)"), - ("Toggle Mode", "checkbox", "toggle_mode", "Enable toggle mode instead of hold mode"), - ("Attack Teammates", "checkbox", "attack_teammates", "Allow triggering on teammates") - ] - - # Create each setting item - for i, (label_text, widget_type, key, description) in enumerate(settings_list): - item_frame = create_setting_item( - section, - label_text, - description, - widget_type, - key, - main_window, - is_last=(i == len(settings_list) - 1) - ) + # Create section for general feature settings + create_features_section(main_window, settings) -def create_timing_settings_section(main_window, parent): - """Create timing settings section for delay configurations.""" +def create_features_section(main_window, parent): + """Create section for configuring main application features.""" # Section frame with modern styling section = ctk.CTkFrame( parent, @@ -217,55 +50,53 @@ def create_timing_settings_section(main_window, parent): border_color=("#e2e8f0", "#2d3748") ) section.pack(fill="x", pady=(0, 30)) - + # Header frame for section title and description header = ctk.CTkFrame(section, fg_color="transparent") header.pack(fill="x", padx=40, pady=(40, 30)) - + # Section title with icon ctk.CTkLabel( header, - text="⏱️ Timing Settings", + text="🔧 Feature Configuration", font=("Chivo", 24, "bold"), text_color=("#1f2937", "#ffffff"), anchor="w" ).pack(side="left") - + # Description of section purpose ctk.CTkLabel( header, - text="Fine-tune shooting delays", + text="Enable or disable main application features", font=("Gambetta", 14), text_color=("#64748b", "#94a3b8"), anchor="e" ).pack(side="right") - - # List of settings for timing configuration + + # List of settings for feature configuration settings_list = [ - ("Min Shot Delay", "entry", "min_delay", "Minimum delay between shots (seconds)"), - ("Max Shot Delay", "entry", "max_delay", "Maximum delay between shots (seconds)"), - ("Post Shot Delay", "entry", "post_delay", "Delay after shooting (seconds)") + ("Enable Trigger", "checkbox", "Trigger", "Toggle the trigger bot feature"), + ("Enable Overlay", "checkbox", "Overlay", "Toggle the ESP overlay feature"), + ("Enable Bunnyhop", "checkbox", "Bunnyhop", "Toggle the bunnyhop feature"), + ("Enable Noflash", "checkbox", "Noflash", "Toggle the noflash feature") ] - + # Create each setting item for i, (label_text, widget_type, key, description) in enumerate(settings_list): - item_frame = create_setting_item( - section, - label_text, - description, - widget_type, - key, + create_setting_item( + section, + label_text, + description, + widget_type, + key, main_window, is_last=(i == len(settings_list) - 1) ) def create_setting_item(parent, label_text, description, widget_type, key, main_window, is_last=False): - """Create a standardized setting item with improved styling.""" - # Frame for the setting item item_frame = ctk.CTkFrame(parent, fg_color="transparent") item_frame.pack(fill="x", padx=40, pady=(0, 30 if not is_last else 40)) - # Container with hover effect container = ctk.CTkFrame( item_frame, corner_radius=12, @@ -275,15 +106,12 @@ def create_setting_item(parent, label_text, description, widget_type, key, main_ ) container.pack(fill="x", pady=(0, 0)) - # Content frame within the container content_frame = ctk.CTkFrame(container, fg_color="transparent") content_frame.pack(fill="x", padx=25, pady=25) - # Frame for label and description label_frame = ctk.CTkFrame(content_frame, fg_color="transparent") label_frame.pack(side="left", fill="x", expand=True) - # Setting name label ctk.CTkLabel( label_frame, text=label_text, @@ -292,7 +120,6 @@ def create_setting_item(parent, label_text, description, widget_type, key, main_ anchor="w" ).pack(fill="x", pady=(0, 4)) - # Description of the setting ctk.CTkLabel( label_frame, text=description, @@ -302,71 +129,38 @@ def create_setting_item(parent, label_text, description, widget_type, key, main_ wraplength=400 ).pack(fill="x") - # Frame for the input widget widget_frame = ctk.CTkFrame(content_frame, fg_color="transparent") widget_frame.pack(side="right", padx=(30, 0)) - # Create entry widget for text input - if widget_type == "entry": - widget = ctk.CTkEntry( + if widget_type == "checkbox": + # Create a BooleanVar with the current config value + var = ctk.BooleanVar(value=main_window.triggerbot.config["General"].get(key, False)) + # Create the checkbox widget + widget = ctk.CTkCheckBox( widget_frame, - width=220, - height=45, - corner_radius=12, + text="", + variable=var, + width=30, + height=30, + corner_radius=8, border_width=2, - border_color=("#d1d5db", "#374151"), - fg_color=("#ffffff", "#1f2937"), - text_color=("#1f2937", "#ffffff"), - font=("Chivo", 14), - justify="center" + fg_color=("#D5006D", "#E91E63"), + hover_color=("#B8004A", "#C2185B"), + checkmark_color="#ffffff", + command=lambda: main_window.save_settings(show_message=False) ) + # Assign the BooleanVar to the appropriate main_window attribute + if key == "Trigger": + main_window.trigger_var = var + elif key == "Overlay": + main_window.overlay_var = var + elif key == "Bunnyhop": + main_window.bunnyhop_var = var + elif key == "Noflash": + main_window.noflash_var = var widget.pack() - - # Assign widget to main_window based on key - if key == "trigger_key": - main_window.trigger_key_entry = widget - widget.insert(0, main_window.bot.config.get('Settings', {}).get('TriggerKey', '')) - elif key == "min_delay": - main_window.min_delay_entry = widget - widget.insert(0, str(main_window.bot.config.get('Settings', {}).get('ShotDelayMin', 0.01))) - elif key == "max_delay": - main_window.max_delay_entry = widget - widget.insert(0, str(main_window.bot.config.get('Settings', {}).get('ShotDelayMax', 0.03))) - elif key == "post_delay": - main_window.post_shot_delay_entry = widget - widget.insert(0, str(main_window.bot.config.get('Settings', {}).get('PostShotDelay', 0.1))) - # Create checkbox widget for boolean settings - elif widget_type == "checkbox": - if key == "toggle_mode": - main_window.toggle_mode_var = ctk.BooleanVar(value=main_window.bot.config.get('Settings', {}).get('ToggleMode', False)) - widget = ctk.CTkCheckBox( - widget_frame, - text="", - variable=main_window.toggle_mode_var, - width=30, - height=30, - corner_radius=8, - border_width=2, - fg_color=("#D5006D", "#E91E63"), - hover_color=("#B8004A", "#C2185B"), - checkmark_color="#ffffff" - ) - elif key == "attack_teammates": - main_window.attack_teammates_var = ctk.BooleanVar(value=main_window.bot.config.get('Settings', {}).get('AttackOnTeammates', False)) - widget = ctk.CTkCheckBox( - widget_frame, - text="", - variable=main_window.attack_teammates_var, - width=30, - height=30, - corner_radius=8, - border_width=2, - fg_color=("#D5006D", "#E91E63"), - hover_color=("#B8004A", "#C2185B"), - checkmark_color="#ffffff" - ) - - widget.pack() + else: + raise ValueError(f"Unsupported widget type: {widget_type}") return item_frame \ No newline at end of file diff --git a/gui/home_tab.py b/gui/home_tab.py index 50c3518..429d5c2 100644 --- a/gui/home_tab.py +++ b/gui/home_tab.py @@ -33,7 +33,7 @@ def populate_dashboard(main_window, frame): # Subtitle providing context subtitle_label = ctk.CTkLabel( title_frame, - text="Monitor and control your CS2 bot", + text="Monitor and control your CS2 client", font=("Gambetta", 16), text_color=("#64748b", "#94a3b8") ) @@ -47,7 +47,7 @@ def populate_dashboard(main_window, frame): status_card, main_window.bot_status_label = create_stat_card( main_window, stats_frame, - "🤖 Bot Status", + "🔮 Status", "Inactive", "#ef4444", "Current operational state" @@ -93,7 +93,7 @@ def populate_dashboard(main_window, frame): # Control center title ctk.CTkLabel( control_header, - text="🎮 Bot Control Center", + text="🎮 Control Center", font=("Chivo", 24, "bold"), text_color=("#1f2937", "#ffffff") ).pack(side="left") @@ -105,8 +105,8 @@ def populate_dashboard(main_window, frame): # Start button with play icon start_button = ctk.CTkButton( control_buttons, - text="▶ Start Bot", - command=main_window.start_bot, + text="▶ Start Client", + command=main_window.start_client, width=180, height=60, corner_radius=16, @@ -121,8 +121,8 @@ def populate_dashboard(main_window, frame): # Stop button with stop icon stop_button = ctk.CTkButton( control_buttons, - text="⏹ Stop Bot", - command=main_window.stop_bot, + text="⏹ Stop Client", + command=main_window.stop_client, width=180, height=60, corner_radius=16, @@ -167,8 +167,8 @@ def populate_dashboard(main_window, frame): # List of guide steps steps = [ ("1", "Launch CS2", "Open Counter-Strike 2 and ensure it's running"), - ("2", "Configure Settings", "Set your trigger key and adjust delays in Settings"), - ("3", "Start Bot", "Click the Start Bot button to activate"), + ("2", "Configure Settings", "Set your trigger settings or overlay settings"), + ("3", "Start Client", "Click the Start Client button to activate"), ("4", "Monitor Logs", "Check the Logs tab for activity and status updates") ] @@ -308,13 +308,13 @@ def update_callback(): # Run fetch in a separate thread threading.Thread(target=update_callback, daemon=True).start() -def update_bot_status(self, status, color): - """Update the bot status indicators across the dashboard.""" +def update_client_status(self, status, color): + """Update the client status indicators across the dashboard.""" # Update header status label self.status_label.configure(text=status, text_color=color) # Update dashboard status label - self.bot_status_label.configure(text=status, text_color=color) - + self.bot_status_label.configure(text=status, text_color=color) # Исправлено с client_status_label + # Update status dot color in header for widget in self.status_frame.winfo_children(): if isinstance(widget, ctk.CTkFrame) and widget.cget("width") == 12: @@ -322,5 +322,5 @@ def update_bot_status(self, status, color): break # Ensure dashboard status updates if widget exists - if hasattr(self, 'bot_status_label') and self.bot_status_label.winfo_exists(): + if hasattr(self, 'bot_status_label') and self.bot_status_label.winfo_exists(): # Исправлено с client_status_label self.bot_status_label.configure(text=status, text_color=color) \ No newline at end of file diff --git a/gui/main_window.py b/gui/main_window.py index d4b62d5..3c4f7d9 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -17,12 +17,18 @@ from classes.utility import Utility from classes.trigger_bot import CS2TriggerBot -from classes.config_manager import ConfigManager +from classes.esp import CS2Overlay +from classes.bunnyhop import CS2Bunnyhop +from classes.noflash import CS2NoFlash +from classes.config_manager import ConfigManager, COLOR_CHOICES from classes.file_watcher import ConfigFileChangeHandler from classes.logger import Logger +from classes.memory_manager import MemoryManager from gui.home_tab import populate_dashboard -from gui.general_settings_tab import populate_settings +from gui.general_settings_tab import populate_general_settings +from gui.trigger_settings_tab import populate_trigger_settings +from gui.overlay_settings_tab import populate_overlay_settings from gui.logs_tab import populate_logs from gui.faq_tab import populate_faq from gui.supporters_tab import populate_supporters @@ -34,9 +40,12 @@ class MainWindow: def __init__(self): """Initialize the main application window and setup UI components.""" # Define repository URL for reference - self.repo_url = "github.com/Jesewe/cs2-triggerbot" - # Initialize bot thread, observer, and log timer as None until set up - self.bot_thread = None + self.repo_url = "github.com/Jesewe/VioletWing" + # Initialize threads, observer, and log timer as None until set up + self.trigger_thread = None + self.overlay_thread = None + self.bunnyhop_thread = None + self.noflash_thread = None self.observer = None self.log_timer = None # Track the last position in the log file for incremental updates @@ -46,13 +55,18 @@ def __init__(self): ctk.set_appearance_mode("dark") ctk.set_default_color_theme("blue") - # Fetch offsets and client data, initialize the TriggerBot instance - offsets, client_data = self.fetch_offsets_or_warn() - self.bot = CS2TriggerBot(offsets, client_data) + # Fetch offsets and client data + self.offsets, self.client_data, self.buttons_data = self.fetch_offsets_or_warn() + + # Create a single MemoryManager instance + self.memory_manager = MemoryManager(self.offsets, self.client_data, self.buttons_data) + + # Initialize feature instances + self.initialize_features() # Create the main window with a title and initial size self.root = ctk.CTk() - self.root.title(f"CS2 TriggerBot {ConfigManager.VERSION}") + self.root.title(f"VioletWing {ConfigManager.VERSION}") self.root.geometry("1300x700") self.root.resizable(True, True) self.root.minsize(1300, 700) @@ -91,6 +105,18 @@ def __init__(self): # Bind window close event to cleanup resources self.root.protocol("WM_DELETE_WINDOW", self.on_closing) + def initialize_features(self): + """Initialize all feature instances with the shared MemoryManager.""" + try: + self.triggerbot = CS2TriggerBot(self.memory_manager) + self.overlay = CS2Overlay(self.memory_manager) + self.bunnyhop = CS2Bunnyhop(self.memory_manager) + self.noflash = CS2NoFlash(self.memory_manager) + logger.info("All features initialized successfully.") + except Exception as e: + logger.error(f"Failed to initialize features: {e}") + messagebox.showerror("Initialization Error", f"Failed to initialize features: {str(e)}") + def setup_ui(self): """Setup the modern user interface components.""" # Create a modern header with branding and controls @@ -120,19 +146,19 @@ def create_modern_header(self): title_frame = ctk.CTkFrame(left_frame, fg_color="transparent") title_frame.pack(side="left") - # Main title "CS2" with accent color + # Main title "Violet" with accent color main_title = ctk.CTkLabel( title_frame, - text="CS2", + text="Violet", font=("Chivo", 28, "bold"), text_color="#D5006D" ) main_title.pack(side="left") - # Subtitle "TriggerBot" in white + # Subtitle "Wing" in white sub_title = ctk.CTkLabel( title_frame, - text="TriggerBot", + text="Wing", font=("Chivo", 28, "bold"), text_color="#E0E0E0" ) @@ -155,7 +181,7 @@ def create_modern_header(self): self.status_frame = ctk.CTkFrame(right_frame, fg_color="transparent") self.status_frame.pack(side="right", padx=(20, 0)) - # Status dot indicating bot activity + # Status dot indicating client activity status_dot = ctk.CTkFrame( self.status_frame, width=12, @@ -203,7 +229,7 @@ def create_modern_header(self): text="GitHub", image=self.github_ctk_image, compound="left", - command=lambda: webbrowser.open("https://github.com/Jesewe/cs2-triggerbot"), + command=lambda: webbrowser.open("https://github.com/Jesewe/VioletWing"), height=32, corner_radius=16, fg_color="#21262d", @@ -235,7 +261,7 @@ def create_modern_header(self): text="Boosty", image=self.boosty_ctk_image, compound="left", - command=lambda: webbrowser.open("https://boosty.to/jesewe"), + command=lambda: webbrowser.open("https://boosty.to/jesewe/donate"), height=32, corner_radius=16, fg_color="#ff6b35", @@ -290,7 +316,7 @@ def download_and_update(self, download_url): # Define paths for current and temporary executables current_exe = sys.executable exe_name = os.path.basename(current_exe) - temp_exe = os.path.join(ConfigManager.UPDATE_DIRECTORY, "new_CS2.Triggerbot.exe") + temp_exe = os.path.join(ConfigManager.UPDATE_DIRECTORY, "new_VioletWing.exe") bat_file = os.path.join(ConfigManager.UPDATE_DIRECTORY, "update.bat") # Download the new executable @@ -303,8 +329,8 @@ def download_and_update(self, download_url): # Create a batch file to handle the update process with open(bat_file, 'w') as f: f.write(f'''@echo off -title CS2 TriggerBot Updater -echo Updating CS2 TriggerBot... +title VioletWing Updater +echo Updating VioletWing... echo. echo Waiting for application to close... timeout /t 3 /nobreak >nul @@ -365,14 +391,18 @@ def create_main_content(self): # Frames for each tab self.dashboard_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent") - self.settings_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent") + self.general_settings_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent") + self.trigger_settings_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent") + self.overlay_settings_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent") self.logs_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent") self.faq_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent") self.supporters_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent") # Populate tab frames once during initialization self.populate_dashboard() - self.populate_settings() + self.populate_general_settings() + self.populate_trigger_settings() + self.populate_overlay_settings() self.populate_logs() self.populate_faq() self.populate_supporters() @@ -396,7 +426,9 @@ def create_sidebar(self, parent): # Navigation items with icons and labels nav_items = [ ("Dashboard", "dashboard", "🏠"), - ("Settings", "settings", "⚙️"), + ("General Settings", "general_settings", "⚙️"), + ("Trigger Settings", "trigger_settings", "🔫"), + ("Overlay Settings", "overlay_settings", "🌍"), ("Logs", "logs", "📋"), ("FAQ", "faq", "❓"), ("Supporters", "supporters", "🤝") @@ -455,7 +487,9 @@ def switch_view(self, view_key): # Hide all frames self.dashboard_frame.pack_forget() - self.settings_frame.pack_forget() + self.general_settings_frame.pack_forget() + self.trigger_settings_frame.pack_forget() + self.overlay_settings_frame.pack_forget() self.logs_frame.pack_forget() self.faq_frame.pack_forget() self.supporters_frame.pack_forget() @@ -463,9 +497,12 @@ def switch_view(self, view_key): # Show the selected frame and update if necessary if view_key == "dashboard": self.dashboard_frame.pack(fill="both", expand=True) - elif view_key == "settings": - self.update_settings_fields() - self.settings_frame.pack(fill="both", expand=True) + elif view_key == "general_settings": + self.general_settings_frame.pack(fill="both", expand=True) + elif view_key == "trigger_settings": + self.trigger_settings_frame.pack(fill="both", expand=True) + elif view_key == "overlay_settings": + self.overlay_settings_frame.pack(fill="both", expand=True) elif view_key == "logs": self.logs_frame.pack(fill="both", expand=True) elif view_key == "faq": @@ -477,9 +514,17 @@ def populate_dashboard(self): """Populate the dashboard frame with controls and stats.""" populate_dashboard(self, self.dashboard_frame) - def populate_settings(self): - """Populate the settings frame with configuration options.""" - populate_settings(self, self.settings_frame) + def populate_general_settings(self): + """Populate the general settings frame with configuration options.""" + populate_general_settings(self, self.general_settings_frame) + + def populate_trigger_settings(self): + """Populate the trigger settings frame with configuration options.""" + populate_trigger_settings(self, self.trigger_settings_frame) + + def populate_overlay_settings(self): + """Populate the overlay settings frame with configuration options.""" + populate_overlay_settings(self, self.overlay_settings_frame) def populate_logs(self): """Populate the logs frame with log display.""" @@ -493,40 +538,20 @@ def populate_supporters(self): """Populate the supporters frame with supporter data.""" populate_supporters(self, self.supporters_frame) - def update_settings_fields(self): - """Update the settings input fields with current configuration.""" - # Retrieve current settings - settings = self.bot.config.get('Settings', {}) - # Update trigger key field - self.trigger_key_entry.delete(0, 'end') - self.trigger_key_entry.insert(0, settings.get('TriggerKey', '')) - # Update toggle mode checkbox - self.toggle_mode_var.set(settings.get('ToggleMode', False)) - # Update attack teammates checkbox - self.attack_teammates_var.set(settings.get('AttackOnTeammates', False)) - # Update minimum delay field - self.min_delay_entry.delete(0, 'end') - self.min_delay_entry.insert(0, str(settings.get('ShotDelayMin', 0.01))) - # Update maximum delay field - self.max_delay_entry.delete(0, 'end') - self.max_delay_entry.insert(0, str(settings.get('ShotDelayMax', 0.03))) - # Update post-shot delay field - self.post_shot_delay_entry.delete(0, 'end') - self.post_shot_delay_entry.insert(0, str(settings.get('PostShotDelay', 0.1))) - def fetch_offsets_or_warn(self): """Attempt to fetch offsets; warn the user and return empty dictionaries on failure.""" try: - offsets, client_data = Utility.fetch_offsets() - if offsets is None or client_data is None: + offsets, client_data, buttons_data = Utility.fetch_offsets() + if offsets is None or client_data is None or buttons_data is None: raise ValueError("Failed to fetch offsets from the server.") - return offsets, client_data + return offsets, client_data, buttons_data except Exception as e: logger.error("Offsets fetch error: %s", e) - return {}, {} + messagebox.showerror("Offset Error", f"Failed to fetch offsets: {str(e)}") + return {}, {}, {} - def update_bot_status(self, status, color): - """Update bot status in header and dashboard.""" + def update_client_status(self, status, color): + """Update client status in header and dashboard.""" # Update header status label self.status_label.configure(text=status, text_color=color) @@ -540,112 +565,492 @@ def update_bot_status(self, status, color): if hasattr(self, 'bot_status_label'): self.bot_status_label.configure(text=status, text_color=color) - def start_bot(self): - """Start the bot if it is not already running.""" - # Check if bot is already active - if self.bot.is_running: - messagebox.showwarning("Bot Already Running", "The bot is already running.") - return - - # Verify if the game is running + def start_client(self): + """Start selected features based on General settings, ensuring no duplicates.""" if not Utility.is_game_running(): messagebox.showerror("Game Not Running", "Could not find cs2.exe process. Make sure the game is running.") return - # Clear stop event and start bot in a new thread - self.bot.stop_event.clear() - self.bot_thread = threading.Thread(target=self.bot.start, daemon=True) - self.bot_thread.start() + # Initialize the MemoryManager once before starting any features + if not self.memory_manager.initialize(): + messagebox.showerror("Initialization Error", "Failed to initialize memory manager. Please check the logs for details.") + return - # Update UI to reflect active status - self.update_bot_status("Active", "#22c55e") + config = ConfigManager.load_config() + any_feature_started = False - def stop_bot(self): - """Stop the bot if it is currently running.""" - # Check if bot is not running - if not self.bot.is_running: - messagebox.showwarning("Bot Not Started", "The bot is not running.") - return + # Start TriggerBot + if config["General"]["Trigger"] and not getattr(self.triggerbot, 'is_running', False): + try: + self.triggerbot.config = config + self.triggerbot.is_running = True + self.trigger_thread = threading.Thread(target=self.triggerbot.start, daemon=True) + self.trigger_thread.start() + logger.info("TriggerBot started.") + any_feature_started = True + except Exception as e: + logger.error(f"Failed to start TriggerBot: {e}") + messagebox.showerror("TriggerBot Error", f"Failed to start TriggerBot: {str(e)}") + + # Start Overlay + if config["General"]["Overlay"] and not getattr(self.overlay, 'is_running', False): + try: + self.overlay.config = config + self.overlay.is_running = True + self.overlay_thread = threading.Thread(target=self.overlay.start, daemon=True) + self.overlay_thread.start() + logger.info("Overlay started.") + any_feature_started = True + except Exception as e: + logger.error(f"Failed to start Overlay: {e}") + messagebox.showerror("Overlay Error", f"Failed to start Overlay: {str(e)}") + + # Start Bunnyhop + if config["General"]["Bunnyhop"] and not getattr(self.bunnyhop, 'is_running', False): + try: + self.bunnyhop.config = config + self.bunnyhop.is_running = True + self.bunnyhop_thread = threading.Thread(target=self.bunnyhop.start, daemon=True) + self.bunnyhop_thread.start() + logger.info("Bunnyhop started.") + any_feature_started = True + except Exception as e: + logger.error(f"Failed to start Bunnyhop: {e}") + messagebox.showerror("Bunnyhop Error", f"Failed to start Bunnyhop: {str(e)}") + + # Start NoFlash + if config["General"]["Noflash"] and not getattr(self.noflash, 'is_running', False): + try: + self.noflash.config = config + self.noflash.is_running = True + self.noflash_thread = threading.Thread(target=self.noflash.start, daemon=True) + self.noflash_thread.start() + logger.info("NoFlash started.") + any_feature_started = True + except Exception as e: + logger.error(f"Failed to start NoFlash: {e}") + messagebox.showerror("NoFlash Error", f"Failed to start NoFlash: {str(e)}") + + if any_feature_started: + self.update_client_status("Active", "#22c55e") + else: + logger.warning("No features enabled in General settings.") + messagebox.showwarning("No Features Enabled", "Please enable at least one feature in General Settings.") + + def stop_client(self): + """Stop all running features and ensure threads are terminated.""" + features_stopped = False + + # Stop TriggerBot + if self.triggerbot and getattr(self.triggerbot, 'is_running', False): + try: + self.triggerbot.stop() + if self.trigger_thread and self.trigger_thread.is_alive(): + self.trigger_thread.join(timeout=2.0) + if self.trigger_thread.is_alive(): + logger.warning("TriggerBot thread did not terminate cleanly.") + else: + logger.info("TriggerBot thread terminated successfully.") + self.trigger_thread = None + self.triggerbot.is_running = False + logger.debug("TriggerBot stopped.") + features_stopped = True + except Exception as e: + logger.error(f"Failed to stop TriggerBot: {e}", exc_info=True) + + # Stop Overlay + if self.overlay and getattr(self.overlay, 'is_running', False): + try: + self.overlay.stop() + if self.overlay_thread and self.overlay_thread.is_alive(): + self.overlay_thread.join(timeout=2.0) + if self.overlay_thread.is_alive(): + logger.warning("Overlay thread did not terminate cleanly.") + else: + logger.info("Overlay thread terminated successfully.") + self.overlay_thread = None + self.overlay.is_running = False + logger.debug("Overlay stopped.") + features_stopped = True + except Exception as e: + logger.error(f"Failed to stop Overlay: {e}", exc_info=True) - # Stop the bot and wait for the thread to finish - self.bot.stop() - if hasattr(self, 'bot_thread') and self.bot_thread is not None: - self.bot_thread.join(timeout=2) - if self.bot_thread.is_alive(): - logger.warning("Bot thread did not terminate cleanly.") - self.bot_thread = None + # Stop Bunnyhop + if self.bunnyhop and getattr(self.bunnyhop, 'is_running', False): + try: + self.bunnyhop.stop() + if self.bunnyhop_thread and self.bunnyhop_thread.is_alive(): + self.bunnyhop_thread.join(timeout=2.0) + if self.bunnyhop_thread.is_alive(): + logger.warning("Bunnyhop thread did not terminate cleanly.") + else: + logger.info("Bunnyhop thread terminated successfully.") + self.bunnyhop_thread = None + self.bunnyhop.is_running = False + logger.debug("Bunnyhop stopped.") + features_stopped = True + except Exception as e: + logger.error(f"Failed to stop Bunnyhop: {e}", exc_info=True) - # Update UI to reflect inactive status - self.update_bot_status("Inactive", "#ef4444") + # Stop NoFlash + if self.noflash and getattr(self.noflash, 'is_running', False): + try: + self.noflash.stop() + if self.noflash_thread and self.noflash_thread.is_alive(): + self.noflash_thread.join(timeout=2.0) + if self.noflash_thread.is_alive(): + logger.warning("NoFlash thread did not terminate cleanly.") + else: + logger.info("NoFlash thread terminated successfully.") + self.noflash_thread = None + self.noflash.is_running = False + logger.debug("NoFlash stopped.") + features_stopped = True + except Exception as e: + logger.error(f"Failed to stop NoFlash: {e}", exc_info=True) - def save_settings(self, show_message=True): - """Save the configuration settings.""" + if features_stopped: + self.update_client_status("Inactive", "#ef4444") + else: + logger.debug("No features were running to stop.") + + def save_settings(self, show_message=False): + """Save the configuration settings and apply to relevant features in real-time.""" try: - # Validate input fields self.validate_inputs() - - # Update configuration with current values - settings = self.bot.config['Settings'] - settings['TriggerKey'] = self.trigger_key_entry.get().strip() - settings['ToggleMode'] = self.toggle_mode_var.get() - settings['AttackOnTeammates'] = self.attack_teammates_var.get() - settings['ShotDelayMin'] = float(self.min_delay_entry.get()) - settings['ShotDelayMax'] = float(self.max_delay_entry.get()) - settings['PostShotDelay'] = float(self.post_shot_delay_entry.get()) - - # Save and apply the updated configuration - ConfigManager.save_config(self.bot.config) - self.bot.update_config(self.bot.config) + old_config = ConfigManager.load_config() + self.update_config_from_ui() + new_config = ConfigManager.load_config() + ConfigManager.save_config(new_config, log_info=False) + + # Define features and their threads + features = { + "Trigger": (self.triggerbot, "trigger_thread"), + "Overlay": (self.overlay, "overlay_thread"), + "Bunnyhop": (self.bunnyhop, "bunnyhop_thread"), + "Noflash": (self.noflash, "noflash_thread") + } + + any_feature_running = False + + # Handle enabling/disabling and config updates + for feature_name, (feature, thread_name) in features.items(): + old_enabled = old_config["General"].get(feature_name, False) + new_enabled = new_config["General"].get(feature_name, False) + is_running = getattr(feature, 'is_running', False) + + if old_enabled != new_enabled: + if new_enabled and not is_running: + # Start the feature + try: + feature.config = new_config + feature.is_running = True + thread = threading.Thread(target=feature.start, daemon=True) + thread.start() + setattr(self, thread_name, thread) + logger.info(f"{feature_name} started.") + any_feature_running = True + except Exception as e: + logger.error(f"Failed to start {feature_name}: {e}") + elif not new_enabled and is_running: + # Stop the feature + try: + feature.stop() + thread = getattr(self, thread_name) + if thread and thread.is_alive(): + thread.join(timeout=2.0) + if thread.is_alive(): + logger.warning(f"{feature_name} thread did not terminate cleanly.") + setattr(self, thread_name, None) + feature.is_running = False + logger.info(f"{feature_name} stopped.") + except Exception as e: + logger.error(f"Failed to stop {feature_name}: {e}") + if is_running: + # Update config for running features without restarting + feature.update_config(new_config) + logger.debug(f"Configuration updated for {feature_name}.") + any_feature_running = True + + # Update UI status + if any_feature_running: + self.update_client_status("Active", "#22c55e") + else: + self.update_client_status("Inactive", "#ef4444") + if show_message: - messagebox.showinfo("Settings Saved", "Configuration has been successfully saved.") + messagebox.showinfo("Settings Saved", "Configuration has been saved successfully.") except ValueError as e: + logger.error(f"Invalid input: {e}") messagebox.showerror("Invalid Input", str(e)) + except Exception as e: + logger.error(f"Unexpected error during save_settings: {e}") + messagebox.showerror("Error", f"Unexpected error: {str(e)}") + + def restart_affected_features(self, old_config, new_config): + """Restart only features affected by configuration changes.""" + any_feature_running = False + + # Helper to check if config section has changed + def config_changed(section): + return old_config.get(section, {}) != new_config.get(section, {}) + + # Restart TriggerBot if its config changed and it's running + if self.triggerbot.is_running and (config_changed("Trigger") or config_changed("General")): + try: + self.triggerbot.stop() + if self.trigger_thread and self.trigger_thread.is_alive(): + self.trigger_thread.join(timeout=2.0) + self.trigger_thread = None + if new_config["General"]["Trigger"]: + self.triggerbot = CS2TriggerBot(self.memory_manager) + self.triggerbot.config = new_config + self.trigger_thread = threading.Thread(target=self.triggerbot.start, daemon=True) + self.trigger_thread.start() + logger.info("TriggerBot restarted with new configuration.") + except Exception as e: + logger.error(f"Failed to restart TriggerBot: {e}") + any_feature_running = True + + # Restart Overlay if its config changed and it's running + if self.overlay.is_running and (config_changed("Overlay") or config_changed("General")): + try: + self.overlay.stop() + if self.overlay_thread and self.overlay_thread.is_alive(): + self.overlay_thread.join(timeout=2.0) + self.overlay_thread = None + if new_config["General"]["Overlay"]: + self.overlay = CS2Overlay(self.memory_manager) + self.overlay.config = new_config + self.overlay_thread = threading.Thread(target=self.overlay.start, daemon=True) + self.overlay_thread.start() + logger.info("Overlay restarted with new configuration.") + except Exception as e: + logger.error(f"Failed to restart Overlay: {e}") + any_feature_running = True + + # Restart Bunnyhop if its config changed and it's running + if self.bunnyhop.is_running and (config_changed("Bunnyhop") or config_changed("General")): + try: + self.bunnyhop.stop() + if self.bunnyhop_thread and self.bunnyhop_thread.is_alive(): + self.bunnyhop_thread.join(timeout=2.0) + self.bunnyhop_thread = None + if new_config["General"]["Bunnyhop"]: + self.bunnyhop = CS2Bunnyhop(self.memory_manager) + self.bunnyhop.config = new_config + self.bunnyhop_thread = threading.Thread(target=self.bunnyhop.start, daemon=True) + self.bunnyhop_thread.start() + logger.info("Bunnyhop restarted with new configuration.") + except Exception as e: + logger.error(f"Failed to restart Bunnyhop: {e}") + any_feature_running = True + + # Restart NoFlash if its config changed and it's running + if self.noflash.is_running and (config_changed("Noflash") or config_changed("General")): + try: + self.noflash.stop() + if self.noflash_thread and self.noflash_thread.is_alive(): + self.noflash_thread.join(timeout=2.0) + self.noflash_thread = None + if new_config["General"]["Noflash"]: + self.noflash = CS2NoFlash(self.memory_manager) + self.noflash.config = new_config + self.noflash_thread = threading.Thread(target=self.noflash.start, daemon=True) + self.noflash_thread.start() + logger.info("NoFlash restarted with new configuration.") + except Exception as e: + logger.error(f"Failed to restart NoFlash: {e}") + any_feature_running = True + + # Update UI status + if any_feature_running: + self.update_client_status("Active", "#22c55e") + else: + self.update_client_status("Inactive", "#ef4444") + + def update_config_from_ui(self): + """Update the configuration from the UI elements.""" + # Update General settings + general_settings = self.triggerbot.config["General"] + if hasattr(self, 'trigger_var'): + general_settings["Trigger"] = self.trigger_var.get() + if hasattr(self, 'overlay_var'): + general_settings["Overlay"] = self.overlay_var.get() + if hasattr(self, 'bunnyhop_var'): + general_settings["Bunnyhop"] = self.bunnyhop_var.get() + if hasattr(self, 'noflash_var'): + general_settings["Noflash"] = self.noflash_var.get() + + # Update Trigger settings + trigger_settings = self.triggerbot.config["Trigger"] + if hasattr(self, 'trigger_key_entry'): + trigger_settings["TriggerKey"] = self.trigger_key_entry.get().strip() + if hasattr(self, 'toggle_mode_var'): + trigger_settings["ToggleMode"] = self.toggle_mode_var.get() + if hasattr(self, 'attack_teammates_var'): + trigger_settings["AttackOnTeammates"] = self.attack_teammates_var.get() + if hasattr(self, 'min_delay_entry'): + try: + trigger_settings["ShotDelayMin"] = float(self.min_delay_entry.get()) + except ValueError: + pass + if hasattr(self, 'max_delay_entry'): + try: + trigger_settings["ShotDelayMax"] = float(self.max_delay_entry.get()) + except ValueError: + pass + if hasattr(self, 'post_shot_delay_entry'): + try: + trigger_settings["PostShotDelay"] = float(self.post_shot_delay_entry.get()) + except ValueError: + pass + + # Update Overlay settings + overlay_settings = self.overlay.config["Overlay"] + if hasattr(self, 'enable_box_var'): + overlay_settings["enable_box"] = self.enable_box_var.get() + if hasattr(self, 'box_line_thickness_slider'): + overlay_settings["box_line_thickness"] = self.box_line_thickness_slider.get() + if hasattr(self, 'box_color_hex_combo'): + overlay_settings["box_color_hex"] = COLOR_CHOICES.get(self.box_color_hex_combo.get(), "#FFA500") + if hasattr(self, 'draw_snaplines_var'): + overlay_settings["draw_snaplines"] = self.draw_snaplines_var.get() + if hasattr(self, 'snaplines_color_hex_combo'): + overlay_settings["snaplines_color_hex"] = COLOR_CHOICES.get(self.snaplines_color_hex_combo.get(), "#FFFFFF") + if hasattr(self, 'text_color_hex_combo'): + overlay_settings["text_color_hex"] = COLOR_CHOICES.get(self.text_color_hex_combo.get(), "#FFFFFF") + if hasattr(self, 'draw_health_numbers_var'): + overlay_settings["draw_health_numbers"] = self.draw_health_numbers_var.get() + if hasattr(self, 'draw_nicknames_var'): + overlay_settings["draw_nicknames"] = self.draw_nicknames_var.get() + if hasattr(self, 'use_transliteration_var'): + overlay_settings["use_transliteration"] = self.use_transliteration_var.get() + if hasattr(self, 'draw_teammates_var'): + overlay_settings["draw_teammates"] = self.draw_teammates_var.get() + if hasattr(self, 'teammate_color_hex_combo'): + overlay_settings["teammate_color_hex"] = COLOR_CHOICES.get(self.teammate_color_hex_combo.get(), "#00FFFF") + if hasattr(self, 'enable_minimap_var'): + overlay_settings["enable_minimap"] = self.enable_minimap_var.get() + if hasattr(self, 'minimap_size_entry'): + try: + minimap_size = int(self.minimap_size_entry.get()) + if 100 <= minimap_size <= 500: + overlay_settings["minimap_size"] = minimap_size + except ValueError: + pass def validate_inputs(self): """Validate user input fields.""" - # Check if trigger key is provided - trigger_key = self.trigger_key_entry.get().strip() - if not trigger_key: - raise ValueError("Trigger key cannot be empty.") + # Validate Trigger settings + if hasattr(self, 'trigger_key_entry'): + trigger_key = self.trigger_key_entry.get().strip() + if not trigger_key: + raise ValueError("Trigger key cannot be empty.") # Validate delay fields as numbers - try: - min_delay = float(self.min_delay_entry.get()) - max_delay = float(self.max_delay_entry.get()) - post_delay = float(self.post_shot_delay_entry.get()) - except ValueError: - raise ValueError("Delay values must be valid numbers.") - - # Ensure delays are non-negative and logical - if min_delay < 0 or max_delay < 0 or post_delay < 0: - raise ValueError("Delay values must be non-negative.") - if min_delay > max_delay: - raise ValueError("Minimum delay cannot be greater than maximum delay.") - - def reset_to_defaults(self): - """Reset all settings to default values.""" - # Retrieve default settings - defaults = ConfigManager.DEFAULT_CONFIG['Settings'] - - # Reset trigger key - self.trigger_key_entry.delete(0, 'end') - self.trigger_key_entry.insert(0, defaults.get('TriggerKey', '')) - - # Reset toggle mode and attack teammates - self.toggle_mode_var.set(defaults.get('ToggleMode', False)) - self.attack_teammates_var.set(defaults.get('AttackOnTeammates', False)) - - # Reset delay fields - self.min_delay_entry.delete(0, 'end') - self.min_delay_entry.insert(0, str(defaults.get('ShotDelayMin', 0.01))) - self.max_delay_entry.delete(0, 'end') - self.max_delay_entry.insert(0, str(defaults.get('ShotDelayMax', 0.03))) - self.post_shot_delay_entry.delete(0, 'end') - self.post_shot_delay_entry.insert(0, str(defaults.get('PostShotDelay', 0.1))) - - # Save without showing a message - self.save_settings(show_message=False) - messagebox.showinfo("Settings Reset", "All settings have been reset to default values.") + if hasattr(self, 'min_delay_entry'): + try: + min_delay = float(self.min_delay_entry.get()) + except ValueError: + raise ValueError("Minimum shot delay must be a valid number.") + if min_delay < 0: + raise ValueError("Minimum shot delay must be non-negative.") + else: + min_delay = None + + if hasattr(self, 'max_delay_entry'): + try: + max_delay = float(self.max_delay_entry.get()) + except ValueError: + raise ValueError("Maximum shot delay must be a valid number.") + if max_delay < 0: + raise ValueError("Maximum shot delay must be non-negative.") + if min_delay is not None and min_delay > max_delay: + raise ValueError("Minimum delay cannot be greater than maximum delay.") + else: + max_delay = None + + if hasattr(self, 'post_shot_delay_entry'): + try: + post_delay = float(self.post_shot_delay_entry.get()) + except ValueError: + raise ValueError("Post-shot delay must be a valid number.") + if post_delay < 0: + raise ValueError("Post-shot delay must be non-negative.") + + # Validate Overlay settings + if hasattr(self, 'minimap_size_entry'): + try: + minimap_size = int(self.minimap_size_entry.get()) + if not (100 <= minimap_size <= 500): + raise ValueError("Minimap size must be between 100 and 500.") + except ValueError: + raise ValueError("Minimap size must be a valid integer.") + + def update_ui_from_config(self): + """Update the UI elements from the configuration.""" + # Update General settings UI + general_settings = self.triggerbot.config["General"] + if hasattr(self, 'trigger_var'): + self.trigger_var.set(general_settings["Trigger"]) + if hasattr(self, 'overlay_var'): + self.overlay_var.set(general_settings["Overlay"]) + if hasattr(self, 'bunnyhop_var'): + self.bunnyhop_var.set(general_settings["Bunnyhop"]) + if hasattr(self, 'noflash_var'): + self.noflash_var.set(general_settings["Noflash"]) + + # Update Trigger settings UI + trigger_settings = self.triggerbot.config["Trigger"] + if hasattr(self, 'trigger_key_entry'): + self.trigger_key_entry.delete(0, "end") + self.trigger_key_entry.insert(0, trigger_settings["TriggerKey"]) + if hasattr(self, 'toggle_mode_var'): + self.toggle_mode_var.set(trigger_settings["ToggleMode"]) + if hasattr(self, 'attack_teammates_var'): + self.attack_teammates_var.set(trigger_settings["AttackOnTeammates"]) + if hasattr(self, 'min_delay_entry'): + self.min_delay_entry.delete(0, "end") + self.min_delay_entry.insert(0, str(trigger_settings["ShotDelayMin"])) + if hasattr(self, 'max_delay_entry'): + self.max_delay_entry.delete(0, "end") + self.max_delay_entry.insert(0, str(trigger_settings["ShotDelayMax"])) + if hasattr(self, 'post_shot_delay_entry'): + self.post_shot_delay_entry.delete(0, "end") + self.post_shot_delay_entry.insert(0, str(trigger_settings["PostShotDelay"])) + + # Update Overlay settings UI + overlay_settings = self.overlay.config["Overlay"] + if hasattr(self, 'enable_box_var'): + self.enable_box_var.set(overlay_settings["enable_box"]) + if hasattr(self, 'box_line_thickness_slider'): + self.box_line_thickness_slider.set(overlay_settings["box_line_thickness"]) + if hasattr(self, 'box_line_thickness_value_label'): + self.box_line_thickness_value_label.configure(text=f"{overlay_settings['box_line_thickness']:.1f}") + if hasattr(self, 'box_color_hex_combo'): + self.box_color_hex_combo.set(Utility.get_color_name_from_hex(overlay_settings["box_color_hex"])) + if hasattr(self, 'draw_snaplines_var'): + self.draw_snaplines_var.set(overlay_settings["draw_snaplines"]) + if hasattr(self, 'snaplines_color_hex_combo'): + self.snaplines_color_hex_combo.set(Utility.get_color_name_from_hex(overlay_settings["snaplines_color_hex"])) + if hasattr(self, 'text_color_hex_combo'): + self.text_color_hex_combo.set(Utility.get_color_name_from_hex(overlay_settings["text_color_hex"])) + if hasattr(self, 'draw_health_numbers_var'): + self.draw_health_numbers_var.set(overlay_settings["draw_health_numbers"]) + if hasattr(self, 'draw_nicknames_var'): + self.draw_nicknames_var.set(overlay_settings["draw_nicknames"]) + if hasattr(self, 'use_transliteration_var'): + self.use_transliteration_var.set(overlay_settings["use_transliteration"]) + if hasattr(self, 'draw_teammates_var'): + self.draw_teammates_var.set(overlay_settings["draw_teammates"]) + if hasattr(self, 'teammate_color_hex_combo'): + self.teammate_color_hex_combo.set(Utility.get_color_name_from_hex(overlay_settings["teammate_color_hex"])) + if hasattr(self, 'enable_minimap_var'): + self.enable_minimap_var.set(overlay_settings["enable_minimap"]) + if hasattr(self, 'minimap_size_entry'): + self.minimap_size_entry.delete(0, "end") + self.minimap_size_entry.insert(0, str(overlay_settings["minimap_size"])) def open_config_directory(self): """Open the configuration directory in the file explorer.""" @@ -653,181 +1058,11 @@ def open_config_directory(self): if platform.system() == "Windows": os.startfile(path) - def show_share_import_dialog(self): - """Show modern share/import dialog for configuration sharing.""" - dialog = ctk.CTkToplevel(self.root) - dialog.title("Share/Import Settings") - dialog.geometry("600x500") - dialog.transient(self.root) - dialog.grab_set() - dialog.resizable(False, False) - - # Center the dialog relative to the main window - self.root.update_idletasks() - root_x = self.root.winfo_rootx() - root_y = self.root.winfo_rooty() - root_w = self.root.winfo_width() - root_h = self.root.winfo_height() - dialog_w = 600 - dialog_h = 500 - x = root_x + (root_w // 2) - (dialog_w // 2) - y = root_y + (root_h // 2) - (dialog_h // 2) - dialog.geometry(f"{dialog_w}x{dialog_h}+{x}+{y}") - - # Main frame for dialog content - main_frame = ctk.CTkFrame(dialog, fg_color="transparent") - main_frame.pack(fill="both", expand=True, padx=30, pady=30) - - # Dialog title - ctk.CTkLabel( - main_frame, - text="🔗 Share/Import Configuration", - font=("Chivo", 24, "bold"), - text_color=("#1f2937", "#E0E0E0") - ).pack(pady=(0, 20)) - - # Description of dialog purpose - ctk.CTkLabel( - main_frame, - text="Generate a shareable code for your settings or import settings from a code", - font=("Gambetta", 14), - text_color=("#6b7280", "#9ca3af") - ).pack(pady=(0, 30)) - - # Text area for displaying or entering codes - text_frame = ctk.CTkFrame( - main_frame, - corner_radius=12, - fg_color=("#f8fafc", "#0d1117"), - border_width=1, - border_color=("#e5e7eb", "#30363d") - ) - text_frame.pack(fill="both", expand=True, pady=(0, 30)) - - self.share_import_text = ctk.CTkTextbox( - text_frame, - corner_radius=12, - border_width=0, - state="disabled", - font=("Chivo", 12), - fg_color="transparent" - ) - self.share_import_text.pack(fill="both", expand=True, padx=15, pady=15) - - # Buttons frame for actions - buttons_frame = ctk.CTkFrame(main_frame, fg_color="transparent") - buttons_frame.pack(fill="x") - - # Generate code button - ctk.CTkButton( - buttons_frame, - text="📤 Generate Code", - command=self.export_settings, - width=140, - height=40, - corner_radius=10, - fg_color="#22c55e", - hover_color="#16a34a", - font=("Chivo", 14, "bold") - ).pack(side="left", padx=(0, 10)) - - # Import settings button - ctk.CTkButton( - buttons_frame, - text="📥 Import Settings", - command=lambda: self.import_settings(dialog), - width=140, - height=40, - corner_radius=10, - fg_color="#3b82f6", - hover_color="#2563eb", - font=("Chivo", 14, "bold") - ).pack(side="left", padx=(0, 10)) - - # Close dialog button - ctk.CTkButton( - buttons_frame, - text="❌ Close", - command=dialog.destroy, - width=100, - height=40, - corner_radius=10, - fg_color="#6b7280", - hover_color="#4b5563", - font=("Chivo", 14, "bold") - ).pack(side="right") - - def export_settings(self): - """Export settings to a shareable code.""" - # Collect current settings into a dictionary - settings = { - 'TriggerKey': self.trigger_key_entry.get(), - 'ToggleMode': self.toggle_mode_var.get(), - 'ShotDelayMin': float(self.min_delay_entry.get()), - 'ShotDelayMax': float(self.max_delay_entry.get()), - 'PostShotDelay': float(self.post_shot_delay_entry.get()), - 'AttackOnTeammates': self.attack_teammates_var.get() - } - - # Serialize, compress, and encode the settings - json_bytes = orjson.dumps(settings) - compressed = zlib.compress(json_bytes) - encoded = base64.b64encode(compressed).decode() - code = f"TB-{encoded}" - - # Display and copy the code to clipboard - self.share_import_text.configure(state="normal") - self.share_import_text.delete("1.0", "end") - self.share_import_text.insert("1.0", code) - self.share_import_text.configure(state="disabled") - self.root.clipboard_clear() - self.root.clipboard_append(code) - - messagebox.showinfo("Code Generated", "Settings code has been generated and copied to clipboard!") - - def import_settings(self, dialog): - """Import settings from a provided code.""" - # Enable text box to read the code - self.share_import_text.configure(state="normal") - code = self.share_import_text.get("1.0", "end-1c").strip() - self.share_import_text.configure(state="disabled") - - # Validate code prefix - if not code.startswith("TB-"): - messagebox.showerror("Invalid Code", "Invalid code format. Must start with 'TB-'") - return - - try: - # Decode, decompress, and deserialize the settings - encoded = code[3:] - compressed = base64.b64decode(encoded) - json_bytes = zlib.decompress(compressed) - settings = orjson.loads(json_bytes) - - # Update UI fields with imported settings - self.trigger_key_entry.delete(0, 'end') - self.trigger_key_entry.insert(0, settings.get('TriggerKey', '')) - self.toggle_mode_var.set(settings.get('ToggleMode', False)) - self.attack_teammates_var.set(settings.get('AttackOnTeammates', False)) - self.min_delay_entry.delete(0, 'end') - self.min_delay_entry.insert(0, str(settings.get('ShotDelayMin', 0.01))) - self.max_delay_entry.delete(0, 'end') - self.max_delay_entry.insert(0, str(settings.get('ShotDelayMax', 0.03))) - self.post_shot_delay_entry.delete(0, 'end') - self.post_shot_delay_entry.insert(0, str(settings.get('PostShotDelay', 0.1))) - - # Save settings and close dialog - self.save_settings(show_message=False) - dialog.destroy() - messagebox.showinfo("Import Successful", "Settings have been imported and saved successfully!") - except Exception as e: - messagebox.showerror("Import Error", f"Error importing settings: {str(e)}") - def init_config_watcher(self): """Initialize file watcher for configuration changes.""" try: # Set up a watcher for config file changes - event_handler = ConfigFileChangeHandler(self.bot) + event_handler = ConfigFileChangeHandler(self) self.observer = Observer() self.observer.schedule(event_handler, path=ConfigManager.CONFIG_DIRECTORY, recursive=False) self.observer.start() @@ -856,7 +1091,7 @@ def update_logs(): self.root.after(0, lambda logs=new_logs: self.update_log_display(logs)) except Exception as e: logger.error(f"Error in log update thread: {e}") - time.sleep(1) + time.sleep(0.1) # Уменьшено времÑ� ожиданиÑ� длÑ� большей отзывчивоÑ�ти # Start log update thread self.log_timer = threading.Thread(target=update_logs, daemon=True) @@ -897,18 +1132,12 @@ def on_closing(self): def cleanup(self): """Cleanup resources before closing the application.""" try: + # Stop all running features + self.stop_client() + # Stop the file watcher if it exists if hasattr(self, 'observer') and self.observer: self.observer.stop() self.observer.join() except Exception as e: - logger.error("Error stopping observer: %s", e) - - # Stop the bot if it’s running - if self.bot.is_running: - self.bot.stop() - if hasattr(self, 'bot_thread') and self.bot_thread is not None: - self.bot_thread.join(timeout=2) - if self.bot_thread.is_alive(): - logger.warning("Bot thread did not terminate cleanly.") - self.bot_thread = None \ No newline at end of file + logger.error("Error during cleanup: %s", e) \ No newline at end of file diff --git a/gui/overlay_settings_tab.py b/gui/overlay_settings_tab.py new file mode 100644 index 0000000..3b6d257 --- /dev/null +++ b/gui/overlay_settings_tab.py @@ -0,0 +1,526 @@ +import customtkinter as ctk +from classes.config_manager import COLOR_CHOICES +from classes.utility import Utility + +def populate_overlay_settings(main_window, frame): + """ + Populates the Overlay Settings tab with UI elements for configuring overlay preferences. + All changes are saved in real-time to the configuration. + """ + # Create a scrollable container for settings + settings = ctk.CTkScrollableFrame( + frame, + fg_color="transparent" + ) + settings.pack(fill="both", expand=True, padx=40, pady=40) + + # Frame for page title and subtitle + title_frame = ctk.CTkFrame(settings, fg_color="transparent") + title_frame.pack(fill="x", pady=(0, 40)) + + # Settings title with an icon + title_label = ctk.CTkLabel( + title_frame, + text="🌍 Overlay Settings", + font=("Chivo", 36, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ) + title_label.pack(side="left") + + # Subtitle providing context + subtitle_label = ctk.CTkLabel( + title_frame, + text="Configure your ESP overlay preferences", + font=("Gambetta", 16), + text_color=("#64748b", "#94a3b8"), + anchor="w" + ) + subtitle_label.pack(side="left", padx=(20, 0), pady=(10, 0)) + + # Create sections for different overlay settings + create_bounding_box_section(main_window, settings) + create_snaplines_section(main_window, settings) + create_text_section(main_window, settings) + create_player_info_section(main_window, settings) + create_team_section(main_window, settings) + create_minimap_section(main_window, settings) + +def create_bounding_box_section(main_window, parent): + """Create bounding box configuration section with related settings.""" + # Section frame with modern styling + section = ctk.CTkFrame( + parent, + corner_radius=20, + fg_color=("#ffffff", "#1a1b23"), + border_width=2, + border_color=("#e2e8f0", "#2d3748") + ) + section.pack(fill="x", pady=(0, 30)) + + # Header frame for section title and description + header = ctk.CTkFrame(section, fg_color="transparent") + header.pack(fill="x", padx=40, pady=(40, 30)) + + # Section title with icon + ctk.CTkLabel( + header, + text="📦 Bounding Box Configuration", + font=("Chivo", 24, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ).pack(side="left") + + # Description of section purpose + ctk.CTkLabel( + header, + text="Settings for enemy bounding boxes", + font=("Gambetta", 14), + text_color=("#64748b", "#94a3b8"), + anchor="e" + ).pack(side="right") + + # List of settings for bounding box configuration + settings_list = [ + ("Enable Bounding Box", "checkbox", "enable_box", "Toggle visibility of enemy bounding boxes"), + ("Line Thickness", "slider", "box_line_thickness", "Adjust thickness of bounding box lines (0.5-5.0)"), + ("Box Color", "combo", "box_color_hex", "Select color for bounding boxes") + ] + + # Create each setting item + for i, (label_text, widget_type, key, description) in enumerate(settings_list): + create_setting_item( + section, + label_text, + description, + widget_type, + key, + main_window, + is_last=(i == len(settings_list) - 1) + ) + +def create_snaplines_section(main_window, parent): + """Create snaplines configuration section with related settings.""" + # Section frame with modern styling + section = ctk.CTkFrame( + parent, + corner_radius=20, + fg_color=("#ffffff", "#1a1b23"), + border_width=2, + border_color=("#e2e8f0", "#2d3748") + ) + section.pack(fill="x", pady=(0, 30)) + + # Header frame for section title and description + header = ctk.CTkFrame(section, fg_color="transparent") + header.pack(fill="x", padx=40, pady=(40, 30)) + + # Section title with icon + ctk.CTkLabel( + header, + text="📍 Snaplines Configuration", + font=("Chivo", 24, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ).pack(side="left") + + # Description of section purpose + ctk.CTkLabel( + header, + text="Settings for snaplines to enemies", + font=("Gambetta", 14), + text_color=("#64748b", "#94a3b8"), + anchor="e" + ).pack(side="right") + + # List of settings for snaplines configuration + settings_list = [ + ("Draw Snaplines", "checkbox", "draw_snaplines", "Toggle drawing of snaplines to enemies"), + ("Snaplines Color", "combo", "snaplines_color_hex", "Select color for snaplines") + ] + + # Create each setting item + for i, (label_text, widget_type, key, description) in enumerate(settings_list): + create_setting_item( + section, + label_text, + description, + widget_type, + key, + main_window, + is_last=(i == len(settings_list) - 1) + ) + +def create_text_section(main_window, parent): + """Create text configuration section with related settings.""" + # Section frame with modern styling + section = ctk.CTkFrame( + parent, + corner_radius=20, + fg_color=("#ffffff", "#1a1b23"), + border_width=2, + border_color=("#e2e8f0", "#2d3748") + ) + section.pack(fill="x", pady=(0, 30)) + + # Header frame for section title and description + header = ctk.CTkFrame(section, fg_color="transparent") + header.pack(fill="x", padx=40, pady=(40, 30)) + + # Section title with icon + ctk.CTkLabel( + header, + text="📝 Text Configuration", + font=("Chivo", 24, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ).pack(side="left") + + # Description of section purpose + ctk.CTkLabel( + header, + text="Settings for text display", + font=("Gambetta", 14), + text_color=("#64748b", "#94a3b8"), + anchor="e" + ).pack(side="right") + + # List of settings for text configuration + settings_list = [ + ("Text Color", "combo", "text_color_hex", "Select color for text") + ] + + # Create each setting item + for i, (label_text, widget_type, key, description) in enumerate(settings_list): + create_setting_item( + section, + label_text, + description, + widget_type, + key, + main_window, + is_last=(i == len(settings_list) - 1) + ) + +def create_player_info_section(main_window, parent): + """Create player information configuration section with related settings.""" + # Section frame with modern styling + section = ctk.CTkFrame( + parent, + corner_radius=20, + fg_color=("#ffffff", "#1a1b23"), + border_width=2, + border_color=("#e2e8f0", "#2d3748") + ) + section.pack(fill="x", pady=(0, 30)) + + # Header frame for section title and description + header = ctk.CTkFrame(section, fg_color="transparent") + header.pack(fill="x", padx=40, pady=(40, 30)) + + # Section title with icon + ctk.CTkLabel( + header, + text="👤 Player Information", + font=("Chivo", 24, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ).pack(side="left") + + # Description of section purpose + ctk.CTkLabel( + header, + text="Settings for displaying player details", + font=("Gambetta", 14), + text_color=("#64748b", "#94a3b8"), + anchor="e" + ).pack(side="right") + + # List of settings for player information configuration + settings_list = [ + ("Draw Health Numbers", "checkbox", "draw_health_numbers", "Show health numbers above players"), + ("Draw Nicknames", "checkbox", "draw_nicknames", "Display player nicknames"), + ("Use Transliteration", "checkbox", "use_transliteration", "Transliterate non-Latin characters") + ] + + # Create each setting item + for i, (label_text, widget_type, key, description) in enumerate(settings_list): + create_setting_item( + section, + label_text, + description, + widget_type, + key, + main_window, + is_last=(i == len(settings_list) - 1) + ) + +def create_team_section(main_window, parent): + """Create team configuration section with related settings.""" + # Section frame with modern styling + section = ctk.CTkFrame( + parent, + corner_radius=20, + fg_color=("#ffffff", "#1a1b23"), + border_width=2, + border_color=("#e2e8f0", "#2d3748") + ) + section.pack(fill="x", pady=(0, 30)) + + # Header frame for section title and description + header = ctk.CTkFrame(section, fg_color="transparent") + header.pack(fill="x", padx=40, pady=(40, 30)) + + # Section title with icon + ctk.CTkLabel( + header, + text="👥 Team Configuration", + font=("Chivo", 24, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ).pack(side="left") + + # Description of section purpose + ctk.CTkLabel( + header, + text="Settings for teammate display", + font=("Gambetta", 14), + text_color=("#64748b", "#94a3b8"), + anchor="e" + ).pack(side="right") + + # List of settings for team configuration + settings_list = [ + ("Draw Teammates", "checkbox", "draw_teammates", "Show teammates on the overlay"), + ("Teammate Color", "combo", "teammate_color_hex", "Select color for teammates") + ] + + # Create each setting item + for i, (label_text, widget_type, key, description) in enumerate(settings_list): + create_setting_item( + section, + label_text, + description, + widget_type, + key, + main_window, + is_last=(i == len(settings_list) - 1) + ) + +def create_minimap_section(main_window, parent): + """Create minimap configuration section with related settings.""" + # Section frame with modern styling + section = ctk.CTkFrame( + parent, + corner_radius=20, + fg_color=("#ffffff", "#1a1b23"), + border_width=2, + border_color=("#e2e8f0", "#2d3748") + ) + section.pack(fill="x", pady=(0, 30)) + + # Header frame for section title and description + header = ctk.CTkFrame(section, fg_color="transparent") + header.pack(fill="x", padx=40, pady=(40, 30)) + + # Section title with icon + ctk.CTkLabel( + header, + text="🌍 Minimap Configuration", + font=("Chivo", 24, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ).pack(side="left") + + # Description of section purpose + ctk.CTkLabel( + header, + text="Settings for the minimap display", + font=("Gambetta", 14), + text_color=("#64748b", "#94a3b8"), + anchor="e" + ).pack(side="right") + + # List of settings for minimap configuration + settings_list = [ + ("Enable Minimap", "checkbox", "enable_minimap", "Toggle minimap visibility"), + ("Minimap Size", "entry", "minimap_size", "Set minimap size (100-500)") + ] + + # Create each setting item + for i, (label_text, widget_type, key, description) in enumerate(settings_list): + create_setting_item( + section, + label_text, + description, + widget_type, + key, + main_window, + is_last=(i == len(settings_list) - 1) + ) + +def create_setting_item(parent, label_text, description, widget_type, key, main_window, is_last=False): + item_frame = ctk.CTkFrame(parent, fg_color="transparent") + item_frame.pack(fill="x", padx=40, pady=(0, 30 if not is_last else 40)) + + container = ctk.CTkFrame( + item_frame, + corner_radius=12, + fg_color=("#f8fafc", "#252830"), + border_width=1, + border_color=("#e2e8f0", "#374151") + ) + container.pack(fill="x", pady=(0, 0)) + + content_frame = ctk.CTkFrame(container, fg_color="transparent") + content_frame.pack(fill="x", padx=25, pady=25) + + label_frame = ctk.CTkFrame(content_frame, fg_color="transparent") + label_frame.pack(side="left", fill="x", expand=True) + + ctk.CTkLabel( + label_frame, + text=label_text, + font=("Chivo", 16, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ).pack(fill="x", pady=(0, 4)) + + ctk.CTkLabel( + label_frame, + text=description, + font=("Gambetta", 13), + text_color=("#64748b", "#94a3b8"), + anchor="w", + wraplength=400 + ).pack(fill="x") + + widget_frame = ctk.CTkFrame(content_frame, fg_color="transparent") + widget_frame.pack(side="right", padx=(30, 0)) + + if widget_type == "checkbox": + var = ctk.BooleanVar(value=main_window.overlay.config["Overlay"][key]) + widget = ctk.CTkCheckBox( + widget_frame, + text="", + variable=var, + width=30, + height=30, + corner_radius=8, + border_width=2, + fg_color=("#D5006D", "#E91E63"), + hover_color=("#B8004A", "#C2185B"), + checkmark_color="#ffffff", + command=lambda: main_window.save_settings(show_message=False) + ) + widget.pack() + main_window.__setattr__(f"{key}_var", var) + + elif widget_type == "entry": + widget = ctk.CTkEntry( + widget_frame, + width=220, + height=45, + corner_radius=12, + border_width=2, + border_color=("#d1d5db", "#374151"), + fg_color=("#ffffff", "#1f2937"), + text_color=("#1f2937", "#ffffff"), + font=("Chivo", 14), + justify="center" + ) + widget.insert(0, str(main_window.overlay.config["Overlay"][key])) + widget.bind("", lambda e: main_window.save_settings(show_message=False)) + widget.bind("", lambda e: main_window.save_settings(show_message=False)) + widget.pack() + main_window.__setattr__(f"{key}_entry", widget) + + elif widget_type == "slider": + # Create a container for the slider and value display + slider_container = ctk.CTkFrame( + widget_frame, + fg_color="transparent" + ) + slider_container.pack() + + # Create a frame for the value label with background + value_frame = ctk.CTkFrame( + slider_container, + corner_radius=8, + fg_color=("#e2e8f0", "#374151"), + width=60, + height=35 + ) + value_frame.pack(side="right", padx=(15, 0)) + value_frame.pack_propagate(False) + + # Value label with improved styling + value_label = ctk.CTkLabel( + value_frame, + text=f"{main_window.overlay.config['Overlay'][key]:.1f}", + font=("Chivo", 14, "bold"), + text_color=("#1f2937", "#ffffff") + ) + value_label.pack(expand=True) + + # Enhanced slider with custom styling + widget = ctk.CTkSlider( + slider_container, + from_=0.5, + to=5.0, + number_of_steps=9, + width=200, + height=20, + corner_radius=10, + button_corner_radius=10, + border_width=0, + fg_color=("#e2e8f0", "#374151"), + progress_color=("#D5006D", "#E91E63"), + button_color=("#ffffff", "#ffffff"), + button_hover_color=("#f8fafc", "#f8fafc"), + command=lambda e: update_slider_value(e, key, main_window) + ) + widget.set(main_window.overlay.config["Overlay"][key]) + widget.pack(side="left") + + # Store references for later use + widget.value_label = value_label + main_window.__setattr__(f"{key}_slider", widget) + main_window.__setattr__(f"{key}_value_label", value_label) + + elif widget_type == "combo": + # Enhanced ComboBox with improved styling + widget = ctk.CTkComboBox( + widget_frame, + values=list(COLOR_CHOICES.keys()), + width=180, + height=45, + corner_radius=12, + border_width=2, + border_color=("#d1d5db", "#374151"), + fg_color=("#ffffff", "#1f2937"), + text_color=("#1f2937", "#ffffff"), + font=("Chivo", 14), + dropdown_font=("Chivo", 13), + button_color=("#D5006D", "#E91E63"), + button_hover_color=("#B8004A", "#C2185B"), + dropdown_fg_color=("#ffffff", "#1a1b23"), + dropdown_hover_color=("#f8fafc", "#2d3748"), + dropdown_text_color=("#1f2937", "#ffffff"), + state="readonly", + justify="center", + command=lambda e: main_window.save_settings(show_message=False) + ) + widget.set(Utility.get_color_name_from_hex(main_window.overlay.config["Overlay"][key])) + widget.bind("", lambda e: main_window.save_settings(show_message=False)) + widget.bind("", lambda e: main_window.save_settings(show_message=False)) + widget.pack() + main_window.__setattr__(f"{key}_combo", widget) + + return item_frame + +def update_slider_value(event, key, main_window): + """Update the slider value label and save settings.""" + value = main_window.__getattribute__(f"{key}_slider").get() + main_window.__getattribute__(f"{key}_slider").value_label.configure(text=f"{value:.1f}") + main_window.save_settings(show_message=False) \ No newline at end of file diff --git a/gui/trigger_settings_tab.py b/gui/trigger_settings_tab.py new file mode 100644 index 0000000..1a66e14 --- /dev/null +++ b/gui/trigger_settings_tab.py @@ -0,0 +1,265 @@ +import customtkinter as ctk + +def populate_trigger_settings(main_window, frame): + """Populate the settings frame with configuration options.""" + # Create a scrollable container for settings + settings = ctk.CTkScrollableFrame( + frame, + fg_color="transparent" + ) + settings.pack(fill="both", expand=True, padx=40, pady=40) + + # Frame for page title and subtitle + title_frame = ctk.CTkFrame(settings, fg_color="transparent") + title_frame.pack(fill="x", pady=(0, 40)) + + # Settings title with an icon + title_label = ctk.CTkLabel( + title_frame, + text="🔫 Trigger Settings", + font=("Chivo", 36, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ) + title_label.pack(side="left") + + # Subtitle providing context + subtitle_label = ctk.CTkLabel( + title_frame, + text="Configure your trigger bot preferences", + font=("Gambetta", 16), + text_color=("#64748b", "#94a3b8"), + anchor="w" + ) + subtitle_label.pack(side="left", padx=(20, 0), pady=(10, 0)) + + # Create sections for trigger and timing settings + create_trigger_config_section(main_window, settings) + create_timing_settings_section(main_window, settings) + +def create_trigger_config_section(main_window, parent): + """Create trigger configuration section with related settings.""" + # Section frame with modern styling + section = ctk.CTkFrame( + parent, + corner_radius=20, + fg_color=("#ffffff", "#1a1b23"), + border_width=2, + border_color=("#e2e8f0", "#2d3748") + ) + section.pack(fill="x", pady=(0, 30)) + + # Header frame for section title and description + header = ctk.CTkFrame(section, fg_color="transparent") + header.pack(fill="x", padx=40, pady=(40, 30)) + + # Section title with icon + ctk.CTkLabel( + header, + text="🎯 Configuration", + font=("Chivo", 24, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ).pack(side="left") + + # Description of section purpose + ctk.CTkLabel( + header, + text="Control how the trigger responds", + font=("Gambetta", 14), + text_color=("#64748b", "#94a3b8"), + anchor="e" + ).pack(side="right") + + # List of settings for trigger configuration + settings_list = [ + ("Trigger Key", "entry", "TriggerKey", "Key to activate trigger (e.g., 'x' or 'mouse4' for mouse button 4)"), + ("Toggle Mode", "checkbox", "ToggleMode", "Enable toggle mode instead of hold mode"), + ("Attack Teammates", "checkbox", "AttackOnTeammates", "Allow triggering on teammates") + ] + + # Create each setting item + for i, (label_text, widget_type, key, description) in enumerate(settings_list): + item_frame = create_setting_item( + section, + label_text, + description, + widget_type, + key, + main_window, + is_last=(i == len(settings_list) - 1) + ) + +def create_timing_settings_section(main_window, parent): + """Create timing settings section for delay configurations.""" + # Section frame with modern styling + section = ctk.CTkFrame( + parent, + corner_radius=20, + fg_color=("#ffffff", "#1a1b23"), + border_width=2, + border_color=("#e2e8f0", "#2d3748") + ) + section.pack(fill="x", pady=(0, 30)) + + # Header frame for section title and description + header = ctk.CTkFrame(section, fg_color="transparent") + header.pack(fill="x", padx=40, pady=(40, 30)) + + # Section title with icon + ctk.CTkLabel( + header, + text="⏱️ Timing Settings", + font=("Chivo", 24, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ).pack(side="left") + + # Description of section purpose + ctk.CTkLabel( + header, + text="Fine-tune shooting delays", + font=("Gambetta", 14), + text_color=("#64748b", "#94a3b8"), + anchor="e" + ).pack(side="right") + + # List of settings for timing configuration + settings_list = [ + ("Min Shot Delay", "entry", "ShotDelayMin", "Minimum delay between shots (seconds)"), + ("Max Shot Delay", "entry", "ShotDelayMax", "Maximum delay between shots (seconds)"), + ("Post Shot Delay", "entry", "PostShotDelay", "Delay after shooting (seconds)") + ] + + # Create each setting item + for i, (label_text, widget_type, key, description) in enumerate(settings_list): + item_frame = create_setting_item( + section, + label_text, + description, + widget_type, + key, + main_window, + is_last=(i == len(settings_list) - 1) + ) + +def create_setting_item(parent, label_text, description, widget_type, key, main_window, is_last=False): + """Create a standardized setting item with improved styling.""" + # Frame for the setting item + item_frame = ctk.CTkFrame(parent, fg_color="transparent") + item_frame.pack(fill="x", padx=40, pady=(0, 30 if not is_last else 40)) + + # Container with hover effect + container = ctk.CTkFrame( + item_frame, + corner_radius=12, + fg_color=("#f8fafc", "#252830"), + border_width=1, + border_color=("#e2e8f0", "#374151") + ) + container.pack(fill="x", pady=(0, 0)) + + # Content frame within the container + content_frame = ctk.CTkFrame(container, fg_color="transparent") + content_frame.pack(fill="x", padx=25, pady=25) + + # Frame for label and description + label_frame = ctk.CTkFrame(content_frame, fg_color="transparent") + label_frame.pack(side="left", fill="x", expand=True) + + # Setting name label + ctk.CTkLabel( + label_frame, + text=label_text, + font=("Chivo", 16, "bold"), + text_color=("#1f2937", "#ffffff"), + anchor="w" + ).pack(fill="x", pady=(0, 4)) + + # Description of the setting + ctk.CTkLabel( + label_frame, + text=description, + font=("Gambetta", 13), + text_color=("#64748b", "#94a3b8"), + anchor="w", + wraplength=400 + ).pack(fill="x") + + # Frame for the input widget + widget_frame = ctk.CTkFrame(content_frame, fg_color="transparent") + widget_frame.pack(side="right", padx=(30, 0)) + + # Create widget based on type + if widget_type == "entry": + widget = ctk.CTkEntry( + widget_frame, + width=220, + height=45, + corner_radius=12, + border_width=2, + border_color=("#d1d5db", "#374151"), + fg_color=("#ffffff", "#1f2937"), + text_color=("#1f2937", "#ffffff"), + font=("Chivo", 14), + justify="center" + ) + if key == "TriggerKey": + main_window.trigger_key_entry = widget + widget.insert(0, main_window.triggerbot.config.get('Trigger', {}).get('TriggerKey', '')) + widget.bind("", lambda e: main_window.save_settings()) + widget.bind("", lambda e: main_window.save_settings()) + elif key == "ShotDelayMin": + main_window.min_delay_entry = widget + widget.insert(0, str(main_window.triggerbot.config.get('Trigger', {}).get('ShotDelayMin', 0.01))) + widget.bind("", lambda e: main_window.save_settings()) + widget.bind("", lambda e: main_window.save_settings()) + elif key == "ShotDelayMax": + main_window.max_delay_entry = widget + widget.insert(0, str(main_window.triggerbot.config.get('Trigger', {}).get('ShotDelayMax', 0.03))) + widget.bind("", lambda e: main_window.save_settings()) + widget.bind("", lambda e: main_window.save_settings()) + elif key == "PostShotDelay": + main_window.post_shot_delay_entry = widget + widget.insert(0, str(main_window.triggerbot.config.get('Trigger', {}).get('PostShotDelay', 0.1))) + widget.bind("", lambda e: main_window.save_settings()) + widget.bind("", lambda e: main_window.save_settings()) + widget.pack() + + elif widget_type == "checkbox": + if key == "ToggleMode": + main_window.toggle_mode_var = ctk.BooleanVar(value=main_window.triggerbot.config.get('Trigger', {}).get('ToggleMode', False)) + widget = ctk.CTkCheckBox( + widget_frame, + text="", + variable=main_window.toggle_mode_var, + width=30, + height=30, + corner_radius=8, + border_width=2, + fg_color=("#D5006D", "#E91E63"), + hover_color=("#B8004A", "#C2185B"), + checkmark_color="#ffffff", + command=main_window.save_settings + ) + elif key == "AttackOnTeammates": + main_window.attack_teammates_var = ctk.BooleanVar(value=main_window.triggerbot.config.get('Trigger', {}).get('AttackOnTeammates', False)) + widget = ctk.CTkCheckBox( + widget_frame, + text="", + variable=main_window.attack_teammates_var, + width=30, + height=30, + corner_radius=8, + border_width=2, + fg_color=("#D5006D", "#E91E63"), + hover_color=("#B8004A", "#C2185B"), + checkmark_color="#ffffff", + command=main_window.save_settings + ) + widget.pack() + + else: + raise ValueError(f"Unsupported widget type: {widget_type}") + + return item_frame \ No newline at end of file diff --git a/main.py b/main.py index 3320ab3..3321cc9 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,10 @@ import sys from classes.logger import Logger -from gui.main_window import MainWindow from classes.config_manager import ConfigManager +from gui.main_window import MainWindow + def main(): # Set up logging for the application. Logger.setup_logging() @@ -17,12 +18,12 @@ def main(): window = MainWindow() window.run() except KeyboardInterrupt: - logger.info("Application interrupted by user") + logger.debug("Application interrupted by user") except Exception as e: logger.error("Unexpected error: %s", e) sys.exit(1) finally: - logger.info("Application shutting down") + logger.debug("Application shutting down") if __name__ == "__main__": main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4f7d1c8..cfd0c7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ watchdog==6.0.0 PyGetWindow==0.0.9 orjson==3.10.18 customtkinter==5.2.2 -pillow==11.2.1 \ No newline at end of file +pillow==11.3.0 \ No newline at end of file diff --git a/src/img/background.bmp b/src/img/background.bmp deleted file mode 100644 index 4f1f81a..0000000 Binary files a/src/img/background.bmp and /dev/null differ diff --git a/src/img/background.png b/src/img/background.png deleted file mode 100644 index 8cf32a3..0000000 Binary files a/src/img/background.png and /dev/null differ diff --git a/src/img/icon.bmp b/src/img/icon.bmp deleted file mode 100644 index 1e24d13..0000000 Binary files a/src/img/icon.bmp and /dev/null differ diff --git a/src/img/icon.ico b/src/img/icon.ico index 0717eec..33781d0 100644 Binary files a/src/img/icon.ico and b/src/img/icon.ico differ diff --git a/src/img/icon.png b/src/img/icon.png index 4e85013..6031423 100644 Binary files a/src/img/icon.png and b/src/img/icon.png differ diff --git a/version.txt b/version.txt index fcd0e39..8cbe820 100644 --- a/version.txt +++ b/version.txt @@ -3,8 +3,8 @@ VSVersionInfo( ffi=FixedFileInfo( - filevers=(1, 2, 5, 0), - prodvers=(1, 2, 5, 0), + filevers=(1, 2, 5, 1), + prodvers=(1, 2, 5, 1), mask=0x3f, flags=0x0, OS=0x4, @@ -19,13 +19,15 @@ VSVersionInfo( u'040904b0', [ StringStruct(u'CompanyName', u'Jesewe'), - StringStruct(u'FileDescription', u'CS2 Triggerbot'), - StringStruct(u'FileVersion', u'1.2.5.0'), - StringStruct(u'InternalName', u'CS2 Triggerbot'), - StringStruct(u'LegalCopyright', u'(C) 2025 Jesewe. MIT License'), - StringStruct(u'OriginalFilename', u'CS2.Triggerbot.exe'), - StringStruct(u'ProductName', u'CS2 Triggerbot'), - StringStruct(u'ProductVersion', u'1.2.5.0') + StringStruct(u'FileDescription', u'VioletWing with Trigger, Overlay, Bunnyhop, and NoFlash features'), + StringStruct(u'FileVersion', u'1.2.5.1'), + StringStruct(u'InternalName', u'VioletWing'), + StringStruct(u'LegalCopyright', u'© 2025 Jesewe. Licensed under GPL-3.0 license'), + StringStruct(u'OriginalFilename', u'VioletWing.exe'), + StringStruct(u'ProductName', u'VioletWing'), + StringStruct(u'ProductVersion', u'1.2.5.1'), + StringStruct(u'Comments', u'A tool for Counter-Strike 2 with automated aiming, ESP, bunnyhop, and noflash features. For educational purposes only.'), + StringStruct(u'LegalTrademarks', u'All trademarks are property of their respective owners.'), ] ) ]