diff --git a/README.md b/README.md index 85d543d..d67885c 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,22 @@ # rebar-adminpanel > [!IMPORTANT] -> It's required that you go to your Rebar directory -> and set up some things first before using the rebar-adminpanel plugin +> Please read all installation steps to prevent unwanted behaviour -## Installation +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/S6S3171498) -1. Go to your Rebar `main` server's directory and then to `./webview/src/index.css` -2. Add following code at the end of `index.css` +## Installation -```css -.neon-button { - @apply w-full rounded-lg border-2 border-transparent px-4 py-3 text-left text-sm text-gray-200 hover:animate-pulse hover:border-red-500 hover:bg-opacity-75 hover:shadow-md; -} -``` +1. Go to your Rebar `main` server's directory. -3. Now clone this repository in to your Rebar `main` directory using git commands. +2. Now clone this repository in to your Rebar `main` directory using git commands. ```bash cd path/to/your/rebar-altv/ git clone https://github.com/programmernb-ctrl/rebar-adminpanel.git src/plugins/rebar-adminpanel ``` -4. Should your character don't have the admin group you'll need to customize the config in `./shared/config.ts` +3. If your character doesn't already have the admin group, you'll need to customize the config in `./shared/config.ts` ```typescript export const adminpanelConfig = { @@ -30,7 +24,7 @@ export const adminpanelConfig = { }; ``` -5. Start the server once by using one of the below commands. The plugin will load __automatically.__ +4. Start the server once by using one of the below commands. The plugin will load __automatically.__ ``` pnpm start @@ -57,7 +51,8 @@ cd path/to/your/rebar-altv/src/plugins/rebar-adminpanel git pull ``` -- If you forked this repo and wanna update it, simply search for `merge from upstream` on [Google](https://www.google.com) +- If you forked this repo and want to update it, simply search for `merge from upstream` on [Google](https://www.google. + com) ## Dependencies diff --git a/server/index.ts b/server/index.ts index a73231f..1e4560e 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,24 +1,47 @@ import * as alt from 'alt-server'; import { useRebar } from '@Server/index.js'; import { adminpanelConfig } from '@Plugins/rebar-adminpanel/shared/config.js'; -import { adminPanelEvents } from '@Plugins/rebar-adminpanel/shared/events.js'; +import { adminpanelEvents } from '@Plugins/rebar-adminpanel/shared/events.js'; const Rebar = useRebar(); +const keyBinding = Rebar.useKeybinder(); const getter = Rebar.get.usePlayersGetter(); -const Keybinder = Rebar.useKeybinder(); +const worldGetter = Rebar.get.useWorldGetter(); + +const syncedBinder = Rebar.systems.useStreamSyncedBinder(); +syncedBinder.syncCharacterKey('cash'); +syncedBinder.syncCharacterKey('bank'); if (adminpanelConfig.Settings.debug) { - alt.log('[rebar-adminpanel] Debug Mode is activated for the plugin.') + alt.logWarning( + '[rebar-adminpanel] Debug Mode is still activated. Consider disable it to prevent unwanted' + ' behaviour', + ); +} + +let keybinding = undefined; +async function showView(player: alt.Player) { + keybinding = keyBinding.on(adminpanelEvents.bindings.F4, () => { + adminpanelShow(player); + }); } +/** + * Checks if the player is a member of the 'admin' group, if true continue with showing the webview + * @param player to show the webview to | alt.Player + */ async function adminpanelShow(player: alt.Player) { - // useWebview(player).show('Adminpanel', 'page', true); + if (!player?.valid) { + return; + } + const character = Rebar.document.character.useCharacter(player); const isMember = character.groups.memberOf('admin'); + // const hasPermission = Rebar.permissions.usePermissions(player).hasPermission('adminpanel'); if (isMember || adminpanelConfig.Settings.adminMode) { const view = Rebar.player.useWebview(player); Rebar.player.useWorld(player).disableControls(); + Rebar.player.useAudio(player).playFrontendSound('Click_Special', 'WEB_NAVIGATION_SOUNDS_PHONE'); view.show('Adminpanel', 'page'); } else { return; @@ -28,21 +51,37 @@ async function adminpanelShow(player: alt.Player) { async function adminpanelHide(player: alt.Player) { const view = Rebar.player.useWebview(player); view.hide('Adminpanel'); + Rebar.player.useAudio(player).playFrontendSound('Click_Fail', 'WEB_NAVIGATION_SOUNDS_PHONE'); Rebar.player.useWorld(player).enableControls(); } -async function makeAdmin(player: alt.Player) { - const rPlayer = Rebar.usePlayer(player); +async function adminpanelHideUser(player: alt.Player) { + const view = Rebar.player.useWebview(player); - rPlayer.character.groups.add('admin'); - rPlayer.account.permissions.grant('admin'); + view.hide('Users'); + Rebar.player.useAudio(player).playFrontendSound('Click_Fail', 'WEB_NAVIGATION_SOUNDS_PHONE'); + Rebar.player.useWorld(player).enableControls(); +} + +async function getAdmin(player: alt.Player) { + const character = Rebar.document.character.useCharacter(player); + const hasGroup = character.groups.memberOf('admin'); + + if (!hasGroup) { + alt.log('[rebar-adminpanel] The admin group is now being added to your character!'); + await character.groups.add('admin'); + } else { + alt.logWarning('[rebar-adminpanel] No need to get it again... You already got the admin group.'); + return; + } } async function adminpanelShowAllUsers(player: alt.Player) { - // useWebview(player).show('Adminpanel', 'page', true); const playersOnline = getter.online(); const view = Rebar.player.useWebview(player); + Rebar.player.useWorld(player).disableControls(); + Rebar.player.useAudio(player).playFrontendSound('Click_Special', 'WEB_NAVIGATION_SOUNDS_PHONE'); view.show('Users', 'persistent'); @@ -51,58 +90,107 @@ async function adminpanelShowAllUsers(player: alt.Player) { name: p.name, discordId: p.discordID, })); - view.emit(adminPanelEvents.WebView.getUsers, playerDetails); + + view.emit(adminpanelEvents.webview.getUsers, playerDetails); } -async function adminpanelHideUser(player: alt.Player) { - const view = Rebar.player.useWebview(player); - view.hide('Users'); - Rebar.player.useWorld(player).enableControls(); +/** + * Gets the waypoint set by the player and then teleports them to the new position + * @param player the player to teleport to the marker + */ +async function adminpanelTpMarker(player: alt.Player) { + if (!player?.valid) return; + + const rPlayer = Rebar.usePlayer(player); + const waypoint = await Rebar.usePlayer(player).waypoint.get(); + + if (!waypoint) { + return; + } + + const interval = alt.setInterval(async () => { + rPlayer.native.invoke('setFocusPosAndVel', waypoint.x, waypoint.y, waypoint.z, 0.0, 0.0, 0.0); + + const groundZResponse = await rPlayer.native.invokeWithResult( + 'getGroundZFor3dCoord', + waypoint.x, + waypoint.y, + waypoint.z, + 1000, + false, + false, + ); + + if (groundZResponse[0] === true && typeof groundZResponse[1] === 'number') { + const groundZ = groundZResponse[1]; + + alt.clearInterval(interval); + + rPlayer.native.invoke('requestCollisionAtCoord', waypoint.x, waypoint.y, groundZ); + + const nextTick = alt.nextTick(async () => { + player.frozen = true; + alt.log(player.dimension); + rPlayer.native.invoke('clearFocus'); + player.pos = new alt.Vector3(waypoint.x, waypoint.y, groundZ); + player.frozen = false; + rPlayer.notify.showNotification('Teleported successfully'); + alt.clearNextTick(nextTick); + }); + } else { + alt.clearInterval(interval); + rPlayer.native.invoke('clearFocus'); + return; + } + }, 1000); } -alt.onRpc(adminPanelEvents.RPC.giveAdmin, async (player: alt.Player) => { +alt.onRpc(adminpanelEvents.rpc.giveAdmin, async (player: alt.Player) => { try { alt.log(`${player.name} called adminpanel:giveadmin rpc`); - await makeAdmin(player); + await getAdmin(player); } catch (error) { - alt.log(`${player.name} called adminpanel rpc but got an error ${error}`); + alt.log(`${player.name} called adminpanel:giveadmin rpc but with an error ${error}`); } }); -alt.onRpc(adminPanelEvents.RPC.toWaypoint, async (player: alt.Player, x: number, y: number, z: number) => { +alt.onRpc(adminpanelEvents.rpc.toWaypoint, async (player: alt.Player, x: number, y: number, z: number) => { + const isClear = await worldGetter.positionIsClear(new alt.Vector3(x, y, z), 'all'); + try { alt.log(`${player.name} called adminpanel:towaypoint rpc with coords: ${x}, ${y}, ${z}`); - if (player && player.valid) { + if (player && player.valid && isClear) { player.pos = new alt.Vector3(x, y, z); // Teleport the player - alt.log(`${player.name} has been teleported to coordinates`); } else { - alt.log(`Invalid player attempted to teleport: ${player.name}`); + alt.log(`Invalid player attempted to teleport: ${player.name} or new position is not clear.`); } - } catch (error) { - alt.log(`${player.name} encountered an error: ${error}`); + } catch (err) { + alt.log(`${player.name} encountered an error with adminpanel:towaypoint rpc: ${err}`); } }); -alt.onRpc(adminPanelEvents.RPC.showAllUsers, async (player: alt.Player) => { +alt.onRpc(adminpanelEvents.rpc.showAllUsers, async (player: alt.Player) => { await adminpanelShowAllUsers(player); }); -alt.onClient(adminPanelEvents.ToServer.closePanel, async (player: alt.Player) => { +alt.onClient(adminpanelEvents.toServer.closePanel, async (player: alt.Player) => { await adminpanelHide(player); }); -alt.onClient(adminPanelEvents.ToServer.closeUsers, async (player: alt.Player) => { +alt.onClient(adminpanelEvents.toServer.closeUsers, async (player: alt.Player) => { await adminpanelHideUser(player); await adminpanelShow(player); }); -alt.once('playerSpawn', async () => { - await showView(); +alt.onClient(adminpanelEvents.toServer.tpMarker, async (player: alt.Player) => { + await adminpanelTpMarker(player); }); -async function showView() { - Keybinder.on(adminPanelEvents.KeyCodes.f4, (player) => { - adminpanelShow(player); - }); -}; +alt.on('playerConnect', async (player: alt.Player) => { + await showView(player); +}); + +alt.on('playerDisconnect', async (player: alt.Player) => { + keyBinding.off(adminpanelEvents.bindings.F4, keybinding); +}); diff --git a/shared/config.ts b/shared/config.ts index 389aead..19e2488 100644 --- a/shared/config.ts +++ b/shared/config.ts @@ -3,4 +3,4 @@ export const adminpanelConfig = { debug: false, adminMode: false, } -}; +}; \ No newline at end of file diff --git a/shared/events.ts b/shared/events.ts index 574a481..a5936ed 100644 --- a/shared/events.ts +++ b/shared/events.ts @@ -1,21 +1,22 @@ -export const adminPanelEvents = { - ToServer: { +export const adminpanelEvents = { + toClient: {}, + toServer: { closePanel: 'adminpanel:close', - closeUsers: 'adminpanel:closeUsers', + closeUsers: 'adminpanel:close:users', + tpMarker: 'adminpanel:teleport:marker:hud', }, - WebView: { + webview: { closeAdminpanelCallback: 'adminpanel', closeUserpanelCallback: 'adminpanel:users', - getUsers: 'adminpanel:getUsers', - + getUsers: 'adminpanel:get:users', }, - RPC: { - showAllUsers: 'adminpanel:showAllUsers', - giveAdmin: 'adminpanel:giveadmin', + rpc: { + showAllUsers: 'adminpanel:show:all:users', + giveAdmin: 'adminpanel:admin', toWaypoint: 'adminpanel:towaypoint', }, - KeyCodes: { - escape: 27, - f4: 115, + bindings: { + ESC: 27, + F4: 115, } }; \ No newline at end of file diff --git a/webview/Adminpanel.vue b/webview/Adminpanel.vue index 5dab918..45d791b 100644 --- a/webview/Adminpanel.vue +++ b/webview/Adminpanel.vue @@ -1,46 +1,36 @@ - diff --git a/webview/Users.vue b/webview/Users.vue index af61be0..4ebb0d9 100644 --- a/webview/Users.vue +++ b/webview/Users.vue @@ -1,84 +1,46 @@ - diff --git a/webview/composables/useAdminPanel.ts b/webview/composables/useAdminPanel.ts new file mode 100644 index 0000000..ac0dc83 --- /dev/null +++ b/webview/composables/useAdminPanel.ts @@ -0,0 +1,42 @@ +import { ref } from 'vue'; +import { useEvents } from '@Composables/useEvents.js'; +import { adminpanelEvents } from '@Plugins/rebar-adminpanel/shared/events.js'; + +const events = useEvents(); + +export const useAdminPanel = () => { + + let coordinatesInput = ref(''); + + const showUsers = async () => { + await events.emitServerRpc(adminpanelEvents.rpc.showAllUsers); + }; + + const giveAdmin = async () => { + await events.emitServerRpc(adminpanelEvents.rpc.giveAdmin); + }; + + const closePanel = () => { + events.emitServer(adminpanelEvents.toServer.closePanel); + } + + const toWaypoint = async () => { + const coordsInput = coordinatesInput.value.trim(); + const coords = coordinatesInput.value.split(',').map(Number); + if (coords.length === 3 && coords.every((num) => !isNaN(num))) { + await events.emitServerRpc(adminpanelEvents.rpc.toWaypoint, ...coords); + } else if (coordsInput === "") { + events.emitServer(adminpanelEvents.toServer.tpMarker); + } else { + return; + } + } + + return { + coordinatesInput, + showUsers, + closePanel, + giveAdmin, + toWaypoint, + } +} \ No newline at end of file diff --git a/webview/composables/useUserSearch.ts b/webview/composables/useUserSearch.ts new file mode 100644 index 0000000..b689b94 --- /dev/null +++ b/webview/composables/useUserSearch.ts @@ -0,0 +1,56 @@ +import { ref, computed } from 'vue'; +import { useEvents } from '@Composables/useEvents.js'; +import { useSyncedMeta } from '@Composables/useSyncedMeta.js'; +import { adminpanelEvents } from '@Plugins/rebar-adminpanel/shared/events.js'; + +const events = useEvents(); +const syncedMeta = useSyncedMeta(); + +export function useUserSearch() { + + const characterData = syncedMeta.getCharacter(); + + const playerDetails = ref<{ id: string; name: string; discordId: number; }[]>([]); + const searchQuery = ref(''); + + const getPlayerDetails = (details: { id: string; name: string; discordId: number; }[]) => { + playerDetails.value = details; + } + + const filteredPlayers = computed(() => { + if (!searchQuery.value) return playerDetails.value; + const lowerCaseQuery = searchQuery.value.toLowerCase(); + return playerDetails.value + .map((player) => ({ + ...player, + highlighted: player.name.toLowerCase().includes(lowerCaseQuery) || player.id.toString().includes(lowerCaseQuery), + })) + .filter((player) => player.highlighted); + }); + + const searchResult = computed(() => { + if (!searchQuery.value) return ''; + const count = filteredPlayers.value.length; + return count > 0 ? `${count} user${count > 1 ? 's' : ''} found` : 'No users found'; + }); + + const searchResultClass = computed(() => { + if (!searchQuery.value) return ''; + return filteredPlayers.value.length > 0 ? 'bg-green-500' : 'bg-yellow-500'; + }) + + const closePanel = () => { + events.emitServer(adminpanelEvents.toServer.closeUsers); + }; + + + return { + closePanel, + characterData, + playerDetails, + searchQuery, + filteredPlayers, + searchResult, + searchResultClass, + } +} \ No newline at end of file