Skip to content

Commit

Permalink
feat(): add animation and call service & controllers (#259)
Browse files Browse the repository at this point in the history
* 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
AvarianKnight authored Jul 6, 2021
1 parent 4ef89f6 commit 398bfa2
Show file tree
Hide file tree
Showing 14 changed files with 413 additions and 299 deletions.
1 change: 1 addition & 0 deletions phone/src/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"APPS_DIALER_NAVBAR_HISTORY": "History",
"APPS_DIALER_NAVBAR_DIAL": "Dial",
"APPS_DIALER_NAVBAR_CONTACTS": "Contacts",
"APPS_DIALER_BUSY_LINE": "The line was busy.",

"APPS_TWITTER_TWEET": "Tweet",
"APPS_TWITTER_TWEET_MESSAGE_PLACEHOLDER": "What's happening?",
Expand Down
3 changes: 3 additions & 0 deletions resources/client/animations/animation.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { AnimationService } from "./animation.service";

export const animationService = new AnimationService();
194 changes: 194 additions & 0 deletions resources/client/animations/animation.service.ts
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);
}
};
}
65 changes: 65 additions & 0 deletions resources/client/calls/cl_calls.controller.ts
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);
});
108 changes: 108 additions & 0 deletions resources/client/calls/cl_calls.service.ts
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,
});
}

}
Loading

0 comments on commit 398bfa2

Please sign in to comment.