-
-
Notifications
You must be signed in to change notification settings - Fork 275
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(): add animation and call service & controllers (#259)
* feat(): add animation and call service & controllers - run animations on interval so we don't lose animations when walking through doors - move cl_call.ts to a class * fix(animation/service): make everything private except for open/closePhone and start/endPhoneCall
- Loading branch information
1 parent
4ef89f6
commit 398bfa2
Showing
14 changed files
with
413 additions
and
299 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { AnimationService } from "./animation.service"; | ||
|
||
export const animationService = new AnimationService(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import { newPhoneProp, removePhoneProp } from '../functions'; | ||
import { Delay } from '../../utils/fivem'; | ||
|
||
export enum AnimationState { | ||
ON_CALL, | ||
PHONE_OPEN, | ||
ON_CAMERA, | ||
} | ||
|
||
export class AnimationService { | ||
private animationInterval: NodeJS.Timeout; | ||
private onCall: boolean = false; | ||
private phoneOpen: boolean = false; | ||
private onCamera: boolean = false; | ||
|
||
private createAnimationInterval() { | ||
this.animationInterval = setInterval(async () => { | ||
const playerPed = PlayerPedId(); | ||
if (this.onCall) { | ||
this.handleCallAnimation(playerPed); | ||
} else if (this.phoneOpen && !this.onCamera) { | ||
this.handleOpenAnimation(playerPed); | ||
} | ||
}, 250); | ||
} | ||
|
||
private setPhoneState(state: AnimationState, stateValue: boolean) { | ||
switch (state) { | ||
case AnimationState.ON_CALL: | ||
this.onCall = stateValue; | ||
break; | ||
case AnimationState.PHONE_OPEN: | ||
this.phoneOpen = stateValue; | ||
break; | ||
case AnimationState.ON_CAMERA: | ||
this.onCamera = stateValue; | ||
break; | ||
} | ||
|
||
if (!this.onCall && !this.phoneOpen) { | ||
if (this.animationInterval) { | ||
clearInterval(this.animationInterval); | ||
this.animationInterval = null; | ||
} | ||
} else if (!this.animationInterval) { | ||
this.createAnimationInterval(); | ||
} | ||
} | ||
|
||
private handleCallAnimation(playerPed: number) { | ||
if (IsPedInAnyVehicle(playerPed, true)) { | ||
this.handleOnCallInVehicle(playerPed); | ||
} else { | ||
this.handleOnCallNormal(playerPed); | ||
} | ||
} | ||
|
||
private handleOpenAnimation(playerPed: number) { | ||
if (IsPedInAnyVehicle(playerPed, true)) { | ||
this.handleOpenVehicleAnim(playerPed); | ||
} else { | ||
this.handleOpenNormalAnim(playerPed); | ||
} | ||
} | ||
|
||
private handleCallEndAnimation(playerPed: number) { | ||
if (IsPedInAnyVehicle(playerPed, true)) { | ||
this.handleCallEndVehicleAnim(playerPed); | ||
} else { | ||
this.handleCallEndNormalAnim(playerPed); | ||
} | ||
} | ||
|
||
private handleCloseAnimation(playerPed: number) { | ||
if (IsPedInAnyVehicle(playerPed, true)) { | ||
this.handleCloseVehicleAnim(playerPed); | ||
} else { | ||
this.handleCloseNormalAnim(playerPed); | ||
} | ||
} | ||
|
||
openPhone(): void { | ||
newPhoneProp(); | ||
if (!this.onCall) { | ||
this.handleOpenAnimation(PlayerPedId()); | ||
} | ||
this.setPhoneState(AnimationState.PHONE_OPEN, true); | ||
} | ||
|
||
closePhone(): void { | ||
removePhoneProp(); | ||
this.setPhoneState(AnimationState.PHONE_OPEN, false); | ||
if (!this.onCall) { | ||
this.handleCloseAnimation(PlayerPedId()); | ||
} | ||
} | ||
|
||
async startPhoneCall(): Promise<void> { | ||
this.handleCallAnimation(PlayerPedId()); | ||
this.setPhoneState(AnimationState.ON_CALL, true); | ||
} | ||
|
||
async endPhoneCall(): Promise<void> { | ||
this.handleCallEndAnimation(PlayerPedId()); | ||
this.setPhoneState(AnimationState.ON_CALL, false); | ||
} | ||
|
||
private async loadAnimDict(dict: any) { | ||
//-- Loads the animation dict. Used in the anim functions. | ||
RequestAnimDict(dict); | ||
while (!HasAnimDictLoaded(dict)) { | ||
await Delay(100); | ||
} | ||
} | ||
|
||
private async handleOpenVehicleAnim(playerPed: number): Promise<void> { | ||
const dict = 'anim@cellphone@in_car@ps'; | ||
const anim = 'cellphone_text_in'; | ||
await this.loadAnimDict(dict); | ||
|
||
if (!IsEntityPlayingAnim(playerPed, dict, anim, 3)) { | ||
SetCurrentPedWeapon(playerPed, 0xa2719263, true); | ||
TaskPlayAnim(playerPed, dict, anim, 7.0, -1, -1, 50, 0, false, false, false); | ||
} | ||
} | ||
|
||
private async handleOpenNormalAnim(playerPed: number): Promise<void> { | ||
//While not in a vehicle it will use this dict. | ||
const dict = 'cellphone@'; | ||
const anim = 'cellphone_text_in'; | ||
await this.loadAnimDict(dict); | ||
|
||
if (!IsEntityPlayingAnim(playerPed, dict, anim, 3)) { | ||
SetCurrentPedWeapon(playerPed, 0xa2719263, true); | ||
TaskPlayAnim(playerPed, dict, anim, 8.0, -1, -1, 50, 0, false, false, false); | ||
} | ||
} | ||
|
||
private async handleCloseVehicleAnim(playerPed: number): Promise<void> { | ||
const DICT = 'anim@cellphone@in_car@ps'; | ||
StopAnimTask(playerPed, DICT, 'cellphone_text_in', 1.0); // Do both incase they were on the phone. | ||
StopAnimTask(playerPed, DICT, 'cellphone_call_to_text', 1.0); | ||
removePhoneProp(); | ||
} | ||
|
||
private async handleCloseNormalAnim(playerPed: number): Promise<void> { | ||
const DICT = 'cellphone@'; | ||
const ANIM = 'cellphone_text_out'; | ||
StopAnimTask(playerPed, DICT, 'cellphone_text_in', 1.0); | ||
await Delay(100); | ||
await this.loadAnimDict(DICT); | ||
TaskPlayAnim(playerPed, DICT, ANIM, 7.0, -1, -1, 50, 0, false, false, false); | ||
await Delay(200); | ||
StopAnimTask(playerPed, DICT, ANIM, 1.0); | ||
removePhoneProp(); | ||
} | ||
|
||
private async handleOnCallInVehicle(playerPed: number): Promise<void> { | ||
const DICT = 'anim@cellphone@in_car@ps'; | ||
const ANIM = 'cellphone_call_listen_base'; | ||
|
||
if (!IsEntityPlayingAnim(playerPed, DICT, ANIM, 3)) { | ||
await this.loadAnimDict(DICT); | ||
TaskPlayAnim(playerPed, DICT, ANIM, 3.0, 3.0, -1, 49, 0, false, false, false); | ||
} | ||
} | ||
|
||
private async handleOnCallNormal(playerPed: number): Promise<void> { | ||
const DICT = 'cellphone@'; | ||
const ANIM = 'cellphone_call_listen_base'; | ||
if (!IsEntityPlayingAnim(playerPed, DICT, ANIM, 3)) { | ||
await this.loadAnimDict(DICT); | ||
TaskPlayAnim(playerPed, DICT, ANIM, 3.0, 3.0, -1, 49, 0, false, false, false); | ||
} | ||
} | ||
|
||
private async handleCallEndVehicleAnim(playerPed: number): Promise<void> { | ||
const DICT = 'anim@cellphone@in_car@ps'; | ||
const ANIM = 'cellphone_call_to_text'; | ||
StopAnimTask(playerPed, DICT, 'cellphone_call_listen_base', 1.0); | ||
await this.loadAnimDict(DICT); | ||
TaskPlayAnim(playerPed, DICT, ANIM, 1.3, 5.0, -1, 50, 0, false, false, false); | ||
} | ||
|
||
private async handleCallEndNormalAnim(playerPed: number): Promise<void> { | ||
const DICT = 'cellphone@'; | ||
const ANIM = 'cellphone_call_to_text'; | ||
|
||
if (IsEntityPlayingAnim(playerPed, 'cellphone@', 'cellphone_call_listen_base', 49)) { | ||
await this.loadAnimDict(DICT); | ||
TaskPlayAnim(playerPed, DICT, ANIM, 2.5, 8.0, -1, 50, 0, false, false, false); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { CallEvents } from '../../../typings/call'; | ||
import { CallHistoryItem } from '../../../typings/call'; | ||
import { IAlertProps } from '../../../typings/alerts'; | ||
import { CallService } from './cl_calls.service'; | ||
import { animationService } from '../animations/animation.controller'; | ||
|
||
const callService = new CallService(); | ||
|
||
RegisterNuiCallbackType(CallEvents.INITIALIZE_CALL); // Fires when the call is started. | ||
on(`__cfx_nui:${CallEvents.INITIALIZE_CALL}`, (data: any, cb: Function) => { | ||
emitNet(CallEvents.INITIALIZE_CALL, data.number); | ||
cb(); | ||
}); | ||
|
||
onNet(CallEvents.START_CALL, (transmitter: string, receiver: string, isTransmitter: boolean) => { | ||
if(isTransmitter){ | ||
animationService.startPhoneCall(); | ||
} | ||
callService.handleStartCall(transmitter, receiver, isTransmitter) | ||
}); | ||
|
||
RegisterNuiCallbackType(CallEvents.ACCEPT_CALL); // Fires when the TARGET accepts. | ||
on(`__cfx_nui:${CallEvents.ACCEPT_CALL}`, (data: any, cb: Function) => { | ||
animationService.startPhoneCall(); | ||
emitNet(CallEvents.ACCEPT_CALL, data.transmitterNumber); | ||
cb(); | ||
}); | ||
|
||
onNet(CallEvents.WAS_ACCEPTED, (channelId: number, currentCall: CallHistoryItem, isTransmitter: boolean) => { | ||
callService.handleCallAccepted(channelId, currentCall, isTransmitter) | ||
}); | ||
|
||
RegisterNuiCallbackType(CallEvents.REJECTED); // Fires when cancelling and rejecting a call. | ||
on(`__cfx_nui:${CallEvents.REJECTED}`, (data: any, cb: Function) => { | ||
console.log('rejected?') | ||
emitNet(CallEvents.REJECTED, data.phoneNumber); | ||
cb(); | ||
}); | ||
|
||
onNet(CallEvents.WAS_REJECTED, async() => { | ||
callService.handleRejectCall(); | ||
animationService.endPhoneCall(); | ||
}); | ||
|
||
RegisterNuiCallbackType(CallEvents.END_CALL); // Fires when ending an ACTIVE call | ||
on(`__cfx_nui:${CallEvents.END_CALL}`, (data: any, cb: Function) => { | ||
const end = Date.now(); | ||
animationService.endPhoneCall(); | ||
emitNet(CallEvents.END_CALL, data.transmitterNumber, end); | ||
cb(); | ||
}); | ||
|
||
onNet(CallEvents.WAS_ENDED, () => { | ||
callService.handleEndCall(); | ||
animationService.endPhoneCall(); | ||
}); | ||
|
||
|
||
onNet(CallEvents.FETCH_CALLS, (calls: CallHistoryItem[]) => { | ||
callService.handleFetchCalls(calls); | ||
}); | ||
|
||
onNet(CallEvents.SEND_ALERT, (alert: IAlertProps) => { | ||
callService.handleSendAlert(alert); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { IAlertProps } from "../../../typings/alerts"; | ||
import { CallEvents, CallHistoryItem, CallRejectReasons } from "../../../typings/call"; | ||
|
||
const exp = (global as any).exports; | ||
|
||
export class CallService { | ||
private currentCall: number = 0; | ||
|
||
|
||
isInCall() { | ||
return this.currentCall !== 0; | ||
} | ||
|
||
openCallModal(show: boolean) { | ||
SendNUIMessage({ | ||
app: 'CALL', | ||
method: CallEvents.SET_CALL_MODAL, | ||
data: show, | ||
}); | ||
} | ||
|
||
handleRejectCall() { | ||
// we don't want to reset our UI if we're in a call already. | ||
if (this.isInCall()) return; | ||
this.openCallModal(false); | ||
SendNUIMessage({ | ||
app: 'CALL', | ||
method: CallEvents.SET_CALLER, | ||
data: { | ||
transmitter: null, | ||
receiver: null, | ||
isTransmitter: null, | ||
accepted: false, | ||
active: false, | ||
}, | ||
}); | ||
} | ||
|
||
handleStartCall(transmitter: string, receiver: string, isTransmitter: boolean) { | ||
// If we're already in a call we want to automatically reject | ||
if (this.isInCall()) return emitNet(CallEvents.REJECTED, transmitter, CallRejectReasons.BUSY_LINE); | ||
this.openCallModal(true); | ||
|
||
SendNUIMessage({ | ||
app: 'CALL', | ||
method: CallEvents.SET_CALLER, | ||
data: { | ||
active: true, | ||
transmitter: transmitter, | ||
receiver: receiver, | ||
isTransmitter: isTransmitter, | ||
accepted: false, | ||
}, | ||
}); | ||
} | ||
|
||
handleCallAccepted(channelId: number, currentCall: CallHistoryItem, isTransmitter: boolean) { | ||
this.currentCall = channelId | ||
exp['pma-voice'].setCallChannel(channelId); | ||
// phoneCallStartAnim(); // Trigger call animation only if the call was accepted. | ||
SendNUIMessage({ | ||
app: 'CALL', | ||
method: CallEvents.SET_CALLER, | ||
data: { | ||
active: true, | ||
transmitter: currentCall.transmitter, | ||
receiver: currentCall.receiver, | ||
isTransmitter: isTransmitter, | ||
accepted: true, | ||
}, | ||
}); | ||
} | ||
|
||
handleEndCall() { | ||
this.currentCall = 0 | ||
exp['pma-voice'].setCallChannel(0); | ||
this.openCallModal(false); | ||
|
||
SendNUIMessage({ | ||
app: 'CALL', | ||
method: CallEvents.SET_CALLER, | ||
data: { | ||
transmitter: null, | ||
receiver: null, | ||
isTransmitter: null, | ||
accepted: false, | ||
active: false, | ||
}, | ||
}) | ||
} | ||
|
||
handleSendAlert(alert: IAlertProps) { | ||
SendNUIMessage({ | ||
app: 'DIALER', | ||
method: CallEvents.SEND_ALERT, | ||
data: alert, | ||
}); | ||
} | ||
|
||
handleFetchCalls(calls: CallHistoryItem[]) { | ||
SendNUIMessage({ | ||
app: 'DIALER', | ||
method: CallEvents.SET_CALL_HISTORY, | ||
data: calls, | ||
}); | ||
} | ||
|
||
} |
Oops, something went wrong.