diff --git a/README.md b/README.md index 1571e7a..34f63c3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,92 @@ # wow.ets.modules -List of modules that have been built using ETS +This is a repo with modules built on top of [ETS Module](https://github.com/araxiaonline/wow-eluna-ts-module). + +In order to use these modules you will need to have: +* Node 18+ installed +* NPM installed +* ETS installed in the repo you intend to transpiles the modules in. +* AzerothCore Server with mod_eluna installed. + +If using AIO based modules then you will need to additionally install +[Rochet2 AIO](https://github.com/Rochet2/AIO) + +> [!TIP] +> **What is AIO?** +> AIO is libary that allows messaging from the core server and game client. This allows developers to build and deploy server side UI changes as if they were AddOns without requiring a user to install anything. Modules in this repo that have __.client__ and __.server__ are AIO based modules. + +## Getting started +Since there are multiple modules available there are multiple approaches you can take to managing and deploying them. You can find the complete current list of supported modules here: [Module Registry](modules/index.md) + +### Option 1 +Single Module Manual Copy: +1. You would clone this repo +2. cd into the module you want to use +3. copy/paste code into your personal ets project. +4. npm run build +5. copy generated lua directories into your server eluna scripts directory. +6. Run gm command or server command on your server .reload eluna + +### Option 2 +MonoRepo Style Manual Copy +1. clone repo +2. npm install +3. npm run build +4. copy functions.lib and any module dictories you want to use to your server. + +### Option 3 +Managed Deploy - +1. clone repo +2. npm install +3. delete any modules you do not want. +4. add server SSH information to ets.env +5. use following command to build: +```bash +npm run build && npx ets deploy -e prod +``` + +The distribution system is basic right now and a WIP, over the next few months there will be an "npm inspired" publish and install commands added to ets that will module management. + +## Module Requirements +Many time scripts require more than just code in order to perform a useful function. More often that not, these are database updates. + +Modules can have other requirements that are necessary to make a module work. These requirements will be commented in the top of the file. + +The following are standard comments you can expect to see + +* REQUIRES SQL - This module has a SQL file that needs to run before installation. +* REQUIRES PATCH FILE - This module has client dependencies that must be installed in order to function +* REQUIRES [MOD_NAME] - An azerothcore module is required to enable functionality required for this module to work. +* REQUIRES AIO - This module requires AIO to be installed in order to function. +* REQUIRES SHARED - This module requires common functionlity from the "classes" folder + +## SQL Files +SQL files will be placed at the root of the repository and matching the same folder structure of the modules. The folder will also mirror how azeroth core stores their sql changes splitting sql files into the respective database files. + +In example: + +A file that addes a new item to the database for achievement tokens would be found at +``` +SQL/gameplay/achievement-tokens/db_world/achievement-tokens.sql +``` + +>[!TIP] +> Azerothcore uses db_world, db_character_db_auth that relates to the databases in MySQL acore_world, acore_character, acore_auth, respectively. + +## Patch files +Patch files should be located in the same directory of the module that requires it. Additional instructions concerning the contents of the patch should be added to the README.md + +## Required Lua Functions +When ETS builds modules it will create 2 additional libarary files that need to be installed at the root of the Azerothcore Eluna Scripts folder. + +``` +common/ +-- functions.lua +-- lualib_bundle.lua +``` +> [!NOTE] +> default installation folder for eluna is lua_scripts typically on the root folder your azerothcore installation. + +## Contributing +We view this repository as a registry for ETS modules and encourage pull requests to the repo with new content. Our goal is to attempt to make it easy to distribute custom modules and build a large library of custom content and features. + +Please make sure to include good documentation in READMEs for functionality and details in PR. \ No newline at end of file diff --git a/modules/commands/set-xp-rate/index.md b/modules/commands/set-xp-rate/index.md index f2807e1..c28c843 100644 --- a/modules/commands/set-xp-rate/index.md +++ b/modules/commands/set-xp-rate/index.md @@ -13,3 +13,4 @@ Chat commands #xprate set [number] - sets the players xp growth rate ``` +[alt_text](https://www.youtube.com/watch?v=XSdfsm3qRIk) \ No newline at end of file diff --git a/modules/commands/set-xp-rate/set-xp-rate.ts b/modules/commands/set-xp-rate/set-xp-rate.ts index ccd7edf..9018df7 100644 --- a/modules/commands/set-xp-rate/set-xp-rate.ts +++ b/modules/commands/set-xp-rate/set-xp-rate.ts @@ -6,6 +6,7 @@ * Type: Command * Adds a command that allows players to set their own XP rate for their character up to 5x normal xp rate. * + * REQUIRES CLASS FILE stats.ts to be installed. */ /** @@ -20,7 +21,7 @@ const setXPcmd = "#xprate set"; const XP_RATE_SETTING = "xp_rate"; -import { PlayerStats } from "../classes/stats"; +import { PlayerStats } from "../../shared/stats"; let xpRateCache = new Map(); diff --git a/modules/creatures/.gitkeep b/modules/creatures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/modules/gameobjects/soulswapper/index.md b/modules/gameobjects/soulswapper/index.md new file mode 100644 index 0000000..396d07a --- /dev/null +++ b/modules/gameobjects/soulswapper/index.md @@ -0,0 +1,36 @@ +## SoulSwapper +This will allow players to send soulbound items that are rare quality or higher to their alts for a fee. + +This uses a GossipMenu and the in-game mail system to transfer items between characters. + +> [!Warning] +> There is currently a limitation of only being able to send things that are in your main backpack. So if you use an addon like bagnon it is the first couple rows. + +**REQUIRES SQL** + +## Configuration + +**ALLOWED_ITEM_CLASSES**: - This is the list of item classes that are allowed to be sent to other characters. [Link to Item Template](https://www.azerothcore.org/wiki/item_template) +- `ALLOWED_ITEM_CLASSES = [2, 4, 9]; // 2 = weapon, 4 = armor, 9 = recipe` + +**ALLOWED_ACCOUNT_CHARS**: - This is the number of characters an account can have. +- `ALLOWED_ACCOUNT_CHARS = 15;` + +**SOULSWAP_BASE_PRICE**: - Base price for sending an item; multipliers will be added on top. +- `SOULSWAP_BASE_PRICE = ToCopper(20);` + +**NO_DISCOUNT_LEVEL**: - The level at which the discount for sending items is no longer applied. +- `NO_DISCOUNT_LEVEL = 70;` + +**DISCOUNT_ADJ**: - Discount percentage applied per 10 levels. +- `DISCOUNT_ADJ = 3;` + +**INTERACTIVE_OBJECT**: - The ID of the object that will interact with the player to handle the soulswap. +- `INTERACTIVE_OBJECT = 750000;` + + + +### Demo + +[alt_text +(https://www.youtube.com/watch?v=rJ92hM93pYA) \ No newline at end of file diff --git a/modules/gameobjects/soulswapper/soulswapper.ts b/modules/gameobjects/soulswapper/soulswapper.ts new file mode 100644 index 0000000..5500c0e --- /dev/null +++ b/modules/gameobjects/soulswapper/soulswapper.ts @@ -0,0 +1,172 @@ +import { ToGold, ToCopper, GetPlayerTax } from "../../shared/money"; +import { AccountInfo } from "../../shared/account"; + +/** + * SoulSwapper GameObject/NPC + * This is a module that allows players to send soulbound items to alts on the same account. + * + * REQUIRES SQL + * REQUIRES CLASSES + */ + +/** + * This is the list of item classes that are allowed to be sent to other characters. + * @link https://www.azerothcore.org/wiki/item_template + */ +const ALLOWED_ITEM_CLASSES = [2,4,9]; // 2 = weapon, 4 = armor, 9 = recipe + +/** + * This is the number of characters an account can have. + */ +const ALLOWED_ACCOUNT_CHARS = 15; + +/** + * Base price for sending an item multipliers will be added on top + */ +const SOULSWAP_BASE_PRICE = ToCopper(20); + +/** + * The level the discount for sending items is no longer applied + */ +const NO_DISCOUNT_LEVEL = 70; + +/** + * Discount percentage applied per 10 levels + */ +const DISCOUNT_ADJ = 3; + +/** + * The id of the object that will interact with the player to handle the soulswap + */ +const INTERACTIVE_OBJECT = 750000; + + +const selectedItem: Record = {}; + +function getCost(guid: number, player: Player): number { + const itemGuid = GetItemGUID(guid); + const theItem = player.GetItemByGUID(itemGuid); + let discount = 1; + + const iLevelModifer = theItem.GetItemLevel() / 40; + + if(player.GetLevel() < NO_DISCOUNT_LEVEL) { + discount = (NO_DISCOUNT_LEVEL - player.GetLevel()) * DISCOUNT_ADJ; + } + + if(discount > 100) { + discount = 90; + } + + return SOULSWAP_BASE_PRICE * iLevelModifer * ((100 - discount) / 100); +} + +const GossipHello : gossip_event_on_hello = (event: number, player: Player, gameobject: EObject) => { + + player.GossipClearMenu(); + + let items = 0; + /** + * Backpack is 255, 23-38 + */ + for(let i=23; i <= 38; i++ ) { + let item = player.GetItemByPos(255, i); + + if(item != undefined) { + + const itemClass = item.GetClass(); + + if( item.IsSoulBound() && (ALLOWED_ITEM_CLASSES.includes(itemClass)) ) { + const quality = item.GetQuality(); + const quantity = item.GetCount(); + const cost = getCost(item.GetGUIDLow(), player); + + if(quality > 2 && quantity === 1) { + items += 1; + player.GossipMenuAddItem(1,`Item: ${item.GetItemLink()} (${ToGold(cost)}g)`,1,item.GetGUIDLow(), undefined, undefined); + } + } + + } + } + + if(items === 0) { + player.SendNotification("You have no soulbound items in your backback to send to your other characters."); + } + + player.GossipMenuAddItem(1,`Stop using the device`,1,50500); + player.GossipSendMenu(1000, gameobject, 10000); + + return true; +} + +const GossipSelect: gossip_event_on_select = (event: number, player: Player, creature: any, selection, action, code, menuId) => { + + const account = new AccountInfo(player.GetAccountId()); + const characters = account.GetCharacters(); + + // 50500 is an item that is not in the game and can safely be used to exit the menu. + if(action === 50500) { + player.GossipClearMenu(); + player.GossipComplete(); + return true; + } + + // if the action is greater than the number + if(action > ALLOWED_ACCOUNT_CHARS) { + for(let numC = 0; numC < characters.length; numC++) { + let name = characters[numC].name; + + if(name != player.GetName()) { + const cost = getCost(action, player); + player.GossipMenuAddItem(2, `Send to: ${name}`, 2, numC+1, undefined, `Are you sure you will to rebind this item? The item will be mailed to ${name}?`, cost); + } + } + + selectedItem[player.GetName()] = action; + + player.GossipSendMenu(1000, creature, 10000); + } + + // Action to select player to receive the item. + if(action <= ALLOWED_ACCOUNT_CHARS) { + + let itemToChange = selectedItem[player.GetName()]; + let itemGuid = GetItemGUID(itemToChange); + + const PlayerItem = player.GetItemByGUID(itemGuid); + print(`Item Info: ${PlayerItem.GetOwner().GetName()} owns ${PlayerItem.GetName()}`); + + let newItemGuid = SendMail( + `Item Rebound ${PlayerItem.GetName()}`, + `Soulbinder has sent you a gift ${PlayerItem.GetName()}`, + characters[action-1].guid, + player.GetGUIDLow(), + MailStationery.MAIL_STATIONERY_DEFAULT, + 0, + 0, + 0, + PlayerItem.GetEntry(), + 1 + ); + + player.RemoveItem(PlayerItem, PlayerItem.GetEntry(), 1); + + player.GossipClearMenu(); + player.GossipComplete(); + } + + return true; +} + +RegisterGameObjectGossipEvent( + INTERACTIVE_OBJECT, + GossipEvents.GOSSIP_EVENT_ON_HELLO, + (...args) => GossipHello(...args) +); + +RegisterGameObjectGossipEvent( + INTERACTIVE_OBJECT, + GossipEvents.GOSSIP_EVENT_ON_SELECT, + (...args) => GossipSelect(...args) +); diff --git a/modules/gameplay/worgoblin/worgoblin.ts b/modules/gameplay/worgoblin/worgoblin.ts index 98bd05b..0f0c72e 100644 --- a/modules/gameplay/worgoblin/worgoblin.ts +++ b/modules/gameplay/worgoblin/worgoblin.ts @@ -1,10 +1,12 @@ -import { ToCopper } from "../classes/money"; +import { ToCopper } from "../../shared/money"; /** * This will change the starting zone for Worgren and Goblins * and provides additional racials for Worgren because they are missing some. * - * REQUIRES WORGOBLIN MOD INSTALLATION AND PATCHES TO BE APPLIED + * REQUIRES MOD_WORGOBLIN INSTALLATION + * REQUIRES PATCH FILES + * * SEE README FOR INSTALLATION INSTRUCTIONS */ diff --git a/modules/items/.gitkeep b/modules/items/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/modules/classes/account.ts b/modules/shared/account.ts similarity index 100% rename from modules/classes/account.ts rename to modules/shared/account.ts diff --git a/modules/classes/money.ts b/modules/shared/money.ts similarity index 100% rename from modules/classes/money.ts rename to modules/shared/money.ts diff --git a/modules/classes/stats.ts b/modules/shared/stats.ts similarity index 100% rename from modules/classes/stats.ts rename to modules/shared/stats.ts diff --git a/modules/classes/triggers.ts b/modules/shared/triggers.ts similarity index 100% rename from modules/classes/triggers.ts rename to modules/shared/triggers.ts diff --git a/modules/classes/ui-utils.ts b/modules/shared/ui-utils.ts similarity index 100% rename from modules/classes/ui-utils.ts rename to modules/shared/ui-utils.ts diff --git a/modules/special/gambler/gambler.server.ts b/modules/special/gambler/gambler.server.ts index de1f26b..e51384f 100644 --- a/modules/special/gambler/gambler.server.ts +++ b/modules/special/gambler/gambler.server.ts @@ -5,7 +5,7 @@ let aio: AIO = {}; * Gambler - Slot Machine * This is the server side code used to add gambling games to the server. * - * REQUIRES DB acore_world.item_template to be updated for custom tokens + * REQUIRES SQL */ /** diff --git a/sql/modules/gameobjects/soulswapper/db_world/soulswapper.sql b/sql/modules/gameobjects/soulswapper/db_world/soulswapper.sql new file mode 100644 index 0000000..2e7663d --- /dev/null +++ b/sql/modules/gameobjects/soulswapper/db_world/soulswapper.sql @@ -0,0 +1,3 @@ +DELETE FROM `gameobject_template` WHERE (`entry` = 750000); +INSERT INTO `gameobject_template` (`entry`, `type`, `displayId`, `name`, `IconName`, `castBarCaption`, `unk1`, `size`, `Data0`, `Data1`, `Data2`, `Data3`, `Data4`, `Data5`, `Data6`, `Data7`, `Data8`, `Data9`, `Data10`, `Data11`, `Data12`, `Data13`, `Data14`, `Data15`, `Data16`, `Data17`, `Data18`, `Data19`, `Data20`, `Data21`, `Data22`, `Data23`, `AIName`, `ScriptName`, `VerifiedBuild`) VALUES +(750000, 1, 6778, 'Soul Swapper', '', '', '', 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0); diff --git a/sql/modules/gameplay/db_world/achievement-tokens.sql b/sql/modules/gameplay/achievement-tokens/db_world/achievement-tokens.sql similarity index 100% rename from sql/modules/gameplay/db_world/achievement-tokens.sql rename to sql/modules/gameplay/achievement-tokens/db_world/achievement-tokens.sql diff --git a/sql/modules/classes/db_character/stats.sql b/sql/modules/shared/db_character/stats.sql similarity index 100% rename from sql/modules/classes/db_character/stats.sql rename to sql/modules/shared/db_character/stats.sql diff --git a/sql/modules/classes/db_character/triggers.sql b/sql/modules/shared/db_character/triggers.sql similarity index 100% rename from sql/modules/classes/db_character/triggers.sql rename to sql/modules/shared/db_character/triggers.sql