tsg grantDefaultWeapon Released

A script module used for granting units weapons without any customization applied to them.

Player holding an S7 Flexfire Sniper Rifle with no customization applied, allowing the custom legendary skin to show Written by Okom on Nov 23, 2024 in Halo. Last edited: Nov 23, 2024.

What is it?

tsg grantDefaultWeapon is a script module that grants the requesting unit (such as a player) the desired weapon type with the default customization applied. Normally when initially picking up a weapon in Halo Infinite, the player's weapon customization is applied to the weapon model. A downside of this is that weapon variants such as a "Legendary" Sniper Rifle become indistinguishable from the normal variant as they both get the same customization applied upon pickup. This script solves that issue.

The script uses Mode Brains, so it's classified as a Mode, but it can be placed and handled like a regular script on a level.

Bookmark here
Player with an MA40 Assault Rifle
Before starting the weapon grant.
Player after just being granted an S7 Flexfire Sniper Rifle that requires a reload
During the weapon grant of an S7 Flexfire. The player must reload due to the ammo adjustment of -50%.
Player holding a reloaded S7 Flexfire Sniper Rifle
After the weapon grant of an S7 Flexfire has completed.

How does it work?

Weapon and vehicle customization is determined by the preferences of the unit who interacts with the item first. The tsg grantDefaultWeapon module grants the requested weapon type first to a cloned player before granting that weapon to the unit who requested it. As a cloned player is a default biped that has no customization preferences, no customization will be applied to the weapon, and it will persist no matter who picks it up afterwards.

Creating the weapon carrier clone

A random, alive player is cloned at Gameplay Start and the clone is put into a stasis at the coordinates X: 0, Y: 0, Z: -9460. From there, the clone will fall into the "deload zone" beginning Z: -9462.6 and will become unable to move or make any actions. At this point the clone is ready to act as a temporary weapon carrier.

Scripting nodes in a node graph
Code for creating the weapon carrier clone.
Cloned player in the deload zone
Cloned player stuck at Z: -9462.6.

Checking that the random player to clone is alive ensures that the cloning won't fail due to cloning a dead player, such as someone joining in-progress.

Requesting a weapon

When a weapon is requested by a unit, the desired weapon type is set in an object-scoped Weapon Type variable for the unit and the code for requesting the weapon is executed. The desired weapon type is given to the weapon carrier clone with the weapon addition method of Replace All, resulting in the clone's inventory being replaced with said weapon.

The Replace All option clears the unit's inventory and gives them only one instance of the weapon. The image below shows the clone dropping the sniper and the weapon in their inventory hasn't deloaded yet.

Scripting nodes in a node graph
Code for requesting a weapon.
Player holding a MA40 AR while standing next to the cloned player
Frame 2 after requesting a weapon.

Granting the weapon

As the weapon is given to a clone instead of the requesting unit and the script should work for multiple units at the same time, units are put in a request queue in order to reliably track who each weapon should be granted to.

Clone dropping the weapon

In order to get the desired weapon away from the clone and into the hands of the requesting unit, it must not be in the clone's inventory. We can achieve this by forcing the clone to drop the weapon. An efficient way to do this is by replacing the clone's primary weapon with a dummy weapon.

After the weapon is dropped, the unit first in the request queue is gathered, removed from the queue and granted the weapon. An attempt is made to grant the requested weapon with the If Room method if the unit has room in their inventory so the requested weapon becomes their primary and their existing primary their secondary.

The For Each Generic Item pattern is used to get a reference to an item at that specific time, which won't change during the execution of the function. For example if the unit's unequipped weapon would change during the execution, some aspects would fail.

Dropping the requesting unit's secondary weapon

Regardless of whether the unit had one or two weapons at the time of request, the unit's current secondary weapon is temporarily dropped and teleported to stasis at X: 0, Y: 0, Z: -9500 so the drop action isn't noticeable. The unit is then granted the requested weapon (again, if the If Room grant worked) and the ammo adjustment is done.

The secondary weapon must be dropped temporarily because the current state of Halo Infinite scripting only allows for emptying the ammo of all weapons in a player's inventory as opposed to just specific weapons.

Scripting nodes in a node graph
Code for determining the weapon receiver, granting the weapon and adjusting the ammo.

Ammo adjustment

The ammo adjustment is done by pulling the previously stored number for how much percentage the ammo should be adjusted by and applying that to the requested weapon once it's in the player's inventory. If the value is positive, that value is used as-is to adjust the ammo count by. If the value is negative, the player's ammo is emptied and then the absolute of the adjustment value is used to add more ammo for the player, resulting in less ammo than default after a reload. The default ammo adjustment value of 0.00 will result in no action.

The "player" wording is used instead of "unit" because the nodes for adjusting player ammo only work if the weapon is in the inventory of a player. This is also why the ammo adjustment is not done while the weapon is held by the clone.

Cleanup

After the ammo adjustment is finished, their ammo adjustment value is reset back to 0.00, the unit's secondary weapon is granted back and the process is finished for the current unit who requested the weapon.

Player holding a low-poly model of an S7 Sniper Rifle while standing next to the cloned player
Frame 5 after requesting a weapon.
Player holding an S7 Flexfire Sniper Rifle while standing next to the cloned player
Frame 10 after requesting a weapon. An ammo adjustment of 0.00 was used for this demo.

Usage

To use the script module, recreate the code shown in the image, set the desired weapon type and adjust the number in the global custom event for how the ammo should be adjusted. 0.00 is default. The trigger for the event can be anything where the unit/player can be extracted from; On Player Mark used in the example.

Scripting nodes in a node graph
Code for initiating a weapon request.

The idea

When designing the custom weapons for the Scripter's Guild Halo 5 Warzone recreation for Halo Infinite, me and Implied Skill ran into the issue of the weapon combinations not being easily distinguishable due to the player's weapon skins making all the variants of one base weapon type (such as an S7 Sniper Rifle) look the same.

The only ways to determine whether a weapon was not the default variant were:

Out of these only the reticle shape was immediately noticeable, which would lead to players being confused about which weapon they were holding.

Conveying the differences between custom weapons is a crucial way to keep a smooth and intuitive gameplay flow for our Warzone recreation, and losing out on custom weapon models was a big hit for our user experience.

Discovered by accident

By accident while I was writing another piece of code to spawn a separate weapon when a specific grunt died, I noticed that the pattern I used for creating that weapon resulted in it having no customization on it. That pattern was similar to what later evolved into tsg grantDefaultWeapon.

We had a discussion about the possibility of integrating such a weapon granting feature into our code for tsg Warzone with Implied Skill and came to the conclusion that it would be a great jump in the weapon readability for our players, as long as it wasn't too heavy on the scripting budget.

Testing

After confirming my hypothesis to be true on how it would work, I made a test map where I would test the worst-case usage scenario. This scenario consisted of giving a specific weapon from a selection of 8 with a preset ammo adjustment to 9 players (8 bots and myself) as fast as the game allows.

I would track which of the 8 random loadouts got granted to each player by attaching a number value to them with a nav marker. This way I could compare the weapon they got granted with the number value to confirm whether everything was working or not.

The weapon carrier clone being given weapons, and dropping them
The clone positioned in the playable space and being given 9 weapons in 0.35 seconds which got assigned to units.
8 bots in a line with number values floating in front of them
Bots with nav markers next to them showing their granted loadout value.

Surprisingly much of the logic worked as intended, but one issue I had to fight for a while was the ammo adjustment value being from different loadouts. After narrowing down the causes and realizing it didn't happen when I was only granting weapons to one player, I realized it was something to do with multiple weapons being granted quickly.

In the end I changed when the current unit in the queue was being removed from the unitsRequestingWeapon list to be right at the beginning instead of at the end of the function and that fixed the issue. To me it's not fully intuitive to remove the requesting unit from the list before they have the weapon in their hands, but it seems to work and I haven't ran into any issues after a large amount of validation testing.