Skip to content

Commit

Permalink
feat: local interactions, closes #66 (#107)
Browse files Browse the repository at this point in the history
* feat: local interactions, closes #66

* chore: update version
  • Loading branch information
Stuyk authored Jul 12, 2024
1 parent 8e3d8f1 commit 369f5e1
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 1 deletion.
35 changes: 35 additions & 0 deletions docs/api/server/controllers/interaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Interactions allow a player to walk up to an invisible trigger and press `E` to
If using `player.pos` for your interaction, ensure you subtract `1` from the z axis to make it usable
!!!

## Global Interaction

```ts
import { useRebar } from '@Server/index.js';

Expand Down Expand Up @@ -39,3 +41,36 @@ interaction.onLeave((player: alt.Player, colshape: alt.Colshape, uid: string) =>
// someone left
});
```

## Local Interaction

Local interactions can only be interacted with for an individual player.

```ts
import { useRebar } from '@Server/index.js';

const Rebar = useRebar();

function doSomething(somePlayer: alt.Player) {
const interaction = Rebar.controllers.useInteractionLocal(somePlayer, 'test', 'Cylinder', [
somePlayer.pos.x,
somePlayer.pos.y,
somePlayer.pos.z - 1,
4,
2,
]);

interaction.onEnter((player, destroy) => {
console.log('entered...');
});

interaction.onLeave((player, destroy) => {
console.log('leave...');
});

interaction.on((player, destroy) => {
console.log('interacted');
destroy();
});
}
```
12 changes: 12 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## Version 46

### Code Changes

- Added `useInteractionLocal` to create local interactions for individual players

### Docs Changes

- Updated `useInteraction` to include `useInteractionLocal`

---

## Version 45

### Code Changes
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"author": "stuyk",
"type": "module",
"version": "45",
"version": "46",
"scripts": {
"dev": "nodemon -x pnpm start",
"dev:linux": "nodemon -x pnpm start:linux",
Expand Down
1 change: 1 addition & 0 deletions src/main/client/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as alt from 'alt-client';
import './blip.js';
import './d2dTextLabel.js';
import './interaction.js';
import './interactionLocal.js';
import './marker.js';
import './object.js';
import './ped.js';
Expand Down
126 changes: 126 additions & 0 deletions src/main/client/controllers/interactionLocal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import * as alt from 'alt-client';
import { Events } from '../../shared/events/index.js';
import { useMessenger } from '../system/messenger.js';

type ColshapeTypes = {
Circle: ConstructorParameters<typeof alt.ColshapeCircle>;
Cuboid: ConstructorParameters<typeof alt.ColshapeCuboid>;
Cylinder: ConstructorParameters<typeof alt.ColshapeCylinder>;
Polygon: ConstructorParameters<typeof alt.ColshapePolygon>;
Rectangle: ConstructorParameters<typeof alt.ColshapeRectangle>;
Sphere: ConstructorParameters<typeof alt.ColshapeSphere>;
};

const DEFAULT_COOLDOWN = 1000;
const DEFAULT_KEY = 69; // E

const messenger = useMessenger();
const colshapes: { [uid: string]: alt.Colshape } = {};
const sessionKey = 'colshape-identifier';
let currentInteraction: string;
let timeout = Date.now();

function onCreate<K extends keyof ColshapeTypes>(uid: string, type: K, args: ColshapeTypes[K]) {
if (colshapes[uid]) {
try {
colshapes[uid].destroy();
} catch (err) {}

delete colshapes[uid];
}

switch (type) {
case 'Circle':
// @ts-ignore
colshapes[uid] = new alt.ColshapeCircle(...args);
break;
case 'Cuboid':
// @ts-ignore
colshapes[uid] = new alt.ColshapeCuboid(...args);
break;
case 'Cylinder':
// @ts-ignore
colshapes[uid] = new alt.ColshapeCylinder(...args);
break;
case 'Polygon':
// @ts-ignore
colshapes[uid] = new alt.ColshapePolygon(...args);
break;
case 'Rectangle':
// @ts-ignore
colshapes[uid] = new alt.ColshapeRectangle(...args);
break;
case 'Sphere':
// @ts-ignore
colshapes[uid] = new alt.ColshapeSphere(...args);
break;
}

colshapes[uid].setMeta(sessionKey, uid);
}

function onDestroy(uid: string) {
if (!colshapes[uid]) {
return;
}

try {
colshapes[uid].destroy();
} catch (err) {}

delete colshapes[uid];
}

function onEnter(colshape: alt.Colshape, entity: alt.Entity) {
if (!(entity instanceof alt.Player)) {
return;
}

if (!colshape.hasMeta(sessionKey)) {
return;
}

currentInteraction = colshape.getMeta(sessionKey) as string;
alt.emitServer(Events.controllers.interactionLocal.onEnter, currentInteraction);
}

function onLeave(colshape: alt.Colshape, entity: alt.Entity) {
if (!(entity instanceof alt.Player)) {
return;
}

if (!colshape.hasMeta(sessionKey)) {
return;
}

const uid = colshape.getMeta(sessionKey) as string;
alt.emitServer(Events.controllers.interactionLocal.onLeave, uid);
currentInteraction = undefined;
}

function on(key: alt.KeyCode) {
if (!currentInteraction) {
return;
}

if (DEFAULT_KEY !== key) {
return;
}

if (timeout > Date.now()) {
return;
}

if (messenger.isChatFocused()) {
return;
}

timeout = Date.now() + DEFAULT_COOLDOWN;
alt.emitServer(Events.controllers.interactionLocal.on, currentInteraction);
}

alt.onServer(Events.controllers.interactionLocal.create, onCreate);
alt.onServer(Events.controllers.interactionLocal.destroy, onDestroy);
alt.on('entityEnterColshape', onEnter);
alt.on('entityLeaveColshape', onLeave);
alt.on('keyup', on);
182 changes: 182 additions & 0 deletions src/main/server/controllers/interactionLocal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import * as alt from 'alt-server';
import { Events } from '../../shared/events/index.js';

type ColshapeTypes = {
Circle: ConstructorParameters<typeof alt.ColshapeCircle>;
Cuboid: ConstructorParameters<typeof alt.ColshapeCuboid>;
Cylinder: ConstructorParameters<typeof alt.ColshapeCylinder>;
Polygon: ConstructorParameters<typeof alt.ColshapePolygon>;
Rectangle: ConstructorParameters<typeof alt.ColshapeRectangle>;
Sphere: ConstructorParameters<typeof alt.ColshapeSphere>;
};

type Callback = (player: alt.Player, destroy: Function) => void;
type Invoke = (player: alt.Player) => void;

const invokers: {
[id: string]: { uid: string; invokeEnter: Invoke; invokeLeave: Invoke; invoke: Invoke; destroy: Function }[];
} = {};

function removeByUid(id: number, uid: string) {
if (!invokers[id]) {
return;
}

for (let i = invokers[id].length - 1; i >= 0; i--) {
if (invokers[id][i].uid !== uid) {
continue;
}

invokers[id].splice(i, 1);
break;
}
}

function removeAll(id: number) {
if (!invokers[id]) {
return;
}

for (let invoker of invokers[id]) {
invoker.destroy();
}

delete invokers[id];
}

function getInvoker(player: alt.Player, uid: string) {
if (!player || !player.valid) {
return undefined;
}

if (!invokers[player.id]) {
return undefined;
}

return invokers[player.id].find((x) => x.uid === uid);
}

/**
* Called when a player enters the interaction
*
* @param {alt.Player} player
* @param {string} uid
* @return
*/
function onClientEnter(player: alt.Player, uid: string) {
const invoker = getInvoker(player, uid);
if (!invoker) {
return;
}

invoker.invokeEnter(player);
}

/**
* Called when a player leaves the interaction
*
* @param {alt.Player} player
* @param {string} uid
* @return
*/
function onClientLeave(player: alt.Player, uid: string) {
const invoker = getInvoker(player, uid);
if (!invoker) {
return;
}

invoker.invokeLeave(player);
}

/**
* Called when a player presses 'E'
*
* @param {alt.Player} player
* @param {string} uid
* @return
*/
function onClient(player: alt.Player, uid: string) {
const invoker = getInvoker(player, uid);
if (!invoker) {
return;
}

invoker.invoke(player);
}

export function useInteractionLocal<K extends keyof ColshapeTypes>(
player: alt.Player,
uid: string,
type: K,
args: ColshapeTypes[K],
) {
const _type: keyof ColshapeTypes = type;
const _args: ColshapeTypes[K] = args;
const _uid = uid;
const onEnterCallbacks: Callback[] = [];
const onLeaveCallbacks: Callback[] = [];
const onCallbacks: Callback[] = [];

function getUid() {
return _uid;
}

function destroy() {
if (!player || !player.valid) {
return;
}

player.emit(Events.controllers.interactionLocal.destroy, _uid);
removeByUid(player.id, _uid);
}

function onEnter(cb: Callback) {
onEnterCallbacks.push(cb);
}

function onLeave(cb: Callback) {
onLeaveCallbacks.push(cb);
}

function on(cb: Callback) {
onCallbacks.push(cb);
}

function invokeEnter(player: alt.Player) {
for (let cb of onEnterCallbacks) {
cb(player, destroy);
}
}

function invokeLeave(player: alt.Player) {
for (let cb of onLeaveCallbacks) {
cb(player, destroy);
}
}

function invoke(player: alt.Player) {
for (let cb of onCallbacks) {
cb(player, destroy);
}
}

if (!invokers[player.id]) {
invokers[player.id] = [];
}

invokers[player.id].push({ uid, invokeEnter, invokeLeave, invoke, destroy });
player.emit(Events.controllers.interactionLocal.create, _uid, _type, _args);

return {
destroy,
getUid,
onEnter,
onLeave,
on,
};
}

alt.on('playerDisconnect', (player: alt.Player) => removeAll(player.id));
alt.onClient(Events.controllers.interactionLocal.on, onClient);
alt.onClient(Events.controllers.interactionLocal.onEnter, onClientEnter);
alt.onClient(Events.controllers.interactionLocal.onLeave, onClientLeave);
Loading

0 comments on commit 369f5e1

Please sign in to comment.