Skip to content

Dual Wield: API Changes

Emanuele Disco edited this page Jul 9, 2024 · 4 revisions

image

I recommend to disable Two Handed Weapons when using offhand weapon to have a better experience. If you want to keep the option on, you might want to bind the speed key to another button (default to offhand grip)

Controls

Under the controls menu you find a new command named Update missing defaults, just below the reset command, this will update the missing bindings to default state, use it to update the new missing default bindings!

offhand trigger - Offhand fire main grip + offhand trigger - Offhand alternate fire

main grip + joy up / down - Switch between offhand weapons

main grip + main joystick left - Bring the main weapon to the off hand main grip + main joystick right - Bring the offhand weapon to the main hand

offhand grip - The new default speed key, previously set to offhand trigger

Mob Compatibility

💡 Brutal Doom and similar mods, dual wield will work with quirks. State jump depending on inventory items will not work well with dual wield.

💡 Sharing the same overlay layer on multiple weapons will not work with dual wield.

💡 When using A_PlaySound or A_StartSound, 0 and 1 are the most safe channels to use, when using other channels you need to be aware that a second weapon held in the off hand might start a sound on that same channel at the same time.

API Changes

PlayerInfo:

bool OverrideAttackPosDir

vector3 AttackPos

double AttackAngle

double AttackPitch

double AttackRoll

vector3 AttackDir(Actor actor, double angle, double pitch)

Usage

Return the current direction of the main weapon in relation to the orientation of the given actor, angle and pitch are not offset, they are the addition of the actual angle of the actor + offset if exists.

Parameters

  • actor - this is the player actor
  • angle - yaw angle of the player + offset angle (you want the offset if you are calculating the speed vector but NOT for the spawn position)
  • pitch - pitch angle of the player + offset angle (you want the offset if you are calculating the speed vector but NOT for the spawn position)

You can find an example that might help you understand the use of this function in the implementation of P_RailAttack, there is also an example at the end of this document

vector3 OffhandPos

double OffhandAngle

double OffhandPitch

double OffhandRoll

vector3 OffhandDir(Actor actor, double angle, double pitch)

Usage

Return the current direction of the offhand weapon in relation to the orientation of the given actor, angle and pitch are not offset, they are the addition of the actual angle of the actor + offset if exists.

Parameters

  • actor - this is the player actor
  • angle - yaw angle of the player + offset angle (you want the offset if you are calculating the speed vector but NOT for the spawn position)
  • pitch - pitch angle of the player + offset angle (you want the offset if you are calculating the speed vector but NOT for the spawn position)

You can find an example that might help you understand the use of this function in the implementation of P_RailAttack, there is also an example at the end of this document


Buttons:

ACS Definition Corresponding action
BT_OFFHANDATTACK Fire primary with offhand weapon
BT_OFFHANDALTATTACK Fire secondary with offhand weapon
BT_OFFHANDRELOAD Reload with offhand weapon
BT_MAINHANDRELOAD Reload with mainhand weapon

Weapon flags:

WEAPON.OFFHANDWEAPON - The weapon will be placed on the offhand

WEAPON.NOHANDSWITCH - The weapon can not be moved from one hand to the other

WEAPON.TWOHANDED - The weapon is a two hand weapon so it can not be used together with other weapons, switching to this weapon remove the other weapon currently wield.

WEAPON.NOAUTOREVERSE - The model will not be reversed when moving the weapon to the offhand.

The following flags can be used to tell to shoot from the offhand instead of mainhand when using the following functions:

LineAttack flags:

LAF_ISOFFHAND

LineTrace flags:

TRF_ISOFFHAND - The weapon should be considered as offhand weapon

TRF_USEWEAPON - The line originates from the calling actor's weapon (default to ReadyWeapon)

SpawnPlayerMissile flags:

ALF_ISOFFHAND

AimLineAttack flags

ALF_ISOFFHAND

RailAttack flags

RAF_ISOFFHAND


SpawnSubMissile

Actor SpawnSubMissile(Class type, Actor target, int aimflags = 0, double angle = 1e37)

New parameters:

  • aimflags: the following flags are available:
    • ALF_ISOFFHAND - the weapon should be considered as offhand weapon.
  • angle: change the angle of the missile velocity vector (default to player angle).

SpawnPlayerMissile

Actor, Actor SpawnPlayerMissile(class type, double angle = 1e37, double x = 0, double y = 0, double z = 0, out FTranslatedLineTarget pLineTarget = null, bool nofreeaim = false, bool noautoaim = false, int aimflags = 0, double pitch = 1e37)

New parameters:

  • aimflags: the following flags are available:
    • ALF_ISOFFHAND - the weapon should be considered as offhand weapon.
  • pitch: can be used to change the pitch of the missile, default to player pitch.

ZScript Example

Action that make use of weapon position data to calculate spawned object position and velocity vector

action void A_MyCustomAction()
{

	let weapon = invoker;
	if (weapon && player.mo.OverrideAttackPosDir)
	{
		Vector3 dir;
		Vector3 yoffsetDir;
		Vector3 zoffsetDir;
		Vector3 spawnpos;
		Vector3 newvel;
		let directionAngle = angle;
		let directionPitch = pitch;
		let directionRoll = roll;
		if (weapon.bOffhandWeapon)
		{
			spawnpos = player.mo.OffhandPos;
			directionRoll = -player.mo.OffhandRoll;
			dir = player.mo.OffhandDir(self, angle, pitch);
			yoffsetDir = player.mo.OffhandDir(self, angle - 90, pitch);
			zoffsetDir = player.mo.OffhandDir(self, angle, pitch + 90);
		}
		else
		{
			spawnpos = player.mo.AttackPos;
			directionRoll = -player.mo.AttackRoll;
			dir = player.mo.AttackDir(self, angle, pitch);
			yoffsetDir = player.mo.AttackDir(self, angle - 90, pitch);
			zoffsetDir = player.mo.AttackDir(self, angle, pitch + 90);
		}

		directionAngle = dir.x;
		directionPitch = dir.y;

		// offset in relation to the weapon to spawn the new object
		double xofs = 10;
		double yofs = 0;
		double zofs = 0;

		// ========== SIMPLE SPAWN POS CALCULATION =============
		// simple position calculation based only on xofs and yofs
		spawnpos += (xofs * cos(dir.x) + yofs * sin(dir.x), xofs * sin(dir.x) - yofs * cos(dir.x), 0);
		// or we can use the method RotateVector
		spawnpos += (RotateVector(xofs, yofs, directionAngle), 0);
		// =======================


		// ========== BETTER SPAWN POS CALCULATION =============
		// spawn position contribution with x offset by taking in consideration the orientation of the weapon
		spawnpos += (
			xofs * cos(dir.x) * cos(dir.y),
			xofs * sin(dir.x) * cos(dir.y),
			xofs * -sin(dir.y)
		);
		// spawn position contribution with y offset by taking in consideration the orientation of the weapon
		spawnpos += (
			yofs * cos(yoffsetDir.x) * cos(yoffsetDir.y),
			yofs * sin(yoffsetDir.x) * cos(yoffsetDir.y),
			yofs * -sin(yoffsetDir.y)
		);
		// spawn position contribution with z offset by taking in consideration the orientation of the weapon
		spawnpos += (
			zofs * cos(zoffsetDir.y) * cos(zoffsetDir.x), 
			zofs * cos(zoffsetDir.y) * sin(zoffsetDir.x),
			zofs * -sin(zoffsetDir.y)
		);
		// =======================


		let mo = Spawn(missile, spawnpos, ALLOW_REPLACE);
		if (mo)
		{
			mo.Angle = directionAngle;
			mo.Pitch = directionPitch;
			mo.Roll = directionRoll;


			// =========== SIMPLE VELOCITY VECTOR (ONLY FORWARD COMPONENT) ============
			// if we use the spawned object speed (forward only) then we can just do the following
			newvel = (
				mo.Speed * cos(dir.y) * cos(dir.x),
				mo.Speed * cos(dir.y) * sin(dir.x),
				mo.Speed * -sin(dir.y)
			);
			mo.Vel = newvel;

			// depending on your case you might want to use a slope for the z component
			double slope = -clamp(tan(directionPitch), -5, 5);
			mo.Vel.Z = mo.Speed * slope;
			// =======================


			// =========== BETTER VELOCITY VECTOR (X,Y,Z COMPONENT) ============
			// if the velocity vector has x, y and z components we need to add all three contributions
			newvel = (0, 0, 0);
			newvel += (
				x * cos(dir.x) * cos(dir.y),
				x * sin(dir.x) * cos(dir.y),
				x * -sin(dir.y)
			);

			newvel += (
				y * cos(yoffsetDir.x) * cos(yoffsetDir.y),
				y * sin(yoffsetDir.x) * cos(yoffsetDir.y),
				y * -sin(yoffsetDir.y)
			);

			newvel += (
				z * cos(zoffsetDir.y) * cos(zoffsetDir.x), 
				z * cos(zoffsetDir.y) * sin(zoffsetDir.x),
				z * -sin(zoffsetDir.y)
			);
			mo.Vel = newvel;
			// =======================

			
		}
	}
}

🍕Buy me a piece of pizza!