diff --git a/brz-core/client/thirdparties.service.ts b/brz-core/client/thirdparties.service.ts index 50b6469..d8d0f68 100644 --- a/brz-core/client/thirdparties.service.ts +++ b/brz-core/client/thirdparties.service.ts @@ -1,17 +1,42 @@ +declare const SETTINGS: any; + const adapters: { [key in string]: { - isEnabled: boolean; hasItem: (itemName: string) => boolean; notify: (message: string, type: "success" | "error") => void; + useItemHookName: string; + useItemHookHandler: (...params: any) => { + itemName: string; + itemType: string; + }; }; } = { qbCore: { - isEnabled: !!exports["qb-core"]?.GetCoreObject()?.Functions?.HasItem, hasItem: (itemName: string) => - !!exports["qb-core"].GetCoreObject().Functions.HasItem(itemName), + !!exports["qb-core"].GetCoreObject?.().Functions.HasItem(itemName), notify: (message: string, type: "success" | "error") => { - exports["qb-core"].GetCoreObject().Functions.Notify(message, type); + exports["qb-core"].GetCoreObject?.().Functions.Notify(message, type); }, + useItemHookName: "inventory:client:ItemBox", + useItemHookHandler: (itemData: any, type: "use" | string) => ({ + itemName: itemData.name, + itemType: type, + }), + }, + ox_inventory: { + hasItem: (itemName: string) => + exports["ox_inventory"].GetItemCount(itemName) > 0, + notify: (message: string, type: "success" | "error") => + TriggerEvent("ox_lib:notify", { + title: "Fishing", + description: message, + type, + }), + useItemHookName: "ox_inventory:usedItem", + useItemHookHandler: (item: string[], slotId: number, metadata: any) => ({ + itemName: item[0], + itemType: "use", + }), }, }; @@ -20,12 +45,17 @@ export const hasItem = (itemName: string) => getAdapter().hasItem(itemName); export const notify = (message: string, type: "success" | "error") => getAdapter().notify(message, type); +export const getUseItemHookName = (): string => getAdapter().useItemHookName; + +export const getUseItemHookHandler = () => getAdapter().useItemHookHandler; + const getAdapter = () => { - const enabledAdapterName = Object.keys(adapters).find( - (adapterName) => adapters[adapterName].isEnabled - ); + const enabledAdapterName = SETTINGS.INVENTORY_SYSTEM || "ox_inventory"; - if (!enabledAdapterName) throw new Error("No enabled adapter found"); + if (!adapters[enabledAdapterName]) + throw new Error( + `FATAL: ${enabledAdapterName} is not supported, please check the documentation for supported inventory systems` + ); return adapters[enabledAdapterName]; }; diff --git a/brz-core/server/thirdparties.service.ts b/brz-core/server/thirdparties.service.ts index 0eddb05..1072778 100644 --- a/brz-core/server/thirdparties.service.ts +++ b/brz-core/server/thirdparties.service.ts @@ -1,33 +1,17 @@ -type InventoryItem = { - weight: number; - unique: boolean; - name: string; - type: string; - description: string; - label: string; - combinable: boolean; - useable: boolean; - shouldClose: boolean; - image: string; -}; +import { Adapter } from "../types/thirdparties.types"; + +declare const SETTINGS: any; + +const qbCoreGetPlayer = () => + SETTINGS.INVENTORY_SYSTEM === "qbCore" && + exports["qb-core"]?.GetCoreObject?.()?.Functions?.GetPlayer; const adapters: { - [key in string]: { - isEnabled: boolean; - removeItem: (source: number, itemName: string) => boolean; - addItem: (source: number, itemName: string) => boolean; - getItem: (itemName: string) => InventoryItem; - notify: ( - source: number, - message: string, - type: "success" | "error" - ) => void; - }; + [key in string]: Adapter; } = { qbCore: { - isEnabled: !!exports["qb-core"]?.GetCoreObject()?.Functions?.RemoveItem, removeItem: (source: number, itemName: string) => { - const player = qbCoreGetPlayer(source); + const player = qbCoreGetPlayer()(source); if (!player) { console.error("Player not found ", source, itemName); @@ -38,7 +22,7 @@ const adapters: { return true; }, addItem: (source: number, itemName: string) => { - const player = qbCoreGetPlayer(source); + const player = qbCoreGetPlayer()(source); if (!player) { console.error("Player not found ", source, itemName); @@ -50,12 +34,12 @@ const adapters: { }, getItem: (itemName: string) => { const item = - exports["qb-core"]?.GetCoreObject()?.Shared?.Items?.[itemName]; + exports["qb-core"]?.GetCoreObject?.()?.Shared?.Items?.[itemName]; return item; }, notify: (source: number, message: string, type: "success" | "error") => { - const player = qbCoreGetPlayer(source); + const player = qbCoreGetPlayer()(source); if (!player) { console.error("Player not found"); @@ -71,11 +55,44 @@ const adapters: { return true; }, }, + ox_inventory: { + removeItem: (source: number, itemName: string) => { + exports["ox_inventory"].RemoveItem(source, itemName, 1); + return true; + }, + addItem: (source: number, itemName: string) => { + exports["ox_inventory"].AddItem(source, itemName, 1); + return true; + }, + getItem: (itemName: string) => { + const item = exports["ox_inventory"].Items(itemName); + + if (!item) return null; + + const { name, weight, label, stack, consume, client = null } = item; + + return { + weight, + unique: false, + name, + type: "item", + description: name, + label, + combinable: stack, + useable: consume, + shouldClose: false, + image: client?.image, + }; + }, + notify: (source: number, message: string, type: "success" | "error") => + TriggerClientEvent("ox_lib:notify", source, { + title: "Fishing", + description: message, + type, + }), + }, }; -const qbCoreGetPlayer = - exports["qb-core"]?.GetCoreObject()?.Functions?.GetPlayer; - export const addItem = (source: number, itemName: string) => getAdapter().addItem(source, itemName); @@ -91,11 +108,12 @@ export const notify = ( ) => getAdapter().notify(source, message, type); const getAdapter = () => { - const enabledAdapterName = Object.keys(adapters).find( - (adapterName) => adapters[adapterName].isEnabled - ); + const enabledAdapterName = SETTINGS.INVENTORY_SYSTEM || "ox_inventory"; - if (!enabledAdapterName) throw new Error("No enabled adapter found"); + if (!adapters[enabledAdapterName]) + throw new Error( + `FATAL: ${enabledAdapterName} is not supported, please check the documentation for supported inventory systems` + ); return adapters[enabledAdapterName]; }; diff --git a/brz-core/types/thirdparties.types.ts b/brz-core/types/thirdparties.types.ts new file mode 100644 index 0000000..f7ccde1 --- /dev/null +++ b/brz-core/types/thirdparties.types.ts @@ -0,0 +1,19 @@ +export type Adapter = { + removeItem: (source: number, itemName: string) => boolean; + addItem: (source: number, itemName: string) => boolean; + getItem: (itemName: string) => InventoryItem | null; + notify: (source: number, message: string, type: "success" | "error") => void; +}; + +type InventoryItem = { + weight: number; + unique: boolean; + name: string; + type: string; + description: string; + label: string; + combinable: boolean; + useable: boolean; + shouldClose: boolean; + image: string; +}; diff --git a/client/actions/baiting.ts b/client/actions/baiting.ts index 32b8eeb..4101a80 100644 --- a/client/actions/baiting.ts +++ b/client/actions/baiting.ts @@ -55,7 +55,6 @@ const startBaitingOpportunity = () => { baitingOpportunityTickInterval = setInterval(baitingOpportunityTick, 10); detectBaitCatchTick = setTick(async () => { - console.log("detectBaitCatchTick"); if (IsControlJustPressed(0, 46)) { SendNUIMessage({ action: "hide-baiting-tooltips", diff --git a/client/client.ts b/client/client.ts index dc4eff1..f03e2b0 100644 --- a/client/client.ts +++ b/client/client.ts @@ -2,6 +2,10 @@ import { Fish } from "@common/types"; import { requestStartFishing, startFishing } from "./fishing"; import { getState, setState } from "./state"; import { t } from "@config/locales"; +import { + getUseItemHookName, + getUseItemHookHandler, +} from "@core/thirdparties.service"; RegisterCommand( t("fish_command"), @@ -22,8 +26,10 @@ onNet("brz-fishing:startFishing", (fishId: keyof Fish) => startFishing(GetPlayerServerId(PlayerId()), fishId) ); -onNet("inventory:client:ItemBox", (itemData: any, type: "use" | string) => { - if (type === "use" && itemData.name === "fishingrod1") { +onNet(getUseItemHookName(), (...params: any) => { + const { itemName, itemType } = getUseItemHookHandler()(params); + + if (itemType === "use" && itemName === "fishingrod1") { changeFishingState(); } }); diff --git a/client/test/client.test.ts b/client/test/client.test.ts index b0624a3..7456c04 100644 --- a/client/test/client.test.ts +++ b/client/test/client.test.ts @@ -15,6 +15,7 @@ global.PlayerId = jest.fn(() => 1); import { requestStartFishing, startFishing } from "@/fishing"; import { getState, setState } from "@/state"; +import { getUseItemHookName } from "@core/thirdparties.service"; jest.mock("@config/locales", () => ({ t: jest.fn().mockImplementation((phase: string) => { @@ -33,6 +34,14 @@ jest.mock("../state", () => ({ setState: jest.fn(), })); +jest.mock("@core/thirdparties.service", () => ({ + getUseItemHookName: jest.fn().mockReturnValue("inventory:client:ItemBox"), + getUseItemHookHandler: jest.fn().mockReturnValue(() => ({ + itemName: "fishingrod1", + itemType: "use", + })), +})); + describe("client", () => { beforeAll(() => { require("../client"); @@ -78,7 +87,11 @@ describe("client", () => { (setState as jest.Mock).mockReset(); (requestStartFishing as jest.Mock).mockReset(); }); - it("should register an onNet event for inventory:client:ItemBox", () => { + it("should register an onNet event for the third party adapter", () => { + (getUseItemHookName as jest.Mock).mockReturnValue( + "inventory:client:ItemBox" + ); + expect(global.onNet).toBeCalledWith( "inventory:client:ItemBox", expect.any(Function) diff --git a/fxmanifest.lua b/fxmanifest.lua index 6f088b1..9369ef4 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -5,6 +5,11 @@ game 'gta5' shared_scripts { 'settings.js', + '@ox_lib/init.lua', +} + +ox_libs { + 'interface', } server_script 'dist/server/**/*.js' diff --git a/installation/QBCore/qb-inventory/html/images/commonbait.png b/installation/QBCore/qb-inventory/html/images/commonbait.png new file mode 100644 index 0000000..4514ade Binary files /dev/null and b/installation/QBCore/qb-inventory/html/images/commonbait.png differ diff --git a/installation/QBCore/qb-inventory/html/images/dolphin.png b/installation/QBCore/qb-inventory/html/images/dolphin.png new file mode 100644 index 0000000..d928e13 Binary files /dev/null and b/installation/QBCore/qb-inventory/html/images/dolphin.png differ diff --git a/installation/QBCore/qb-inventory/html/images/fish.png b/installation/QBCore/qb-inventory/html/images/fish.png new file mode 100644 index 0000000..5605a77 Binary files /dev/null and b/installation/QBCore/qb-inventory/html/images/fish.png differ diff --git a/installation/QBCore/qb-inventory/html/images/fishingrod.png b/installation/QBCore/qb-inventory/html/images/fishingrod.png new file mode 100644 index 0000000..8ced335 Binary files /dev/null and b/installation/QBCore/qb-inventory/html/images/fishingrod.png differ diff --git a/installation/QBCore/qb-inventory/html/images/hammershark.png b/installation/QBCore/qb-inventory/html/images/hammershark.png new file mode 100644 index 0000000..ab8d43a Binary files /dev/null and b/installation/QBCore/qb-inventory/html/images/hammershark.png differ diff --git a/installation/QBCore/qb-inventory/html/images/humpback.png b/installation/QBCore/qb-inventory/html/images/humpback.png new file mode 100644 index 0000000..93e3214 Binary files /dev/null and b/installation/QBCore/qb-inventory/html/images/humpback.png differ diff --git a/installation/QBCore/qb-inventory/html/images/killerwhale.png b/installation/QBCore/qb-inventory/html/images/killerwhale.png new file mode 100644 index 0000000..b6ee2ee Binary files /dev/null and b/installation/QBCore/qb-inventory/html/images/killerwhale.png differ diff --git a/installation/QBCore/qb-inventory/html/images/stingray.png b/installation/QBCore/qb-inventory/html/images/stingray.png new file mode 100644 index 0000000..3cdf90a Binary files /dev/null and b/installation/QBCore/qb-inventory/html/images/stingray.png differ diff --git a/installation/QBCore/qb-inventory/html/images/tigershark.png b/installation/QBCore/qb-inventory/html/images/tigershark.png new file mode 100644 index 0000000..e769977 Binary files /dev/null and b/installation/QBCore/qb-inventory/html/images/tigershark.png differ diff --git a/installation/ox-inventory/web/images/commonbait.png b/installation/ox-inventory/web/images/commonbait.png new file mode 100644 index 0000000..4514ade Binary files /dev/null and b/installation/ox-inventory/web/images/commonbait.png differ diff --git a/installation/ox-inventory/web/images/dolphin.png b/installation/ox-inventory/web/images/dolphin.png new file mode 100644 index 0000000..d928e13 Binary files /dev/null and b/installation/ox-inventory/web/images/dolphin.png differ diff --git a/installation/ox-inventory/web/images/fish.png b/installation/ox-inventory/web/images/fish.png new file mode 100644 index 0000000..5605a77 Binary files /dev/null and b/installation/ox-inventory/web/images/fish.png differ diff --git a/installation/ox-inventory/web/images/fishingrod.png b/installation/ox-inventory/web/images/fishingrod.png new file mode 100644 index 0000000..8ced335 Binary files /dev/null and b/installation/ox-inventory/web/images/fishingrod.png differ diff --git a/installation/ox-inventory/web/images/hammershark.png b/installation/ox-inventory/web/images/hammershark.png new file mode 100644 index 0000000..ab8d43a Binary files /dev/null and b/installation/ox-inventory/web/images/hammershark.png differ diff --git a/installation/ox-inventory/web/images/humpback.png b/installation/ox-inventory/web/images/humpback.png new file mode 100644 index 0000000..93e3214 Binary files /dev/null and b/installation/ox-inventory/web/images/humpback.png differ diff --git a/installation/ox-inventory/web/images/killerwhale.png b/installation/ox-inventory/web/images/killerwhale.png new file mode 100644 index 0000000..b6ee2ee Binary files /dev/null and b/installation/ox-inventory/web/images/killerwhale.png differ diff --git a/installation/ox-inventory/web/images/stingray.png b/installation/ox-inventory/web/images/stingray.png new file mode 100644 index 0000000..3cdf90a Binary files /dev/null and b/installation/ox-inventory/web/images/stingray.png differ diff --git a/installation/ox-inventory/web/images/tigershark.png b/installation/ox-inventory/web/images/tigershark.png new file mode 100644 index 0000000..e769977 Binary files /dev/null and b/installation/ox-inventory/web/images/tigershark.png differ diff --git a/nui/fishing.ts b/nui/fishing.ts index 376c324..6c1231d 100644 --- a/nui/fishing.ts +++ b/nui/fishing.ts @@ -71,7 +71,7 @@ const actionHandlers: { const { param, value } = event.data as EmitFishingSetParamAction; paramsHandlers[ param as keyof Omit - ](value); + ]?.(value); }, "fish-2d-position": (event: any) => { const { center, posX, posY } = event.data as EmitFish2DPositionAction; diff --git a/server/server.ts b/server/server.ts index 36f6465..d900863 100644 --- a/server/server.ts +++ b/server/server.ts @@ -33,6 +33,13 @@ export const processCatchFishEvent = (playerId: PlayerId) => { const itemInfo = getItem(fish.itemName); + if (!itemInfo) { + console.error( + `ERROR: Item ${fish.itemName} does not exist. Please be sure you added all fishing items to your inventory system.` + ); + return; + } + addItem(playerId, fish.itemName); notify( playerId, diff --git a/server/test/server.test.ts b/server/test/server.test.ts index e30ef44..75d3b7c 100644 --- a/server/test/server.test.ts +++ b/server/test/server.test.ts @@ -135,6 +135,22 @@ describe("brz-fishing server-side script", () => { "success" ); }); + + it("should log an error message when item is not found", () => { + const consoleError = jest.spyOn(global.console, "error"); + + (getItem as jest.Mock).mockReturnValueOnce(null); + + processRequestStartFishing(1); + + processCatchFishEvent(1); + + expect(consoleError).toHaveBeenCalledWith( + expect.stringMatching( + /ERROR: Item (.*?) does not exist. Please be sure you added all fishing items to your inventory system./ + ) + ); + }); }); describe("processUseBaitEvent", () => { diff --git a/settings.js b/settings.js index fad2b31..7a16054 100644 --- a/settings.js +++ b/settings.js @@ -1,4 +1,5 @@ const SETTINGS = { + INVENTORY_SYSTEM: "ox_inventory", // ox_inventory or qbCore DEFAULT_LANG: "en-us", MAXIMUM_LINE_TENSION: 100, LINE_TENSION_INCREASE_RATE: 5,