Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 39 additions & 15 deletions packages/frontend/src/game/player/controls/shoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,67 @@ type Shoot = Pick<PlayerControls, "playerStateRef" | "gamepad">;
interface ShootProps extends Shoot {
clock: Clock;
gameStateRef: GameStateRef;
lastFrameTime: React.MutableRefObject<number>;
lastShotTime: React.MutableRefObject<number>;
sprintEndedTimer: React.MutableRefObject<number>;
wasShootPressed: React.MutableRefObject<boolean>;
wasSprinting: React.MutableRefObject<boolean>;
}

export const shoot = (props: ShootProps) => {
const currentWeapon = props.playerStateRef.current.currentWeapon;
const {
clock,
gameStateRef,
lastFrameTime,
lastShotTime,
wasShootPressed,
sprintEndedTimer,
playerStateRef,
wasSprinting,
} = props;
const currentWeapon = playerStateRef.current.currentWeapon;
if (props.gamepad == null || currentWeapon == null) return;

const activeWeaponId = getActiveWeaponId(
getPlayerId(),
props.gameStateRef.current.weapon.weapons,
);
const activeWeaponId = getActiveWeaponId(getPlayerId(), gameStateRef.current.weapon.weapons);
if (activeWeaponId == null) return;

const activeWeapon =
props.gameStateRef.current.weapon.weapons[activeWeaponId] ?? raise("Weapon not found");

const { lastShotTime, wasShootPressed } = props;
gameStateRef.current.weapon.weapons[activeWeaponId] ?? raise("Weapon not found");

const isShootPressed = !!props.gamepad.buttons[7]?.pressed;
const firingMode = WEAPONS[currentWeapon].firingMode;
const fireRateMs = WEAPONS[currentWeapon].fireRateMs / 1000; // Convert to seconds for getElapsedTime()
const fireRate = WEAPONS[currentWeapon].fireRateMs / 1000; // Convert to seconds for getElapsedTime()
const sprintToFireTime = WEAPONS[currentWeapon].sprintToFireTimeMs / 1000; // Convert to seconds
const currentTime = clock.getElapsedTime();

// Sprint to fire logic
if (!playerStateRef.current.isSprinting && wasSprinting.current) {
sprintEndedTimer.current = 0;
}
wasSprinting.current = playerStateRef.current.isSprinting;

const delta = currentTime - lastFrameTime.current;
lastFrameTime.current = currentTime;
sprintEndedTimer.current += delta;
if (sprintEndedTimer.current < sprintToFireTime) {
return;
}

const currentTime = props.clock.getElapsedTime();
sprintEndedTimer.current = Infinity; // Reset timer after sprint to fire time is reached

if (playerStateRef.current.isSprinting) return;
// TODO: Bug, isShooting state should be handled on server https://app.clickup.com/t/86b5v1jnn
if (firingMode === "Auto") {
// Auto fire (only shoot on button press AND weapon fire rate interval)
if (
isShootPressed &&
currentTime - lastShotTime.current >= fireRateMs &&
currentTime - lastShotTime.current >= fireRate &&
activeWeapon.currentAmmoInMagazine > 0
) {
props.playerStateRef.current.isShooting = true;
playerStateRef.current.isShooting = true;
lastShotTime.current = currentTime;
} else {
props.playerStateRef.current.isShooting = false;
playerStateRef.current.isShooting = false;
}
}

Expand All @@ -58,10 +82,10 @@ export const shoot = (props: ShootProps) => {

if (firingMode === "Single") {
if (isShootPressed && !wasShootPressed.current) {
props.playerStateRef.current.isShooting = true;
playerStateRef.current.isShooting = true;
lastShotTime.current = currentTime;
} else {
props.playerStateRef.current.isShooting = false;
playerStateRef.current.isShooting = false;
}
}

Expand Down
8 changes: 8 additions & 0 deletions packages/frontend/src/game/player/hooks/useControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export const useControls = (props: UseControlsProps) => {
const wasShootPressed = React.useRef(false);
const lastShotTime = React.useRef(0);

// Movement state
const sprintEndedTimer = React.useRef(Infinity);
const wasSprinting = React.useRef(false);
const lastFrameTime = React.useRef(0);

useFrame(({ clock }, delta) => {
// ! Player cannot perform this action if they are not alive
if (!props.playerStateRef.current.isAlive) return;
Expand Down Expand Up @@ -80,9 +85,12 @@ export const useControls = (props: UseControlsProps) => {
clock,
gameStateRef: props.gameStateRef,
gamepad,
lastFrameTime,
lastShotTime,
playerStateRef: props.playerStateRef,
sprintEndedTimer,
wasShootPressed,
wasSprinting,
});
});
};
5 changes: 5 additions & 0 deletions packages/lib/src/objects/weapon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface WeaponStats {
reloadTimeMs: number; // The time it takes to reload the weapon in milliseconds
reserveAmmo: number; // Recommended to be a multiple of bulletsPerMagazine
shotsPerBurst?: number; // Only set if firingMode is "Burst"
sprintToFireTimeMs: number; // The time from when the player stops sprinting to when they can fire the weapon
weaponClass: WeaponClass; // The class of the weapon
weaponType: WeaponType; // The type of weapon
weight: number; // Determines how fast the player can move with the weapon
Expand All @@ -61,6 +62,7 @@ export const WEAPONS = {
},
reloadTimeMs: 3000,
reserveAmmo: 150,
sprintToFireTimeMs: 250,
weaponClass: "Assault Rifle",
weaponType: "Primary",
weight: 1,
Expand All @@ -82,6 +84,7 @@ export const WEAPONS = {
},
reloadTimeMs: 3000,
reserveAmmo: 150,
sprintToFireTimeMs: 250,
weaponClass: "Assault Rifle",
weaponType: "Primary",
weight: 1,
Expand All @@ -105,6 +108,7 @@ export const WEAPONS = {
},
reloadTimeMs: 3000,
reserveAmmo: 150,
sprintToFireTimeMs: 250,
weaponClass: "Assault Rifle",
weaponType: "Primary",
weight: 1,
Expand All @@ -126,6 +130,7 @@ export const WEAPONS = {
},
reloadTimeMs: 3000,
reserveAmmo: 150,
sprintToFireTimeMs: 250,
weaponClass: "Assault Rifle",
weaponType: "Primary",
weight: 1,
Expand Down