diff --git a/docs/api/server/document/document-account.md b/docs/api/server/document/document-account.md index e6f78fd72..bc3b087de 100644 --- a/docs/api/server/document/document-account.md +++ b/docs/api/server/document/document-account.md @@ -21,6 +21,17 @@ const someAccountData = someDatabaseFetchOrCreateFunction(); const document = Rebar.document.account.useAccountBinder(player).bind(someAccountData); ``` +## Checking Validity + +If you need to check if a player has a document bound to them, you can use the following method. + +```ts +if (!Rebar.document.account.useAccount(player).isValid()) { + // No account bound + return; +} +``` + ## Getting Data Data can be retrieved for the bound account like this. diff --git a/docs/api/server/document/document-character.md b/docs/api/server/document/document-character.md index c8240b361..364d04992 100644 --- a/docs/api/server/document/document-character.md +++ b/docs/api/server/document/document-character.md @@ -21,6 +21,17 @@ const someCharacterData = someDatabaseFetchOrCreateFunction(); const document = Rebar.document.character.useCharacterBinder(player).bind(someCharacterData); ``` +## Checking Validity + +If you need to check if a player has a document bound to them, you can use the following method. + +```ts +if (!Rebar.document.character.useCharacter(player).isValid()) { + // No character bound + return; +} +``` + ## Getting Data Data can be retrieved for the bound character like this. diff --git a/docs/api/server/document/document-vehicle.md b/docs/api/server/document/document-vehicle.md index 6ec6a14f3..de25f02d2 100644 --- a/docs/api/server/document/document-vehicle.md +++ b/docs/api/server/document/document-vehicle.md @@ -21,6 +21,17 @@ const someVehicleData = someDatabaseFetchOrCreateFunction(); const document = Rebar.document.vehicle.useVehicleBinder(someVehicle).bind(someVehicleData); ``` +## Checking Validity + +If you need to check if a vehicle has a document bound to them, you can use the following method. + +```ts +if (!Rebar.document.vehicle.useVehicle(player).isValid()) { + // No vehicle document bound + return; +} +``` + ## Getting Data Data can be retrieved for the bound character like this. diff --git a/docs/api/server/events/events.md b/docs/api/server/events/events.md new file mode 100644 index 000000000..eaeb0f17c --- /dev/null +++ b/docs/api/server/events/events.md @@ -0,0 +1,32 @@ +# Events + +These events are unique to the Rebar framework, and help provide information about when something happens. + +## Usage + +```ts +import { useRebar } from '@Server/index.js'; + +const Rebar = useRebar(); +const RebarEvents = Rebar.events.useEvents(); + +// Called when an account is bound to a player +RebarEvents.on('account-bound', (player, document) => { + console.log(document); +}); + +// Called when a character is bound to a player +RebarEvents.on('character-bound', (player, document) => { + console.log(document); +}); + +// Called when a vehicle document is bound to a vehicle +RebarEvents.on('vehicle-bound', (vehicle, document) => { + console.log(document); +}); + +// Called when a player sends a message +RebarEvents.on('message', (player, msg) => { + console.log(msg); +}); +``` diff --git a/docs/api/server/events/index.yml b/docs/api/server/events/index.yml new file mode 100644 index 000000000..ed2640a43 --- /dev/null +++ b/docs/api/server/events/index.yml @@ -0,0 +1,3 @@ +expanded: false +label: 'Events' +order: -1000 diff --git a/docs/api/server/player/player-status.md b/docs/api/server/player/player-status.md new file mode 100644 index 000000000..71fbe8453 --- /dev/null +++ b/docs/api/server/player/player-status.md @@ -0,0 +1,12 @@ +# Check + +Check if a player has an account bound, or character bound. + +```ts +import { useRebar } from '@Server/index.js'; + +const Rebar = useRebar(); + +Rebar.player.useStatus(somePlayer).hasCharacter(); // Returns true / false +Rebar.player.useStatus(somePlayer).hasAccount(); // Returns true / false +``` diff --git a/docs/changelog.md b/docs/changelog.md index 41d6576f9..d40ea1144 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,26 @@ order: -1000 # Changelog +## Version 5 + +### Code Changes + +- Added `isValid` to `character`, `account`, and `vehicle` documents to check if an entity has a bound document +- Added `useStatus` to `player` API pathway to check for `account` and `character` status +- Added `events` to the `Rebar` API + - Added on account bound + - Added on character bound + - Added on vehicle bound + - Added on message +- Fixed small bug with case-sensitive commands +- Fixed bug that allowed sending messages when a `character` was not bound + +### Docs Changes + +- Added `isValid` examples to `character`, `account`, and `vehicle`. +- Added `useStatus` to `player` section +- Added `events` section to Server API + ## Version 4 ### Code Changes diff --git a/package.json b/package.json index 1383da74d..8a266a530 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "stuyk", "type": "module", - "version": "4", + "version": "5", "scripts": { "clean": "shx rm -rf resources/core", "dev": "nodemon -V -x pnpm start", diff --git a/src/main/server/document/account.ts b/src/main/server/document/account.ts index b77d133be..1575d81b4 100644 --- a/src/main/server/document/account.ts +++ b/src/main/server/document/account.ts @@ -6,12 +6,29 @@ import { useDatabase } from '@Server/database/index.js'; import { CollectionNames, KeyChangeCallback } from './shared.js'; import { Character } from '@Shared/types/character.js'; import { usePermission } from '@Server/systems/permission.js'; +import { useRebar } from '../index.js'; +const Rebar = useRebar(); const sessionKey = 'document:account'; const callbacks: { [key: string]: Array } = {}; const db = useDatabase(); export function useAccount(player: alt.Player) { + /** + * Check if the player currently has an account bound to them + */ + function isValid() { + if (!player.hasMeta(sessionKey)) { + return false; + } + + if (!player.getMeta(sessionKey)) { + return false; + } + + return true; + } + /** * Return current player data and their associated account object. * @@ -220,7 +237,18 @@ export function useAccount(player: alt.Player) { setBanned, }; - return { addPermission, get, getCharacters, getField, permission, set, setBulk, setPassword, checkPassword }; + return { + addPermission, + get, + getCharacters, + getField, + isValid, + permission, + set, + setBulk, + setPassword, + checkPassword, + }; } export function useAccountBinder(player: alt.Player) { @@ -237,6 +265,7 @@ export function useAccountBinder(player: alt.Player) { } player.setMeta(sessionKey, document); + Rebar.events.useEvents().invoke('account-bound', player, document); return useAccount(player); } diff --git a/src/main/server/document/character.ts b/src/main/server/document/character.ts index 3dd887545..1d732a38d 100644 --- a/src/main/server/document/character.ts +++ b/src/main/server/document/character.ts @@ -6,12 +6,29 @@ import { CollectionNames, KeyChangeCallback } from './shared.js'; import { Vehicle } from 'main/shared/types/vehicle.js'; import { usePermission } from '@Server/systems/permission.js'; import { usePermissionGroup } from '@Server/systems/permissionGroup.js'; +import { useRebar } from '../index.js'; +const Rebar = useRebar(); const sessionKey = 'document:character'; const callbacks: { [key: string]: Array } = {}; const db = useDatabase(); export function useCharacter(player: alt.Player) { + /** + * Check if the player currently has a character bound to them + */ + function isValid() { + if (!player.hasMeta(sessionKey)) { + return false; + } + + if (!player.getMeta(sessionKey)) { + return false; + } + + return true; + } + /** * Return current player data and their associated character object. * @@ -270,7 +287,7 @@ export function useCharacter(player: alt.Player) { hasGroupPermission, }; - return { get, getField, getVehicles, permission, set, setBulk }; + return { get, getField, isValid, getVehicles, permission, set, setBulk }; } export function useCharacterBinder(player: alt.Player) { @@ -287,6 +304,7 @@ export function useCharacterBinder(player: alt.Player) { } player.setMeta(sessionKey, document); + Rebar.events.useEvents().invoke('character-bound', player, document); return useCharacter(player); } diff --git a/src/main/server/document/vehicle.ts b/src/main/server/document/vehicle.ts index 8d56d59cf..8fca9c63e 100644 --- a/src/main/server/document/vehicle.ts +++ b/src/main/server/document/vehicle.ts @@ -3,12 +3,29 @@ import { Vehicle } from '@Shared/types/vehicle.js'; import { KnownKeys } from '@Shared/utilityTypes/index.js'; import { useDatabase } from '@Server/database/index.js'; import { CollectionNames, KeyChangeCallback } from './shared.js'; +import { useRebar } from '../index.js'; +const Rebar = useRebar(); const sessionKey = 'document:vehicle'; const callbacks: { [key: string]: Array> } = {}; const db = useDatabase(); export function useVehicle(vehicle: alt.Vehicle) { + /** + * Check if the vehicle currently has a character bound to them + */ + function isValid() { + if (!vehicle.hasMeta(sessionKey)) { + return false; + } + + if (!vehicle.getMeta(sessionKey)) { + return false; + } + + return true; + } + /** * Return current vehicle data and their associated Vehicle object. * @@ -117,7 +134,7 @@ export function useVehicle(vehicle: alt.Vehicle) { }); } - return { get, getField, set, setBulk }; + return { get, getField, isValid, set, setBulk }; } export function useVehicleBinder(vehicle: alt.Vehicle) { @@ -134,6 +151,7 @@ export function useVehicleBinder(vehicle: alt.Vehicle) { } vehicle.setMeta(sessionKey, document); + Rebar.events.useEvents().invoke('vehicle-bound', vehicle, document); return useVehicle(vehicle); } diff --git a/src/main/server/events/index.ts b/src/main/server/events/index.ts new file mode 100644 index 000000000..95d089014 --- /dev/null +++ b/src/main/server/events/index.ts @@ -0,0 +1,39 @@ +import * as alt from 'alt-server'; +import { Account } from '@Shared/types/account.js'; +import { Character } from '@Shared/types/character.js'; +import { Vehicle } from '@Shared/types/vehicle.js'; + +type RebarEvents = { + 'account-bound': (player: alt.Player, document: Account) => void; + 'character-bound': (player: alt.Player, document: Character) => void; + 'vehicle-bound': (vehicle: alt.Vehicle, document: Vehicle) => void; + message: (player: alt.Player, message: string) => void; +}; + +type EventCallbacks = { [key in K]: RebarEvents[K][] }; + +const eventCallbacks: EventCallbacks = { + 'account-bound': [], + 'character-bound': [], + 'vehicle-bound': [], + message: [], +}; + +export function useEvents() { + function on(event: K, callback: RebarEvents[K]) { + eventCallbacks[event].push(callback); + } + + function invoke(event: K, ...args: Parameters) { + for (let cb of eventCallbacks[event]) { + // Normally I would not do this but I know this works, and TypeScript is being a jerk. + // @ts-ignore + cb(...args); + } + } + + return { + invoke, + on, + }; +} diff --git a/src/main/server/index.ts b/src/main/server/index.ts index 23019c519..5a93197da 100644 --- a/src/main/server/index.ts +++ b/src/main/server/index.ts @@ -1,13 +1,19 @@ import './startup.js'; + import { useApi } from './api/index.js'; + import { useConfig } from './config/index.js'; -import { useBlipGlobal } from './controllers/blip.js'; + import { useBlipLocal } from './controllers/blip.js'; +import { useBlipGlobal } from './controllers/blip.js'; +import { usePickupGlobal } from './controllers/pickups.js'; import { useInteraction } from './controllers/interaction.js'; -import { useMarkerGlobal, useMarkerLocal } from './controllers/markers.js'; import { useObjectGlobal, useObjectLocal } from './controllers/object.js'; +import { useMarkerGlobal, useMarkerLocal } from './controllers/markers.js'; import { useTextLabelGlobal, useTextLabelLocal } from './controllers/textlabel.js'; + import { useDatabase } from './database/index.js'; + import { CollectionNames } from './document/shared.js'; import { useAccount, @@ -21,25 +27,31 @@ import { useVehicleEvents, useVirtual, } from './document/index.js'; + +import { useEvents } from './events/index.js'; + +import { useWorldGetter } from './getters/world.js'; import { usePlayerGetter } from './getters/player.js'; import { usePlayersGetter } from './getters/players.js'; -import { useVehiclesGetter } from './getters/vehicles.js'; import { useVehicleGetter } from './getters/vehicle.js'; -import { useWorldGetter } from './getters/world.js'; -import { useAnimation } from './player/animation.js'; -import { usePlayerAppearance } from './player/appearance.js'; +import { useVehiclesGetter } from './getters/vehicles.js'; + import { useAudio } from './player/audio.js'; -import { useClothing } from './player/clothing.js'; +import { useWorld } from './player/world.js'; import { useNative } from './player/native.js'; import { useNotify } from './player/notify.js'; +import { useStatus } from './player/status.js'; import { useWebview } from './player/webview.js'; -import { useWorld } from './player/world.js'; +import { useClothing } from './player/clothing.js'; +import { useAnimation } from './player/animation.js'; +import { usePlayerAppearance } from './player/appearance.js'; + +import { useMessenger } from './systems/messenger.js'; import { usePermission } from './systems/permission.js'; import { usePermissionGroup } from './systems/permissionGroup.js'; -import { sha256, sha256Random } from './utility/hash.js'; + import { check, hash } from './utility/password.js'; -import { usePickupGlobal } from './controllers/pickups.js'; -import { useMessenger } from './systems/messenger.js'; +import { sha256, sha256Random } from './utility/hash.js'; export function useRebar() { return { @@ -84,6 +96,9 @@ export function useRebar() { useVirtual, }, }, + events: { + useEvents, + }, get: { usePlayerGetter, usePlayersGetter, @@ -93,6 +108,7 @@ export function useRebar() { }, player: { useAnimation, + useStatus, usePlayerAppearance, useAudio, useClothing, diff --git a/src/main/server/player/status.ts b/src/main/server/player/status.ts new file mode 100644 index 000000000..00a14641e --- /dev/null +++ b/src/main/server/player/status.ts @@ -0,0 +1,19 @@ +import * as alt from 'alt-server'; +import { useRebar } from '../index.js'; + +const Rebar = useRebar(); + +export function useStatus(player: alt.Player) { + function hasAccount() { + return Rebar.document.account.useAccount(player).isValid(); + } + + function hasCharacter() { + return Rebar.document.character.useCharacter(player).isValid(); + } + + return { + hasAccount, + hasCharacter, + }; +} diff --git a/src/main/server/systems/messenger.ts b/src/main/server/systems/messenger.ts index 66ecf90dd..801bae2df 100644 --- a/src/main/server/systems/messenger.ts +++ b/src/main/server/systems/messenger.ts @@ -43,12 +43,14 @@ let endCommandRegistrationTime = Date.now(); export function useMessenger() { function registerCommand(command: Command) { + command.name = command.name.replaceAll('/', ''); + command.name = command.name.toLowerCase(); + const index = commands.findIndex((x) => x.name === command.name); if (index >= 1) { throw new Error(`${command.name} is already a registered command.`); } - command.name = command.name.replaceAll('/', ''); commands.push(command); endCommandRegistrationTime = Date.now() + 5000; } @@ -76,6 +78,7 @@ export function useMessenger() { function invokeCommand(player: alt.Player, cmdName: string, ...args: any[]): boolean { cmdName = cmdName.replace('/', ''); + cmdName = cmdName.toLowerCase(); const index = commands.findIndex((x) => x.name === cmdName); if (index <= -1) { @@ -165,6 +168,10 @@ function processMessage(player: alt.Player, msg: string) { return; } + if (!Rebar.player.useStatus(player).hasCharacter()) { + return; + } + const messageSystem = useMessenger(); if (msg.charAt(0) !== '/') { msg = cleanMessage(msg); @@ -172,12 +179,13 @@ function processMessage(player: alt.Player, msg: string) { cb(player, msg); } + Rebar.events.useEvents().invoke('message', player, msg); return; } const args = msg.split(' '); const commandName = args.shift(); - messageSystem.commands.invoke(player, commandName.toLowerCase(), ...args); + messageSystem.commands.invoke(player, commandName, ...args); } alt.onClient(Events.systems.messenger.process, processMessage);