├── .github
├── README_release.md
├── create_all_in_one.py
└── workflows
│ └── release.yml
├── .gitignore
├── Examples
└── Gun.nut
├── LICENSE
├── README.md
├── SRC
├── ActionScheduler
│ ├── action.nut
│ ├── action_scheduler.nut
│ ├── event_handler.nut
│ ├── init.nut
│ └── readme.md
├── Animations
│ ├── alpha.nut
│ ├── angles.nut
│ ├── color.nut
│ ├── init.nut
│ ├── position.nut
│ └── readme.md
├── HUD
│ ├── game_text.nut
│ ├── hint_instructor.nut
│ ├── init.nut
│ └── readme.md
├── IDT
│ ├── array.nut
│ ├── entity.nut
│ ├── entity_creator.nut
│ ├── init.nut
│ ├── list.nut
│ ├── readme.md
│ └── tree_sort.nut
├── Math
│ ├── algebraic.nut
│ ├── easing_equation.nut
│ ├── init.nut
│ ├── lerp.nut
│ ├── matrix.nut
│ ├── quaternion.nut
│ ├── readme.md
│ └── utils_vector.nut
├── PCapture-Lib.nut
├── ScriptEvents
│ ├── event_listener.nut
│ ├── game_event.nut
│ ├── init.nut
│ └── readme.md
├── TracePlus
│ ├── bbox_analyzer.md
│ ├── bbox_analyzer.nut
│ ├── bboxcast.nut
│ ├── calculate_normal.md
│ ├── calculate_normal.nut
│ ├── cheap_trace.nut
│ ├── init.nut
│ ├── portal_casting.nut
│ ├── readme.md
│ ├── results.nut
│ └── trace_settings.nut
└── Utils
│ ├── console_commands.nut
│ ├── const.nut
│ ├── debug.nut
│ ├── file.nut
│ ├── improvements.nut
│ ├── init.nut
│ ├── macros.nut
│ ├── player_hooks.nut
│ ├── portals.nut
│ └── readme.md
├── Short_Documentation.md
├── Tests
├── ActionScheduler.nut
├── IDT.nut
├── Math.nut
├── ScriptEvents.nut
├── Template.nut
├── TracePlus.nut
├── Utils.nut
└── test_exec.nut
└── other
├── PcLib_snippets.code-snippets
└── logo.png
/.github/README_release.md:
--------------------------------------------------------------------------------
1 | # PCapture-Lib is your best choise!
2 | PCapture-Lib is a powerful VScripts library that supercharges your Portal 2 modding capabilities. It provides advanced features like portal-aware ray tracing, optimized collision detection, enhanced data structures, and much more, simplifying complex scripting tasks and unlocking new gameplay possibilities.
3 |
4 | ## Installation
5 |
6 | 1. **Download:** Get the latest release of `PCapture-Lib.nut`.
7 | 2. **Place:** Put the `PCapture-Lib.nut` file in your mod's `portal2/scripts/vscripts` directory. You can also place it in subdirectories within `vscripts` if you prefer (e.g., `portal2/scripts/vscripts/MyMod/PCapture-Lib.nut`).
8 | 3. **Import:** Add the following line to your VScript file to import the library:
9 |
10 | ```lua
11 | DoIncludeScript("PCapture-Lib", getroottable())
12 | ```
13 |
14 | *Note:* If you placed `PCapture-Lib.nut` in a subdirectory, adjust the import path accordingly. For example:
15 |
16 | ```lua
17 | DoIncludeScript("MyMod/PCapture-Lib", getroottable())
18 | ```
19 |
20 | That's it! You're now ready to use PCapture-Lib's features in your mod.
21 |
22 | ## Get Started
23 |
24 | Check out the full documentation and examples in the [repository](https://github.com/IaVashik/PCapture-LIB/) to learn more about what PCapture-Lib can do.
25 |
26 | **Happy modding!**
--------------------------------------------------------------------------------
/.github/create_all_in_one.py:
--------------------------------------------------------------------------------
1 | def create_all_in_one(input_file, output_file):
2 | """
3 | Creates an all-in-one version of PCapture-LIB by recursively
4 | replacing IncludeScript calls with the contents of included files.
5 |
6 | Args:
7 | input_file (str): The path to the main PCapture-LIB file.
8 | output_file (str): The path to the output file for the all-in-one version.
9 | """
10 | with open(output_file, "w", encoding="utf-8") as outfile:
11 | _process_file(input_file, outfile)
12 |
13 | def _process_file(file_path, outfile):
14 | """
15 | Recursively processes a file, replacing IncludeScript calls.
16 |
17 | Args:
18 | file_path (str): The path to the file to process.
19 | outfile (file): The output file to write the processed code to.
20 | """
21 | file_path = file_path.replace("PCapture-LIB/", "")
22 | with open(file_path, "r", encoding="utf-8") as infile:
23 | for line in infile:
24 | included_file_path = None
25 | if line.startswith("IncludeScript("):
26 | included_file_path = line[len("IncludeScript("):-2].replace('"', '').strip()
27 | if line.startswith("DoIncludeScript("):
28 | included_file_path = line[len("DoIncludeScript("):-len('", rootScope)')].replace('"', '').strip() # bruh
29 |
30 |
31 | if included_file_path is not None:
32 | if not included_file_path.endswith(".nut"):
33 | included_file_path += ".nut"
34 | _process_file(included_file_path, outfile)
35 | else:
36 | outfile.write(line)
37 | outfile.write("\n")
38 |
39 |
40 | create_all_in_one("SRC/PCapture-Lib.nut", "OUTPUT/PCapture-Lib.nut")
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release PCapture-Lib
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | release:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout repository
13 | uses: actions/checkout@v4
14 |
15 | - name: Set up Python
16 | uses: actions/setup-python@v5
17 | with:
18 | python-version: '3.x'
19 |
20 | - name: Create necessary directories
21 | run: |
22 | mkdir -p OUTPUT
23 |
24 | - name: Run all-in-one script
25 | run: |
26 | python .github/create_all_in_one.py
27 |
28 | - name: Prepare release assets
29 | run: |
30 | mkdir release
31 | cp OUTPUT/PCapture-Lib.nut release/
32 | cp .github/README_release.md release/README.md
33 | cd release && zip "PCapture-Lib All-in-One.zip" PCapture-Lib.nut README.md
34 | echo "ASSET_ALL_IN_ONE=$(pwd)/PCapture-Lib All-in-One.zip" >> $GITHUB_ENV
35 |
36 | - name: Upload release
37 | uses: softprops/action-gh-release@v1
38 | with:
39 | files: ${{ env.ASSET_ALL_IN_ONE }}
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.rc
2 | *.exe
3 | pyscripts/
--------------------------------------------------------------------------------
/Examples/Gun.nut:
--------------------------------------------------------------------------------
1 | // Initialize the PCapture-Lib library. This must be done before any library functions are used.
2 | DoIncludeScript("PCapture-Lib", getroottable())
3 |
4 | // A simple weapon class demonstrating various PCapture-Lib features.
5 | class Weapon {
6 | // A flag to prevent actions (like firing or reloading) from happening too quickly.
7 | locked = false;
8 | // Current ammo count.
9 | ammo = 10;
10 | // This will hold our HUD element to display ammo and status messages.
11 | game_text = null;
12 | // The entity (player) that owns this weapon.
13 | player = null;
14 |
15 | // The constructor is called when a new Weapon object is created.
16 | constructor(owner) {
17 | // Store the player who owns this weapon.
18 | this.player = owner
19 | // Create a ScreenText HUD element using the HUD module.
20 | // It's positioned at the bottom-center of the screen and will display for 1.1 seconds each time it's enabled.
21 | this.game_text = HUD.ScreenText(Vector(-1, -0.95, 0), "", 1.1)
22 | // Set up the input handling for firing and reloading.
23 | this.InitGameUi()
24 | }
25 |
26 | // This function sets up the system to capture player input (mouse clicks).
27 | function InitGameUi() {
28 | // Store a reference to this weapon instance ('this') to be used inside the function closures below.
29 | local weapon = this
30 | // Use entLib to create a 'game_ui' entity. This entity can capture player inputs.
31 | local gameui = entLib.CreateByClassname("game_ui", {FieldOfView = -1});
32 |
33 | // Use ConnectOutputEx to link the 'PressedAttack' output (left-click) to a VScript function.
34 | // The ':(weapon)' syntax use 'free-variable', capturing the 'weapon' variable so we can access it.
35 | gameui.ConnectOutputEx("PressedAttack", function() : (weapon) {
36 | weapon.OnPrimaryAction()
37 | })
38 | // Link 'PressedAttack2' (right-click) to the Reload function.
39 | gameui.ConnectOutputEx("PressedAttack2", function() : (weapon) {
40 | weapon.Reload()
41 | })
42 |
43 | // Activate the game_ui entity for the specific player, so it starts listening for their input.
44 | EntFireByHandle(gameui, "Activate", "", 0, this.player)
45 | }
46 |
47 | // A function to prevent the player from firing or reloading for a set amount of time.
48 | function LockInterface(delay) {
49 | // If already locked, do nothing.
50 | if(this.locked) return dev.warning("Already locked")
51 | // Lock the interface immediately.
52 | this.locked = true
53 |
54 | // Use the ActionScheduler to unlock the interface after the specified delay.
55 | ScheduleEvent.Add(
56 | "global", // A general-purpose event name.
57 | function() { // The action to execute when the timer finishes.
58 | this.locked = false
59 | },
60 | delay, // The time in seconds to wait.
61 | null, // Optional arguments for the action function (none needed here).
62 | this // The 'scope'. This is crucial for 'this.locked' to refer to the weapon instance.
63 | )
64 | }
65 |
66 | // This function is called when the player left-clicks.
67 | function OnPrimaryAction() {
68 | // Don't fire if the weapon is locked (e.g., during reload or between shots).
69 | if(this.locked) return
70 |
71 | this.ammo -= 1
72 | // Update the HUD text with the new ammo count and display it by calling Enable().
73 | this.game_text.SetText("Ammo: " + this.ammo + "/10").Enable()
74 | // Automatically reload if out of ammo.
75 | if(this.ammo == 0) this.Reload()
76 |
77 | // Play a firing sound on the player.
78 | this.player.EmitSound("weapons/wheatley/wheatley_fire_02.wav") // placeholder
79 |
80 | // Perform a precise BBox raycast from the player's eyes that correctly interacts with portals.
81 | local trace = TracePlus.FromEyes.PortalBbox(3000, this.player)
82 | // Use the dev utility to draw a small box at the hit position for debugging.
83 | dev.drawbox(trace.GetHitpos(), Vector(50, 25, 25), 0.7)
84 | // If the trace hit a world brush (like a wall), do nothing further.
85 | if(trace.DidHitWorld()) return
86 |
87 | // Get the entity that was hit by the trace.
88 | local hitEnt = trace.GetEntity()
89 | // Fire the 'Break' input on the hit entity (this is a placeholder for damage logic).
90 | EntFireByHandle(hitEnt, "Break")
91 |
92 | // Lock the weapon for 0.5 seconds to control the fire rate.
93 | this.LockInterface(0.5)
94 | }
95 |
96 | // This function is called when the player right-clicks or runs out of ammo.
97 | function Reload() {
98 | // Don't reload if already locked.
99 | if(this.locked) return
100 |
101 | // Update HUD to show 'Reloading...'.
102 | this.game_text.SetText("Reloading...").Enable()
103 | // Lock the weapon for the duration of the reload animation/sound.
104 | this.LockInterface(1.3)
105 | // Reset the ammo count.
106 | this.ammo = 10
107 | // Play a reload sound.
108 | this.player.EmitSound("weapons/wheatley/sphere_firing_powerup_01.wav") // placeholder
109 | }
110 | }
111 |
112 | // Use the macros utility to precache sounds, ensuring they play without delay the first time.
113 | macros.Precache([
114 | "weapons/wheatley/sphere_firing_powerup_01.wav",
115 | "weapons/wheatley/wheatley_fire_02.wav"
116 | ])
117 |
118 | // --- TESTING ---
119 | // Create an instance of the Weapon class for the current player to start the script.
120 | Weapon(GetPlayer())
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2024, IaVashik
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | Unleash the Full Potential of Portal 2 Scripting with PCapture-Lib!
6 |
7 |
8 |
9 | 
10 |
11 | **PCapture-Lib v3.8 is a groundbreaking VScripts library for Portal 2, empowering modders to create truly innovative and immersive gameplay experiences.** It offers a most powerful suite of tools and enhancements, simplifying complex scripting challenges and allowing you to focus on bringing your creative vision to life. Whether you're a seasoned modder or just starting out, PCapture-Lib will elevate your mod projects to the next level.
12 |
13 | ---
14 |
15 | Killer Features
16 |
17 |
18 | * **Advanced Ray Tracing with Portal Support:** Seamlessly integrate portal interactions into your raycasts with `TracePlus.PortalBbox` (precise) and `TracePlus.PortalCheap` (fast). This game-changing feature opens up a world of possibilities for portal-based mechanics, previously unattainable with standard VScripts.
19 |
20 | * **Optimized Bounding Box TraceCasting:** Perform highly efficient collision detection with `TracePlus.Bbox`, utilizing advanced techniques like segment-based search, entity filtering and caching, and optional binary refinement. This ensures smooth performance even in complex map scenarios.
21 |
22 | * **Powerful `CBaseEntity` Wrapper:** The `pcapEntity` class, accessible through `entLib`, provides an extensive set of methods for manipulating entity properties, controlling animations, handling sounds, managing outputs and inputs, and much more. This dramatically simplifies entity scripting and allows for advanced entity control.
23 |
24 | * **Asynchronous Event Scheduling:** The `ActionScheduler` module offers a robust event scheduling system with support for delayed actions, intervals, and asynchronous operations using the `yield` keyword. This allows for more flexible and responsive scripting compared to standard VScripts mechanisms.
25 |
26 | * **Enhanced Data Structures:** Leverage powerful data structures like `ArrayEx`, `List`, and `AVLTree` from the `IDT` module to manage and manipulate data efficiently. These additions provide significant improvements over standard VScripts arrays and offer more flexibility for complex data handling.
27 |
28 | * **Comprehensive Math Module:** Utilize a rich set of mathematical functions and objects, including quaternions (`Quaternion`), matrices (`Matrix`), linear interpolation (`lerp`), and easing functions (`ease`), for complex calculations and animations.
29 |
30 | * **File Operations and Utilities:** Streamline file reading and writing with the `File` class and access various utility functions for tasks like logging, player hooks, portal management, and more.
31 |
32 | * **Entity Script Animations:** Easily create smooth animations for entity alpha (opacity), color and etc. using functions like `AlphaTransition`, `ColorTransition`, `PositionTransition`... Real-time animation variants are also available for more dynamic control.
33 |
34 | * **HUD Elements:** Create custom HUD elements like screen text (`ScreenText`) and instructor hints (`HintInstructor`) to provide feedback and guidance to players.
35 |
36 | ### And more! You can find the whole list [here](Short_Documentation.md)
37 |
38 |
39 |
40 | ---
41 |
42 | # Getting Started
43 |
44 | There are two ways to install **PCapture-Lib**: you can download the latest release (recommended) or install directly from the source.
45 |
46 | ## Installation from Release (Recommended)
47 |
48 | 1. **Download:** Get the latest release from the [Releases](https://github.com/IaVashik/PCapture-LIB/releases) page.
49 | 2. **Copy:** Extract and copy the `PCapture-Lib.nut` file into your Portal 2 VScripts directory:
50 | `Portal 2/portal2/scripts/vscripts/`
51 | *Note: Although you can place the file in any subdirectory within `vscripts`, placing it directly in `vscripts` helps avoid confusion for beginner developers.*
52 | 3. **Initialization:** In your VScripts, include the library using:
53 | ```lua
54 | DoIncludeScript("PCapture-Lib", getroottable())
55 | ```
56 |
57 | ## Installation from Source
58 |
59 | 1. **Clone:** Clone the repository into your Portal 2 scripts directory:
60 | `Portal 2/portal2/scripts/vscripts/`
61 | 2. **Initialization:** In your VScripts, include the library using:
62 | ```lua
63 | IncludeScript("PCapture-Lib/SRC/PCapture-Lib")
64 | ```
65 |
66 | ## Documentation
67 |
68 | [Short_Documentation.md](Short_Documentation.md) provides a quick overview of available methods for each module. For detailed explanations, including argument descriptions and code examples, refer to the `readme.md` files inside `SRC/*/readme.md`. Each module section in *Short_Documentation* contains anchors linking to its detailed documentation.
69 |
70 |
71 | # About
72 |
73 | PCapture-Lib was originally developed as part of the Project Capture modification, but it has evolved into a standalone library to benefit the entire Portal 2 modding community. By addressing common scripting challenges and providing powerful tools, PCapture-Lib aims to empower modders of all levels to create more ambitious and innovative maps.
74 |
75 | ## Contributing
76 |
77 | Contributions to PCapture-Lib are welcome and encouraged! If you encounter any bugs, have feature requests, or would like to contribute code improvements, please open an issue or submit a pull request on the [GitHub repository](https://github.com/iaVashik/PCapture-LIB/).
78 |
79 | ## License
80 |
81 | This library was created by [laVashik](https://lavashik.lol/). **Credit is required when using this library in your projects**
82 |
83 | Licensed under the BSD 3-Clause License - see the [LICENSE](LICENSE) file for details.
--------------------------------------------------------------------------------
/SRC/ActionScheduler/action.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Represents a scheduled action.
3 | *
4 | * This class encapsulates information about an action that is scheduled to be executed at a specific time.
5 | */
6 | ::ScheduleAction <- class {
7 | // The entity that created the event.
8 | scope = null;
9 | // The action to execute when the scheduled time arrives.
10 | action = null;
11 | // The time at which the action is scheduled to be executed.
12 | executionTime = null;
13 | // Optional arguments to pass to the action function.
14 | args = null;
15 |
16 | /*
17 | * Constructor for a ScheduleAction object.
18 | *
19 | * @param {pcapEntity} scope - The entity that created the event.
20 | * @param {string|function} action - The action to execute when the event is triggered.
21 | * @param {number} delay - The time at which the event is scheduled to be executed.
22 | * @param {array|null} args - Optional arguments to pass to the action function.
23 | */
24 | constructor(scope, action, delay, args = null) {
25 | this.scope = scope
26 | this.action = action
27 | this.executionTime = delay + Time()
28 |
29 | this.args = args
30 | }
31 |
32 | /*
33 | * Executes the scheduled action.
34 | *
35 | * If the action is a string, it is compiled into a function before execution.
36 | * If arguments are provided, they are passed to the action function.
37 | *
38 | * @returns {any} - The result of the action function.
39 | */
40 | function run() {
41 | if(type(this.action) == "string")
42 | this.action = compilestring(action)
43 |
44 | if(this.args == null) {
45 | return this.action.call(scope)
46 | }
47 |
48 | // SAFETY: checks are now in action_scheluder
49 | // if(typeof this.args != "array" && typeof this.args != "ArrayEx" && typeof this.args != "List") {
50 | // // throw("Invalid arguments for ScheduleEvent! The argument must be itterable, not (" + args + ")")
51 | // }
52 |
53 | local actionArgs = [this.scope]
54 | actionArgs.extend(this.args)
55 | return action.acall(actionArgs)
56 | }
57 |
58 | /*
59 | * Processes a generator action.
60 | *
61 | * @param {Generator} generator - The generator to be processed.
62 | * @returns {any} - The result of processing the generator.
63 | */
64 | function processGenerator(generator, eventName) {
65 | if(generator.getstatus() == "dead") return
66 | local delay = resume generator
67 | if(delay == null) return
68 |
69 | try {delay = delay.tofloat()}
70 | catch(err) {throw "Invalid value for sleep. " + err}
71 |
72 | // todo Optimization: can edit this, change its time, and move in queue
73 | if(eventName in ScheduleEvent.eventsList)
74 | ScheduleEvent.Add(eventName, generator, delay, null, this.scope)
75 | }
76 |
77 | function GetInfo() return "[Scope] " + scope + "\n[Action] " + action + "\n[executionTime] " + executionTime
78 | function _typeof() return "ScheduleAction"
79 | function _tostring() return "ScheduleAction: (" + this.executionTime + ")"
80 | function _cmp(other) {
81 | if (this.executionTime > other.executionTime) return 1;
82 | else if (this.executionTime < other.executionTime) return -1;
83 | return 0;
84 | }
85 | }
--------------------------------------------------------------------------------
/SRC/ActionScheduler/action_scheduler.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Adds a single action to a scheduled event with the specified name.
3 | *
4 | * @param {string|any} eventName - The name of the event to add the action to. If the event does not exist, it is created.
5 | * @param {string|function} action - The action to execute when the scheduled time arrives. This can be a string containing VScripts code or a function object.
6 | * @param {number} timeDelay - The delay in seconds before executing the action.
7 | * @param {array|null} args - An optional array of arguments to pass to the action function.
8 | * @param {object} scope - The scope in which to execute the action (default is `this`).
9 | */
10 | ScheduleEvent["Add"] <- function(eventName, action, timeDelay, args = null, scope = this) {
11 | if (typeof action != "string" && typeof action != "function" && typeof action != "native function" && typeof action != "generator") throw("ScheduleEvent.Add: 'action' must be a function or a string, but got " + typeof action);
12 | if (typeof timeDelay != "integer" && typeof timeDelay != "float") throw("ScheduleEvent.Add: 'timeDelay' must be a number, but got " + typeof timeDelay);
13 | local argsType = typeof args;
14 | if(args && argsType != "array" && argsType != "ArrayEx" && argsType != "List") throw("ScheduleEvent.Add: 'args' must be an array, List, or null, but got " + typeof args)
15 |
16 | if ( !(eventName in ScheduleEvent.eventsList) ) {
17 | ScheduleEvent.eventsList[eventName] <- List()
18 | dev.trace("Created new Event - \"{}\"", eventName)
19 | }
20 |
21 | local newScheduledEvent = ScheduleAction(scope, action, timeDelay, args)
22 | local currentActionList = ScheduleEvent.eventsList[eventName]
23 |
24 |
25 | if(currentActionList.len() == 0 || currentActionList.top() <= newScheduledEvent) {
26 | currentActionList.append(newScheduledEvent)
27 | return
28 | }
29 |
30 | //* --- A binary search.
31 | local low = 0
32 | local high = currentActionList.len() - 1
33 | local mid
34 |
35 | local tempActionArr = currentActionList.toarray()
36 | while (low <= high) {
37 | mid = (low + high) / 2
38 | if (tempActionArr[mid] < newScheduledEvent) {
39 | low = mid + 1
40 | }
41 | else if (tempActionArr[mid] > newScheduledEvent) {
42 | high = mid - 1
43 | }
44 | else {
45 | low = mid
46 | break
47 | }
48 | }
49 |
50 | currentActionList.insert(low, newScheduledEvent)
51 | }
52 |
53 | /*
54 | * Adds an action to a scheduled event that will be executed repeatedly at a fixed interval.
55 | *
56 | * @param {string|any} eventName - The name of the event to add the interval to. If the event does not exist, it is created.
57 | * @param {string|function} action - The action to execute at each interval.
58 | * @param {number} interval - The time interval in seconds between executions of the action.
59 | * @param {number} initialDelay - The initial delay in seconds before the first execution of the action (default is 0).
60 | * @param {array|null} args - An optional array of arguments to pass to the action function.
61 | * @param {object} scope - The scope in which to execute the action (default is `this`).
62 | */
63 | ScheduleEvent["AddInterval"] <- function(eventName, action, interval, initialDelay = 0, args = null, scope = this) {
64 | if (typeof action != "string" && typeof action != "function" && typeof action != "native function" && typeof action != "generator") throw("ScheduleEvent.AddInterval: 'action' must be a function or a string, but got " + typeof action);
65 | if (typeof interval != "integer" && typeof interval != "float") throw("ScheduleEvent.AddInterval: 'interval' must be a number, but got " + typeof interval);
66 | if (typeof initialDelay != "integer" && typeof initialDelay != "float") throw("ScheduleEvent.AddInterval: 'initialDelay' must be a number, but got " + typeof initialDelay);
67 | local argsType = typeof args;
68 | if(args && argsType != "array" && argsType != "ArrayEx" && argsType != "List") throw("ScheduleEvent.AddInterval: 'args' must be an array, List, or null, but got " + typeof args)
69 |
70 | ScheduleEvent.Add(eventName, action, initialDelay, args, scope)
71 | ScheduleEvent.Add(eventName, ScheduleEvent.AddInterval, initialDelay + interval, [eventName, action, interval, 0, args, scope], scope)
72 | }
73 |
74 | /*
75 | * Adds multiple actions to a scheduled event, ensuring they are sorted by execution time.
76 | *
77 | * @param {string|any} eventName - The name of the event to add the actions to. If the event does not exist, it is created.
78 | * @param {array|List} actions - An array or List of `ScheduleAction` objects to add to the event.
79 | * @param {boolean} noSort - If true, the actions will not be sorted by execution time (default is false).
80 | *
81 | * **Caution:** Use the `noSort` parameter with care. Incorrect usage may lead to unexpected behavior or break the event scheduling logic.
82 | */
83 | ScheduleEvent["AddActions"] <- function(eventName, actions, noSort = false) {
84 | local actionsType = typeof actions;
85 | if (actionsType != "array" && actionsType != "List" && actionsType != "ArrayEx") throw("ScheduleEvent.AddActions: 'actions' must be an array or a List, but got " + actionsType);
86 | if (actions.len() > 0) {
87 | if (typeof actions[0] != "ScheduleAction") {
88 | dev.warning("ScheduleEvent.AddActions: The 'actions' container does not appear to hold ScheduleAction objects. This might cause errors.");
89 | }
90 | }
91 |
92 | if (eventName in ScheduleEvent.eventsList ) {
93 | ScheduleEvent.eventsList[eventName].extend(actions)
94 | ScheduleEvent.eventsList[eventName].sort()
95 | if(developer() > 0) dev.trace("Added {} actions to Event \"{}\".", actions.len(), eventName)
96 | return
97 | }
98 |
99 | if(!noSort) actions.sort()
100 |
101 | if(typeof actions == "List") {
102 | ScheduleEvent.eventsList[eventName] <- actions
103 | } else {
104 | ScheduleEvent.eventsList[eventName] <- List.FromArray(actions)
105 | }
106 |
107 | if(developer() > 0) dev.trace("Created new Event \"{}\" with {} actions.", eventName, actions.len())
108 | }
109 |
110 |
111 | /*
112 | * Cancels a scheduled event with the given name, optionally after a delay.
113 | *
114 | * @param {string|any} eventName - The name of the event to cancel.
115 | * @param {number} delay - An optional delay in seconds before canceling the event.
116 | */
117 | ScheduleEvent["Cancel"] <- function(eventName, delay = 0) {
118 | if(eventName == "global")
119 | throw("The global event cannot be closed!")
120 | if(!(eventName in ScheduleEvent.eventsList))
121 | throw("There is no event named " + eventName)
122 | if(delay > 0)
123 | return ScheduleEvent.Add("global", format("ScheduleEvent.Cancel(\"%s\")", eventName), delay)
124 |
125 | ScheduleEvent.eventsList.rawdelete(eventName)
126 |
127 | if(developer() > 0) dev.trace("Event \"{}\" closed! Actial events: {}", eventName, macros.GetKeys(ScheduleEvent.eventsList))
128 | }
129 |
130 | /*
131 | * Attempts to cancel a scheduled event with the given name, optionally after a delay.
132 | *
133 | * @param {string} eventName - The name of the event to cancel.
134 | * @param {number} delay - An optional delay in seconds before attempting to cancel the event.
135 | * @returns {boolean} - True if the event was found and canceled, false otherwise.
136 | *
137 | * This function is similar to `ScheduleEvent.Cancel`, but it does not throw an error if the event is not found.
138 | * It's useful for situations where you're unsure if an event exists and want to attempt cancellation without risking errors.
139 | */
140 | ScheduleEvent["TryCancel"] <- function(eventName, delay = 0) {
141 | if(delay > 0)
142 | return ScheduleEvent.Add("global", ScheduleEvent.TryCancel, delay, [eventName])
143 | local isValid = ScheduleEvent.IsValid(eventName)
144 | if(isValid) ScheduleEvent.Cancel(eventName)
145 | return isValid
146 | }
147 |
148 |
149 | /*
150 | * Cancels all scheduled actions that match the given action, optionally after a delay.
151 | *
152 | * @param {functioan} action - The action to cancel.
153 | * @param {number} delay - An optional delay in seconds before canceling the actions.
154 | */
155 | ScheduleEvent["CancelByAction"] <- function(action, delay = 0) {
156 | if(delay > 0)
157 | return ScheduleEvent.Add("global", format("ScheduleEvent.Cancel(\"%s\")", eventName), delay)
158 |
159 | foreach(name, events in ScheduleEvent.eventsList) {
160 | foreach(eventAction in events) {
161 | if(eventAction.action == action) {
162 | events.remove(eventAction)
163 | dev.trace("\"{}\" was deleted from \"{}\"", eventAction, name)
164 | }
165 | }
166 | }
167 | }
168 |
169 | /*
170 | * Cancels all scheduled events and actions, effectively clearing the event scheduler.
171 | */
172 | ScheduleEvent["CancelAll"] <- function() {
173 | ScheduleEvent.eventsList = {global = List()}
174 | dev.trace("All scheduled events have been canceled!")
175 | }
176 |
177 |
178 | /*
179 | * Gets info about a scheduled event.
180 | *
181 | * @param {string} eventName - Name of event to get info for.
182 | * @returns {List|null} - The event info object or null if not found.
183 | */
184 | ScheduleEvent["GetEvent"] <- function(eventName) {
185 | return eventName in ScheduleEvent.eventsList ? ScheduleEvent.eventsList[eventName] : null
186 | }
187 |
188 |
189 | /*
190 | * Checks if event is valid
191 | *
192 | * @param {string} eventName - Name of event to get info for.
193 | * @returns {bool} - Object exists or not.
194 | */
195 | ScheduleEvent["IsValid"] <- function(eventName) {
196 | return eventName in ScheduleEvent.eventsList && ScheduleEvent.eventsList[eventName].len() != 0
197 | }
--------------------------------------------------------------------------------
/SRC/ActionScheduler/event_handler.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Executes scheduled events when their time is up.
3 | *
4 | * This function iterates over all scheduled events and checks if their scheduled execution time has arrived.
5 | * If so, it executes the event's action and removes it from the list of scheduled events.
6 | * The function then schedules itself to run again after a short delay to continue processing events.
7 | */
8 | ::ScheduledEventLoop <- function() {
9 | local time = Time() + 0.0001
10 | local eventsToDelete = List() // Buffer for safe deletion
11 |
12 | // Iterate over each event name and its corresponding event list.
13 | foreach(eventName, eventInfo in ScheduleEvent.eventsList) {
14 | local event
15 | // Process events until the list is empty or the next event's time hasn't arrived yet.
16 | while(eventInfo.length > 0 && time >= (event = eventInfo.first()).executionTime) {
17 | eventInfo.remove(0)
18 | try {
19 | local gtor = event.action
20 | if(typeof event.action == "generator" || typeof (gtor = event.run()) == "generator") {
21 | event.processGenerator(gtor, eventName)
22 | }
23 | }
24 | catch(exception) {
25 | //* Stack unwinding
26 | macros.fprint("AN ERROR HAS OCCURED IN ScheduleEvent [{}]", exception)
27 | printl("\nCALLSTACK")
28 | local stack
29 | for(local i = 1; stack = getstackinfos(i); i++)
30 | macros.fprint("*FUNCTION [{}()] {} line [{}]", stack.func, stack.src, stack.line)
31 |
32 | macros.fprint("\nSCHEDULED EVENT\n[Name] {}\n{}\n[Exception] {}\n[Event Action List] {}", eventName, event.GetInfo(), exception, ScheduleEvent.eventsList[eventName])
33 |
34 | if(type(event.action) == "function" || type(event.action) == "native function") {
35 | printl("\nFUNCTION INFO")
36 | foreach(key, val in event.action.getinfos()) {
37 | if(type(val) == "array") val = ArrayEx.FromArray(val)
38 | macros.fprint("[{}] {}", key.toupper(), val)
39 | }
40 | }
41 |
42 | SendToConsole("playvol resource/warning.wav 1")
43 | }
44 | }
45 | if(eventName != "global" && eventInfo.length == 0) {
46 | eventsToDelete.append(eventName)
47 | }
48 | }
49 |
50 | // Safely delete events after completion of the main loop.
51 | foreach (eventName in eventsToDelete.iter()) {
52 | if (eventName in ScheduleEvent.eventsList) {
53 | ScheduleEvent.eventsList.rawdelete(eventName)
54 | if(developer() > 0) dev.trace("Event {} closed.", eventName)
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/SRC/ActionScheduler/init.nut:
--------------------------------------------------------------------------------
1 | /*+--------------------------------------------------------------------------------+
2 | | PCapture Vscripts Library |
3 | +----------------------------------------------------------------------------------+
4 | | Author: |
5 | | Event Orchestrator - laVashik ・人・ |
6 | +----------------------------------------------------------------------------------+
7 | | The Events module, offering an advanced event scheduling and management system |
8 | | for creating complex, timed events with precision and control. |
9 | +----------------------------------------------------------------------------------+ */
10 |
11 |
12 | ::ScheduleEvent <- {
13 | // Object to store scheduled events
14 | eventsList = {global = List()},
15 |
16 | Add = null,
17 | AddActions = null,
18 | AddInterval = null,
19 |
20 | Cancel = null,
21 | CancelByAction = null,
22 | CancelAll = null,
23 |
24 | GetEvent = null,
25 |
26 | IsValid = null,
27 | }
28 |
29 | IncludeScript("PCapture-LIB/SRC/ActionScheduler/action")
30 | IncludeScript("PCapture-LIB/SRC/ActionScheduler/action_scheduler")
31 | IncludeScript("PCapture-LIB/SRC/ActionScheduler/event_handler")
32 |
33 | // Create a logic_timer to process the event loop.
34 | local timer = entLib.CreateByClassname("logic_timer", {RefireTime=0.001, targetname="@ScheduledEventLoop"})
35 | timer.ConnectOutput("OnTimer", "ScheduledEventLoop")
36 | EntFire("@ScheduledEventLoop", "Enable")
--------------------------------------------------------------------------------
/SRC/Animations/alpha.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Creates an animation that transitions the alpha (opacity) of entities over time.
3 | *
4 | * @param {array|CBaseEntity|pcapEntity} entities - The entities to animate.
5 | * @param {number} startOpacity - The starting opacity value (0-255).
6 | * @param {number} endOpacity - The ending opacity value (0-255).
7 | * @param {number} time - The duration of the animation in seconds.
8 | * @param {table} animSetting - A table containing additional animation settings. (optional)
9 | * @returns {number} The duration of the animation in seconds.
10 | */
11 | animate["AlphaTransition"] <- function(entities, startOpacity, endOpacity, time, animSetting = {}) {
12 | if (typeof time != "integer" && typeof time != "float") throw("AlphaTransition: 'time' argument must be a number, but got " + typeof time);
13 | if (typeof animSetting != "table") throw("AlphaTransition: 'animSetting' argument must be a table, but got " + typeof animSetting);
14 |
15 | local animSetting = AnimEvent("alpha", animSetting, entities, time)
16 | local vars = {
17 | startOpacity = startOpacity,
18 | opacityDelta = endOpacity - startOpacity,
19 | easeFunc = animSetting.easeFunc
20 | }
21 |
22 | animate.applyAnimation(
23 | animSetting,
24 | function(step, steps, v) {return v.startOpacity + v.opacityDelta * v.easeFunc(step / steps)},
25 | function(ent, newAlpha) {ent.SetAlpha(newAlpha)},
26 | vars
27 | )
28 |
29 | return animSetting.delay
30 | }
31 |
32 | animate.RT["AlphaTransition"] <- function(entities, startOpacity, endOpacity, time, animSetting = {}) {
33 | if (typeof time != "integer" && typeof time != "float") throw("AlphaTransition: 'time' argument must be a number, but got " + typeof time);
34 | if (typeof animSetting != "table") throw("AlphaTransition: 'animSetting' argument must be a table, but got " + typeof animSetting);
35 |
36 | local animSetting = AnimEvent("alpha", animSetting, entities, time)
37 | local vars = {
38 | startOpacity = startOpacity,
39 | opacityDelta = endOpacity - startOpacity,
40 | easeFunc = animSetting.easeFunc
41 | }
42 |
43 | animate.applyRTAnimation(
44 | animSetting,
45 | function(step, steps, v) {return v.startOpacity + v.opacityDelta * v.easeFunc(step / steps)},
46 | function(ent, newAlpha) {ent.SetAlpha(newAlpha)},
47 | vars
48 | )
49 |
50 | return animSetting.delay
51 | }
--------------------------------------------------------------------------------
/SRC/Animations/angles.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Creates an animation that transitions the angles of entities over time.
3 | *
4 | * @param {array|CBaseEntity|pcapEntity} entities - The entities to animate.
5 | * @param {Vector} startAngles - The starting angles.
6 | * @param {Vector} endAngles - The ending angles.
7 | * @param {number} time - The duration of the animation in seconds.
8 | * @param {table} animSetting - A table containing additional animation settings. (optional)
9 | * @returns {number} The duration of the animation in seconds.
10 | */
11 | animate["AnglesTransitionByTime"] <- function(entities, startAngles, endAngles, time, animSetting = {}) {
12 | if (typeof startAngles != "Vector") throw("AnglesTransitionByTime: 'startAngles' argument must be a Vector, but got " + typeof startAngles);
13 | if (typeof endAngles != "Vector") throw("AnglesTransitionByTime: 'endAngles' argument must be a Vector, but got " + typeof endAngles);
14 | if (typeof time != "integer" && typeof time != "float") throw("AnglesTransitionByTime: 'time' argument must be a number, but got " + typeof time);
15 | if (typeof animSetting != "table") throw("AnglesTransitionByTime: 'animSetting' argument must be a table, but got " + typeof animSetting);
16 |
17 | local animSetting = AnimEvent("angles", animSetting, entities, time)
18 |
19 | local deltaAngleX = ((((endAngles.x - startAngles.x) % 360) + 540) % 360) - 180;
20 | local deltaAngleY = ((((endAngles.y - startAngles.y) % 360) + 540) % 360) - 180;
21 | local deltaAngleZ = ((((endAngles.z - startAngles.z) % 360) + 540) % 360) - 180;
22 |
23 | local vars = {
24 | startAngles = startAngles,
25 | angleDelta = Vector(deltaAngleX, deltaAngleY, deltaAngleZ),
26 | easeFunc = animSetting.easeFunc
27 | }
28 |
29 | animate.applyAnimation(
30 | animSetting,
31 | function(step, steps, v){return v.startAngles + v.angleDelta * v.easeFunc(step / steps)},
32 | function(ent, newAngle) {ent.SetAbsAngles(newAngle)},
33 | vars
34 | )
35 |
36 | return animSetting.delay
37 | }
38 |
39 | animate.RT["AnglesTransitionByTime"] <- function(entities, startAngles, endAngles, time, animSetting = {}) {
40 | if (typeof startAngles != "Vector") throw("AnglesTransitionByTime: 'startAngles' argument must be a Vector, but got " + typeof startAngles);
41 | if (typeof endAngles != "Vector") throw("AnglesTransitionByTime: 'endAngles' argument must be a Vector, but got " + typeof endAngles);
42 | if (typeof time != "integer" && typeof time != "float") throw("AnglesTransitionByTime: 'time' argument must be a number, but got " + typeof time);
43 | if (typeof animSetting != "table") throw("AnglesTransitionByTime: 'animSetting' argument must be a table, but got " + typeof animSetting);
44 |
45 | local animSetting = AnimEvent("angles", animSetting, entities, time)
46 |
47 | local deltaAngleX = ((((endAngles.x - startAngles.x) % 360) + 540) % 360) - 180;
48 | local deltaAngleY = ((((endAngles.y - startAngles.y) % 360) + 540) % 360) - 180;
49 | local deltaAngleZ = ((((endAngles.z - startAngles.z) % 360) + 540) % 360) - 180;
50 |
51 | local vars = {
52 | startAngles = startAngles,
53 | angleDelta = Vector(deltaAngleX, deltaAngleY, deltaAngleZ),
54 | easeFunc = animSetting.easeFunc
55 | }
56 |
57 | animate.applyRTAnimation(
58 | animSetting,
59 | function(step, steps, v){return v.startAngles + v.angleDelta * v.easeFunc(step / steps)},
60 | function(ent, newAngle) {ent.SetAbsAngles(newAngle)},
61 | vars
62 | )
63 |
64 | return animSetting.delay
65 | }
--------------------------------------------------------------------------------
/SRC/Animations/color.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Creates an animation that transitions the color of entities over time.
3 | *
4 | * @param {array|CBaseEntity|pcapEntity} entities - The entities to animate.
5 | * @param {string|Vector} startColor - The starting color as a string (e.g., "255 0 0") or a Vector.
6 | * @param {string|Vector} endColor - The ending color as a string or a Vector.
7 | * @param {number} time - The duration of the animation in seconds.
8 | * @param {table} animSetting - A table containing additional animation settings. (optional)
9 | * @returns {number} The duration of the animation in seconds.
10 | */
11 | animate["ColorTransition"] <- function(entities, startColor, endColor, time, animSetting = {}) {
12 | if (typeof startColor != "Vector" && typeof endColor != "string") throw("ColorTransition: 'startColor' argument must be a Vector, but got " + typeof startColor);
13 | if (typeof endColor != "Vector" && typeof endColor != "string") throw("ColorTransition: 'endColor' argument must be a Vector, but got " + typeof endColor);
14 | if (typeof time != "integer" && typeof time != "float") throw("ColorTransition: 'time' argument must be a number, but got " + typeof time);
15 | if (typeof animSetting != "table") throw("ColorTransition: 'animSetting' argument must be a table, but got " + typeof animSetting);
16 |
17 | local animSetting = AnimEvent("color", animSetting, entities, time)
18 | local vars = {
19 | startColor = startColor,
20 | endColor = endColor,
21 | easeFunc = animSetting.easeFunc
22 | }
23 |
24 | animate.applyAnimation(
25 | animSetting,
26 | function(step, transitionFrames, v) {return math.lerp.color(v.startColor, v.endColor, v.easeFunc(step / transitionFrames))},
27 | function(ent, newColor) {ent.SetColor(newColor)},
28 | vars
29 | )
30 |
31 | return animSetting.delay
32 | }
33 |
34 | animate.RT["ColorTransition"] <- function(entities, startColor, endColor, time, animSetting = {}) {
35 | if (typeof startColor != "Vector" && typeof endColor != "string") throw("ColorTransition: 'startColor' argument must be a Vector, but got " + typeof startColor);
36 | if (typeof endColor != "Vector" && typeof endColor != "string") throw("ColorTransition: 'endColor' argument must be a Vector, but got " + typeof endColor);
37 | if (typeof time != "integer" && typeof time != "float") throw("ColorTransition: 'time' argument must be a number, but got " + typeof time);
38 | if (typeof animSetting != "table") throw("ColorTransition: 'animSetting' argument must be a table, but got " + typeof animSetting);
39 |
40 | local animSetting = AnimEvent("color", animSetting, entities, time)
41 | local vars = {
42 | startColor = startColor,
43 | endColor = endColor,
44 | easeFunc = animSetting.easeFunc
45 | }
46 |
47 | animate.applyRTAnimation(
48 | animSetting,
49 | function(step, transitionFrames, v) {return math.lerp.color(v.startColor, v.endColor, v.easeFunc(step / transitionFrames))},
50 | function(ent, newColor) {ent.SetColor(newColor)},
51 | vars
52 | )
53 |
54 | return animSetting.delay
55 | }
--------------------------------------------------------------------------------
/SRC/Animations/position.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Creates an animation that transitions the position of entities over time.
3 | *
4 | * @param {array|CBaseEntity|pcapEntity} entities - The entities to animate.
5 | * @param {Vector} startPos - The starting position.
6 | * @param {Vector} endPos - The ending position.
7 | * @param {number} time - The duration of the animation in seconds.
8 | * @param {table} animSetting - A table containing additional animation settings. (optional)
9 | * @returns {number} The duration of the animation in seconds.
10 | */
11 | animate["PositionTransitionByTime"] <- function(entities, startPos, endPos, time, animSetting = {}) {
12 | if (typeof startPos != "Vector") throw("PositionTransitionByTime: 'startPos' argument must be a Vector, but got " + typeof startPos);
13 | if (typeof endPos != "Vector") throw("PositionTransitionByTime: 'endPos' argument must be a Vector, but got " + typeof endPos);
14 | if (typeof time != "integer" && typeof time != "float") throw("PositionTransitionByTime: 'time' argument must be a number, but got " + typeof time);
15 | if (typeof animSetting != "table") throw("PositionTransitionByTime: 'animSetting' argument must be a table, but got " + typeof animSetting);
16 |
17 | local animSetting = AnimEvent("position", animSetting, entities, time)
18 | local vars = {
19 | startPos = startPos,
20 | dist = endPos - startPos,
21 | easeFunc = animSetting.easeFunc
22 | }
23 |
24 | animate.applyAnimation(
25 | animSetting,
26 | function(step, steps, v) {return v.startPos + v.dist * v.easeFunc(step / steps)},
27 | function(ent, newPosition) {ent.SetAbsOrigin(newPosition)},
28 | vars
29 | )
30 |
31 | return animSetting.delay
32 | }
33 |
34 | animate.RT["PositionTransitionByTime"] <- function(entities, startPos, endPos, time, animSetting = {}) {
35 | if (typeof startPos != "Vector") throw("PositionTransitionByTime: 'startPos' argument must be a Vector, but got " + typeof startPos);
36 | if (typeof endPos != "Vector") throw("PositionTransitionByTime: 'endPos' argument must be a Vector, but got " + typeof endPos);
37 | if (typeof time != "integer" && typeof time != "float") throw("PositionTransitionByTime: 'time' argument must be a number, but got " + typeof time);
38 | if (typeof animSetting != "table") throw("PositionTransitionByTime: 'animSetting' argument must be a table, but got " + typeof animSetting);
39 |
40 | local animSetting = AnimEvent("position", animSetting, entities, time)
41 | local vars = {
42 | startPos = startPos,
43 | dist = endPos - startPos,
44 | easeFunc = animSetting.easeFunc
45 | }
46 |
47 | animate.applyRTAnimation(
48 | animSetting,
49 | function(step, steps, v) {return v.startPos + v.dist * v.easeFunc(step / steps)},
50 | function(ent, newPosition) {ent.SetAbsOrigin(newPosition)},
51 | vars
52 | )
53 |
54 | return animSetting.delay
55 | }
56 |
57 |
58 | /*
59 | * Creates an animation that transitions the position of entities over time based on a specified speed.
60 | *
61 | * @param {array|CBaseEntity|pcapEntity} entities - The entities to animate.
62 | * @param {Vector} startPos - The starting position.
63 | * @param {Vector} endPos - The ending position.
64 | * @param {number} speed - The speed of the animation in units per tick.
65 | * @param {table} animSetting - A table containing additional animation settings. (optional)
66 | *
67 | * The animation will calculate the time it takes to travel from the start position to the end position based on the specified speed.
68 | * It will then use this time to create a smooth transition of the entities' positions over that duration.
69 | * @returns {number} The duration of the animation in seconds.
70 | */
71 | animate["PositionTransitionBySpeed"] <- function(entities, startPos, endPos, speed, animSetting = {}) {
72 | if (typeof startPos != "Vector") throw("PositionTransitionBySpeed: 'startPos' argument must be a Vector, but got " + typeof startPos);
73 | if (typeof endPos != "Vector") throw("PositionTransitionBySpeed: 'endPos' argument must be a Vector, but got " + typeof endPos);
74 | if (typeof speed != "integer" && typeof speed != "float") throw("PositionTransitionBySpeed: 'speed' argument must be a number, but got " + typeof speed);
75 | if (typeof animSetting != "table") throw("PositionTransitionBySpeed: 'animSetting' argument must be a table, but got " + typeof animSetting);
76 |
77 | local animSetting = AnimEvent("position", animSetting, entities)
78 | local vars = {
79 | startPos = startPos,
80 | dist = endPos - startPos,
81 | easeFunc = animSetting.easeFunc
82 | }
83 |
84 | animate.applyAnimation(
85 | animSetting,
86 | function(step, steps, v) {return v.startPos + v.dist * v.easeFunc(step / steps)},
87 | function(ent, newPosition) {ent.SetAbsOrigin(newPosition)},
88 | vars,
89 | vars.dist.Length() / speed.tofloat() // steps
90 | )
91 |
92 | return animSetting.delay
93 | }
94 |
95 | animate.RT["PositionTransitionBySpeed"] <- function(entities, startPos, endPos, speed, animSetting = {}) {
96 | if (typeof startPos != "Vector") throw("PositionTransitionBySpeed: 'startPos' argument must be a Vector, but got " + typeof startPos);
97 | if (typeof endPos != "Vector") throw("PositionTransitionBySpeed: 'endPos' argument must be a Vector, but got " + typeof endPos);
98 | if (typeof speed != "integer" && typeof speed != "float") throw("PositionTransitionBySpeed: 'speed' argument must be a number, but got " + typeof speed);
99 | if (typeof animSetting != "table") throw("PositionTransitionBySpeed: 'animSetting' argument must be a table, but got " + typeof animSetting);
100 |
101 | local animSetting = AnimEvent("position", animSetting, entities)
102 | local vars = {
103 | startPos = startPos,
104 | dist = endPos - startPos,
105 | easeFunc = animSetting.easeFunc
106 | }
107 |
108 | animate.applyRTAnimation(
109 | animSetting,
110 | function(step, steps, v) {return v.startPos + v.dist * v.easeFunc(step / steps)},
111 | function(ent, newPosition) {ent.SetAbsOrigin(newPosition)},
112 | vars,
113 | vars.dist.Length() / speed.tofloat() // steps
114 | )
115 |
116 | return animSetting.delay
117 | }
--------------------------------------------------------------------------------
/SRC/HUD/game_text.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * A class for displaying on-screen text using the "game_text" entity.
3 | */
4 | HUD["ScreenText"] <- class {
5 | // The underlying pcapEntity object representing the "game_text" entity.
6 | CPcapEntity = null;
7 | holdtime = 0;
8 |
9 | /*
10 | * Constructor for a ScreenText object.
11 | *
12 | * @param {Vector} position - The position of the text on the screen.
13 | * @param {string} message - The text message to display.
14 | * @param {number} holdtime - The duration in seconds to display the text. (optional, default=10)
15 | * @param {string} targetname - The targetname of the "game_text" entity. (optional)
16 | */
17 | constructor(position, message, holdtime = 10, targetname = "") {
18 | if (typeof position != "Vector") throw("HUD.ScreenText: 'position' must be a Vector, but got " + typeof position);
19 | if (typeof message != "string") throw("HUD.ScreenText: 'message' must be a string, but got " + typeof message);
20 | if (typeof holdtime != "integer" && typeof holdtime != "float") throw("HUD.ScreenText: 'holdtime' must be a number, but got " + typeof holdtime);
21 |
22 | this.holdtime = holdtime
23 | this.CPcapEntity = entLib.CreateByClassname("game_text", {
24 | // Set initial properties for the text display entity
25 | channel = 2,
26 | color = "170 170 170",
27 | color2 = "0 0 0",
28 | effect = 0,
29 | fadein = 0,
30 | fadeout = 0,
31 | fxtime = 0,
32 | holdtime = holdtime,
33 | x = position.x,
34 | y = position.y,
35 | spawnflags = 0,
36 | message = message,
37 | targetname = targetname
38 | })
39 | }
40 |
41 | // Displays the on-screen text.
42 | function Enable() null
43 | // Hides the on-screen text.
44 | function Disable() null
45 | // Updates and redisplays the on-screen text.
46 | function Update() null
47 |
48 | //* Something like builders methods
49 | // Changes the message of the text display
50 | function SetText(message) null
51 | // Sets the channel of the text display.
52 | function SetChannel(value) null
53 | // Sets the primary color of the text display as a string.
54 | function SetColor(string_color) null
55 | // Sets the secondary color of the text display as a string.
56 | function SetColor2(string_color) null
57 | // Sets the effect of the text display.
58 | function SetEffect(value) null
59 | // Sets the fade-in time of the text display.
60 | function SetFadeIn(value) null
61 | // Sets the fade-out time of the text display.
62 | function SetFadeOut(value) null
63 | // Sets the hold time (duration) of the text display.
64 | function SetHoldTime(time) null
65 | // Sets the position of the text display.
66 | function SetPos(Vector) null
67 | }
68 |
69 |
70 | /*
71 | * Displays the on-screen text.
72 | */
73 | function HUD::ScreenText::Enable(holdtime = null) {
74 | this.CPcapEntity.SetKeyValue("holdtime", holdtime ? holdtime : this.holdtime)
75 | EntFireByHandle(this.CPcapEntity, "Display")
76 | return this
77 | }
78 |
79 | /*
80 | * Hides the on-screen text.
81 | */
82 | function HUD::ScreenText::Disable() {
83 | this.CPcapEntity.SetKeyValue("holdtime", 0.01)
84 | EntFireByHandle(this.CPcapEntity, "Display")
85 | }
86 |
87 | /*
88 | * Updates and redisplays the on-screen text.
89 | */
90 | function HUD::ScreenText::Update() {
91 | this.Enable()
92 | }
93 |
94 | /*
95 | * Changes the message of the text display and redisplays it.
96 | *
97 | * @param {string} message - The new text message to display.
98 | */
99 | function HUD::ScreenText::SetText(message) {
100 | this.CPcapEntity.SetKeyValue("message", message)
101 | return this
102 | }
103 |
104 | /*
105 | * Sets the channel of the text display.
106 | *
107 | * @param {number} channel - The channel to set.
108 | */
109 | function HUD::ScreenText::SetChannel(channel) {
110 | this.CPcapEntity.SetKeyValue("channel", channel)
111 | return this
112 | }
113 |
114 | /*
115 | * Sets the primary color of the text display as a string.
116 | *
117 | * @param {string} color - The color string, e.g., "255 0 0".
118 | */
119 | function HUD::ScreenText::SetColor(color) {
120 | this.CPcapEntity.SetKeyValue("color", color)
121 | return this
122 | }
123 |
124 | /*
125 | * Sets the secondary color of the text display as a string.
126 | *
127 | * @param {string} color - The color string, e.g., "255 0 0".
128 | */
129 | function HUD::ScreenText::SetColor2(color) {
130 | this.CPcapEntity.SetKeyValue("color2", color)
131 | return this
132 | }
133 |
134 | /*
135 | * Sets the effect of the text display.
136 | *
137 | * @param {number} index - The index of the effect to set.
138 | */
139 | function HUD::ScreenText::SetEffect(idx) {
140 | this.CPcapEntity.SetKeyValue("effect", idx)
141 | return this
142 | }
143 |
144 | /*
145 | * Sets the fade-in time of the text display.
146 | *
147 | * @param {number} value - The fade-in time in seconds.
148 | */
149 | function HUD::ScreenText::SetFadeIn(value) {
150 | this.CPcapEntity.SetKeyValue("fadein", value)
151 | return this
152 | }
153 |
154 | /*
155 | * Sets the fade-out time of the text display.
156 | *
157 | * @param {number} value - The fade-out time in seconds.
158 | */
159 | function HUD::ScreenText::SetFadeOut(value) {
160 | this.CPcapEntity.SetKeyValue("fadeout", value)
161 | return this
162 | }
163 |
164 | /*
165 | * Sets the hold time (duration) of the text display.
166 | *
167 | * @param {number} time - The hold time in seconds.
168 | */
169 | function HUD::ScreenText::SetHoldTime(time) {
170 | this.holdtime = time
171 | this.CPcapEntity.SetKeyValue("holdtime", time)
172 | return this
173 | }
174 |
175 | /*
176 | * Sets the position of the text display.
177 | *
178 | * @param {Vector} position - The new position of the text.
179 | */
180 | function HUD::ScreenText::SetPos(position) {
181 | this.CPcapEntity.SetKeyValue("x", position.x)
182 | this.CPcapEntity.SetKeyValue("y", position.y)
183 | return this
184 | }
--------------------------------------------------------------------------------
/SRC/HUD/hint_instructor.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * A class for displaying hints using the "env_instructor_hint" entity.
3 | */
4 | HUD["HintInstructor"] <- class {
5 | // The underlying pcapEntity object representing the "env_instructor_hint" entity.
6 | CPcapEntity = null;
7 |
8 | /*
9 | * Constructor for a HintInstructor object.
10 | *
11 | * @param {string} message - The hint message to display.
12 | * @param {number} holdtime - The duration in seconds to display the hint. (optional, default=5)
13 | * @param {string} icon - The icon to display with the hint. (optional, default="icon_tip")
14 | * @param {number} showOnHud - Whether to display the hint on the HUD or at the target entity's position (1 for HUD, 0 for target entity). (optional, default=1)
15 | * @param {string} targetname - The targetname of the "env_instructor_hint" entity. (optional, default="")
16 | */
17 | constructor(message, holdtime = 5, icon = "icon_tip", showOnHud = 1, targetname = "") {
18 | if (typeof message != "string") throw("HUD.HintInstructor: 'message' must be a string, but got " + typeof message);
19 | if (typeof holdtime != "integer" && typeof holdtime != "float") throw("HUD.HintInstructor: 'holdtime' must be a number, but got " + typeof holdtime);
20 | if (typeof icon != "string") throw("HUD.HintInstructor: 'icon' must be a string, but got " + typeof message);
21 |
22 | this.CPcapEntity = entLib.CreateByClassname("env_instructor_hint", {
23 | // The text of your hint. 100 character limit.
24 | hint_caption = message,
25 | // Either show at the position of the Target Entity, or show the hint directly on the HUD at a fixed position.
26 | hint_static = showOnHud,
27 | hint_allow_nodraw_target = showOnHud,
28 | // The color of the caption text.
29 | hint_color = "255, 255, 255",
30 | // The icon to use when the hint is within the player's view.
31 | hint_icon_onscreen = icon,
32 | hint_icon_offscreen = icon,
33 | // The automatic timeout for the hint. 0 will persist until stopped with EndHint.
34 | hint_timeout = holdtime,
35 | hint_forcecaption = 1,
36 | hint_nooffscreen = 0,
37 | hint_range = 0,
38 | targetname = targetname
39 | })
40 | }
41 |
42 | // Displays the hint.
43 | function Enable() null
44 | // Hides the hint.
45 | function Disable() null
46 | // Updates and redisplays the hint.
47 | function Update() null
48 |
49 | //* Something like builders methods
50 | // Changes the message of the hint.
51 | function SetText(message) null
52 | // Sets the bind to display with the hint icon.
53 | function SetBind(bind) null
54 | // Sets the positioning of the hint (on HUD or at target entity).
55 | function SetPositioning(value, ent) null
56 | // Sets the color of the hint text as a string.
57 | function SetColor(string_color) null
58 | // Sets the icon to display when the hint is on-screen.
59 | function SetIconOnScreen(icon) null
60 | // Sets the icon to display when the hint is off-screen.
61 | function SetIconOffScreen(bind) null
62 | // Sets the hold time (duration) of the hint.
63 | function SetHoldTime(time) null
64 | // Sets the distance at which the hint is visible.
65 | function SetDistance(value) null
66 | // Sets the visual effects for the hint.
67 | function SetEffects(sizePulsing, alphaPulsing, shaking) null
68 | }
69 |
70 | // Implementation of 'enable' to display the on-screen text
71 | /*
72 | * Displays the hint.
73 | */
74 | function HUD::HintInstructor::Enable() {
75 | EntFireByHandle(this.CPcapEntity, "ShowHint")
76 | }
77 |
78 | // Implementation of 'disable' to hide the on-screen text
79 | /*
80 | * Hides the hint.
81 | */
82 | function HUD::HintInstructor::Disable() {
83 | EntFireByHandle(this.CPcapEntity, "EndHint")
84 | }
85 |
86 | /*
87 | * Updates and redisplays the hint.
88 | */
89 | function HUD::HintInstructor::Update() {
90 | this.Enable()
91 | }
92 |
93 | /*
94 | * Changes the message of the hint and redisplays it.
95 | *
96 | * @param {string} message - The new hint message to display.
97 | */
98 | function HUD::HintInstructor::SetText(message) {
99 | this.CPcapEntity.SetKeyValue("hint_caption", message)
100 | return this
101 | }
102 |
103 | /*
104 | * Sets the bind to display with the hint icon and updates the icon to "use_binding".
105 | *
106 | * @param {string} bind - The bind name to display.
107 | */
108 | function HUD::HintInstructor::SetBind(bind) {
109 | this.CPcapEntity.SetKeyValue("hint_binding", bind)
110 | this.CPcapEntity.SetKeyValue("hint_icon_onscreen", "use_binding")
111 | return this
112 | }
113 |
114 | /*
115 | * Sets the positioning of the hint (on HUD or at target entity).
116 | *
117 | * @param {number} value - 1 to display the hint on the HUD, 0 to display it at the target entity's position.
118 | * @param {CBaseEntity|pcapEntity|null} entity - The target entity to position the hint at (only used if value is 0). (optional)
119 | */
120 | function HUD::HintInstructor::SetPositioning(value, ent = null) { // showOnHud
121 | this.CPcapEntity.SetKeyValue("hint_static", value)
122 | this.CPcapEntity.SetKeyValue("hint_target", ent)
123 | return this
124 | }
125 |
126 | /*
127 | * Sets the color of the hint text as a string.
128 | *
129 | * @param {string} color - The color string, e.g., "255 0 0".
130 | */
131 | function HUD::HintInstructor::SetColor(color) {
132 | this.CPcapEntity.SetKeyValue("hint_color", color)
133 | return this
134 | }
135 |
136 | /*
137 | * Sets the icon to display when the hint is on-screen.
138 | *
139 | * @param {string} icon - The icon name to display.
140 | */
141 | function HUD::HintInstructor::SetIconOnScreen(icon) {
142 | this.CPcapEntity.SetKeyValue("hint_icon_onscreen", icon)
143 | return this
144 | }
145 |
146 | /*
147 | * Sets the icon to display when the hint is off-screen.
148 | *
149 | * @param {string} icon - The icon name to display.
150 | */
151 | function HUD::HintInstructor::SetIconOffScreen(icon) {
152 | this.CPcapEntity.SetKeyValue("hint_icon_offscreen", icon)
153 | return this
154 | }
155 |
156 | /*
157 | * Sets the hold time (duration) of the hint.
158 | *
159 | * @param {number} time - The hold time in seconds.
160 | */
161 | function HUD::HintInstructor::SetHoldTime(time) {
162 | this.CPcapEntity.SetKeyValue("hint_timeout", time)
163 | return this
164 | }
165 |
166 | /*
167 | * Sets the distance at which the hint is visible.
168 | *
169 | * @param {number} distance - The distance in units.
170 | */
171 | function HUD::HintInstructor::SetDistance(value) {
172 | this.CPcapEntity.SetKeyValue("hint_range", value)
173 | return this
174 | }
175 |
176 | /*
177 | * Sets the visual effects for the hint.
178 | *
179 | * @param {number} sizePulsing - The size pulsing option (0 for no pulsing, 1 for pulsing).
180 | * @param {number} alphaPulsing - The alpha pulsing option (0 for no pulsing, 1 for pulsing).
181 | * @param {number} shaking - The shaking option (0 for no shaking, 1 for shaking).
182 | */
183 | function HUD::HintInstructor::SetEffects(sizePulsing, alphaPulsing, shaking) {
184 | this.CPcapEntity.SetKeyValue("hint_pulseoption", sizePulsing)
185 | this.CPcapEntity.SetKeyValue("hint_alphaoption", alphaPulsing)
186 | this.CPcapEntity.SetKeyValue("hint_shakeoption", shaking)
187 | return this
188 | }
--------------------------------------------------------------------------------
/SRC/HUD/init.nut:
--------------------------------------------------------------------------------
1 | /*+--------------------------------------------------------------------------------+
2 | | PCapture Vscripts Library |
3 | +----------------------------------------------------------------------------------+
4 | | Author: |
5 | | Interface Illusionist - laVashik :| |
6 | +----------------------------------------------------------------------------------+
7 | | The HUD module, providing tools to craft immersive and informative on-screen |
8 | | elements like text displays and interactive hints. |
9 | +----------------------------------------------------------------------------------+ */
10 |
11 | ::HUD <- {}
12 |
13 | IncludeScript("PCapture-LIB/SRC/HUD/game_text")
14 | IncludeScript("PCapture-LIB/SRC/HUD/hint_instructor")
--------------------------------------------------------------------------------
/SRC/IDT/entity_creator.nut:
--------------------------------------------------------------------------------
1 | ::entLib <- class {
2 | /*
3 | * Creates an entity of the specified classname with the provided keyvalues.
4 | *
5 | * @param {string} classname - The classname of the entity.
6 | * @param {table} keyvalues - The key-value pairs for the entity.
7 | * @returns {pcapEntity} - The created entity object.
8 | */
9 | function CreateByClassname(classname, keyvalues = {}) {
10 | local new_entity = entLib.FromEntity(Entities.CreateByClassname(classname))
11 | foreach(key, value in keyvalues) {
12 | new_entity.SetKeyValue(key, value)
13 | }
14 |
15 | pcapEntityCache[new_entity.CBaseEntity] <- new_entity
16 |
17 | return new_entity
18 | }
19 |
20 |
21 | /*
22 | * Creates a prop entity with the specified parameters.
23 | *
24 | * @param {string} classname - The classname of the prop.
25 | * @param {Vector} origin - The initial origin (position) of the prop.
26 | * @param {string} modelname - The model name of the prop.
27 | * @param {number} activity - The initial activity of the prop. (optional, default=1)
28 | * @param {table} keyvalues - Additional key-value pairs for the prop. (optional)
29 | * @returns {pcapEntity} - The created prop entity object.
30 | */
31 | function CreateProp(classname, origin, modelname, activity = 1, keyvalues = {}) {
32 | local new_entity = entLib.FromEntity(CreateProp(classname, origin, modelname, activity))
33 | foreach(key, value in keyvalues) {
34 | new_entity.SetKeyValue(key, value)
35 | }
36 |
37 | pcapEntityCache[new_entity.CBaseEntity] <- new_entity
38 |
39 | return new_entity
40 | }
41 |
42 |
43 | /*
44 | * Wraps a CBaseEntity object in a pcapEntity object.
45 | *
46 | * @param {CBaseEntity} CBaseEntity - The CBaseEntity object to wrap.
47 | * @returns {pcapEntity} - The wrapped entity object.
48 | */
49 | function FromEntity(CBaseEntity) {
50 | if(typeof CBaseEntity == "pcapEntity")
51 | return CBaseEntity
52 | return entLib.__init(CBaseEntity)
53 | }
54 |
55 |
56 | /*
57 | * Finds an entity with the specified classname.
58 | *
59 | * @param {string} classname - The classname to search for.
60 | * @param {CBaseEntity|pcapEntity} start_ent - The starting entity to search within. (optional)
61 | * @returns {pcapEntity|null} - The found entity object, or null if not found.
62 | */
63 | function FindByClassname(classname, start_ent = null) {
64 | if(start_ent && typeof start_ent == "pcapEntity")
65 | start_ent = start_ent.CBaseEntity
66 | local new_entity = Entities.FindByClassname(start_ent, classname)
67 | return entLib.__init(new_entity)
68 | }
69 |
70 |
71 | /*
72 | * Finds an entity with the specified classname within a given radius from the origin.
73 | *
74 | * @param {string} classname - The classname to search for.
75 | * @param {Vector} origin - The origin position.
76 | * @param {number} radius - The search radius.
77 | * @param {CBaseEntity|pcapEntity} start_ent - The starting entity to search within. (optional)
78 | * @returns {pcapEntity|null} - The found entity object, or null if not found.
79 | */
80 | function FindByClassnameWithin(classname, origin, radius, start_ent = null) {
81 | if(start_ent && typeof start_ent == "pcapEntity")
82 | start_ent = start_ent.CBaseEntity
83 | local new_entity = Entities.FindByClassnameWithin(start_ent, classname, origin, radius)
84 | return entLib.__init(new_entity)
85 | }
86 |
87 |
88 | /*
89 | * Finds an entity with the specified targetname within the given starting entity.
90 | *
91 | * @param {string} targetname - The targetname to search for.
92 | * @param {CBaseEntity|pcapEntity} start_ent - The starting entity to search within. (optional)
93 | * @returns {pcapEntity|null} - The found entity object, or null if not found.
94 | */
95 | function FindByName(targetname, start_ent = null) {
96 | if(start_ent && typeof start_ent == "pcapEntity")
97 | start_ent = start_ent.CBaseEntity
98 | local new_entity = Entities.FindByName(start_ent, targetname)
99 | return entLib.__init(new_entity)
100 | }
101 |
102 |
103 | /*
104 | * Finds an entity with the specified targetname within a given radius from the origin.
105 | *
106 | * @param {string} targetname - The targetname to search for.
107 | * @param {Vector} origin - The origin position.
108 | * @param {number} radius - The search radius.
109 | * @param {CBaseEntity|pcapEntity} start_ent - The starting entity to search within. (optional)
110 | * @returns {pcapEntity|null} - The found entity object, or null if not found.
111 | */
112 | function FindByNameWithin(targetname, origin, radius, start_ent = null) {
113 | if(start_ent && typeof start_ent == "pcapEntity")
114 | start_ent = start_ent.CBaseEntity
115 | local new_entity = Entities.FindByNameWithin(start_ent, targetname, origin, radius)
116 | return entLib.__init(new_entity)
117 | }
118 |
119 |
120 | /*
121 | * Finds an entity with the specified model within the given starting entity.
122 | *
123 | * @param {string} model - The model to search for.
124 | * @param {CBaseEntity|pcapEntity} start_ent - The starting entity to search within. (optional)
125 | * @returns {pcapEntity|null} - The found entity object, or null if not found.
126 | */
127 | function FindByModel(model, start_ent = null) {
128 | if(start_ent && typeof start_ent == "pcapEntity")
129 | start_ent = start_ent.CBaseEntity
130 | local new_entity = Entities.FindByModel(start_ent, model)
131 | return entLib.__init(new_entity)
132 | }
133 |
134 |
135 | /*
136 | * Finds an entity with the specified model within a given radius from the origin.
137 | *
138 | * @param {string} model - The model to search for.
139 | * @param {Vector} origin - The origin position.
140 | * @param {number} radius - The search radius.
141 | * @param {CBaseEntity|pcapEntity} start_ent - The starting entity to search within. (optional)
142 | * @returns {pcapEntity|null} - The found entity object, or null if not found.
143 | */
144 | function FindByModelWithin(model, origin, radius, start_ent = null) {
145 | if(start_ent && typeof start_ent == "pcapEntity")
146 | start_ent = start_ent.CBaseEntity
147 | local new_entity = null
148 | for(local ent; ent = Entities.FindByClassnameWithin(ent, "*", origin, radius);) {
149 | if(ent.GetModelName() == model && ent != start_ent) {
150 | new_entity = ent;
151 | break;
152 | }
153 | }
154 |
155 | return entLib.__init(new_entity)
156 | }
157 |
158 |
159 | /*
160 | * Finds entities within a sphere defined by the origin and radius.
161 | *
162 | * @param {Vector} origin - The origin position of the sphere.
163 | * @param {number} radius - The radius of the sphere.
164 | * @param {CBaseEntity|pcapEntity} start_ent - The starting entity to search within. (optional)
165 | * @returns {pcapEntity|null} - The found entity object, or null if not found.
166 | */
167 | function FindInSphere(origin, radius, start_ent = null) {
168 | if(start_ent && typeof start_ent == "pcapEntity")
169 | start_ent = start_ent.CBaseEntity
170 | local new_entity = Entities.FindInSphere(start_ent, origin, radius)
171 | return entLib.__init(new_entity)
172 | }
173 |
174 |
175 |
176 | /*
177 | * Initializes an entity object.
178 | *
179 | * @param {CBaseEntity} entity - The entity object.
180 | * @returns {pcapEntity} - A new entity object.
181 | */
182 | function __init(CBaseEntity) {
183 | if(!CBaseEntity || !CBaseEntity.IsValid())
184 | return null
185 |
186 | if(CBaseEntity in pcapEntityCache) {
187 | return pcapEntityCache[CBaseEntity]
188 | } else {
189 | local pcapEnt = pcapEntity(CBaseEntity)
190 | pcapEntityCache[CBaseEntity] <- pcapEnt
191 | return pcapEnt
192 | }
193 | }
194 | }
--------------------------------------------------------------------------------
/SRC/IDT/init.nut:
--------------------------------------------------------------------------------
1 | /*+--------------------------------------------------------------------------------+
2 | | PCapture Vscripts Library |
3 | +----------------------------------------------------------------------------------+
4 | | Author: |
5 | | Data Structure Maestro - laVashik ^v^ |
6 | +----------------------------------------------------------------------------------+
7 | | The Improved Data Types module, offering enhanced versions of standard VScripts|
8 | | data structures like arrays, lists, and trees for efficient data management. |
9 | +----------------------------------------------------------------------------------+ */
10 |
11 | IncludeScript("PCapture-LIB/SRC/IDT/entity")
12 | IncludeScript("PCapture-LIB/SRC/IDT/entity_creator")
13 |
14 | IncludeScript("PCapture-LIB/SRC/IDT/array")
15 | IncludeScript("PCapture-LIB/SRC/IDT/list")
16 | IncludeScript("PCapture-LIB/SRC/IDT/tree_sort")
17 |
18 | // dissolve entity for pcapEnts
19 | if(("dissolver" in getroottable()) == false) {
20 | ::dissolver <- entLib.CreateByClassname("env_entity_dissolver", {targetname = "@dissolver"})
21 | }
--------------------------------------------------------------------------------
/SRC/Math/algebraic.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Finds the minimum value among the given arguments.
3 | *
4 | * @param {...number} vargv - The numbers to compare.
5 | * @returns {number} - The minimum value.
6 | */
7 | math["min"] <- function(...) {
8 | local min = vargv[0]
9 | for(local i = 0; i< vargc; i++) {
10 | if(min > vargv[i])
11 | min = vargv[i]
12 | }
13 |
14 | return min
15 | }
16 |
17 |
18 | /*
19 | * Finds the maximum value among the given arguments.
20 | *
21 | * @param {...number} vargv - The numbers to compare.
22 | * @returns {number} - The maximum value.
23 | */
24 | math["max"] <- function(...) {
25 | local max = vargv[0]
26 | for(local i = 0; i< vargc; i++) {
27 | if(vargv[i] > max)
28 | max = vargv[i]
29 | }
30 |
31 | return max
32 | }
33 |
34 |
35 | /*
36 | * Clamps a number within the specified range.
37 | *
38 | * @param {number} number - The number to clamp.
39 | * @param {number} min - The minimum value.
40 | * @param {number} max - The maximum value (optional).
41 | * @returns {number} - The clamped value.
42 | */
43 | math["clamp"] <- function(number, min, max = 99999) {
44 | if ( number < min ) return min;
45 | if ( number > max ) return max;
46 | return number
47 | }
48 |
49 |
50 | /*
51 | * Rounds a number to the specified precision.
52 | *
53 | * @param {number} value - The number to round.
54 | * @param {int} precision - The precision (e.g., 1000 for rounding to three decimal places).
55 | * @returns {number} - The rounded value.
56 | */
57 | math["round"] <- function(value, precision = 1) {
58 | return floor(value * precision + 0.5) / precision
59 | }
60 |
61 |
62 | /*
63 | * Returns the sign of a number.
64 | *
65 | * @param {number} x - The number.
66 | * @returns {number} - The sign of the number (-1, 0, or 1).
67 | */
68 | math["Sign"] <- function(x) {
69 | if (x > 0) {
70 | return 1;
71 | } else if (x < 0) {
72 | return -1;
73 | } else {
74 | return 0;
75 | }
76 | }
77 |
78 |
79 | /*
80 | * Copies the sign of one value to another.
81 | *
82 | * @param {number} value - The value to copy the sign to.
83 | * @param {number} sign - The sign to copy.
84 | * @returns {number} - The value with the copied sign.
85 | */
86 | math["copysign"] <- function(value, sign) {
87 | if (sign < 0 || value < 0) {
88 | return -value;
89 | }
90 | return value
91 | }
92 |
93 |
94 | /*
95 | * Remaps a value from the range [A, B] to the range [C, D].
96 | *
97 | * If A is equal to B, the value will be clamped to C or D depending on its relationship to B.
98 | *
99 | * @param {number} value - The value to remap.
100 | * @param {number} A - The start of the input range.
101 | * @param {number} B - The end of the input range.
102 | * @param {number} C - The start of the output range.
103 | * @param {number} D - The end of the output range.
104 | * @returns {number} - The remapped value.
105 | */
106 | math["RemapVal"] <- function( value, A, B, C, D )
107 | {
108 | if ( A == B )
109 | {
110 | if ( value >= B )
111 | return D;
112 | return C;
113 | };
114 | return C + (D - C) * (value - A) / (B - A);
115 | }
--------------------------------------------------------------------------------
/SRC/Math/easing_equation.nut:
--------------------------------------------------------------------------------
1 | // More info here: https://gizma.com/easing/
2 |
3 | math["ease"] <- {}
4 | local ease = math["ease"]
5 |
6 | ease["InSine"] <- function(t) {
7 | return 1 - cos((t * PI) / 2);
8 | }
9 |
10 | ease["OutSine"] <- function(t) {
11 | return sin((t * PI) / 2);
12 | }
13 |
14 | ease["InOutSine"] <- function(t) {
15 | return -(cos(PI * t) - 1) / 2;
16 | }
17 |
18 | ease["InQuad"] <- function(t) {
19 | return t * t;
20 | }
21 |
22 | ease["OutQuad"] <- function(t) {
23 | return 1 - (1 - t) * (1 - t);
24 | }
25 |
26 | ease["InOutQuad"] <- function(t) {
27 | return t < 0.5 ? 2 * t * t : 1 - pow(-2 * t + 2, 2) / 2;
28 | }
29 |
30 | ease["InCubic"] <- function(t) {
31 | return t * t * t;
32 | }
33 |
34 | ease["OutCubic"] <- function(t) {
35 | return 1 - pow(1 - t, 3);
36 | }
37 |
38 | ease["InOutCubic"] <- function(t) {
39 | return t < 0.5 ? 4 * t * t * t : 1 - pow(-2 * t + 2, 3) / 2;
40 | }
41 |
42 | ease["InQuart"] <- function(t) {
43 | return t * t * t * t;
44 | }
45 |
46 | ease["OutQuart"] <- function(t) {
47 | return 1 - pow(1 - t, 4);
48 | }
49 |
50 | ease["InOutQuart"] <- function(t) {
51 | return t < 0.5 ? 8 * t * t * t * t : 1 - pow(-2 * t + 2, 4) / 2;
52 | }
53 |
54 | ease["InQuint"] <- function(t) {
55 | return t * t * t * t * t;
56 | }
57 |
58 | ease["OutQuint"] <- function(t) {
59 | return 1 - pow(1 - t, 5);
60 | }
61 |
62 | ease["InOutQuint"] <- function(t) {
63 | return t < 0.5 ? 16 * t * t * t * t * t : 1 - pow(-2 * t + 2, 5) / 2;
64 | }
65 |
66 | ease["InExpo"] <- function(t) {
67 | return t == 0 ? 0 : pow(2, 10 * t - 10);
68 | }
69 |
70 | ease["OutExpo"] <- function(t) {
71 | return t == 1 ? 1 : 1 - pow(2, -10 * t);
72 | }
73 |
74 | ease["InOutExpo"] <- function(t) {
75 | return t == 0 ? 0 : t == 1 ? 1 : t < 0.5 ? pow(2, 20 * t - 10) / 2 : (2 - pow(2, -20 * t + 10)) / 2;
76 | }
77 |
78 | ease["InCirc"] <- function(t) {
79 | return 1 - sqrt(1 - pow(t, 2));
80 | }
81 |
82 | ease["OutCirc"] <- function(t) {
83 | return sqrt(1 - pow(t - 1, 2));
84 | }
85 |
86 | ease["InOutCirc"] <- function(t) {
87 | return t < 0.5 ? (1 - sqrt(1 - pow(2 * t, 2))) / 2 : (sqrt(1 - pow(-2 * t + 2, 2)) + 1) / 2;
88 | }
89 |
90 | ease["InBack"] <- function(t) {
91 | local c1 = 1.70158;
92 | local c3 = c1 + 1;
93 | return c3 * t * t * t - c1 * t * t;
94 | }
95 |
96 | ease["OutBack"] <- function(t) {
97 | local c1 = 1.70158;
98 | local c3 = c1 + 1;
99 | return 1 + c3 * pow(t - 1, 3) + c1 * pow(t - 1, 2);
100 | }
101 |
102 | ease["InOutBack"] <- function(t) {
103 | local c1 = 1.70158;
104 | local c2 = c1 * 1.525;
105 | return t < 0.5
106 | ? (pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
107 | : (pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
108 | }
109 |
110 | ease["InElastic"] <- function(t) {
111 | local c4 = (2 * PI) / 3;
112 | return t == 0
113 | ? 0
114 | : t == 1
115 | ? 1
116 | : -pow(2, 10 * t - 10) * sin((t * 10 - 10.75) * c4);
117 | }
118 |
119 | ease["OutElastic"] <- function(t) {
120 | local c4 = (2 * PI) / 3;
121 | return t == 0
122 | ? 0
123 | : t == 1
124 | ? 1
125 | : pow(2, -10 * t) * sin((t * 10 - 0.75) * c4) + 1;
126 | }
127 |
128 | ease["InOutElastic"] <- function(t) {
129 | local c5 = (2 * PI) / 4.5;
130 | return t == 0
131 | ? 0
132 | : t == 1
133 | ? 1
134 | : t < 0.5
135 | ? -(pow(2, 20 * t - 10) * sin((20 * t - 11.125) * c5)) / 2
136 | : (pow(2, -20 * t + 10) * sin((20 * t - 11.125) * c5)) / 2 + 1;
137 | }
138 |
139 | ease["InBounce"] <- function(t) {
140 | return 1 - math.ease.OutBounce(1 - t); // todo
141 | }
142 |
143 | ease["OutBounce"] <- function(t) {
144 | local n1 = 7.5625;
145 | local d1 = 2.75;
146 | if (t < 1 / d1) {
147 | return n1 * t * t;
148 | } else if (t < 2 / d1) {
149 | return n1 * (t -= 1.5 / d1) * t + 0.75;
150 | } else if (t < 2.5 / d1) {
151 | return n1 * (t -= 2.25 / d1) * t + 0.9375;
152 | } else {
153 | return n1 * (t -= 2.625 / d1) * t + 0.984375;
154 | }
155 | }
156 |
157 | ease["InOutBounce"] <- function(t) {
158 | return t < 0.5
159 | ? (1 - math.ease.OutBounce(1 - 2 * t)) / 2
160 | : (1 + math.ease.OutBounce(2 * t - 1)) / 2;
161 | }
--------------------------------------------------------------------------------
/SRC/Math/init.nut:
--------------------------------------------------------------------------------
1 | /*+--------------------------------------------------------------------------------+
2 | | PCapture Vscripts Library |
3 | +----------------------------------------------------------------------------------+
4 | | Author: |
5 | | Visionary Mathematician - laVashik :O |
6 | +----------------------------------------------------------------------------------+
7 | | The core mathematical module, providing essential functions and objects for |
8 | | performing calculations, transformations, and interpolations in VScripts. |
9 | +----------------------------------------------------------------------------------+ */
10 |
11 | ::math <- {}
12 |
13 | IncludeScript("PCapture-LIB/SRC/Math/algebraic")
14 | IncludeScript("PCapture-LIB/SRC/Math/utils_vector")
15 | IncludeScript("PCapture-LIB/SRC/Math/lerp")
16 | IncludeScript("PCapture-LIB/SRC/Math/easing_equation")
17 | IncludeScript("PCapture-LIB/SRC/Math/quaternion")
18 | IncludeScript("PCapture-LIB/SRC/Math/matrix")
--------------------------------------------------------------------------------
/SRC/Math/lerp.nut:
--------------------------------------------------------------------------------
1 | math["lerp"] <- {}
2 | local lerp = math["lerp"]
3 |
4 |
5 | /*
6 | * Performs linear interpolation (lerp) between start and end based on the parameter t.
7 | *
8 | * @param {number} start - The starting value.
9 | * @param {number} end - The ending value.
10 | * @param {number} t - The interpolation parameter.
11 | * @returns {number} - The interpolated value.
12 | */
13 | lerp["number"] <- function(start, end, t) {
14 | return start * (1 - t) + end * t;
15 | }
16 |
17 |
18 | /*
19 | * Performs linear interpolation (lerp) between two vectors.
20 | *
21 | * @param {Vector} start - The starting vector.
22 | * @param {Vector} end - The ending vector.
23 | * @param {number} t - The interpolation parameter.
24 | * @returns {Vector} - The interpolated vector.
25 | */
26 | lerp["vector"] <- function(start, end, t) {
27 | return Vector(this.number(start.x, end.x, t), this.number(start.y, end.y, t), this.number(start.z, end.z, t));
28 | }
29 |
30 |
31 | /*
32 | * Performs linear interpolation (lerp) between two colors.
33 | *
34 | * @param {Vector|string} start - The starting color vector or string representation (e.g., "255 0 0").
35 | * @param {Vector|string} end - The ending color vector or string representation.
36 | * @param {number} t - The interpolation parameter.
37 | * @returns {string} - The interpolated color string representation (e.g., "128 64 0").
38 | */
39 | lerp["color"] <- function(start, end, t) {
40 | if (type(start) == "string") {
41 | start = macros.StrToVec(start)
42 | }
43 | if (type(end) == "string") {
44 | end = macros.StrToVec(end)
45 | }
46 |
47 | return floor(this.number(start.x, end.x, t)) + " " + floor(this.number(start.y, end.y, t)) + " " + floor(this.number(start.z, end.z, t))
48 | }
49 |
50 | // SLERP for vector
51 | lerp["sVector"] <- function(start, end, t) {
52 | local q1 = math.Quaternion.fromEuler(start)
53 | local q2 = math.Quaternion.fromEuler(end)
54 | return q1.slerp(q2, t).toVector()
55 | }
56 |
57 | /*
58 | * Performs smooth interpolation between two values using a smoothstep function.
59 | *
60 | * @param {number} edge0 - The lower edge of the interpolation range.
61 | * @param {number} edge1 - The upper edge of the interpolation range.
62 | * @param {number} value - The interpolation parameter.
63 | * @returns {number} - The interpolated value.
64 | */
65 | lerp["SmoothStep"] <- function(edge0, edge1, value) {
66 | local t = math.clamp((value - edge0) / (edge1 - edge0), 0.0, 1.0);
67 | return t * t * (3.0 - 2.0 * t)
68 | }
69 |
70 |
71 | /*
72 | * Performs linear interpolation between two values.
73 | *
74 | * @param {number} f1 - The start value.
75 | * @param {number} f2 - The end value.
76 | * @param {number} i1 - The start interpolation parameter.
77 | * @param {number} i2 - The end interpolation parameter.
78 | * @param {number} value - The interpolation parameter.
79 | * @returns {number} - The interpolated value.
80 | */
81 | lerp["FLerp"] <- function( f1, f2, i1, i2, value ) {
82 | return f1 + (f2 - f1) * (value - i1) / (i2 - i1);
83 | }
--------------------------------------------------------------------------------
/SRC/Math/matrix.nut:
--------------------------------------------------------------------------------
1 | math["Matrix"] <- class {
2 | a = 1; b = 0; c = 0;
3 | d = 0; e = 1; f = 0;
4 | g = 0; h = 0; k = 1;
5 |
6 | /*
7 | * Creates a new matrix.
8 | *
9 | * @param {number} a - The value at row 1, column 1.
10 | * @param {number} b - The value at row 1, column 2.
11 | * @param {number} c - The value at row 1, column 3.
12 | * @param {number} d - The value at row 2, column 1.
13 | * @param {number} e - The value at row 2, column 2.
14 | * @param {number} f - The value at row 2, column 3.
15 | * @param {number} g - The value at row 3, column 1.
16 | * @param {number} h - The value at row 3, column 2.
17 | * @param {number} k - The value at row 3, column 3.
18 | */
19 | constructor(a = 1, b = 0, c = 0,
20 | d = 0, e = 1, f = 0,
21 | g = 0, h = 0, k = 1
22 | ) {
23 | this.a = a; this.b = b; this.c = c;
24 | this.d = d; this.e = e; this.f = f;
25 | this.g = g; this.h = h; this.k = k;
26 | }
27 | /*
28 | * Creates a rotation matrix from Euler angles.
29 | *
30 | * @param {Vector} angles - Euler angles in degrees (pitch, yaw, roll).
31 | * @returns {Matrix} - The rotation matrix.
32 | */
33 | function fromEuler(angles) {
34 | local sinX = sin(-angles.z / 180 * PI);
35 | local cosX = cos(-angles.z / 180 * PI);
36 | local sinY = sin(-angles.x / 180 * PI);
37 | local cosY = cos(-angles.x / 180 * PI);
38 | local sinZ = sin(-angles.y / 180 * PI);
39 | local cosZ = cos(-angles.y / 180 * PI);
40 | return math.Matrix(
41 | cosY * cosZ, -sinX * -sinY * cosZ + cosX * sinZ, cosX * -sinY * cosZ + sinX * sinZ,
42 | cosY * -sinZ, -sinX * -sinY * -sinZ + cosX * cosZ, cosX * -sinY * -sinZ + sinX * cosZ,
43 | sinY, -sinX * cosY, cosX * cosY
44 | );
45 | }
46 |
47 | /*
48 | * Rotates a point using the matrix.
49 | *
50 | * @param {Vector} point - The point to rotate.
51 | * @returns {Vector} - The rotated point.
52 | */
53 | function rotateVector(point) {
54 | return Vector(
55 | point.x * this.a + point.y * this.b + point.z * this.c,
56 | point.x * this.d + point.y * this.e + point.z * this.f,
57 | point.x * this.g + point.y * this.h + point.z * this.k
58 | );
59 | }
60 |
61 | /*
62 | * Unrotates a point using the matrix.
63 | *
64 | * @param {Vector} point - The point to unrotate.
65 | * @returns {Vector} - The unrotated point.
66 | */
67 | function unrotateVector(point) {
68 | return Vector(
69 | point.x * this.a + point.y * this.d + point.z * this.g,
70 | point.x * this.b + point.y * this.e + point.z * this.h,
71 | point.x * this.c + point.y * this.f + point.z * this.k
72 | );
73 | }
74 |
75 | /*
76 | * Transposes the matrix.
77 | *
78 | * @returns {Matrix} - The transposed matrix.
79 | */
80 | function transpose() {
81 | return math.Matrix(
82 | this.a, this.d, this.g,
83 | this.b, this.e, this.h,
84 | this.c, this.f, this.k
85 | );
86 | }
87 |
88 | /*
89 | * Calculates the inverse of the matrix.
90 | *
91 | * @returns {Matrix} - The inverse matrix.
92 | * @throws {Error} - If the matrix is singular (determinant is zero).
93 | */
94 | function inverse() {
95 | local det = this.determinant();
96 | if (det == 0) {
97 | throw "Matrix is singular (determinant is zero)"
98 | }
99 | local invDet = 1 / det;
100 | return math.Matrix(
101 | (this.e * this.k - this.f * this.h) * invDet,
102 | (this.c * this.h - this.b * this.k) * invDet,
103 | (this.b * this.f - this.c * this.e) * invDet,
104 | (this.f * this.g - this.d * this.k) * invDet,
105 | (this.a * this.k - this.c * this.g) * invDet,
106 | (this.c * this.d - this.a * this.f) * invDet,
107 | (this.d * this.h - this.e * this.g) * invDet,
108 | (this.b * this.g - this.a * this.h) * invDet,
109 | (this.a * this.e - this.b * this.d) * invDet
110 | );
111 | }
112 |
113 | /*
114 | * Calculates the determinant of the matrix.
115 | *
116 | * @returns {number} - The determinant of the matrix.
117 | */
118 | function determinant() {
119 | return this.a * (this.e * this.k - this.f * this.h) -
120 | this.b * (this.d * this.k - this.f * this.g) +
121 | this.c * (this.d * this.h - this.e * this.g);
122 | }
123 |
124 | /*
125 | * Scales the matrix by a given factor.
126 | *
127 | * @param {number} factor - The scaling factor.
128 | * @returns {Matrix} - The scaled matrix.
129 | */
130 | function scale(factor) {
131 | return math.Matrix(
132 | this.a * factor, this.b * factor, this.c * factor,
133 | this.d * factor, this.e * factor, this.f * factor,
134 | this.g * factor, this.h * factor, this.k * factor
135 | );
136 | }
137 |
138 | /*
139 | * Rotates the matrix around the X axis by a given angle.
140 | *
141 | * @param {number} angle - The angle of rotation in radians.
142 | * @returns {Matrix} - The rotated matrix.
143 | */
144 | function rotateX(angle) {
145 | local sinAngle = sin(angle);
146 | local cosAngle = cos(angle);
147 | return math.Matrix(
148 | 1, 0, 0,
149 | 0, cosAngle, -sinAngle,
150 | 0, sinAngle, cosAngle
151 | ) * this;
152 | }
153 |
154 | /*
155 | * Multiplies two matrices.
156 | *
157 | * @param {Matrix} other - The other matrix.
158 | * @returns {Matrix} - The result of the multiplication.
159 | */
160 | function _mul(other) {
161 | return math.Matrix(
162 | this.a * other.a + this.b * other.d + this.c * other.g,
163 | this.a * other.b + this.b * other.e + this.c * other.h,
164 | this.a * other.c + this.b * other.f + this.c * other.k,
165 | this.d * other.a + this.e * other.d + this.f * other.g,
166 | this.d * other.b + this.e * other.e + this.f * other.h,
167 | this.d * other.c + this.e * other.f + this.f * other.k,
168 | this.g * other.a + this.h * other.d + this.k * other.g,
169 | this.g * other.b + this.h * other.e + this.k * other.h,
170 | this.g * other.c + this.h * other.f + this.k * other.k
171 | );
172 | }
173 |
174 | /*
175 | * Adds two matrices.
176 | *
177 | * @param {Matrix} other - The other matrix.
178 | * @returns {Matrix} - The result of the addition.
179 | */
180 | function _add(other) {
181 | return math.Matrix(
182 | this.a + other.a, this.b + other.b, this.c + other.c,
183 | this.d + other.d, this.e + other.e, this.f + other.f,
184 | this.g + other.g, this.h + other.h, this.k + other.k
185 | );
186 | }
187 |
188 | /*
189 | * Subtracts two matrices.
190 | *
191 | * @param {Matrix} other - The other matrix.
192 | * @returns {Matrix} - The result of the subtraction.
193 | */
194 | function _sub(other) {
195 | return math.Matrix(
196 | this.a - other.a, this.b - other.b, this.c - other.c,
197 | this.d - other.d, this.e - other.e, this.f - other.f,
198 | this.g - other.g, this.h - other.h, this.k - other.k
199 | );
200 | }
201 |
202 | /*
203 | * Checks if two matrices are equal based on their components and sum.
204 | *
205 | * @param {Matrix} other - The other matrix to compare.
206 | * @returns {boolean} - True if the matrices are equal, false otherwise.
207 | */
208 | function isEqually(other) {
209 | return this.cmp(other) == 0
210 | }
211 |
212 | /*
213 | * Compares two matrices based on the sum of their components.
214 | *
215 | * @param {Matrix} other - The other matrix to compare.
216 | * @returns {number} - 1 if this matrix's sum is greater, -1 if less, 0 if equal.
217 | */
218 | function cmp(other) {
219 | if (typeof other != "Matrix") {
220 | throw "Cannot compare matrix with non-matrix type";
221 | }
222 |
223 | local thisSum = a + b - c + d + e + f - g + h - k;
224 | local otherSum = other.a + other.b - other.c + other.d + other.e + other.f - other.g + other.h - other.k;
225 |
226 | if (thisSum > otherSum) {
227 | return 1;
228 | } else if (thisSum < otherSum) {
229 | return -1;
230 | } else {
231 | return 0;
232 | }
233 | }
234 |
235 | function _cmp(other) return this.cmp(other)
236 |
237 | function _tostring() {
238 | return "Matrix: {" + a + ", " + b + ", " + c + "\n\t " + d + ", " + e + ", " + f + "\n\t " + g + ", " + h + ", " + k + "}"
239 | }
240 |
241 | function _typeof() {
242 | return "Matrix"
243 | }
244 | }
--------------------------------------------------------------------------------
/SRC/Math/utils_vector.nut:
--------------------------------------------------------------------------------
1 | math["vector"] <- {}
2 | local mVector = math["vector"]
3 |
4 | /*
5 | * Checks if two vectors are equal based on their rounded components.
6 | *
7 | * @param {Vector} vector - The first vector.
8 | * @param {Vector} other - The second vector.
9 | * @returns {boolean} - True if the vectors are exactly equal, false otherwise.
10 | */
11 | mVector["isEqually"] <- function(vector, other) {
12 | return ::abs(vector.x - other.x) == 0 &&
13 | ::abs(vector.y - other.y) == 0 &&
14 | ::abs(vector.z - other.z) == 0
15 | }
16 |
17 |
18 | /*
19 | * Checks if two vectors are approximately equal, within a certain precision.
20 | * This function rounds the components of both vectors before comparing them.
21 | *
22 | * @param {Vector} vector - The first vector.
23 | * @param {Vector} other - The second vector.
24 | * @param {int} precision - The precision factor (e.g., 1000 for rounding to three decimal places).
25 | * @returns {boolean} - True if the vectors are approximately equal, false otherwise.
26 | */
27 | mVector["isEqually2"] <- function(vector, other, precision = 1000) {
28 | vector = math.vector.round(vector, precision)
29 | other = math.vector.round(other, precision)
30 | return vector.x == other.x &&
31 | vector.y == other.y &&
32 | vector.z == other.z
33 | }
34 |
35 |
36 | /*
37 | * Compares two vectors based on their lengths.
38 | *
39 | * @param {Vector} vector - The first vector.
40 | * @param {Vector} other - The second vector.
41 | * @returns {int} - 1 if the first vector is longer, -1 if the second vector is longer, and 0 if they have equal lengths.
42 | */mVector["cmp"] <- function(vector, other) {
43 | local l1 = vector.Length()
44 | local l2 = other.Length()
45 | if(l1 > l2) return 1
46 | if(l1 < l2) return -1
47 | return 0
48 | }
49 |
50 |
51 | /*
52 | * Performs element-wise multiplication of two vectors.
53 | *
54 | * @param {Vector} vector - The first vector.
55 | * @param {Vector} other - The second vector.
56 | * @returns {Vector} - A new vector with the result of the element-wise multiplication.
57 | */
58 | mVector["mul"] <- function(vector, other) {
59 | return Vector(vector.x * other.x, vector.y * other.y, vector.z * other.z)
60 | }
61 |
62 | /*
63 | * Rotates a vector by a given angle.
64 | *
65 | * @param {Vector} vector - The vector to rotate.
66 | * @param {Vector} angle - The Euler angles in degrees (pitch, yaw, roll) representing the rotation.
67 | * @returns {Vector} - The rotated vector.
68 | */
69 | mVector["rotate"] <- function(vector, angle) {
70 | return math.Quaternion.fromEuler(angle).rotateVector(vector)
71 | }
72 |
73 | /*
74 | * Un-rotates a vector by a given angle.
75 | *
76 | * @param {Vector} vector - The vector to un-rotate.
77 | * @param {Vector} angle - The Euler angles in degrees (pitch, yaw, roll) representing the rotation to reverse.
78 | * @returns {Vector} - The un-rotated vector.
79 | */
80 | mVector["unrotate"] <- function(vector, angle) {
81 | return math.Quaternion.fromEuler(angle).unrotateVector(vector)
82 | }
83 |
84 | /*
85 | * Generates a random vector within the specified range.
86 | *
87 | * If `min` and `max` are both vectors, each component of the resulting vector will be a random value between the corresponding components of `min` and `max`.
88 | * If `min` and `max` are numbers, all components of the resulting vector will be random values between `min` and `max`.
89 | *
90 | * @param {Vector|number} min - The minimum values for each component or a single minimum value for all components.
91 | * @param {Vector|number} max - The maximum values for each component or a single maximum value for all components.
92 | * @returns {Vector} - The generated random vector.
93 | */
94 | mVector["random"] <- function(min, max) {
95 | if(typeof min == "Vector" && typeof max == "Vector")
96 | return Vector(RandomFloat(min.x, max.x), RandomFloat(min.y, max.y), RandomFloat(min.z, max.z))
97 | return Vector(RandomFloat(min, max), RandomFloat(min, max), RandomFloat(min, max))
98 | }
99 |
100 | /*
101 | * Reflects a direction vector off a surface with a given normal.
102 | *
103 | * @param {Vector} dir - The direction vector to reflect.
104 | * @param {Vector} normal - The normal vector of the surface.
105 | * @returns {Vector} - The reflected direction vector.
106 | */
107 | mVector["reflect"] <- function(dir, normal) {
108 | return dir - normal * (dir.Dot(normal) * 2)
109 | }
110 |
111 | /*
112 | * Clamps the components of a vector within the specified range.
113 | *
114 | * @param {Vector} vector - The vector to clamp.
115 | * @param {number} min - The minimum value for each component.
116 | * @param {number} max - The maximum value for each component.
117 | * @returns {Vector} - The clamped vector.
118 | */
119 | mVector["clamp"] <- function(vector, min = 0, max = 255) {
120 | return Vector(math.clamp(vector.x, min, max), math.clamp(vector.y, min, max), math.clamp(vector.z, min, max))
121 | }
122 |
123 | /*
124 | * Resizes a vector to a new length while maintaining its direction.
125 | *
126 | * @param {Vector} vector - The vector to resize.
127 | * @param {number} newLength - The desired new length of the vector.
128 | * @returns {Vector} - The resized vector with the specified length.
129 | */
130 | mVector["resize"] <- function(vector, newLength) {
131 | local currentLength = vector.Length()
132 | return vector * (newLength / currentLength)
133 | }
134 |
135 | /*
136 | * Rounds the elements of a vector to the specified precision.
137 | *
138 | * @param {Vector} vec - The vector to round.
139 | * @param {int} precision - The precision (e.g., 1000 for rounding to three decimal places).
140 | * @returns {Vector} - The rounded vector.
141 | */
142 | mVector["round"] <- function(vec, precision = 1000) {
143 | vec.x = floor(vec.x * precision + 0.5) / precision
144 | vec.y = floor(vec.y * precision + 0.5) / precision
145 | vec.z = floor(vec.z * precision + 0.5) / precision
146 | return vec
147 | }
148 |
149 | /*
150 | * Returns a vector with the signs (-1, 0, or 1) of each component of the input vector.
151 | *
152 | * @param {Vector} vec - The input vector.
153 | * @returns {Vector} - A new vector with the signs of the input vector's components.
154 | */
155 | mVector["sign"] <- function(vec) {
156 | return Vector(math.Sign(vec.x), math.Sign(vec.y), math.Sign(vec.z))
157 | }
158 |
159 | /*
160 | * Calculates the absolute value of each component in a vector.
161 | *
162 | * @param {Vector} vector - The vector to calculate the absolute values for.
163 | * @returns {Vector} - A new vector with the absolute values of the original vector's components.
164 | */
165 | mVector["abs"] <- function(vector) {
166 | return Vector(::abs(vector.x), ::abs(vector.y), ::abs(vector.z))
167 | }
168 |
--------------------------------------------------------------------------------
/SRC/PCapture-Lib.nut:
--------------------------------------------------------------------------------
1 | /*+--------------------------------------------------------------------------------+
2 | | PCapture Vscripts Library |
3 | +---------------------------------------------------------------------------------+
4 | | Author: |
5 | | One-of-a-Kind - laVashik |
6 | +---------------------------------------------------------------------------------+
7 | | pcapture-lib.nut |
8 | | The main file in the library. initializes required parts of the library |
9 | | GitHud repo: https://github.com/IaVashik/PCapture-LIB |
10 | +----------------------------------------------------------------------------------+ */
11 |
12 | local version = "PCapture-Lib 3.8 Stable"
13 | local rootScope = getroottable()
14 |
15 | // `Self` must be in any case, even if the script is run directly by the interpreter
16 | if (!("self" in this)) {
17 | self <- Entities.First()
18 | } else {
19 | getroottable()["self"] <- self
20 | }
21 |
22 | if("LIB_VERSION" in getroottable() && version.find("Debug") == null) {
23 | printl("\n")
24 | dev.warning("PCapture-Lib already initialized.")
25 | if(LIB_VERSION != version) {
26 | dev.error("Attempting to initialize different versions of the PCapture-Lib library!")
27 | dev.fprint("Version \"{}\" != \"{}\"", LIB_VERSION, version)
28 | }
29 | return
30 | }
31 |
32 | /*
33 | * Global Tables Initialization:
34 | *
35 | * This section initializes the fundamental global tables required for the PCapture Library.
36 | * These tables are essential for ensuring the correct functioning
37 | * of the library's various components during runtime.
38 | */
39 | ::pcapEntityCache <- {}
40 | ::EntitiesScopes <- {}
41 | ::TracePlusIgnoreEnts <- {}
42 |
43 | DoIncludeScript("PCapture-LIB/SRC/IDT/init.nut", rootScope)
44 | DoIncludeScript("PCapture-LIB/SRC/Math/init.nut", rootScope)
45 | DoIncludeScript("PCapture-LIB/SRC/ActionScheduler/init.nut", rootScope)
46 | DoIncludeScript("PCapture-LIB/SRC/Utils/init.nut", rootScope)
47 |
48 | ::LibLogger <- LoggerLevels.Info
49 | ::MAX_PORTAL_CAST_DEPTH <- 4
50 | ::ALWAYS_PRECACHED_MODEL <- "models/weapons/w_portalgun.mdl" // needed for pcapEntity::EmitSoundEx
51 |
52 | DoIncludeScript("PCapture-LIB/SRC/TracePlus/init.nut", rootScope)
53 | DoIncludeScript("PCapture-LIB/SRC/Animations/init.nut", rootScope)
54 | DoIncludeScript("PCapture-LIB/SRC/ScriptEvents/init.nut", rootScope)
55 | DoIncludeScript("PCapture-LIB/SRC/HUD/init.nut", rootScope)
56 |
57 | // Garbage collector for `PCapEntity::EntitiesScopes`
58 | ScheduleEvent.AddInterval("global", function() {
59 | foreach(ent, _ in EntitiesScopes) {
60 | if(!ent || !ent.IsValid()) {
61 | delete EntitiesScopes[ent]
62 | }
63 | }
64 | }, 5, 0)
65 |
66 |
67 | /*
68 | * Initializes eye tracking for all players, allowing retrieval of their coordinates and viewing directions.
69 | * This serves as a workaround for the absence of the EyeForward function in Portal 2.
70 | *
71 | * In multiplayer sessions, it reinitializes eye tracking after 1 second to ensure that players are set up
72 | * correctly, as there is typically a slight delay (1-2 seconds) in player initialization.
73 | *
74 | * When running in the Portal 2 Multiplayer Mod (P2MM), it schedules repeated initialization every second
75 | * to dynamically accommodate new players joining the session.
76 | */
77 |
78 | TrackPlayerJoins()
79 | ScheduleEvent.Add("global", TrackPlayerJoins, 0.4)
80 | if(IsMultiplayer()) {
81 | ScheduleEvent.Add("global", TrackPlayerJoins, 2) // Thanks Volve for making it take so long for players to initialize
82 | ScheduleEvent.AddInterval("global", HandlePlayerEventsMP, 0.3)
83 |
84 | if("TEAM_SINGLEPLAYER" in getroottable())
85 | // This session is running in P2MM, actively monitoring players.
86 | ScheduleEvent.AddInterval("global", TrackPlayerJoins, 1, 2)
87 | }
88 | else {
89 | ScheduleEvent.AddInterval("global", HandlePlayerEventsSP, 0.5, 0.5)
90 | }
91 |
92 |
93 | // Global settings for the portals correct working
94 | SetupLinkedPortals()
95 | local globalDetector = _CreatePortalDetector("CheckAllIDs", true)
96 | globalDetector.ConnectOutputEx("OnStartTouchPortal", function() {entLib.FromEntity(activator).SetTraceIgnore(false)})
97 | globalDetector.ConnectOutputEx("OnEndTouchPortal", function() {entLib.FromEntity(activator).SetTraceIgnore(true)})
98 |
99 |
100 | ::LIB_VERSION <- version
101 | ::PCaptureLibInited <- true
102 |
103 | /*
104 | * Prints information about the PCapture library upon initialization.
105 | *
106 | * This includes the library name, version, author, and a link to the GitHub repository.
107 | */
108 | printl("\n----------------------------------------")
109 | printl("Welcome to " + LIB_VERSION)
110 | printl("Author: laVashik Production") // The God of VScripts :P
111 | printl("GitHub: https://github.com/IaVashik/PCapture-LIB")
112 | printl("----------------------------------------\n")
113 |
--------------------------------------------------------------------------------
/SRC/ScriptEvents/event_listener.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Listens for and handles custom "game events".
3 | */
4 | ::EventListener <- {
5 | /*
6 | * Notifies the listener of a triggered event.
7 | *
8 | * @param {string} eventName - The name of the triggered event.
9 | * @param {any} ... - Optional arguments associated with the event.
10 | * @returns {any|null} - The result of the event's action, or null if the event is not found or the filter fails.
11 | */
12 | function Notify(eventName, ...) {
13 | if((eventName in AllScriptEvents) == false)
14 | return dev.warning("Unknown VGameEvent {" + eventName + "}")
15 |
16 | local varg = array(vargc)
17 | for(local i = 0; i< vargc; i++) {
18 | varg[i] = vargv[i]
19 | }
20 |
21 | return AllScriptEvents[eventName].Trigger(varg)
22 |
23 | }
24 |
25 | /*
26 | * Gets a VGameEvent object by name.
27 | *
28 | * @param {string} eventName - The name of the event to retrieve.
29 | * @returns {VGameEvent|null} - The VGameEvent object, or null if the event is not found.
30 | */
31 | function GetEvent(eventName) {
32 | return eventName in AllScriptEvents ? AllScriptEvents[eventName] : null
33 | }
34 | }
--------------------------------------------------------------------------------
/SRC/ScriptEvents/game_event.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Represents a custom "game event".
3 | *
4 | * This class allows you to create and manage custom "game events" with triggers, filters, and actions.
5 | */
6 | ::VGameEvent <- class {
7 | // The name of the event.
8 | eventName = null;
9 | // The number of times the event can be triggered before it becomes inactive (-1 for unlimited triggers).
10 | triggerCount = null;
11 | // The actions to be performed when the event is triggered.
12 | actions = null;
13 | // A filter function to check conditions before triggering the event.
14 | filterFunction = null;
15 |
16 | // Constructor for initializing the VGameEvent
17 | /*
18 | * Constructor for a VGameEvent object.
19 | *
20 | * @param {string} eventName - The name of the event.
21 | * @param {number} triggerCount - The number of times the event can be triggered (-1 for unlimited). (optional, default=-1)
22 | * @param {function} actionsList - The action to be performed when the event is triggered. (optional)
23 | */
24 | constructor(eventName, triggerCount = -1, actionsList = null) {
25 | this.eventName = eventName
26 | this.triggerCount = triggerCount
27 | this.actions = List()
28 |
29 | if(actionsList) this.actions.append(actionsList)
30 |
31 | AllScriptEvents[eventName] <- this
32 | }
33 |
34 | /*
35 | * Adds the action function for the event.
36 | *
37 | * @param {function} actionFunction - The function to execute when the event is triggered.
38 | */
39 | function AddAction(actionFunction) null
40 |
41 | function ClearActions() null
42 |
43 | /*
44 | * Sets the filter function for the event.
45 | *
46 | * The filter function should return true if the event should be triggered, false otherwise.
47 | *
48 | * @param {function} filterFunction - The function to use for filtering trigger conditions.
49 | */
50 | function SetFilter(filterFunc) null
51 |
52 | /*
53 | * Triggers the event if the trigger count allows and the filter function (if set) returns true.
54 | *
55 | * @param {any} args - Optional arguments to pass to the actions function.
56 | */
57 | function Trigger(args = null) null
58 |
59 | /*
60 | * Forces the event to trigger, ignoring the filter function and trigger count.
61 | *
62 | * @param {any} args - Optional arguments to pass to the actions function.
63 | */
64 | function ForceTrigger(args = null) null
65 | }
66 |
67 |
68 |
69 | /*
70 | * Add the action function for the event.
71 | *
72 | * @param {function} actionFunction - The function to execute when the event is triggered.
73 | */
74 | function VGameEvent::AddAction(actionFunction) {
75 | this.actions.append(actionFunction)
76 | }
77 |
78 | function VGameEvent::ClearActions() {
79 | this.actions.clear()
80 | }
81 |
82 | /*
83 | * Sets the filter function for the event.
84 | *
85 | * The filter function should return true if the event should be triggered, false otherwise.
86 | *
87 | * @param {function} filterFunction - The function to use for filtering trigger conditions.
88 | */
89 | function VGameEvent::SetFilter(filterFunc) {
90 | this.filterFunction = filterFunc
91 | }
92 |
93 |
94 | /*
95 | * Triggers the event if the trigger count allows and the filter function (if set) returns true.
96 | *
97 | * @param {array} args - Optional arguments to pass to the actions function.
98 | */
99 | function VGameEvent::Trigger(args) {
100 | if(this.actions.len() == 0) return
101 |
102 | if (this.triggerCount != 0 && (this.filterFunction == null || this.filterFunction(args))) {
103 | if (this.triggerCount > 0) {
104 | // Decrement the trigger count if it is not unlimited
105 | this.triggerCount--
106 | }
107 |
108 | return this.ForceTrigger(args)
109 | }
110 | }
111 |
112 |
113 | /*
114 | * Forces the event to trigger, ignoring the filter function and trigger count.
115 | *
116 | * @param {array} args - Optional arguments to pass to the actions function.
117 | */
118 | function VGameEvent::ForceTrigger(args) {
119 | args.insert(0, this)
120 | foreach(action in this.actions) {
121 | action.acall(args)
122 | }
123 | dev.trace("VScript Event Fired - " + this.eventName)
124 | return true // todo: what ._.
125 | }
--------------------------------------------------------------------------------
/SRC/ScriptEvents/init.nut:
--------------------------------------------------------------------------------
1 | /*+--------------------------------------------------------------------------------+
2 | | PCapture Vscripts Library |
3 | +----------------------------------------------------------------------------------+
4 | | Author: |
5 | | Game Event Guru - laVashik >_< |
6 | +----------------------------------------------------------------------------------+
7 | | The Script Events module, empowering you to create and handle custom |
8 | | "game events" with triggers, filters, and actions, |
9 | | surpassing the limitations of standard VScripts game events. |
10 | +----------------------------------------------------------------------------------+ */
11 |
12 | ::AllScriptEvents <- {}
13 |
14 | IncludeScript("PCapture-LIB/SRC/ScriptEvents/event_listener")
15 | IncludeScript("PCapture-LIB/SRC/ScriptEvents/game_event")
--------------------------------------------------------------------------------
/SRC/ScriptEvents/readme.md:
--------------------------------------------------------------------------------
1 | # Script Events Module
2 |
3 | This module provides a framework for creating and managing custom "game events" within VScript, similar to Source Engine's Game Events. These events, referred to as VGameEvents (VScript Game Events), allow for modular and extensible scripting by enabling different parts of your code to communicate and react to specific occurrences without direct coupling. This is particularly useful for creating custom mechanics and extending existing functionality.
4 |
5 | ## Table of Contents
6 |
7 | * [`ScriptEvents/game_event.nut`](#scripteventsgame_eventnut)
8 | * [`VGameEvent(eventName, triggerCount = -1, actionsList = null)`](#vgameeventeventname-triggercount---1-actionslist--null)
9 | * [`AddAction(actionFunction)`](#addactionactionfunction)
10 | * [`ClearActions()`](#clearactions)
11 | * [`SetFilter(filterFunc)`](#setfilterfilterfunc)
12 | * [`Trigger(args = null)`](#triggerargs--null)
13 | * [`ForceTrigger(args = null)`](#forcetriggerargs--null)
14 | * [`ScriptEvents/event_listener.nut`](#scripteventsevent_listenernut)
15 | * [`Notify(eventName, ...)`](#notifyeventname-)
16 | * [`GetEvent(EventName)`](#geteventeventname)
17 | * [`Creating and Using VGameEvents`](#creating-and-using-vgameevents)
18 |
19 | ## [ScriptEvents/game_event.nut](game_event.nut)
20 |
21 | ### `VGameEvent`
22 |
23 | The `VGameEvent` class represents a custom game event. It encapsulates the event's name, trigger conditions, actions to be performed when triggered, and optional filters.
24 |
25 | #### `VGameEvent(eventName, triggerCount = -1, actionsList = null)`
26 |
27 | **Constructor**
28 |
29 | Creates a new `VGameEvent` object.
30 |
31 | **Parameters:**
32 |
33 | * `eventName` (string): The unique name of the event.
34 | * `triggerCount` (number, optional): The number of times the event can be triggered before becoming inactive. Set to -1 for unlimited triggers (default is -1).
35 | * `actionsList` (function, optional): An initial action function to be added to the event's action list.
36 |
37 |
38 | **Example:**
39 |
40 | ```squirrel
41 | // Create a new VGameEvent named "my_custom_event"
42 | local myEvent = VGameEvent("my_custom_event")
43 |
44 | // Create a VGameEvent with a limited trigger count and an initial action
45 | local limitedEvent = VGameEvent("limited_event", 3, function(event, data) {
46 | printl("Limited event triggered with data: " + data)
47 | })
48 | ```
49 |
50 |
51 | #### `AddAction(actionFunction)`
52 |
53 | Adds an action function to the event's action list. This function will be executed when the event is triggered.
54 |
55 | **Parameters:**
56 |
57 | * `actionFunction` (function): The function to execute when the event is triggered. The function should accept the event object and any additional arguments passed during the trigger as parameters.
58 |
59 |
60 | **Example:**
61 |
62 | ```squirrel
63 | // Add an action to the "my_custom_event" event
64 | myEvent.AddAction(function(event, message) {
65 | printl("Event triggered: " + event.eventName + " with message: " + message)
66 | })
67 | ```
68 |
69 | #### `ClearActions()`
70 |
71 | Clears all action functions associated with the event.
72 |
73 | **Example:**
74 |
75 | ```squirrel
76 | // Clear all actions for the "my_custom_event" event
77 | myEvent.ClearActions()
78 | ```
79 |
80 |
81 | #### `SetFilter(filterFunc)`
82 |
83 | Sets a filter function for the event. The filter function will be executed before triggering the event's actions. If the filter function returns `false`, the event's actions will not be executed.
84 |
85 | **Parameters:**
86 |
87 | * `filterFunc` (function): The function to use for filtering trigger conditions. The function should accept any arguments passed during the trigger as parameters and return `true` to allow the event to trigger, or `false` to prevent it.
88 |
89 |
90 | **Example:**
91 |
92 | ```squirrel
93 | // Set a filter function for the "my_custom_event" event
94 | myEvent.SetFilter(function(player) {
95 | // Only trigger the event if the player's health is above 50
96 | return player.GetHealth() > 50
97 | })
98 | ```
99 |
100 | #### `Trigger(args = null)`
101 |
102 | Triggers the event if the trigger count allows and the filter function (if set) returns `true`.
103 |
104 | **Parameters:**
105 |
106 | * `args` (any, optional): Optional arguments to pass to the action functions.
107 |
108 |
109 | **Example:**
110 |
111 | ```squirrel
112 | // Trigger the "my_custom_event" event
113 | myEvent.Trigger("Hello from the event!")
114 | ```
115 |
116 |
117 | #### `ForceTrigger(args = null)`
118 |
119 | Forces the event to trigger, ignoring the filter function and trigger count.
120 |
121 | **Parameters:**
122 |
123 | * `args` (any, optional): Optional arguments to pass to the action functions.
124 |
125 |
126 | **Example:**
127 |
128 | ```squirrel
129 | // Force trigger the "my_custom_event" event
130 | myEvent.ForceTrigger("This will always trigger!")
131 | ```
132 |
133 | ## [ScriptEvents/event_listener.nut](event_listener.nut)
134 |
135 | ### `EventListener`
136 |
137 | The `EventListener` object provides a global interface for interacting with VGameEvents.
138 |
139 | #### `Notify(eventName, ...)`
140 |
141 | Notifies the listener of a triggered event. This function will attempt to trigger the event with the given name, passing any additional arguments to the event's action functions.
142 |
143 | **Parameters:**
144 |
145 | * `eventName` (string): The name of the event to trigger.
146 | * `...` (any): Optional arguments to pass to the event's action functions.
147 |
148 |
149 | **Example:**
150 |
151 | ```squirrel
152 | // Trigger the "my_custom_event" event using EventListener
153 | EventListener.Notify("my_custom_event", "Hello from EventListener!")
154 | ```
155 |
156 | #### `GetEvent(EventName)`
157 |
158 | Retrieves a VGameEvent object by its name.
159 |
160 | **Parameters:**
161 |
162 | * `EventName` (string): The name of the event to retrieve.
163 |
164 | **Returns:**
165 |
166 | * `VGameEvent|null`: The VGameEvent object if found, or `null` if the event does not exist.
167 |
168 |
169 | **Example:**
170 |
171 | ```squirrel
172 | // Get the "my_custom_event" VGameEvent object
173 | local myEvent = EventListener.GetEvent("my_custom_event")
174 | ```
175 |
176 | ## Creating and Using VGameEvents
177 |
178 | 1. **Define your events:** Create `VGameEvent` objects, typically in a central location, giving them descriptive names.
179 |
180 | 2. **Add actions:** Use the `AddAction` method to define the behavior that should occur when the event is triggered.
181 |
182 | 3. **Trigger events:** Use `EventListener.Notify` to trigger the event by name in your code.
183 |
184 | 4. **Handle events:** The action functions you defined will be executed when the event is triggered, allowing you to respond to the event in a modular and decoupled manner.
185 |
186 | **Real Examples from Project Capture:**
187 |
188 | **Defining Events:**
189 |
190 | ```squirrel
191 | PCaptureGameEvents <- {
192 | /* Core ASQRA Camera Events */
193 | CameraPicked = GameEvent("asqra_camera_picked_up"), /* Triggered when the ASQRA camera is picked up. */
194 | MemoryFlushed = GameEvent("asqra_memory_flushed"), /* Triggered when the camera's memory is flushed, clearing all captured objects. */
195 | ModeChanged = GameEvent("asqra_mode_changed"), /* Triggered when the camera mode changes between Capture and Placement modes. */
196 | // ... more events
197 | }
198 | ```
199 |
200 | **Triggering Events:**
201 |
202 | Events can be triggered from various parts of the code, for example, when a specific action occurs:
203 |
204 | ```squirrel
205 | function ASQRA::FlushMemory() {
206 | this.EnableCameraMode()
207 |
208 | // ... other logic ...
209 |
210 | // Trigger the MemoryFlushed event
211 | EventListener.Notify("asqra_memory_flushed", this) // `this` is the ASQRA
212 | }
213 | ```
214 |
215 | **Adding Actions (Handling Events):**
216 |
217 | You can add actions to events to define how the game should respond when the event is triggered:
218 |
219 | ```squirrel
220 | // Add an action to the ModeChanged event
221 | PCaptureGameEvents.ModeChanged.AddAction(function(event, camera) {
222 | // Display a message on the camera entity when the mode changes
223 | camera.ShowMessage("Mode changed")
224 | })
225 | ```
226 |
227 | This module provides a powerful tool for creating complex and extensible VScript systems by facilitating communication and event-driven logic within your scripts. By defining and triggering custom events, you can create more modular and maintainable code, making it easier to add new features and modify existing behavior without affecting other parts of your script.
--------------------------------------------------------------------------------
/SRC/TracePlus/bbox_analyzer.md:
--------------------------------------------------------------------------------
1 | ## BBox Casting Algorithm
2 |
3 | This document describes the implementation of a highly optimized BBox (Bounding Box) Casting algorithm used for precise collision detection in a game environment. This algorithm is designed to be performant even when dealing with a large number of entities, ensuring minimal impact on frame rate. This algorithm belongs to the class of algorithms that utilize approximation and heuristics to achieve practical performance in collision detection. :>
4 |
5 | ### Algorithm Overview
6 |
7 | The BBox Casting algorithm utilizes a multi-tiered search approach with caching and refinement techniques to efficiently determine if a ray intersects with the bounding box of any entity within the game world. This approach minimizes the number of expensive collision checks, significantly improving performance compared to a naive brute-force approach.
8 |
9 | ### Algorithm Steps:
10 |
11 | 1. **Initial Position Validation:** The algorithm starts by validating the initial starting position of the ray to ensure it is within the allowed bounds. If invalid, the algorithm immediately returns the starting position and no entity, guaranteeing correctness and preventing potential scripting errors.
12 |
13 | 2. **Initial Trace:** A fast, inexpensive trace using the standard `TraceLine` function is performed to get an initial hit position against the world geometry, providing a baseline for further analysis.
14 |
15 | 3. **Segment-Based Search:** The ray's trajectory from the start position to the initial hit position is divided into segments based on a predefined `JumpPercent` constant. This segmentation allows for localized searches, improving efficiency.
16 |
17 | 4. **"Dirty" Search (Entity Collection with Preliminary Filtering):** For each segment, a "dirty search" is performed using `Entities.FindByClassnameWithin()`, checking for entities within a sphere centered at the segment's midpoint. The sphere's radius is dynamically calculated based on the segment's length and a scaling factor. For each entity found:
18 | * **Filtering and Caching:** Entities undergo filtering based on the `TracePlus.Settings` object (ignore classes, priority classes, custom filters). To avoid redundant calculations, a caching mechanism (`EntBufferTable`) stores entity information (origin, bounding box dimensions) for entities that haven't changed their position. This cached data is reused in subsequent checks.
19 | * **AABB Intersection Test:** A fast `RayAabbIntersect` function checks if the ray segment intersects with the entity's bounding box. If an intersection is found, the `BufferedEntity` is added to the `entBuffer`. If not, it is marked to be ignored (`ignoreMe = true`) in subsequent segments, preventing unnecessary checks and further improving efficiency.
20 |
21 | 5. **Deep Search (Precise Collision Check):** If the "dirty search" yields any filtered entities in the `entBuffer`, a "deep search" is initiated within the current segment. This search iterates through a number of points along the segment, determined by the `searchSteps` which is based on the search radius and `depthAccuracy` setting in `TracePlus.Settings`. For each point, a precise `PointInBBox()` check is performed to determine if the point lies within the bounding box of any entity in the `entBuffer`.
22 |
23 | 6. **Binary Refinement (Optional):** If the `bynaryRefinement` setting in `TracePlus.Settings` is enabled, and a point is found inside the bounding box during the "deep search," an additional binary refinement search is performed. This search iteratively narrows down the hit point to a more precise location within the bounding box, improving accuracy, especially for surface normal calculations.
24 |
25 | 7. **Result Handling:** If a collision is confirmed during the "deep search" (with or without binary refinement), the algorithm returns the refined hit point and the corresponding entity, indicating a successful hit. If no collision is found within a segment, the entity buffer is cleared, and the algorithm proceeds to the next segment.
26 |
27 | 8. **Final Result:** If no intersection is found after processing all segments, the algorithm returns the initial hit position obtained in the first step and `null` for the entity, indicating that the ray did not hit any entity's bounding box.
28 |
29 | ### Optimizations:
30 |
31 | * **Initial Position Validation:** Prevents errors and ensures algorithm reliability.
32 | * **Segment-Based Search:** Dividing the ray into segments and performing a coarse "dirty search" before a more precise "deep search" drastically reduces unnecessary checks, particularly when entities are sparsely distributed.
33 | * **Entity Filtering and Caching:** Using `TracePlus.Settings` and the `EntBufferTable` avoids redundant checks and calculations for ignored entities or entities that haven't moved.
34 | * **AABB Intersection Test with Ignore Flag:** The `RayAabbIntersect` function, coupled with the `ignoreMe` flag for `BufferedEntity`, efficiently filters out entities that are guaranteed not to intersect with the ray in the current and subsequent segments.
35 | * **Dynamic Search Radius:** The search radius for `FindByClassnameWithin()` is dynamically adjusted based on the segment's length, ensuring that the search area is appropriate for the current segment.
36 | * **Binary Refinement:** The optional binary refinement step further improves the hit point accuracy when enabled.
37 |
38 | ### Code Example:
39 |
40 | ```squirrel
41 | // ... (BufferedEntity class, JumpPercent constant, EntBufferTable) ...
42 |
43 | function TraceLineAnalyzer::Trace(startPos, endPos, ignoreEntities, note = null) {
44 | // ... (Initial Position Validation, Initial Trace, entBuffer initialization, halfSegment, segmentsLenght, searchRadius, searchSteps calculations) ...
45 |
46 | for (local segment = 0; segment < 1; segment += JumpPercent) {
47 | // ... ("Dirty Search" with RayAabbIntersect logic) ...
48 |
49 | // ... ("Deep Search" logic) ...
50 |
51 | // ... (Binary Refinement logic - optional) ...
52 |
53 | // ... (Cleanup buffer) ...
54 | }
55 |
56 | // ... (Return result) ...
57 | }
58 | ```
59 |
60 | ### Conclusion:
61 |
62 | The BBox Casting algorithm implemented here presents a highly optimized solution for accurate and efficient collision detection in a game environment. By utilizing a tiered search strategy, caching relevant data, employing early termination techniques with AABB intersection testing, and offering optional binary refinement, this algorithm minimizes computational overhead and ensures smooth gameplay even in scenarios involving numerous entities.
--------------------------------------------------------------------------------
/SRC/TracePlus/bboxcast.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Performs a bbox cast (trace with bounding box) from the specified start and end positions.
3 | *
4 | * @param {Vector} startPos - The start position of the trace.
5 | * @param {Vector} endPos - The end position of the trace.
6 | * @param {array|CBaseEntity|null} ignoreEntities - A list of entities or a single entity to ignore during the trace. (optional)
7 | * @param {TraceSettings} settings - The settings to use for the trace. (optional, defaults to TracePlus.defaultSettings)
8 | * @param {string|null} note - An optional note associated with the trace.
9 | * @returns {BboxTraceResult} - The trace result object.
10 | */
11 | TracePlus["Bbox"] <- function(startPos, endPos, ignoreEntities = null, settings = TracePlus.defaultSettings, note = null) {
12 | if(typeof startPos != "Vector") throw("TracePlus.Bbox: 'startPos' argument must be a Vector, but got " + typeof startPos);
13 | if(typeof endPos != "Vector") throw("TracePlus.Bbox: 'endPos' argument must be a Vector, but got " + typeof endPos);
14 | if(typeof settings != "TraceSettings") throw("TracePlus.Bbox: 'settings' argument must be a TraceSettings, but got " + typeof settings);
15 | if(ignoreEntities != null) {
16 | local ignoreType = typeof ignoreEntities;
17 | if (ignoreType == "array" || ignoreType == "ArrayEx" || ignoreType == "List") {
18 | foreach(idx, ent in ignoreEntities) {
19 | if (typeof ent != "pcapEntity" && !(ent instanceof CBaseEntity)) {
20 | throw(format("TracePlus.Bbox: 'ignoreEntities' array/list contains a non-entity value at index %d (got %s). It must contain only entity handles.", idx, typeof ent));
21 | }
22 | }
23 | } else if (ignoreType != "pcapEntity" && !(ignoreEntities instanceof CBaseEntity)) {
24 | throw("TracePlus.Bbox: 'ignoreEntities' argument must be an entity handle, an array/list of entities, or null, but got " + ignoreType);
25 | }
26 | }
27 |
28 | local SCOPE = {} // TODO potential place for improvement
29 |
30 | SCOPE.startpos <- startPos;
31 | SCOPE.endpos <- endPos;
32 | SCOPE.ignoreEntities <- ignoreEntities
33 | SCOPE.settings <- settings
34 | SCOPE.note <- note
35 |
36 | local result = TraceLineAnalyzer(startPos, endPos, ignoreEntities, settings, note)
37 |
38 | return TracePlus.Result.Bbox(SCOPE, result.GetHitpos(), result.GetEntity())
39 | }
40 |
41 | /*
42 | * Performs a bbox cast from the player's eyes.
43 | *
44 | * @param {number} distance - The distance of the trace.
45 | * @param {CBaseEntity|pcapEntity} player - The player entity.
46 | * @param {array|CBaseEntity|null} ignoreEntities - A list of entities or a single entity to ignore during the trace. (optional)
47 | * @param {TraceSettings} settings - The settings to use for the trace. (optional, defaults to TracePlus.defaultSettings)
48 | * @returns {BboxTraceResult} - The trace result object.
49 | */
50 | TracePlus["FromEyes"]["Bbox"] <- function(distance, player, ignoreEntities = null, settings = TracePlus.defaultSettings) {
51 | // Calculate the start and end positions of the trace
52 | local startPos = player.EyePosition()
53 | local endPos = macros.GetEyeEndpos(player, distance)
54 |
55 | ignoreEntities = TracePlus.Settings.UpdateIgnoreEntities(ignoreEntities, player)
56 |
57 | // Perform the bboxcast trace and return the trace result
58 | return TracePlus.Bbox(startPos, endPos, ignoreEntities, settings)
59 | }
--------------------------------------------------------------------------------
/SRC/TracePlus/calculate_normal.md:
--------------------------------------------------------------------------------
1 | ## Calculate Normal Algorithms
2 |
3 | This document describes the algorithms implemented in the `calculate_normal.nut` file for determining the impact normal of a surface hit by a trace. Three distinct algorithms are employed, each tailored for different scenarios:
4 |
5 | ### 1. Three-Ray Method for World Geometry (`CalculateImpactNormal`)
6 |
7 | This algorithm calculates the impact normal for surfaces that are part of the world geometry (not dynamic entities). It's based on the "three-ray method," which involves performing three traces to determine the orientation of the surface at the hit point.
8 |
9 | **Steps:**
10 |
11 | 1. **Calculate Trace Direction:** The normalized direction vector of the original trace (from start position to hit position) is calculated.
12 |
13 | 2. **Determine New Start Positions:** Two new start positions for additional traces are calculated by offsetting the original start position slightly along two vectors perpendicular to the trace direction. These offsets ensure that the additional traces will hit the surface at different points, allowing for the calculation of the normal vector.
14 |
15 | 3. **Perform Additional Traces:** Two cheap traces are performed from the new start positions in the same direction as the original trace. The hit positions of these traces are recorded as intersection points.
16 |
17 | 4. **Calculate Normal Vector:** The normal vector of the surface is calculated using the cross product of two edge vectors formed by the hit position of the original trace and the two intersection points obtained from the additional traces. The resulting vector is normalized to have a length of 1.
18 |
19 | **Limitations:**
20 |
21 | This method is primarily suitable for world geometry because it relies on the ability to perform cheap traces in close proximity to the hit point without hitting other entities.
22 |
23 | ### 2. Optimized Bounding Box Normal Calculation (`CalculateImpactNormalFromBbox`)
24 |
25 | This algorithm calculates the impact normal for dynamic entities using an optimized approach based on the entity's bounding box. It leverages the property that one of the coordinates of the hit point is likely to be the same (or very close) across some four vertices of the bounding box.
26 |
27 | **Steps:**
28 |
29 | 1. **Retrieve Bounding Box Vertices:** The eight vertices of the hit entity's bounding box are retrieved.
30 |
31 | 2. **Identify Closest Vertices:** A specialized function `_getFaceVertices` is used to identify the four vertices that form the face of the bounding box closest to the hit point. This function relies on finding vertices with nearly identical coordinates along one of the axes (X, Y, or Z).
32 |
33 | 3. **Calculate Face Normal:** If four appropriate vertices are found, the normal vector of the face is calculated using the cross product of two edge vectors of the face.
34 |
35 | 4. **Verify Normal Direction:** The dot product of the face normal and the trace direction vector is calculated. If the dot product is positive, it indicates that the normal vector is pointing in the wrong direction (towards the trace origin), and it's inverted.
36 |
37 | 5. **Fallback to `CalculateImpactNormalFromBbox2`:** If `_getFaceVertices` fails to find four suitable vertices (e.g., due to an inaccurate hit point), the algorithm falls back to the `CalculateImpactNormalFromBbox2` method.
38 |
39 | ### 3. Fallback Bounding Box Normal Calculation (`CalculateImpactNormalFromBbox2`)
40 |
41 | This algorithm serves as a fallback when the `CalculateImpactNormalFromBbox` method cannot determine the normal vector reliably. It provides a less precise but more robust way to estimate the normal using the three closest vertices of the bounding box.
42 |
43 | **Steps:**
44 |
45 | 1. **Retrieve Bounding Box Vertices:** The eight vertices of the hit entity's bounding box are retrieved.
46 |
47 | 2. **Find Three Closest Vertices:** The three vertices closest to the hit point are identified based on their Euclidean distance.
48 |
49 | 3. **Calculate Triangle Normal:** The normal vector of the triangle formed by the three closest vertices is calculated using the cross product of two edge vectors of the triangle.
50 |
51 | 4. **Verify Normal Direction:** Similar to the previous algorithm, the dot product of the triangle normal and the trace direction vector is checked, and the normal vector is inverted if necessary.
52 |
53 | **Limitations:**
54 |
55 | This method can be less accurate, especially for rectangular objects, as the three closest vertices might not always represent the actual surface orientation at the hit point. For example, if the hit point is near a corner of a rectangular object, the closest vertices might be on opposite corners, leading to an incorrect normal calculation.
56 |
57 |
58 | **Conclusion:**
59 |
60 | The `calculate_normal.nut` file provides three different algorithms for calculating impact normals, each with its own strengths and limitations. The choice of algorithm depends on the type of surface being hit (world geometry or dynamic entity) and the desired level of accuracy. The optimized `CalculateImpactNormalFromBbox` method is generally preferred for dynamic entities, while the fallback `CalculateImpactNormalFromBbox2` method provides a more robust but potentially less precise alternative. The `CalculateImpactNormal` method is suitable for world geometry where the three-ray method can be applied reliably.
61 |
62 |
--------------------------------------------------------------------------------
/SRC/TracePlus/calculate_normal.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Calculates two new start positions for additional traces used in impact normal calculation.
3 | *
4 | * @param {Vector} startPos - The original start position of the trace.
5 | * @param {Vector} dir - The direction vector of the trace.
6 | * @returns {array} - An array containing two new start positions as Vectors.
7 | */
8 | function _getNewStartsPos(startPos, dir) {
9 | // Calculate offset vectors perpendicular to the trace direction
10 | local perpDir = Vector(-dir.y, dir.x, 0)
11 | local offset1 = perpDir
12 | local offset2 = dir.Cross(offset1)
13 |
14 | // Calculate new start positions for two additional traces
15 | local newStart1 = startPos + offset1
16 | local newStart2 = startPos + offset2
17 |
18 | return [
19 | newStart1,
20 | newStart2
21 | ]
22 | }
23 |
24 | /*
25 | * Performs a cheap trace from a new start position to find an intersection point.
26 | *
27 | * @param {Vector} newStart - The new start position for the trace.
28 | * @param {Vector} dir - The direction vector of the trace.
29 | * @returns {Vector} - The hit position of the trace.
30 | */
31 | function _getIntPoint(newStart, dir) {
32 | return TracePlus.Cheap(newStart, (newStart + dir * 8000)).GetHitpos()
33 | }
34 |
35 | /*
36 | * Calculates the normal vector of a triangle .
37 | *
38 | * @param {Vector} v1 - The first .
39 | * @param {Vector} v2 - The second .
40 | * @param {Vector} v3 - The third .
41 | * @returns {Vector} - The normal vector of the triangle.
42 | */
43 | function _calculateNormal(v1, v2, v3) {
44 | // Calculate two edge vectors of the triangle.
45 | local edge1 = v2 - v1
46 | local edge2 = v3 - v1
47 |
48 | // Calculate the normal vector using the cross product of the edge vectors.
49 | local normal = edge1.Cross(edge2)
50 | normal.Norm()
51 |
52 | return normal
53 | }
54 |
55 | /*
56 | * Finds the three closest vertices to a given point from a list of vertices.
57 | *
58 | * @param {Vector} point - The point to find the closest vertices to.
59 | * @param {array} vertices - An array of Vector objects representing the vertices.
60 | * @returns {array} - An array containing the three closest vertices as Vector objects.
61 | */
62 | function _findClosestVertices(point, vertices) {
63 | // Sort the vertices based on their distance to the point.
64 | vertices.sort(function(a, b):(point) {
65 | return (a - point).LengthSqr() - (b - point).LengthSqr()
66 | })
67 |
68 | // Return the three closest vertices.
69 | return vertices.slice(0, 3)
70 | }
71 |
72 | function _numberIsCloseTo(num1, num2, tolerance = 1) {
73 | return abs(num1 - num2) <= tolerance;
74 | }
75 |
76 | /*
77 | Gets the proper vertices of a face (4 of them), regarding the hitPoint
78 | allVertices - Array of Vectors, where each vector represents the position of a vertex point of the bounding box
79 | hitPoint - vector of the position where the ray hit
80 | */
81 | function _getFaceVertices(allVertices, hitPoint, origin) {
82 | local resultX = []
83 | local resultY = []
84 | local resultZ = []
85 | hitPoint -= origin
86 |
87 | foreach(vertexPoint in allVertices) {
88 | if(_numberIsCloseTo(vertexPoint.x, hitPoint.x)) resultX.append(vertexPoint)
89 | else if(_numberIsCloseTo(vertexPoint.y, hitPoint.y)) resultY.append(vertexPoint)
90 | else if(_numberIsCloseTo(vertexPoint.z, hitPoint.z)) resultZ.append(vertexPoint)
91 | if(3 in resultX || 3 in resultY || 3 in resultZ) break
92 | }
93 |
94 | if(3 in resultX) return resultX
95 | if(3 in resultY) return resultY
96 | if(3 in resultZ) return resultZ
97 |
98 | return null
99 | }
100 |
101 |
102 | /*
103 | * Calculates the impact normal of a surface hit by a trace.
104 | *
105 | * @param {Vector} startPos - The start position of the trace.
106 | * @param {Vector} hitPos - The hit position of the trace.
107 | * @returns {Vector} - The calculated impact normal vector.
108 | */
109 | ::CalculateImpactNormal <- function(startPos, hitPos) {
110 | // Calculate the normalized direction vector from startpos to hitpos
111 | local dir = hitPos - startPos
112 | dir.Norm()
113 |
114 | // Get two new start positions for additional traces.
115 | local newStartsPos = _getNewStartsPos(startPos, dir)
116 |
117 | // Perform cheap traces from the new start positions to find intersection points.
118 | local point1 = _getIntPoint(newStartsPos[0], dir)
119 | local point2 = _getIntPoint(newStartsPos[1], dir)
120 |
121 | return _calculateNormal(hitPos, point2, point1)
122 | }
123 |
124 | ::CalculateImpactNormalFromBbox <- function(startPos, hitPos, hitEntity) {
125 | // The algorithm proposed by Enderek
126 | local closestVertices = _getFaceVertices(hitEntity.getBBoxPoints(), hitPos, hitEntity.GetOrigin())
127 | if(!closestVertices)
128 | return CalculateImpactNormalFromBbox2(startPos, hitPos, hitEntity)
129 |
130 | local faceNormal = _calculateNormal(closestVertices[0], closestVertices[1], closestVertices[2])
131 |
132 | local traceDir = (hitPos - startPos)
133 | traceDir.Norm()
134 | if (faceNormal.Dot(traceDir) > 0)
135 | return faceNormal * -1
136 |
137 | return faceNormal
138 | }
139 |
140 | /*
141 | * Calculates the impact normal of a surface hit by a trace using the bounding box of the hit entity.
142 | *
143 | * @param {Vector} startPos - The start position of the trace.
144 | * @param {Vector} hitPos - The hit position of the trace.
145 | * @param {BboxTraceResult} traceResult - The trace result object.
146 | * @returns {Vector} - The calculated impact normal vector.
147 | */
148 | ::CalculateImpactNormalFromBbox2 <- function(startPos, hitPos, hitEntity) {
149 | // Get the entity bounding box vertices.
150 | local bboxVertices = hitEntity.getBBoxPoints()
151 |
152 | // Find the three closest vertices to the hit position.
153 | local closestVertices = _findClosestVertices(hitPos - hitEntity.GetOrigin(), bboxVertices)
154 |
155 | // Calculate the normal vector of the face formed by the three closest vertices.
156 | local faceNormal = _calculateNormal(closestVertices[0], closestVertices[1], closestVertices[2])
157 |
158 | // Ensure the normal vector points away from the trace direction.
159 | local traceDir = (hitPos - startPos)
160 | traceDir.Norm()
161 | if (faceNormal.Dot(traceDir) > 0)
162 | return faceNormal * -1
163 |
164 | return faceNormal
165 | }
--------------------------------------------------------------------------------
/SRC/TracePlus/cheap_trace.nut:
--------------------------------------------------------------------------------
1 | // Haha, pseudo-constuctor-class
2 | /*
3 | * Performs a cheap (fast but less accurate) trace.
4 | *
5 | * @param {Vector} startpos - The start position of the trace.
6 | * @param {Vector} endpos - The end position of the trace.
7 | * @returns {CheapTraceResult} - The trace result object.
8 | */
9 | TracePlus["Cheap"] <- function(startpos, endpos) {
10 | local SCOPE = {}
11 |
12 | SCOPE.startpos <- startpos
13 | SCOPE.endpos <- endpos
14 | // SCOPE.type <- "CheapTrace"
15 |
16 | SCOPE.fraction <- TraceLine(startpos, endpos, null)
17 | local hitpos = startpos + (endpos - startpos) * SCOPE.fraction
18 |
19 | return TracePlus.Result.Cheap(SCOPE, hitpos)
20 | }
21 |
22 |
23 | /*
24 | * Performs a cheap trace from the player's eyes.
25 | *
26 | * @param {number} distance - The distance of the trace.
27 | * @param {CBaseEntity|pcapEntity} player - The player entity.
28 | * @returns {CheapTraceResult} - The trace result object.
29 | */
30 | TracePlus["FromEyes"]["Cheap"] <- function(distance, player) {
31 | // Calculate the start and end positions of the trace
32 | local startpos = player.EyePosition()
33 | local endpos = macros.GetEyeEndpos(player, distance)
34 |
35 | return TracePlus.Cheap(startpos, endpos)
36 | }
--------------------------------------------------------------------------------
/SRC/TracePlus/init.nut:
--------------------------------------------------------------------------------
1 | /*+--------------------------------------------------------------------------------+
2 | | PCapture Vscripts Library |
3 | +----------------------------------------------------------------------------------+
4 | | Author: |
5 | | Ray Tracing Virtuoso - laVashik ˙▿˙ |
6 | +----------------------------------------------------------------------------------+
7 | | The TracePlus module, taking ray tracing to the next level with advanced |
8 | | features like portal support and precise collision detection algorithms. |
9 | +----------------------------------------------------------------------------------+ */
10 |
11 | ::TracePlus <- {
12 | Result = {},
13 | Portals = {},
14 | FromEyes = {},
15 |
16 | Settings = null,
17 | defaultSettings = null,
18 |
19 | Cheap = null,
20 | Bbox = null,
21 | }
22 |
23 | IncludeScript("PCapture-LIB/SRC/TracePlus/results")
24 | IncludeScript("PCapture-LIB/SRC/TracePlus/trace_settings")
25 | TracePlus.defaultSettings = TracePlus.Settings.new()
26 |
27 | IncludeScript("PCapture-LIB/SRC/TracePlus/cheap_trace")
28 | IncludeScript("PCapture-LIB/SRC/TracePlus/bboxcast")
29 | IncludeScript("PCapture-LIB/SRC/TracePlus/portal_casting")
30 |
31 | IncludeScript("PCapture-LIB/SRC/TracePlus/bbox_analyzer")
32 | IncludeScript("PCapture-LIB/SRC/TracePlus/calculate_normal")
--------------------------------------------------------------------------------
/SRC/TracePlus/portal_casting.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Applies portal transformations to a trace, calculating a new start and end position for the trace after passing through the portal.
3 | *
4 | * @param {Vector} startPos - The original start position of the trace.
5 | * @param {Vector} hitPos - The hit position of the trace on the portal.
6 | * @param {int} rayDist - Distance of the new ray.
7 | * @param {pcapEntity} portal - The portal entity.
8 | * @param {pcapEntity} partnerPortal - The partner portal entity.
9 | * @returns {table} - A table containing the new startPos and endPos for the trace after passing through the portal.
10 | */
11 | ::_ApplyPortal <- function(startPos, hitPos, rayDist, portal, partnerPortal) {
12 | local portalAngles = portal.GetAngles();
13 | local partnerAngles = partnerPortal.GetAngles();
14 | local offset = math.vector.unrotate(hitPos - portal.GetOrigin(), portalAngles);
15 | local dir = math.vector.unrotate(hitPos - startPos, portalAngles);
16 |
17 | offset = Vector(offset.x * -1, offset.y * -1, offset.z)
18 | dir = Vector(dir.x * -1, dir.y * -1, dir.z)
19 |
20 | dir = math.vector.rotate(dir, partnerAngles)
21 | dir.Norm()
22 |
23 | local newStart = partnerPortal.GetOrigin() + math.vector.rotate(offset, partnerAngles)
24 | return {
25 | startPos = newStart,
26 | endPos = newStart + dir * rayDist
27 | }
28 | }
29 |
30 |
31 | /*
32 | * Performs a cheap trace with portal support.
33 | *
34 | * @param {Vector} startPos - The start position of the trace.
35 | * @param {Vector} endPos - The end position of the trace.
36 | * @returns {CheapTraceResult} - The trace result object, including information about portal entries.
37 | */
38 | TracePlus["PortalCheap"] <- function(startPos, endPos) {
39 | local length = (endPos - startPos).Length()
40 | local previousTraceData
41 | // Portal castings
42 | for (local i = 0; i < MAX_PORTAL_CAST_DEPTH; i++) {
43 | local traceData = TracePlus.Cheap(startPos, endPos)
44 | traceData.portalEntryInfo = previousTraceData
45 |
46 | local hitPos = traceData.GetHitpos()
47 | length -= (hitPos - startPos).Length()
48 |
49 | // Find a nearby portal entity.
50 | local portal = entLib.FindByClassnameWithin("prop_portal", hitPos, 5)
51 | if(!portal)
52 | portal = entLib.FindByClassnameWithin("linked_portal_door", hitPos, 10)
53 | if(!portal)
54 | return traceData
55 |
56 | local normal = traceData.GetImpactNormal()
57 | if(normal.Dot(portal.GetForwardVector()) < 0.8)
58 | return traceData
59 |
60 | local partnerPortal = portal.GetPartnerInstance()
61 | if (partnerPortal == null) {
62 | return traceData
63 | }
64 |
65 | // Calculate new start and end positions for the trace after passing through the portal.
66 | local ray = _ApplyPortal(startPos, hitPos, length, portal, partnerPortal);
67 | // Adjust the start position slightly to prevent the trace from getting stuck.
68 | startPos = ray.startPos + partnerPortal.GetForwardVector()
69 | endPos = ray.endPos
70 | previousTraceData = traceData
71 | }
72 | return previousTraceData
73 | }
74 |
75 | /*
76 | * Performs a cheap trace with portal support from the player's eyes.
77 | *
78 | * @param {number} distance - The distance of the trace.
79 | * @param {CBaseEntity|pcapEntity} player - The player entity.
80 | * @returns {CheapTraceResult} - The trace result object.
81 | */
82 | TracePlus["FromEyes"]["PortalCheap"] <- function(distance, player) {
83 | // Calculate the start and end positions of the trace
84 | local startpos = player.EyePosition()
85 | local endpos = macros.GetEyeEndpos(player, distance)
86 |
87 | return TracePlus.PortalCheap(startpos, endpos)
88 | }
89 |
90 |
91 | /*
92 | * Performs a bbox cast with portal support.
93 | *
94 | * @param {Vector} startPos - The start position of the trace.
95 | * @param {Vector} endPos - The end position of the trace.
96 | * @param {array|CBaseEntity|null} ignoreEntities - A list of entities or a single entity to ignore during the trace. (optional)
97 | * @param {TraceSettings} settings - The settings to use for the trace. (optional, defaults to TracePlus.defaultSettings)
98 | * @param {any|null} note - An optional note associated with the trace.
99 | * @returns {BboxTraceResult} - The trace result object, including information about portal entries.
100 | */
101 | TracePlus["PortalBbox"] <- function(startPos, endPos, ignoreEntities = null, settings = TracePlus.defaultSettings, note = null) {
102 | local length = (endPos - startPos).Length()
103 | local previousTraceData
104 | // Portal castings
105 | for (local i = 0; i < MAX_PORTAL_CAST_DEPTH; i++) {
106 | local traceData = TracePlus.Bbox(startPos, endPos, ignoreEntities, settings, note)
107 | traceData.portalEntryInfo = previousTraceData
108 |
109 | local hitPos = traceData.GetHitpos()
110 | local portal = traceData.GetEntity()
111 | length -= (hitPos - startPos).Length()
112 |
113 | if(!portal || portal.GetClassname() != "linked_portal_door")
114 | portal = entLib.FindByClassnameWithin("prop_portal", hitPos, 1) // todo: i should optimize it...
115 | if(!portal)
116 | return traceData
117 |
118 | local partnerPortal = portal.GetPartnerInstance()
119 | if (partnerPortal == null) {
120 | dev.warning("Portal {} doesn't have a partner ;(", portal)
121 | return traceData
122 | }
123 |
124 | if(portal.GetUserData("TracePlusIgnore") || partnerPortal.GetUserData("TracePlusIgnore"))
125 | return traceData
126 |
127 | if(portal.GetClassname() == "prop_portal") {
128 | local normal = traceData.GetImpactNormal()
129 | if(normal.Dot(portal.GetForwardVector()) < 0.8)
130 | return traceData
131 | }
132 |
133 | ignoreEntities = TracePlus.Settings.UpdateIgnoreEntities(ignoreEntities, partnerPortal)
134 |
135 | // Calculate new start and end positions for the trace after passing through the portal.
136 | local ray = _ApplyPortal(startPos, hitPos, length, portal, partnerPortal);
137 | // Adjust the start position slightly to prevent the trace from getting stuck.
138 | startPos = ray.startPos + partnerPortal.GetForwardVector() * 7 // Workaround to avoid the TraceLine starting inside a wall.
139 | endPos = ray.endPos
140 | previousTraceData = traceData
141 | }
142 | return previousTraceData
143 | }
144 |
145 | /*
146 | * Performs a bbox cast with portal support from the player's eyes.
147 | *
148 | * @param {number} distance - The distance of the trace.
149 | * @param {CBaseEntity|pcapEntity} player - The player entity.
150 | * @param {array|CBaseEntity|null} ignoreEntities - A list of entities or a single entity to ignore during the trace. (optional)
151 | * @param {TraceSettings} settings - The settings to use for the trace. (optional, defaults to TracePlus.defaultSettings)
152 | * @param {any|null} note - An optional note associated with the trace.
153 | * @returns {BboxTraceResult} - The trace result object.
154 | */
155 | TracePlus["FromEyes"]["PortalBbox"] <- function(distance, player, ignoreEntities = null, settings = TracePlus.defaultSettings, note = null) {
156 | // Calculate the start and end positions of the trace
157 | local startPos = player.EyePosition()
158 | local endPos = macros.GetEyeEndpos(player, distance)
159 | ignoreEntities = TracePlus.Settings.UpdateIgnoreEntities(ignoreEntities, player)
160 |
161 | // Perform the bboxcast trace and return the trace result
162 | return TracePlus.PortalBbox(startPos, endPos, ignoreEntities, settings, note)
163 | }
--------------------------------------------------------------------------------
/SRC/TracePlus/results.nut:
--------------------------------------------------------------------------------
1 | local results = TracePlus["Result"]
2 |
3 | results["Cheap"] <- class {
4 | traceHandler = null;
5 | hitpos = null;
6 |
7 | surfaceNormal = null;
8 | portalEntryInfo = null;
9 |
10 | /*
11 | * Constructor for a CheapTraceResult object.
12 | *
13 | * @param {table} traceHandler - The trace handler object containing trace information.
14 | * @param {Vector} hitpos - The hit position of the trace.
15 | */
16 | constructor(traceHandler, hitpos) {
17 | this.traceHandler = traceHandler
18 | this.hitpos = hitpos
19 | }
20 |
21 | /*
22 | * Gets the start position of the trace.
23 | *
24 | * @returns {Vector} - The start position.
25 | */
26 | function GetStartPos() {
27 | return this.traceHandler.startpos
28 | }
29 |
30 | /*
31 | * Gets the end position of the trace.
32 | *
33 | * @returns {Vector} - The end position.
34 | */
35 | function GetEndPos() {
36 | return this.traceHandler.endpos
37 | }
38 |
39 | /*
40 | * Gets the hit position of the trace.
41 | *
42 | * @returns {Vector} - The hit position.
43 | */
44 | function GetHitpos() {
45 | return this.hitpos
46 | }
47 |
48 | /*
49 | * Gets the fraction of the trace distance where the hit occurred.
50 | *
51 | * @returns {number} - The hit fraction.
52 | */
53 | function GetFraction() {
54 | return this.traceHandler.fraction
55 | }
56 |
57 | /*
58 | * Checks if the trace hit anything.
59 | *
60 | * @returns {boolean} - True if the trace hit something, false otherwise.
61 | */
62 | function DidHit() {
63 | return this.traceHandler.fraction != 1
64 | }
65 |
66 | /*
67 | * Gets the direction vector of the trace.
68 | *
69 | * @returns {Vector} - The direction vector.
70 | */
71 | function GetDir() {
72 | return (this.GetEndPos() - this.GetStartPos())
73 | }
74 |
75 | /*
76 | * Gets the portal entry information for the trace.
77 | *
78 | * @returns {CheapTraceResult|null} - The portal entry information, or null if not available.
79 | */
80 | function GetPortalEntryInfo() {
81 | return this.portalEntryInfo
82 | }
83 |
84 | /*
85 | * Gets an array of all portal entry information for the trace, including nested portals.
86 | *
87 | * @returns {List} - An array of CheapTraceResult objects representing portal entries.
88 | */
89 | function GetAggregatedPortalEntryInfo() {
90 | local list = List(this)
91 | for(local trace = this; trace = trace.portalEntryInfo;) {
92 | list.append(trace)
93 | }
94 | return list.reverse()
95 | }
96 |
97 | /*
98 | * Gets the impact normal of the surface hit by the trace.
99 | *
100 | * @returns {Vector} - The impact normal vector.
101 | */
102 | function GetImpactNormal() {
103 | // If the surface normal is already calculated, return it
104 | if(this.surfaceNormal)
105 | return this.surfaceNormal
106 |
107 | this.surfaceNormal = CalculateImpactNormal(this.GetStartPos(), this.hitpos)
108 | return this.surfaceNormal
109 | }
110 |
111 | function _typeof() return "TraceResult"
112 | function _tostring() {
113 | return "TraceResult | startpos: " + GetStartPos() + ", endpos: " + GetEndPos() + ", fraction: " + GetFraction() + ", hitpos: " + GetHitpos()
114 | }
115 | }
116 |
117 |
118 |
119 | // Don't forget, the class extension is broken in VScripts
120 | // Valvo's, I hate u :<
121 | results["Bbox"] <- class {
122 | traceHandler = null;
123 | hitpos = null;
124 | hitent = null;
125 |
126 | surfaceNormal = null;
127 | portalEntryInfo = null;
128 |
129 | /*
130 | * Constructor for a BboxTraceResult object.
131 | *
132 | * @param {table} traceHandler - The trace handler object containing trace information.
133 | * @param {Vector} hitpos - The hit position of the trace.
134 | * @param {CBaseEntity} hitent - The entity hit by the trace.
135 | */
136 | constructor(traceHandler, hitpos, hitent) {
137 | this.traceHandler = traceHandler
138 | this.hitpos = hitpos
139 | this.hitent = hitent
140 | }
141 |
142 | /*
143 | * Gets the start position of the trace.
144 | *
145 | * @returns {Vector} - The start position.
146 | */
147 | function GetStartPos() {
148 | return this.traceHandler.startpos
149 | }
150 |
151 | /*
152 | * Gets the end position of the trace.
153 | *
154 | * @returns {Vector} - The end position.
155 | */
156 | function GetEndPos() {
157 | return this.traceHandler.endpos
158 | }
159 |
160 | /*
161 | * Gets the hit position of the trace.
162 | *
163 | * @returns {Vector} - The hit position.
164 | */
165 | function GetHitpos() {
166 | return this.hitpos
167 | }
168 |
169 | /*
170 | * Gets the entity hit by the trace.
171 | *
172 | * @returns {pcapEntity|null} - The hit entity, or null if no entity was hit.
173 | */
174 | function GetEntity() {
175 | if(this.hitent && this.hitent.IsValid())
176 | return entLib.FromEntity(this.hitent)
177 | }
178 |
179 | /*
180 | * Gets the classname of the entity hit by the trace.
181 | *
182 | * @returns {string|null} - The classname of the hit entity, or null if no entity was hit.
183 | */
184 | function GetEntityClassname() {
185 | return (this.hitent && this.hitent.IsValid()) ? this.hitent.GetClassname() : null
186 | }
187 |
188 | /*
189 | * Gets the list of entities that were ignored during the trace.
190 | *
191 | * @returns {array|CBaseEntity|null} - The list of ignored entities, or null if no entities were ignored.
192 | */
193 | function GetIngoreEntities() {
194 | return this.traceHandler.ignoreEntities
195 | }
196 |
197 | /*
198 | * Gets the settings used for the trace.
199 | *
200 | * @returns {TraceSettings} - The trace settings object.
201 | */
202 | function GetTraceSettings() {
203 | return this.traceHandler.settings
204 | }
205 |
206 | /*
207 | * Gets the note associated with the trace.
208 | *
209 | * @returns {string|null} - The trace note, or null if no note was provided.
210 | */
211 | function GetNote() {
212 | return this.traceHandler.note
213 | }
214 |
215 | /*
216 | * Checks if the trace hit anything.
217 | *
218 | * @returns {boolean} - True if the trace hit something, false otherwise.
219 | */
220 | function DidHit() {
221 | return this.GetFraction() != 1
222 | }
223 |
224 | /*
225 | * Checks if the trace hit the world geometry (not an entity).
226 | *
227 | * @returns {boolean} - True if the trace hit the world, false otherwise.
228 | */
229 | function DidHitWorld() {
230 | return !this.hitent && DidHit()
231 | }
232 |
233 | /*
234 | * Gets the fraction of the trace distance where the hit occurred.
235 | *
236 | * @returns {number} - The hit fraction.
237 | */
238 | function GetFraction() {
239 | return macros.GetDist(this.GetStartPos(), this.GetHitpos()) / macros.GetDist(this.GetStartPos(), this.GetEndPos())
240 | }
241 |
242 | /*
243 | * Gets the direction vector of the trace.
244 | *
245 | * @returns {Vector} - The direction vector.
246 | */
247 | function GetDir() {
248 | return (this.GetEndPos() - this.GetStartPos())
249 | }
250 |
251 | /*
252 | * Gets the portal entry information for the trace.
253 | *
254 | * @returns {BboxTraceResult|null} - The portal entry information, or null if not available.
255 | */
256 | function GetPortalEntryInfo() {
257 | return this.portalEntryInfo
258 | }
259 |
260 | /*
261 | * Gets an array of all portal entry information for the trace, including nested portals.
262 | *
263 | * @returns {List} - An array of BboxTraceResult objects representing portal entries.
264 | */
265 | function GetAggregatedPortalEntryInfo() {
266 | local list = List(this)
267 | for(local trace = this; trace = trace.portalEntryInfo;) {
268 | list.append(trace)
269 | }
270 | return list.reverse()
271 | }
272 |
273 | /*
274 | * Gets the impact normal of the surface hit by the trace.
275 | *
276 | * @returns {Vector} - The impact normal vector.
277 | */
278 | function GetImpactNormal() {
279 | // If the surface normal is already calculated, return it
280 | if(this.surfaceNormal)
281 | return this.surfaceNormal
282 |
283 | local hitEnt = this.GetEntity()
284 | if(hitEnt) {
285 | this.surfaceNormal = CalculateImpactNormalFromBbox(this.GetStartPos(), this.hitpos, hitEnt)
286 | } else {
287 | this.surfaceNormal = CalculateImpactNormal(this.GetStartPos(), this.hitpos)
288 | }
289 |
290 | return this.surfaceNormal
291 | }
292 |
293 | function _typeof() return "BboxTraceResult"
294 | function _tostring() {
295 | return "TraceResult | startpos: " + GetStartPos() + ", endpos: " + GetEndPos() + ", hitpos: " + GetHitpos() + ", entity: " + GetEntity()
296 | }
297 | }
--------------------------------------------------------------------------------
/SRC/TracePlus/trace_settings.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Settings for ray traces.
3 | */
4 | TracePlus["Settings"] <- class {
5 | // An array of entity classnames to ignore during traces.
6 | ignoreClasses = ArrayEx("viewmodel", "weapon_", "beam",
7 | "trigger_", "phys_", "env_", "point_", "info_", "vgui_", "logic_",
8 | "clone", "prop_portal", "portal_base2D", "func_clip", "func_instance",
9 | "func_portal_detector",
10 | "worldspawn", "soundent", "player_manager", "bodyque", "ai_network"
11 | );
12 | // An array of entity classnames to prioritize during traces.
13 | priorityClasses = ArrayEx();
14 | // An array of entity model names to ignore during traces.
15 | ignoredModels = ArrayEx();
16 |
17 |
18 | // A custom function to determine if a ray should hit an entity.
19 | shouldRayHitEntity = null;
20 | // A custom function to determine if an entity should be ignored during a trace.
21 | shouldIgnoreEntity = null;
22 |
23 | depthAccuracy = 5;
24 | bynaryRefinement = false;
25 |
26 | /*
27 | * Creates a new TraceSettings object with default values or from a table of settings.
28 | *
29 | * @param {table} settingsTable - A table containing settings to override the defaults. (optional)
30 | * @returns {TraceSettings} - A new TraceSettings object.
31 | */
32 | function new(settingsTable = {}) {
33 | local result = TracePlus.Settings()
34 |
35 | // Set the ignoreClasses setting from the settings table or use the default.
36 | result.SetIgnoredClasses(macros.GetFromTable(settingsTable, "ignoreClasses", TracePlus.Settings.ignoreClasses))
37 | // Set the priorityClasses setting from the settings table or use the default.
38 | result.SetPriorityClasses(macros.GetFromTable(settingsTable, "priorityClasses", TracePlus.Settings.priorityClasses))
39 | // Set the ignoredModels setting from the settings table or use the default.
40 | result.SetIgnoredModels(macros.GetFromTable(settingsTable, "ignoredModels", TracePlus.Settings.ignoredModels))
41 |
42 | // Set the shouldRayHitEntity setting from the settings table or use the default.
43 | result.SetCollisionFilter(macros.GetFromTable(settingsTable, "shouldRayHitEntity", null))
44 | // Set the shouldIgnoreEntity setting from the settings table or use the default.
45 | result.SetIgnoreFilter(macros.GetFromTable(settingsTable, "shouldIgnoreEntity", null))
46 |
47 | // Set the depth accuracy for the BBox trace. Lower values are more precise for hitting thin objects but cost more performance. Default is 5.
48 | result.SetDepthAccuracy(macros.GetFromTable(settingsTable, "depthAccuracy", 5))
49 | // Enable or disable binary refinement. This performs an extra search step to get a more accurate hit position, which is vital for calculating correct surface normals. Default is false.
50 | result.SetBynaryRefinement(macros.GetFromTable(settingsTable, "bynaryRefinement", false))
51 |
52 | return result
53 | }
54 |
55 |
56 | /*
57 | * Sets the list of entity classnames to ignore during traces.
58 | *
59 | * @param {array|ArrayEx} ignoreClassesArray - An array or ArrayEx containing entity classnames to ignore.
60 | */
61 | function SetIgnoredClasses(ignoreClassesArray) {
62 | this.ignoreClasses = ArrayEx.FromArray(ignoreClassesArray)
63 | return this
64 | }
65 |
66 | /*
67 | * Sets the list of entity classnames to prioritize during traces.
68 | *
69 | * @param {array|ArrayEx} priorityClassesArray - An array or ArrayEx containing entity classnames to prioritize.
70 | */
71 | function SetPriorityClasses(priorityClassesArray) {
72 | this.priorityClasses = ArrayEx.FromArray(priorityClassesArray)
73 | return this
74 | }
75 |
76 | /*
77 | * Sets the list of entity model names to ignore during traces.
78 | *
79 | * @param {array|ArrayEx} ignoredModelsArray - An array or ArrayEx containing entity model names to ignore.
80 | */
81 | function SetIgnoredModels(ignoredModelsArray) {
82 | this.ignoredModels = ArrayEx.FromArray(ignoredModelsArray)
83 | return this
84 | }
85 |
86 | function SetDepthAccuracy(value) {
87 | this.depthAccuracy = math.clamp(value, 0.3, 15)
88 | return this
89 | }
90 |
91 | function SetBynaryRefinement(bool) {
92 | this.bynaryRefinement = bool
93 | return this
94 | }
95 |
96 | /*
97 | * Appends an entity classname to the list of ignored classes.
98 | *
99 | * @param {string} className - The classname to append.
100 | */
101 | function AppendIgnoredClass(className) {
102 | // CoW Mechanism
103 | if(this.ignoreClasses == TracePlus.Settings.ignoreClasses)
104 | this.ignoreClasses = clone this.ignoreClasses
105 |
106 | this.ignoreClasses.append(className)
107 | return this
108 | }
109 |
110 | /*
111 | * Appends an entity classname to the list of priority classes.
112 | *
113 | * @param {string} className - The classname to append.
114 | */
115 | function AppendPriorityClasses(className) {
116 | // CoW Mechanism
117 | if(this.priorityClasses == TracePlus.Settings.priorityClasses)
118 | this.priorityClasses = clone this.priorityClasses
119 |
120 | this.priorityClasses.append(className)
121 | return this
122 | }
123 |
124 | /*
125 | * Appends an entity model name to the list of ignored models.
126 | *
127 | * @param {string} modelName - The model name to append.
128 | */
129 | function AppendIgnoredModel(modelName) {
130 | // CoW Mechanism
131 | if(this.ignoredModels == TracePlus.Settings.ignoredModels)
132 | this.ignoredModels = clone this.ignoredModels
133 |
134 | this.ignoredModels.append(modelName)
135 | return this
136 | }
137 |
138 |
139 |
140 | /*
141 | * Gets the list of entity classnames to ignore during traces.
142 | *
143 | * @returns {ArrayEx} - An ArrayEx containing the ignored classnames.
144 | */
145 | function GetIgnoreClasses() {
146 | return this.ignoreClasses
147 | }
148 |
149 | /*
150 | * Gets the list of entity classnames to prioritize during traces.
151 | *
152 | * @returns {ArrayEx} - An ArrayEx containing the priority classnames.
153 | */
154 | function GetPriorityClasses() {
155 | return this.priorityClasses
156 | }
157 |
158 | /*
159 | * Gets the list of entity model names to ignore during traces.
160 | *
161 | * @returns {ArrayEx} - An ArrayEx containing the ignored model names.
162 | */
163 | function GetIgnoredModels() {
164 | return this.ignoredModels
165 | }
166 |
167 | /*
168 | * Sets a custom function to determine if a ray should hit an entity.
169 | *
170 | * @param {function} filterFunction - The filter function to set.
171 | */
172 | function SetCollisionFilter(filterFunction) { // aka. SetHitFilter
173 | this.shouldRayHitEntity = filterFunction
174 | return this
175 | }
176 |
177 | /*
178 | * Sets a custom function to determine if an entity should be ignored during a trace.
179 | *
180 | * @param {function} filterFunction - The filter function to set.
181 | */
182 | function SetIgnoreFilter(filterFunction) {
183 | this.shouldIgnoreEntity = filterFunction
184 | return this
185 | }
186 |
187 | /*
188 | * Gets the custom collision filter function.
189 | *
190 | * @returns {function|null} - The collision filter function, or null if not set.
191 | */
192 | function GetCollisionFilter() {
193 | return this.shouldRayHitEntity
194 | }
195 |
196 | /*
197 | * Gets the custom ignore filter function.
198 | *
199 | * @returns {function|null} - The ignore filter function, or null if not set.
200 | */
201 | function GetIgnoreFilter() {
202 | return this.shouldIgnoreEntity
203 | }
204 |
205 | /*
206 | * Applies the custom collision filter function to an entity.
207 | *
208 | * @param {CBaseEntity|pcapEntity} entity - The entity to check.
209 | * @param {string|null} note - An optional note associated with the trace.
210 | * @returns {boolean} - True if the ray should hit the entity, false otherwise.
211 | */
212 | function ApplyCollisionFilter(entity, note) {
213 | return this.shouldRayHitEntity ? this.shouldRayHitEntity(entity, note) : false
214 | }
215 |
216 | /*
217 | * Applies the custom ignore filter function to an entity.
218 | *
219 | * @param {CBaseEntity|pcapEntity} entity - The entity to check.
220 | * @param {string|null} note - An optional note associated with the trace.
221 | * @returns {boolean} - True if the entity should be ignored, false otherwise.
222 | */
223 | function ApplyIgnoreFilter(entity, note) {
224 | return this.shouldIgnoreEntity ? this.shouldIgnoreEntity(entity, note) : false
225 | }
226 |
227 | /*
228 | * Updates the list of entities to ignore during a trace, including the player entity.
229 | *
230 | * @param {array|CBaseEntity|null} ignoreEntities - The current list of entities to ignore.
231 | * @param {CBaseEntity|pcapEntity} newEnt - The new entity to add to the ignore list.
232 | * @returns {array} - The updated list of entities to ignore.
233 | */
234 | function UpdateIgnoreEntities(ignoreEntities, newEnt) {
235 | // Check if any entities should be ignored during the trace
236 | if (ignoreEntities) {
237 | // If ignoreEntities is an array, append the player entity to it
238 | if (typeof ignoreEntities == "array" || typeof ignoreEntities == "ArrayEx") {
239 | ignoreEntities.append(newEnt)
240 | }
241 | // If ignoreEntities is a single entity, create a new array with both the player and ignoreEntities
242 | else {
243 | ignoreEntities = [newEnt, ignoreEntities]
244 | }
245 | }
246 | // If no ignoreEntities is provided, ignore the player only
247 | else {
248 | ignoreEntities = newEnt
249 | }
250 |
251 | return ignoreEntities
252 | }
253 |
254 | function Clone() {
255 | return TracePlus.Settings()
256 | .SetIgnoredClasses(this.ignoreClasses.Clone())
257 | .SetPriorityClasses(this.priorityClasses.Clone())
258 | .SetIgnoredModels(this.ignoredModels.Clone())
259 | .SetCollisionFilter(this.shouldRayHitEntity)
260 | .SetIgnoreFilter(this.shouldIgnoreEntity)
261 | .SetBynaryRefinement(this.bynaryRefinement)
262 | .SetDepthAccuracy(this.depthAccuracy)
263 | }
264 |
265 | function _typeof() return "TraceSettings"
266 | function _cloned() return this.Clone()
267 | }
--------------------------------------------------------------------------------
/SRC/Utils/console_commands.nut:
--------------------------------------------------------------------------------
1 | commands_separator <- ",\n"
2 |
3 | // Basic information
4 | macros.CreateCommand("PCapLib_version", "script printl(::_lib_version_)")
5 |
6 | // Logger level control commands
7 | macros.CreateCommand("PCapLib_level_trace", "script ::LibLogger = LoggerLevels.Trace")
8 | macros.CreateCommand("PCapLib_level_debug", "script ::LibLogger = LoggerLevels.Debug")
9 | macros.CreateCommand("PCapLib_level_info", "script ::LibLogger = LoggerLevels.Info")
10 | macros.CreateCommand("PCapLib_level_warn", "script ::LibLogger = LoggerLevels.Warning")
11 | macros.CreateCommand("PCapLib_level_error", "script ::LibLogger = LoggerLevels.Error")
12 | macros.CreateCommand("PCapLib_level_off", "script ::LibLogger = LoggerLevels.Off")
13 |
14 | // Schedule event control commands
15 | macros.CreateCommand("PCapLib_schedule_list", "script printl(macros.GetKeys(ScheduleEvent.eventsList).join(commands_separator))")
16 | macros.CreateCommand("PCapLib_schedule_clear", "script ScheduleEvent.CancelAll()")
17 |
18 | // Information dump commands
19 | macros.CreateCommand("PCapLib_vscript_event_list", "script printl(macros.GetKeys(::AllScriptEvents).join(commands_separator))")
20 | macros.CreateCommand("PCapLib_players_list", "script printl(AllPlayers.join(commands_separator))")
21 |
22 | // Simple debug commands
23 | macros.CreateCommand("script_FrameTime", "script printl(FrameTime())")
--------------------------------------------------------------------------------
/SRC/Utils/const.nut:
--------------------------------------------------------------------------------
1 | // Collides with everything.
2 | const COLLISION_GROUP_NONE = 0
3 |
4 | // Collides with nothing but world and static stuff.
5 | const COLLISION_GROUP_DEBRIS = 1
6 |
7 | // Same as debris, but hits triggers.
8 | const COLLISION_GROUP_DEBRIS_TRIGGER = 2
9 |
10 | // Like DEBRIS, but doesn't collide with same group.
11 | const COLLISION_GROUP_INTERACTIVE_DEBRIS = 3
12 |
13 | // Collides with everything except interactive debris or debris.
14 | const COLLISION_GROUP_INTERACTIVE = 4
15 |
16 | // Used by players, ignores PASSABLE_DOOR.
17 | const COLLISION_GROUP_PLAYER = 5
18 |
19 | // Breakable glass, ignores same group and NPC line-of-sight.
20 | const COLLISION_GROUP_BREAKABLE_GLASS = 6
21 |
22 | // Driveable vehicles, always collides with VEHICLE_CLIP.
23 | const COLLISION_GROUP_VEHICLE = 7
24 |
25 | // Player movement collision.
26 | const COLLISION_GROUP_PLAYER_MOVEMENT = 8
27 |
28 | // Used by NPCs, always collides with DOOR_BLOCKER.
29 | const COLLISION_GROUP_NPC = 9
30 |
31 | // Entities inside vehicles, no collisions.
32 | const COLLISION_GROUP_IN_VEHICLE = 10
33 |
34 | // Weapons, including dropped ones.
35 | const COLLISION_GROUP_WEAPON = 11
36 |
37 | // Only collides with VEHICLE.
38 | const COLLISION_GROUP_VEHICLE_CLIP = 12
39 |
40 | // Projectiles, ignore other projectiles.
41 | const COLLISION_GROUP_PROJECTILE = 13
42 |
43 | // Blocks NPCs, may collide with some projectiles.
44 | const COLLISION_GROUP_DOOR_BLOCKER = 14
45 |
46 | // Passable doors, allows players through.
47 | const COLLISION_GROUP_PASSABLE_DOOR = 15
48 |
49 | // Dissolved entities, only collide with NONE.
50 | const COLLISION_GROUP_DISSOLVING = 16
51 |
52 | // Pushes props away from the player.
53 | const COLLISION_GROUP_PUSHAWAY = 17
54 |
55 | // NPCs potentially stuck in a player.
56 | const COLLISION_GROUP_NPC_ACTOR = 18
57 |
58 | // NPCs in scripted sequences with collisions disabled.
59 | const COLLISION_GROUP_NPC_SCRIPTED = 19
60 |
61 | // It doesn't seem to be used anywhere in the engine.
62 | const COLLISION_GROUP_PZ_CLIP = 20
63 |
64 | // Solid only to the camera's test trace (Outdated).
65 | const COLLISION_GROUP_CAMERA_SOLID = 21
66 |
67 | // Solid only to the placement tool's test trace (Outdated).
68 | const COLLISION_GROUP_PLACEMENT_SOLID = 22
69 |
70 | // Held objects that shouldn't collide with players.
71 | const COLLISION_GROUP_PLAYER_HELD = 23
72 |
73 | // Cubes need a collision group that acts roughly like COLLISION_GROUP_NONE but doesn't collide with debris or interactive.
74 | const COLLISION_GROUP_WEIGHTED_CUBE = 24
75 |
76 | // No collision at all.
77 | const SOLID_NONE = 0
78 |
79 | // Uses Quake Physics engine.
80 | const SOLID_BSP = 1
81 |
82 | // Uses an axis-aligned bounding box (AABB).
83 | const SOLID_AABB = 2
84 |
85 | // Uses an oriented bounding box (OBB).
86 | const SOLID_OBB = 3
87 |
88 | // Uses an OBB constrained to yaw rotation.
89 | const SOLID_OBB_YAW = 4
90 |
91 | // Custom/test solid type.
92 | const SOLID_CUSTOM = 5
93 |
94 | // Uses VPhysics engine for realistic physics.
95 | const SOLID_VPHYSICS = 6
--------------------------------------------------------------------------------
/SRC/Utils/debug.nut:
--------------------------------------------------------------------------------
1 | ::LoggerLevels <- class {
2 | Trace = 1
3 | Debug = 5
4 | Info = 10
5 | Warning = 30
6 | Error = 60
7 | Off = 1000
8 | }
9 |
10 |
11 | /*
12 | * A collection of debugging utilities.
13 | */
14 | ::dev <- {
15 | /*
16 | * Draws the bounding box of an entity for the specified time.
17 | *
18 | * @param {CBaseEntity|pcapEntity} ent - The entity to draw the bounding box for.
19 | * @param {Vector} color - The color of the box.
20 | * @param {number} time - The duration of the display in seconds.
21 | */
22 | DrawEntityBBox = function(ent, color, time) {
23 | if (developer() == 0) return
24 | DebugDrawBox(ent.GetOrigin(), ent.GetBoundingMins(), ent.GetBoundingMaxs(), color.x, color.y, color.z, 9, time)
25 | },
26 |
27 | /*
28 | * Draws the AABB of an entity for the specified time.
29 | *
30 | * @param {CBaseEntity|pcapEntity} ent - The entity to draw the bounding box for.
31 | * @param {Vector} color - The color of the box.
32 | * @param {number} time - The duration of the display in seconds.
33 | */
34 | DrawEntityAABB = function(ent, color, time) {
35 | if (developer() == 0) return
36 | DebugDrawBox(ent.GetOrigin(), ent.CreateAABB(0), ent.CreateAABB(7), color.x, color.y, color.z, 9, time)
37 | },
38 |
39 | /*
40 | * Draws a box at the specified vector position for the specified time.
41 | *
42 | * @param {Vector} vector - The position of the box.
43 | * @param {Vector} color - The color of the box. (optional)
44 | * @param {number} time - The duration of the display in seconds. (optional)
45 | */
46 | drawbox = function(vector, color = Vector(125, 125, 125), time = 0.05) {
47 | if (developer() == 0) return
48 | DebugDrawBox(vector, Vector(-1,-1,-1), Vector(1,1,1), color.x, color.y, color.z, 100, time)
49 | },
50 |
51 | // only for PCapture-LIB debugging
52 | trace = function(msg, ...) {
53 | if (developer() == 0 || LibLogger > LoggerLevels.Trace) return
54 | // region - repeatable code, thanks to Squirrel
55 | local args = array(vargc + 2)
56 | args[0] = this
57 | args[1] = msg
58 |
59 | for(local i = 0; i< vargc; i++) {
60 | args[i + 2] = vargv[i]
61 | }
62 | // endregion
63 |
64 | local msg = macros.format.acall(args)
65 | printl("-- PCapture-Lib [" + Time().tostring() + "]: " + msg)
66 | },
67 |
68 | /*
69 | * Logs a debug message to the console if debug logging is enabled.
70 | *
71 | * @param {string} msg - The debug message string containing `{}` placeholders.
72 | */
73 | debug = function(msg, ...) {
74 | if (developer() == 0 || LibLogger > LoggerLevels.Debug) return
75 | // region - repeatable code, thanks to Squirrel
76 | local args = array(vargc + 2)
77 | args[0] = this
78 | args[1] = msg
79 |
80 | for(local i = 0; i< vargc; i++) {
81 | args[i + 2] = vargv[i]
82 | }
83 | // endregion
84 |
85 | local msg = macros.format.acall(args)
86 | printl("~ " + msg)
87 | },
88 |
89 |
90 | /*
91 | * Logs a info message to the console only if developer mode is enabled.
92 | *
93 | * @param {string} msg - The message to log string containing `{}` placeholders.
94 | */
95 | info = function(msg, ...) {
96 | if (developer() == 0 || LibLogger > LoggerLevels.Info) return
97 | // region - repeatable code, thanks to Squirrel
98 | local args = array(vargc + 2)
99 | args[0] = this
100 | args[1] = msg
101 |
102 | for(local i = 0; i< vargc; i++) {
103 | args[i + 2] = vargv[i]
104 | }
105 | // endregion
106 |
107 | local msg = macros.format.acall(args)
108 | printl("• " + msg)
109 | },
110 |
111 | /*
112 | * Displays a warning message in a specific format.
113 | *
114 | * @param {string} msg - The warning message string containing `{}` placeholders.
115 | */
116 | warning = function(msg, ...) {
117 | if (developer() == 0 || LibLogger > LoggerLevels.Warning) return
118 | local pattern = "► Warning ({} [{}]): " + msg
119 | // region - repeatable code, thanks to Squirrel
120 | local info = dev._getInfo()
121 | local args = array(vargc + 4)
122 | args[0] = this
123 | args[1] = pattern // pattern
124 | args[2] = info[0] // func name
125 | args[3] = info[1] // func line
126 |
127 | for(local i = 0; i< vargc; i++) {
128 | args[i + 4] = vargv[i]
129 | }
130 | // endregion
131 | printl(macros.format.acall(args))
132 | },
133 |
134 | /*
135 | * Displays an error message in a specific format.
136 | *
137 | * @param {string} msg - The error message string containing `{}` placeholders.
138 | */
139 | error = function(msg, ...) {
140 | if (developer() == 0 || LibLogger > LoggerLevels.Error) return
141 | local pattern = "▄▀ *ERROR*: [func = {}; line = {}] | " + msg
142 | // region - repeatable code, thanks to Squirrel
143 | local info = dev._getInfo()
144 | local args = array(vargc + 4)
145 | args[0] = this
146 | args[1] = pattern // pattern
147 | args[2] = info[0] // func name
148 | args[3] = info[1] // func line
149 |
150 | for(local i = 0; i< vargc; i++) {
151 | args[i + 4] = vargv[i]
152 | }
153 | // endregion
154 | printl(macros.format.acall(args))
155 | SendToConsole("playvol resource/warning.wav 1")
156 | },
157 |
158 | _getInfo = function() {
159 | local last_info
160 | for(local i = 0; getstackinfos(i); i++)
161 | last_info = i
162 | local info = getstackinfos(last_info)
163 |
164 | local funcName = getstackinfos(last_info).func
165 | if (funcName == "main" || funcName == "unknown")
166 | funcName = "file " + info.src
167 |
168 | local line = info.line
169 | return [funcName, line]
170 | }
171 | }
--------------------------------------------------------------------------------
/SRC/Utils/file.nut:
--------------------------------------------------------------------------------
1 | /*
2 | * Represents a file for reading and writing.
3 | */
4 | ::File <- class {
5 | // The path to the file.
6 | path = null;
7 | // The name of the file without the extension.
8 | name = null;
9 |
10 | /*
11 | * Constructor for a File object.
12 | *
13 | * @param {string} path - The path to the file.
14 | */
15 | constructor(path) {
16 | path = split(path, "/").top()
17 | this.name = split(path, "/").top()
18 |
19 | if(path.find(".log") == null) {
20 | path += ".log"
21 | } else {
22 | this.name = this.name.slice(0, -4)
23 | }
24 |
25 | this.path = path
26 | if(!(this.name in getroottable()))
27 | getroottable()[this.name] <- []
28 | }
29 |
30 | /*
31 | * Appends text to the file.
32 | *
33 | * @param {string} text - The text to append.
34 | */
35 | function write(text) {
36 | local cmd = "script " + name + ".append(\\\"" + text + "\\\");"
37 | this._write(cmd)
38 | }
39 |
40 | function writeRawData(text) {
41 | this._write(text)
42 | }
43 |
44 | /*
45 | * Writes a command to the console to manipulate the file.
46 | *
47 | * @param {string} command - The command to execute.
48 | */
49 | function _write(command) {
50 | SendToConsole("con_logfile cfg/" + this.path)
51 | SendToConsole("script printl(\"" + command + "\")")
52 | SendToConsole("con_logfile off")
53 | }
54 |
55 | /*
56 | * Reads the lines of the file and returns them as an array.
57 | *
58 | * @returns {array} - An array of strings, where each string is a line from the file.
59 | */
60 | function readlines() {
61 | if(this.name in getroottable())
62 | return getroottable()[this.name]
63 | return []
64 | }
65 |
66 | /*
67 | * Reads the entire contents of the file and returns it as a string.
68 | *
69 | * @returns {string} - The contents of the file as a string.
70 | */
71 | function read() {
72 | local result = ""
73 | foreach(line in this.readlines()){
74 | result += line + "\n"
75 | }
76 | return result
77 | }
78 |
79 | /*
80 | * Clears the contents of the file.
81 | */
82 | function clear() {
83 | this._write("script " + name + ".clear()")
84 | }
85 |
86 | /*
87 | * Updates information about the file by executing it.
88 | */
89 | function updateInfo() {
90 | getroottable()[this.name].clear()
91 | SendToConsole("exec " + path)
92 | }
93 | }
--------------------------------------------------------------------------------
/SRC/Utils/improvements.nut:
--------------------------------------------------------------------------------
1 | if("GetPlayerEx" in getroottable()) {
2 | return
3 | }
4 |
5 | /*
6 | * Limits frametime to avoid zero values.
7 | *
8 | * @returns {number} Clamped frametime
9 | */
10 | ::_frametime <- FrameTime
11 | /*
12 | * Returns the current frame time, ensuring it is never zero.
13 | *
14 | * This function overrides the standard FrameTime function to prevent issues that can arise from zero frame times.
15 | * If the frame time is zero, it returns a small default value (0.016).
16 | *
17 | * @returns {number} - The current frame time.
18 | */
19 | ::FrameTime <- function() {
20 | local tick = _frametime()
21 | if(tick == 0)
22 | return 0.016
23 | return tick
24 | }
25 |
26 | ::_uniquestring <- UniqueString
27 | /*
28 | * Generates a unique string with the specified prefix.
29 | *
30 | * This function overrides the standard UniqueString function to include a prefix in the generated string.
31 | *
32 | * @param {string} prefix - The prefix to use for the unique string. (optional, default="u")
33 | * @returns {string} - The generated unique string.
34 | */
35 | ::UniqueString <- function(prefix = "u") {
36 | return prefix + "_" + _uniquestring().slice(0, -1)
37 | }
38 |
39 | ::_EntFireByHandle <- EntFireByHandle
40 | /*
41 | * Wrapper for EntFireByHandle to handle PCapLib objects.
42 | *
43 | * This function overrides the standard EntFireByHandle function to handle pcapEntity objects and extract the underlying CBaseEntity objects.
44 | * It also allows specifying an activator and caller entity.
45 | *
46 | * @param {CBaseEntity|pcapEntity} target - The target entity to trigger the input on.
47 | * @param {string} action - The name of the input to trigger.
48 | * @param {string} value - The value to pass to the input. (optional)
49 | * @param {number} delay - The delay in seconds before triggering the input. (optional)
50 | * @param {CBaseEntity|pcapEntity} activator - The activator entity (the entity that triggered the input). (optional)
51 | * @param {CBaseEntity|pcapEntity} caller - The caller entity (the entity that called the function). (optional)
52 | */
53 | ::EntFireByHandle <- function(target, action, value = "", delay = 0, activator = null, caller = null, eventName = null) {
54 | // Extract the underlying entity from the pcapEntity wrapper
55 | if (typeof target == "pcapEntity")
56 | target = target.CBaseEntity
57 | if (typeof activator == "pcapEntity")
58 | activator = activator.CBaseEntity
59 | if (typeof caller == "pcapEntity")
60 | caller = target.CBaseEntity
61 |
62 | if(!eventName)
63 | return _EntFireByHandle(target, action, value, delay, activator, caller)
64 | ScheduleEvent.Add(eventName, _EntFireByHandle, delay, [target, action, value, 0, activator, caller])
65 | }
66 |
67 | /*
68 | * Retrieves a player entity with extended functionality.
69 | *
70 | * @param {int} index - The index of the player (0-based).
71 | * @returns {pcapEntity} - An extended player entity with additional methods.
72 | */
73 | ::GetPlayerEx <- function(index = 0) {
74 | if(!IsMultiplayer())
75 | return entLib.FromEntity(GetPlayer())
76 |
77 | if(index >= AllPlayers.len()) return null
78 | return AllPlayers[index]
79 | }
--------------------------------------------------------------------------------
/SRC/Utils/init.nut:
--------------------------------------------------------------------------------
1 | /*+--------------------------------------------------------------------------------+
2 | | PCapture Vscripts Library |
3 | +----------------------------------------------------------------------------------+
4 | | Author: |
5 | | Squirrel Whisperer - laVashik :> |
6 | +----------------------------------------------------------------------------------+
7 | | A toolbox of versatile utilities for script execution, debugging, file |
8 | | operations, and entity manipulation, empowering VScripts developers. |
9 | +----------------------------------------------------------------------------------+ */
10 |
11 | IncludeScript("PCapture-LIB/SRC/Utils/debug")
12 | IncludeScript("PCapture-LIB/SRC/Utils/file")
13 | IncludeScript("PCapture-LIB/SRC/Utils/improvements")
14 | IncludeScript("PCapture-LIB/SRC/Utils/portals")
15 | IncludeScript("PCapture-LIB/SRC/Utils/macros")
16 | IncludeScript("PCapture-LIB/SRC/Utils/const")
17 | IncludeScript("PCapture-LIB/SRC/Utils/player_hooks")
18 | ScheduleEvent.Add("global", function() {
19 | IncludeScript("PCapture-LIB/SRC/Utils/console_commands")
20 | }, 1, null, this)
--------------------------------------------------------------------------------
/SRC/Utils/player_hooks.nut:
--------------------------------------------------------------------------------
1 | if("AllPlayers" in getroottable()) return
2 |
3 | ::AllPlayers <- ArrayEx()
4 |
5 | /*
6 | * Gets an array of all players in the game.
7 | *
8 | * @returns {array} - An array of pcapEntity objects representing the players.
9 | */
10 | ::GetPlayers <- function() { // -> ArrayEx
11 | return AllPlayers
12 | }
13 |
14 |
15 | /*
16 | * Tracks players joining the game and initializes their.
17 | *
18 | * This function iterates over all player entities in the game, attaching eye control
19 | * and initializing new portal pairs for each new player. It calls OnPlayerJoin for
20 | * each player added to the AllPlayers list.
21 | */
22 | ::TrackPlayerJoins <- function() {
23 | for(local player; player = entLib.FindByClassname("player", player);) {
24 | if(player.GetUserData("Eye")) continue // if already inited (AllPlayers.contains(player))
25 |
26 | // Attach eye control to the new player
27 | AttachEyeControl(player)
28 | // Initialize a new portal pair for this player:
29 | InitPortalPair(AllPlayers.len())
30 |
31 | AllPlayers.append(player)
32 | // Trigger join event
33 | OnPlayerJoined(player)
34 | }
35 | }
36 |
37 | /*
38 | * Handles player events in MP, such as health checks and death events.
39 | *
40 | * This function iterates over all players in the AllPlayers list, checking if
41 | * each player is valid and updating their state accordingly. It calls OnDeath
42 | * for dead players and schedules their respawn logic.
43 | */
44 | ::HandlePlayerEventsMP <- function() {
45 | foreach(player in AllPlayers){
46 | if(!player.IsValid()) {
47 | OnPlayerLeft(player)
48 | AllPlayers.remove(AllPlayers.search(player))
49 | continue
50 | }
51 |
52 | if(player.GetHealth() > 0 || player.GetHealth() == -999) continue
53 |
54 | OnPlayerDeath(player)
55 | ScheduleEvent.AddInterval("global", _monitorRespawn, 0.3, 0, null, player)
56 | player.SetHealth(-999)
57 | }
58 | }
59 |
60 | ::HandlePlayerEventsSP <- function() {
61 | local h = AllPlayers[0].GetHealth()
62 | if(h > 0 || h == -999) return
63 | OnPlayerDeath(AllPlayers[0])
64 | AllPlayers[0].SetHealth(-999)
65 | }
66 |
67 | /*
68 | * Monitors player respawn status.
69 | */
70 | function _monitorRespawn() {
71 | if(this.GetHealth() > 0)
72 | return OnPlayerRespawn(this)
73 | }
74 |
75 |
76 | /*
77 | * Attaches eye control entities to all players in the game.
78 | *
79 | * This function creates logic_measure_movement and info_target entities for each player to track their eye position and angles.
80 | * It is called automatically at the initialization of the library and periodically in multiplayer games.
81 | */
82 | function AttachEyeControl(player) {
83 | if(player.GetUserData("Eye")) return
84 |
85 | local controlName = UniqueString("eyeControl")
86 | local eyeControlEntity = entLib.CreateByClassname("logic_measure_movement", {
87 | targetname = controlName, measuretype = 1}
88 | )
89 |
90 | local eyeName = UniqueString("eyePoint")
91 | local eyePointEntity = entLib.CreateByClassname("info_target", {targetname = eyeName})
92 |
93 | local playerName = player.GetName() == "" ? "!player" : player.GetName()
94 |
95 | EntFireByHandle(eyeControlEntity, "setmeasuretarget", playerName)
96 | EntFireByHandle(eyeControlEntity, "setmeasurereference", controlName);
97 | EntFireByHandle(eyeControlEntity, "SetTargetReference", controlName);
98 | EntFireByHandle(eyeControlEntity, "Settarget", eyeName);
99 | EntFireByHandle(eyeControlEntity, "Enable")
100 |
101 | player.SetUserData("Eye", eyePointEntity)
102 | }
103 |
104 |
105 | // Empty Hooks
106 | function OnPlayerJoined(player) {}
107 | function OnPlayerLeft(player) {}
108 | function OnPlayerDeath(player) {}
109 | function OnPlayerRespawn(player) {}
--------------------------------------------------------------------------------
/SRC/Utils/portals.nut:
--------------------------------------------------------------------------------
1 | ::InitedPortals <- {}
2 |
3 | /*
4 | * Creates a `func_portal_detector` entity with specified key-value pairs and settings for portal detection.
5 | *
6 | * This function is not part of the public API.
7 | */
8 | ::_CreatePortalDetector <- function(extraKey, extraValue) {
9 | local detector = entLib.CreateByClassname("func_portal_detector", {solid = 0, CollisionGroup = 10})
10 | detector.SetKeyValue(extraKey, extraValue)
11 | detector.SetBBox(Vector(32000, 32000, 32000) * -1, Vector(32000, 32000, 32000))
12 | detector.SetTraceIgnore(true)
13 | EntFireByHandle(detector, "Enable")
14 |
15 | return detector
16 | }
17 |
18 | /*
19 | * Initializes a portal pair for portal casting (tracing lines through portals).
20 | *
21 | * This function creates a detector entity with a specific `LinkageGroupID` and connects its `OnStartTouchPortal`
22 | * to a script function that sets the activator entity's health to the portal's `LinkageGroupID`.
23 | * This allows for identifying the paired portal during portal traces.
24 | *
25 | * @param {number} id - The ID of the portal pair.
26 | */
27 | ::InitPortalPair <- function(id) {
28 | // If it was previously initialized, just turn on the detector.
29 | if(id in InitedPortals) return InitedPortals[id]
30 |
31 | local detector = _CreatePortalDetector("LinkageGroupID", id)
32 |
33 | // The "free variables" syntax is different in Sq2 and Sq3, which is why I try to avoid executing it
34 | detector.ConnectOutputEx("OnStartTouchPortal", compilestring("activator.SetHealth(" + id + ")"))
35 | detector.ConnectOutputEx("OnEndTouchPortal", compilestring("activator.SetHealth(9999)"))
36 | // Marking the portal as initialized
37 | InitedPortals[id] <- detector
38 |
39 | return detector
40 | }
41 |
42 | /*
43 | * Initializes linked portal doors for portal tracing.
44 | * This function is not part of the public API.
45 | *
46 | * This function iterates through all entities of class "linked_portal_door" and performs the following actions:
47 | * 1. Check if this entity has been processed before.
48 | * 2. Extracts bounding box dimensions from the portal's model name (assuming a specific naming convention).
49 | * 3. Rotates the bounding box dimensions based on the portal's angles.
50 | * 4. Sets the bounding box of the portal using the calculated dimensions.
51 | *
52 | * This function is called automatically at the initialization.
53 | */
54 | ::SetupLinkedPortals <- function() {
55 | local portalStateHandler = function(state) {
56 | local p = entLib.FromEntity(self)
57 | p.SetTraceIgnore(state)
58 | local p2 = p.GetPartnerInstance()
59 | if(p2) p2.SetTraceIgnore(state)
60 | return true
61 | }
62 | // Iterate through all linked_portal_door entities.
63 | for(local portal; portal = entLib.FindByClassname("linked_portal_door", portal);) {
64 | // Skip if the portal already processed
65 | if(portal.GetUserData("processed"))
66 | continue
67 |
68 | portal.SetInputHook("Open", function():(portalStateHandler) {return portalStateHandler(false)})
69 | portal.SetInputHook("Close", function():(portalStateHandler) {return portalStateHandler(false)})
70 |
71 | local partner = portal.GetPartnerInstance()
72 |
73 | // Skip if the portal model name is empty (no bounding box information available).
74 | if(portal.GetModelName() == "")
75 | continue
76 |
77 | // Extract bounding box dimensions from the model name (assuming a specific format).
78 | local wpInfo = split(portal.GetModelName(), " ")
79 | // Rotate the bounding box dimensions based on the portal's angles.
80 | local wpBBox = math.vector.abs(Vector(8, wpInfo[0].tointeger(), wpInfo[1].tointeger()))
81 | // Set the bounding box of the portal using the calculated dimensions.
82 | portal.SetBBox(wpBBox * -1, wpBBox)
83 | portal.SetUserData("processed", true)
84 | }
85 | }
86 |
87 | /*
88 | * Finds the partner portal for a given `prop_portal` entity.
89 | *
90 | * @param {pcapEntity} portal - The `prop_portal` entity to find the partner for.
91 | * @returns {pcapEntity|null} - The partner portal entity, or `null` if no partner is found.
92 | */
93 | ::FindPartnerForPropPortal <- function(portal) {
94 | // Find the partner portal entity based on the determined model name.
95 | local portalPairId = portal.GetHealth()
96 | // for(local partner; partner = entLib.FindByModel(mdl, partner);) {
97 | for(local partner; partner = entLib.FindByClassname("prop_portal", partner);) {
98 | local partnerPairId = partner.GetHealth()
99 | if(portalPairId != partnerPairId || partner.entindex() == portal.entindex() || partner.GetClassname() != "prop_portal" || partner.GetUserData("TracePlusIgnore"))
100 | continue
101 |
102 | return partner
103 | }
104 |
105 | return null
106 | }
107 |
108 | /*
109 | * Checks if the given entity is a blue portal.
110 | *
111 | * @param {pcapEntity} ent - The entity to check.
112 | * @returns {boolean} - True if the entity is a blue portal, false otherwise.
113 | */
114 | ::IsBluePortal <- function(ent) {
115 | return ent.GetModelName().find("portal2") == null
116 | }
--------------------------------------------------------------------------------
/Tests/ActionScheduler.nut:
--------------------------------------------------------------------------------
1 | // Events Module Unit Tests
2 | if(!("RunTests" in getroottable())) IncludeScript("PCapture-LIB/Tests/test_exec")
3 |
4 | events_tests <- {
5 | function schedule_event_test() {
6 | local testFunc = function(){
7 | return assert(true)
8 | }
9 |
10 | ScheduleEvent.Add("schedule_event_test", testFunc, 0.1)
11 | return assert(ScheduleEvent.GetEvent("schedule_event_test").len() == 1)
12 | },
13 |
14 | function cancel_scheduled_event_test() {
15 | local testFunc = function() {
16 | return assert(false) // This should not be called
17 | }
18 |
19 | ScheduleEvent.Add("cancel_scheduled_event_test", testFunc, 0.1)
20 | ScheduleEvent.Cancel("cancel_scheduled_event_test")
21 | return assert(ScheduleEvent.IsValid("cancel_scheduled_event_test") == false)
22 | },
23 |
24 | function event_info_test() {
25 | local testFunc = function() {
26 | return assert(true)
27 | }
28 |
29 | ScheduleEvent.Add("event_info_test", testFunc, 0.1, null)
30 | local eventInfo = ScheduleEvent.GetEvent("event_info_test")
31 | return assert(eventInfo.len() == 1)
32 | },
33 |
34 | function add_new_actions_test() {
35 | local actions = List()
36 | for(local i = 50; i >= 0; i--) {
37 | actions.append(ScheduleAction(this, "1 + 1", RandomFloat(1, 10), null))
38 | }
39 |
40 | ScheduleEvent.AddActions("add_new_actions_test", actions, false)
41 |
42 | local list = ScheduleEvent.GetEvent("add_new_actions_test")
43 | for(local i = 1; i < list.len(); i++) {
44 | if(list[i - 1] > list[i]) {
45 | return assert(false)
46 | }
47 | }
48 | },
49 |
50 | function add_actions_test() {
51 | local create = function() {
52 | local actions = List()
53 | for(local i = 30; i >= 0; i--) {
54 | actions.append(ScheduleAction(this, "1 + 1", RandomFloat(1, 10), null))
55 | }
56 | return actions
57 | }
58 |
59 | ScheduleEvent.AddActions("add_actions_test", create(), false)
60 | ScheduleEvent.AddActions("add_actions_test", create(), false)
61 | ScheduleEvent.AddActions("add_actions_test", create(), false)
62 |
63 | local list = ScheduleEvent.GetEvent("add_actions_test")
64 |
65 | for(local i = 1; i < list.len(); i++) {
66 | if(list[i - 1] >= list[i]) {
67 | return assert(false)
68 | }
69 | }
70 | },
71 |
72 | function add_new_actions_nosort_test() {
73 | local actions = List()
74 | for(local i = 50; i >= 0; i--) {
75 | actions.append(ScheduleAction(this, "1 + 1", (50 - i.tofloat()) / 10, null))
76 | }
77 |
78 | ScheduleEvent.AddActions("add_new_actions_nosort_test", actions, true)
79 |
80 | local list = ScheduleEvent.GetEvent("add_new_actions_nosort_test")
81 | for(local i = 1; i < list.len(); i++) {
82 | if(list[i - 1] > list[i]) {
83 | return assert(false)
84 | }
85 | }
86 | },
87 |
88 | function add_actions_nosort_test() {
89 | local create = function(delay) {
90 | local actions = List()
91 | for(local i = 5; i >= 0; i--) {
92 | actions.append(ScheduleAction(this, "1 + 1", (5 - i.tofloat()) / 10 + delay, null))
93 | }
94 | return actions
95 | }
96 |
97 | ScheduleEvent.AddActions("add_actions_nosort_test", create(0), true)
98 | // ScheduleEvent.AddActions("add_actions_nosort_test", create(1), true)
99 | // ScheduleEvent.AddActions("add_actions_nosort_test", create(3), true)
100 | // ScheduleEvent.AddActions("add_actions_nosort_test", create(0.5), true)
101 | // ScheduleEvent.AddActions("add_actions_nosort_test", create(4), true)
102 | // ScheduleEvent.AddActions("add_actions_nosort_test", create(2), true)
103 | ScheduleEvent.AddActions("add_actions_nosort_test", create(0.3), true)
104 |
105 | local list = ScheduleEvent.GetEvent("add_actions_nosort_test")
106 | printl(list)
107 | for(local i = 1; i < list.len(); i++) {
108 | if(list[i - 1] > list[i]) {
109 | printl(list[i - 1] + " > " + list[i] + " {"+i+"}")
110 | return assert(false)
111 | }
112 | }
113 | },
114 |
115 | function event_validity_test() {
116 | local testFunc = function() {
117 | return assert(true)
118 | }
119 |
120 | ScheduleEvent.Add("event_validity_test", testFunc, 0.1)
121 | return assert(ScheduleEvent.IsValid("event_validity_test"))
122 | },
123 |
124 | function schedule_event_with_args_test() {
125 | local testFunc = function(arg1, arg2) {
126 | return assert(arg1 == 1 && arg2 == 2)
127 | }
128 |
129 | ScheduleEvent.Add("schedule_event_with_args_test", testFunc, 0.1, [1, 2], null)
130 | return assert(ScheduleEvent.GetEvent("schedule_event_with_args_test").len() == 1)
131 | },
132 |
133 | function schedule_event_with_string_action_test() {
134 | local testString = "printl(\"This is a test\")"
135 | ScheduleEvent.Add("string_script_test", testString, 0.1)
136 | return assert(ScheduleEvent.GetEvent("string_script_test").len() == 1)
137 | },
138 |
139 | function schedule_event_with_delay_test() {
140 | local startTime = Time()
141 | local testFunc = function(startTime) {
142 | return assert(Time() >= startTime + 0.1)
143 | }
144 |
145 | ScheduleEvent.Add("test_with_delay", testFunc, 0.1, [startTime])
146 | return assert(ScheduleEvent.GetEvent("test_with_delay").len() == 1)
147 | },
148 |
149 | function cancel_scheduled_event_with_delay_test() {
150 | local testFunc = function() {
151 | return assert(false)
152 | }
153 |
154 | ScheduleEvent.Add("cancel_test_event", testFunc, 0.2)
155 | ScheduleEvent.Cancel("cancel_test_event", 0.1)
156 | return assert(ScheduleEvent.IsValid("cancel_test_event"))
157 | },
158 |
159 | function cancel_test_event() {
160 | local x = ThisTest()
161 | x.test()
162 | },
163 | }
164 |
165 | class ThisTest {
166 | something = false
167 | constructor() {}
168 |
169 | function theta() {
170 | printl("Hello, Theta!")
171 | this.something = true
172 | }
173 |
174 | function test() {
175 | ScheduleEvent.Add("this_test_event", theta, 0.2, null, this)
176 | ScheduleEvent.Add("this_test_event", function() {
177 | if(!this.something) throw("WTF BRO?")
178 | }, 0.3, null, this)
179 | }
180 | }
181 |
182 | // Run all tests
183 | RunTests("Events", events_tests)
--------------------------------------------------------------------------------
/Tests/ScriptEvents.nut:
--------------------------------------------------------------------------------
1 |
2 | if(!("RunTests" in getroottable())) IncludeScript("PCapture-Lib/Tests/test_exec")
3 |
4 | scriptEventsTests <- {
5 | function vGameEventCreationTest() {
6 | local gameEvent = VGameEvent("testEvent");
7 | return assert(gameEvent.eventName == "testEvent");
8 | },
9 |
10 | function vGameEventAddActionTest() {
11 | local gameEvent = VGameEvent("testEventWithAction");
12 | local actionFunc = function() {};
13 | gameEvent.AddAction(actionFunc);
14 | return assert(gameEvent.actions.len() == 1);
15 | },
16 |
17 | function vGameEventClearActionsTest() {
18 | local gameEvent = VGameEvent("testEventClearActions");
19 | gameEvent.AddAction(function() {});
20 | gameEvent.ClearActions();
21 | return assert(gameEvent.actions.len() == 0);
22 | },
23 |
24 | function vGameEventSetFilterTest() {
25 | local gameEvent = VGameEvent("testEventWithFilter");
26 | local filterFunc = function() { return true; };
27 | gameEvent.SetFilter(filterFunc);
28 | return assert(gameEvent.filterFunction == filterFunc);
29 | },
30 |
31 | function eventListenerNotifyTest() {
32 | local eventName = "listenerNotifyTestEvent";
33 | local eventTriggered = [false]; // wrapper
34 | local gameEvent = VGameEvent(eventName);
35 | gameEvent.AddAction(function(eventTriggered) {
36 | eventTriggered[0] = true;
37 | });
38 | EventListener.Notify(eventName, eventTriggered);
39 | return assert(eventTriggered[0] == true);
40 | },
41 |
42 | function vGameEventForceTriggerTest() {
43 | local eventName = "forceTriggerTestEvent";
44 | local eventTriggered = [false]; // wrapper
45 | local gameEvent = VGameEvent(eventName);
46 | gameEvent.AddAction(function(eventTriggered) {
47 | eventTriggered[0] = true;
48 | });
49 | gameEvent.ForceTrigger([eventTriggered]);
50 | return assert(eventTriggered[0] == true);
51 | },
52 |
53 | function eventListenerGetEventTest() {
54 | local eventName = "getEventTestEvent";
55 | local gameEvent = VGameEvent(eventName);
56 | local retrievedEvent = EventListener.GetEvent(eventName);
57 | return assert(retrievedEvent == gameEvent);
58 | },
59 |
60 | function vGameEventTriggerCountLimitTest() {
61 | local eventName = "triggerCountLimitEvent";
62 | local triggerCount = 2;
63 | local counter = [0]; // wrapper
64 | local gameEvent = VGameEvent(eventName, triggerCount);
65 | gameEvent.AddAction(function(counter) {
66 | counter[0]++;
67 | });
68 |
69 | EventListener.Notify(eventName, counter);
70 | EventListener.Notify(eventName, counter);
71 | EventListener.Notify(eventName, counter); // Triggered more times than allowed
72 |
73 | return assert(counter[0] == triggerCount); // Check if event triggered only twice
74 | },
75 |
76 | function vGameEventFilterFunctionTest() {
77 | local eventName = "filterFunctionEvent";
78 | local eventTriggered = [0]; // wrapper
79 | local gameEvent = VGameEvent(eventName);
80 | gameEvent.SetFilter(function(args) {
81 | return args[1] == "allowed"; // Filter to only allow triggers with "allowed" argument
82 | });
83 | gameEvent.AddAction(function(eventTriggered, _) {
84 | eventTriggered[0] += 1;
85 | });
86 |
87 | EventListener.Notify(eventName, eventTriggered, "denied"); // Should not trigger due to filter
88 | EventListener.Notify(eventName, eventTriggered, "allowed"); // Should trigger
89 |
90 | return assert(eventTriggered[0] == 1); // Check if event was triggered only when filter allowed
91 | },
92 |
93 | function eventListenerNotifyUnknownEventTest() {
94 | local result = EventListener.Notify("unknownEvent"); // Notify an event that does not exist
95 | return assert(result == null); // Should return null for unknown event
96 | },
97 | };
98 |
99 | // Run all tests
100 | RunTests("ScriptEvents", scriptEventsTests);
--------------------------------------------------------------------------------
/Tests/Template.nut:
--------------------------------------------------------------------------------
1 | if(!("RunTests" in getroottable())) IncludeScript("PCapture-LIB/Tests/test_exec")
2 |
3 | template_tests <- {
4 | function some_test() {
5 | return assert(4 == 2*2)
6 | },
7 |
8 | function some_test_2() {
9 | return assert(true)
10 | },
11 |
12 | function some_test_3() {
13 | return assert(4 == 5)
14 | },
15 | }
16 |
17 | // Run all tests
18 | RunTests("Template", template_tests)
--------------------------------------------------------------------------------
/Tests/TracePlus.nut:
--------------------------------------------------------------------------------
1 | if(!("RunTests" in getroottable())) IncludeScript("PCapture-Lib/Tests/test_exec")
2 |
3 | tracePlusTests <- {
4 | function cheapTraceTest() {
5 | local startPos = Vector(0, 0, 0)
6 | local endPos = Vector(100, 0, 0)
7 | local traceResult = TracePlus.Cheap(startPos, endPos)
8 | return assert(traceResult.GetStartPos() == startPos && traceResult.GetEndPos() == endPos)
9 | },
10 |
11 | function cheapTraceFromEyesTest() {
12 | local player = GetPlayerEx()
13 | local distance = 100
14 | local traceResult = TracePlus.FromEyes.Cheap(distance, player)
15 | return assert(macros.isEqually(traceResult.GetStartPos(), player.EyePosition()))
16 | },
17 |
18 | function bboxTraceTest() {
19 | local startPos = Vector(0, 0, 0)
20 | local endPos = Vector(100, 0, 0)
21 | local traceResult = TracePlus.Bbox(startPos, endPos)
22 | return assert(macros.isEqually(traceResult.GetStartPos(), startPos) && macros.isEqually(traceResult.GetEndPos(), endPos))
23 | },
24 |
25 | function bboxTraceFromEyesTest() {
26 | local player = GetPlayerEx()
27 | local distance = 100
28 | local traceResult = TracePlus.FromEyes.Bbox(distance, player)
29 | return assert(macros.isEqually(traceResult.GetStartPos(), player.EyePosition()))
30 | },
31 |
32 | function portalCheapTraceTest() {
33 | local startPos = Vector(0, 0, 0)
34 | local endPos = Vector(100, 0, 0)
35 | local traceResult = TracePlus.PortalCheap(startPos, endPos)
36 | return assert(traceResult.GetStartPos() == startPos && traceResult.GetEndPos() == endPos)
37 | },
38 |
39 | function portalCheapTraceFromEyesTest() {
40 | local player = GetPlayerEx()
41 | local distance = 100
42 | local traceResult = TracePlus.FromEyes.PortalCheap(distance, player)
43 | return assert(macros.isEqually(traceResult.GetStartPos(), player.EyePosition()))
44 | },
45 |
46 | function portalBboxTraceTest() {
47 | local startPos = Vector(0, 0, 0)
48 | local endPos = Vector(100, 0, 0)
49 | local traceResult = TracePlus.PortalBbox(startPos, endPos)
50 | return assert(traceResult.GetStartPos() == startPos && traceResult.GetEndPos() == endPos)
51 | },
52 |
53 | function portalBboxTraceFromEyesTest() {
54 | local player = GetPlayerEx()
55 | local distance = 100
56 | local traceResult = TracePlus.FromEyes.PortalBbox(distance, player)
57 | return assert(macros.isEqually(traceResult.GetStartPos(), player.EyePosition()))
58 | },
59 |
60 | function traceSettingsNewTest() {
61 | local settings = TracePlus.Settings.new()
62 | return assert(typeof settings == "TraceSettings")
63 | },
64 |
65 | function traceSettingsSetIgnoredClassesTest() {
66 | local settings = TracePlus.Settings.new()
67 | local ignoredClasses = ArrayEx("prop_physics")
68 | settings.SetIgnoredClasses(ignoredClasses)
69 | return assert(settings.GetIgnoreClasses() == ignoredClasses)
70 | },
71 |
72 | function traceSettingsSetPriorityClassesTest() {
73 | local settings = TracePlus.Settings.new()
74 | local priorityClasses = ArrayEx("player")
75 | settings.SetPriorityClasses(priorityClasses)
76 | return assert(settings.GetPriorityClasses() == priorityClasses)
77 | },
78 |
79 | function traceSettingsSetIgnoredModelsTest() {
80 | local settings = TracePlus.Settings.new()
81 | local ignoredModels = ArrayEx("models/editor/info_player_start.mdl")
82 | settings.SetIgnoredModels(ignoredModels)
83 | return assert(settings.GetIgnoredModels() == ignoredModels)
84 | },
85 |
86 | function traceSettingsSetDepthAccuracyTest() {
87 | local settings = TracePlus.Settings.new()
88 | settings.SetDepthAccuracy(10)
89 | return assert(settings.depthAccuracy == 10)
90 | },
91 |
92 | function traceSettingsSetBinaryRefinementTest() {
93 | local settings = TracePlus.Settings.new()
94 | settings.SetBynaryRefinement(true)
95 | return assert(settings.bynaryRefinement == true)
96 | },
97 |
98 | function traceSettingsAppendIgnoredClassTest() {
99 | local settings = TracePlus.Settings.new()
100 | settings.AppendIgnoredClass("prop_physics")
101 | return assert(settings.GetIgnoreClasses().contains("prop_physics"))
102 | },
103 |
104 | function traceSettingsAppendPriorityClassesTest() {
105 | local settings = TracePlus.Settings.new()
106 | settings.AppendPriorityClasses("player")
107 | return assert(settings.GetPriorityClasses().contains("player"))
108 | },
109 |
110 | function traceSettingsAppendIgnoredModelTest() {
111 | local settings = TracePlus.Settings.new()
112 | settings.AppendIgnoredModel("models/editor/info_player_start.mdl")
113 | return assert(settings.GetIgnoredModels().contains("models/editor/info_player_start.mdl"))
114 | },
115 |
116 | function traceSettingsSetCollisionFilterTest() {
117 | local settings = TracePlus.Settings.new()
118 | local filterFunction = function() { return true }
119 | settings.SetCollisionFilter(filterFunction)
120 | return assert(settings.GetCollisionFilter() == filterFunction)
121 | },
122 |
123 | function traceSettingsSetIgnoreFilterTest() {
124 | local settings = TracePlus.Settings.new()
125 | local filterFunction = function() { return false }
126 | settings.SetIgnoreFilter(filterFunction)
127 | return assert(settings.GetIgnoreFilter() == filterFunction)
128 | },
129 |
130 | function traceSettingsApplyCollisionFilterTest() {
131 | local settings = TracePlus.Settings.new()
132 | local filterFunction = function(_ent, _note) { return 1708 }
133 | settings.SetCollisionFilter(filterFunction)
134 | return assert(settings.ApplyCollisionFilter(GetPlayerEx(), null) == 1708)
135 | },
136 |
137 | function traceSettingsApplyIgnoreFilterTest() {
138 | local settings = TracePlus.Settings.new()
139 | local filterFunction = function(_ent, _note) { return false }
140 | settings.SetIgnoreFilter(filterFunction)
141 | return assert(settings.ApplyIgnoreFilter(GetPlayerEx(), null) == false)
142 | },
143 |
144 | function traceSettingsUpdateIgnoreEntitiesTest() {
145 | local settings = TracePlus.Settings.new()
146 | local ignoreEntities = ArrayEx()
147 | local updatedIgnoreEntities = settings.UpdateIgnoreEntities(ignoreEntities, GetPlayerEx())
148 | return assert(updatedIgnoreEntities.len() == 1)
149 | },
150 | }
151 |
152 | // Run all tests
153 | RunTests("TracePlus", tracePlusTests)
--------------------------------------------------------------------------------
/Tests/Utils.nut:
--------------------------------------------------------------------------------
1 | // Utils Module Unit Tests
2 | if(!("RunTests" in getroottable())) IncludeScript("PCapture-LIB/Tests/test_exec")
3 |
4 | utils_tests <- {
5 | // --- Debug Tests ---
6 | function debug_draw_entity_bbox_test() {
7 | local ent = GetPlayerEx()
8 | dev.DrawEntityBBox(ent, Vector(125, 0, 0), 10.0)
9 | },
10 |
11 | function debug_log_test() {
12 | dev.info("This is a test log message.")
13 | },
14 |
15 | function debug_warning_test() {
16 | dev.warning("This is a test warning message.")
17 | },
18 |
19 | function debug_error_test() {
20 | dev.error("This is a test error message.")
21 | },
22 |
23 | function debug_format_test() {
24 | local formattedString = macros.format("Name: {}, Age: {}", "John", 30)
25 | return assert(formattedString == "Name: John, Age: 30")
26 | },
27 |
28 | // --- Macros Tests ---
29 | function macros_get_from_table_test() {
30 | local table = {key1 = "value1", key2 = "value2"}
31 | return assert(macros.GetFromTable(table, "key1") == "value1" && macros.GetFromTable(table, "key3", "default") == "default")
32 | },
33 |
34 | function macros_get_dist_test() {
35 | local vec1 = Vector(0, 0, 0)
36 | local vec2 = Vector(10, 0, 0)
37 | return assert(macros.GetDist(vec1, vec2) == 10)
38 | },
39 |
40 | function macros_strtovec_test() {
41 | local vec = macros.StrToVec("10 20 30")
42 | return assert(vec.x == 10 && vec.y == 20 && vec.z == 30)
43 | },
44 |
45 | function macros_get_prefix_test() {
46 | return assert(macros.GetPrefix("prefix-postfix") == "prefix-")
47 | },
48 |
49 | function macros_get_postfix_test() {
50 | return assert(macros.GetPostfix("prefix-postfix") == "-postfix")
51 | },
52 |
53 | function macros_isEqually() {
54 | return assert(
55 | macros.isEqually(11343, 11343) &&
56 | macros.isEqually(1.543543, 1.54354) &&
57 | macros.isEqually(Vector(1,2,3.43), Vector(1,2,3.43)) &&
58 | macros.isEqually(GetPlayer(), GetPlayer()) &&
59 | macros.isEqually(GetPlayerEx(), GetPlayer()) &&
60 | macros.isEqually(math.Quaternion.fromEuler(Vector(0, 90, 0)), math.Quaternion.fromEuler(Vector(0, 90, 0))) &&
61 | macros.isEqually(math.Matrix.fromEuler(Vector(0, 90, 0)), math.Matrix.fromEuler(Vector(0, 90, 0)))
62 | )
63 | }
64 | }
65 |
66 | // Run all tests
67 | RunTests("Utils", utils_tests)
--------------------------------------------------------------------------------
/Tests/test_exec.nut:
--------------------------------------------------------------------------------
1 | IncludeScript("PCapture-Lib/SRC/PCapture-Lib")
2 |
3 | ::LibLogger <- LoggerLevels.Off
4 |
5 | ::RunTests <- function(testName, tests_table) {
6 | macros.fprint("\n{} tests are running...", testName)
7 | printl("-------------------------------------------------")
8 |
9 | local passed_tests = tests_table.len()
10 | local unsuccessful = List()
11 | foreach(name, test_func in tests_table) {
12 | try {
13 | test_func()
14 | } catch(exception) {
15 | macros.fprint("{} test error: function {} ({})", testName, name, exception)
16 | unsuccessful.append(name)
17 | passed_tests--
18 | }
19 | }
20 |
21 | local resTest = "Tests passed successfully!"
22 | if(unsuccessful.len() > 0) {
23 | resTest = macros.format("{} tests with error:\n* {}", testName, unsuccessful.join("\n* "))
24 | printl("")
25 | }
26 | macros.fprint("~~ {} tests result: {}/{} passed. ~~", testName, passed_tests, tests_table.len())
27 | printl(resTest)
28 | printl("-------------------------------------------------\n")
29 | }
30 |
31 | function RunAllTests() {
32 | printl("\n~~~~~~~~~~~~~~~~~~~~~~~~~\nRUN ALL TESTS\n~~~~~~~~~~~~~~~~~~~~~~~~~")
33 | IncludeScript("PCapture-LIB/Tests/Math")
34 | IncludeScript("PCapture-LIB/Tests/IDT")
35 | IncludeScript("PCapture-LIB/Tests/Utils")
36 | IncludeScript("PCapture-LIB/Tests/ActionScheduler")
37 | IncludeScript("PCapture-LIB/Tests/ScriptEvents")
38 | IncludeScript("PCapture-LIB/Tests/TracePlus")
39 | }
40 |
41 | if(getstackinfos(2) == null) {
42 | RunAllTests()
43 | }
--------------------------------------------------------------------------------
/other/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LaVashikk/PCapture-LIB/667bf495dc2814ffad9a3de99a210f8fa28f3093/other/logo.png
--------------------------------------------------------------------------------