Added changes for AIO Advanced topic

This commit is contained in:
2024-03-14 23:45:04 -04:00
parent 3f62fe390c
commit b8a761a448
4 changed files with 402 additions and 52 deletions

View File

@@ -4,7 +4,8 @@
- [Configuration](./ets/Configuration.md)
- [Building Modules](./ets/Modules.md)
- [VSCode Integration](./ets/VSCodeIntegration.md)
- [Advanced Topics](./ets/AdvancedTopics.md)
- Advanced Topics
- [Building Custom UIs](./ets/BuildingCustomUIs.md)
- Eluna Classes
- [Achievement](./classes/Achievement.md)
- [Aura](./classes/Aura.md)

View File

@@ -1,51 +0,0 @@
# Advanced Topics
For developers looking to dive deeper into the capabilities of ETS-Cli, this section explores advanced features and integrations, including using Rochet2's AIO with WoW API for creating complex server-served addons.
## Using Rochet2's AIO with WoW API
Rochet2's AIO system is a powerful tool for creating intricate addons that can be served directly from the server to the client, enhancing gameplay with new features and interactions.
### Installation and Setup
Before incorporating AIO into your modules, you must first install and configure it on your server. Visit the [AIO GitHub page](https://github.com/Rochet2/AIO) for detailed installation instructions.
### Example: Creating a Gambling Game
This example demonstrates how to use AIO alongside the WoW API to build AIO UI elements into the client without requiring user patching or installing plugins.
**Server-side Script (aio.server.ts):**
```typescript
/** @ts-expect-error */
let aio: AIO = {};
const SLOT_GAME_OBJECT = 750001;
const ShowGambler: player_event_on_command = (event: number, player: Player, command: string): boolean => {
if (command === 'gamble') {
aio.Handle(player, 'GamblerMain', 'ShowFrame');
return false;
}
return true;
};
```
**Client-side Script (aio.client.ts)**
```typescript
/** @ts-expect-error */
let aio: AIO = {};
if (!aio.AddAddon()) {
const gamblerHandlers = aio.AddHandlers('GamblerMain', {});
function ShowSlots(player: Player) {
const GamblerMainFrame = CreateFrame("Frame", "GamblerMainFrame", UIParent, "UIPanelDialogTemplate");
GamblerMainFrame.SetSize(512, 324);
GamblerMainFrame.SetMovable(false);
GamblerMainFrame.SetPoint("CENTER");
GamblerMainFrame.EnableMouse(true);
GamblerMainFrame.EnableKeyboard(true);
GamblerMainFrame.Hide();
}
```

View File

@@ -0,0 +1,84 @@
# Building Custom UIs
For developers looking to dive deeper into the capabilities of ETS-Cli, this section explores advanced features and integrations, including using Rochet2's AIO with WoW API for creating complex server-served addons.
## Using Rochet2's AIO with WoW API
Rochet2's AIO system is a powerful tool for creating intricate addons that can be served directly from the server to the client, enhancing gameplay with new features and interactions.
### Installation and Setup
Before incorporating AIO into your modules, you must first install and configure it on your server. Visit the [AIO GitHub page](https://github.com/Rochet2/AIO) for detailed installation instructions.
### Building AIO Modules
Designing Addons that will be served through AIO works the same as developing addons for World of Warcraft retail. You leverate the [WoWAPI](https://wowpedia.fandom.com/wiki/World_of_Warcraft_API) to build your UI. This site will have all methods that are currently available up to 10+. This means you will need to review commands to make sure they were available for the client you are supporting. For instance if you are building for AzerothCore you will need to make sure the API call is available for 3.3.5.
There are some key differences when building AIO modules vs AddOns
- Everything **MUST** be scripted you can not use XML
- There is not a TOC file to declare dependencies
- The Addons are shipped to the same global namespace so you need to be mindful of how you set global variables.
- If you do not use Eluna Typescript, then you are also limited to not being able to use require as it is not an available command.
### Using WoW API in Modules
ETS comes bundle with declarations for many of the WoWAPI calls that are available in 3.3.5a. (If a method is not present please submit an issue and it will get added). This enables easier IDE support for creating custom UI's faster. If you are a GitHub Co-pilot user you will find code completion very helpful as you build.
You can see a full example below of WoWAPI calls, here is a really basic example:
```Typescript
if(!aio.AddAddon()) {
const myHandlers = aio.AddHandlers('AIOTest', {});
const MainFrame = CreateFrame("Frame", "MainFrame", UIParent, "UIPanelDialogTemplate");
let frame = MainFrame;
frame.SetSize(800,600);
frame.SetMovable(true);
frame.RegisterForDrag("LeftButton");
frame.SetPoint("CENTER", 0, 20);
frame.EnableMouse(true);
frame.Hide();
frame.SetScript("OnDragStart", frame.StartMoving);
frame.SetScript("OnHide", frame.StopMovingOrSizing);
frame.SetScript("OnDragStop", frame.StopMovingOrSizing);
frame.show();
}
```
### Project Structure
Naming convention, due to the nature of how the TypeScriptToLua plugin transpiles code with the plugin you **MUST** identify your client file. module.```client```.ts The client portion is required or your transpile will not work correctly.
**Basic structure**
```file
module/
├── module.client.ts
├── module.server.ts
```
**Advanced Structure with shared common and local libraries**
```file
common/
├── account.ts
└── ui.utils.ts
module/
├── module.client.ts
├── module.server.ts
└── libs/
└── module.functions.ts
```
In order to use this functionality you use typical imports. **This is only referencing client files, server files imports can be used however, as eluna supports "require" method call where the WoW Client does not.
```typescript
// ---- File: module.client.ts
// common import
import { Colorize } from '../common/ui.utils.ts';
// local import
import {MyFunction} from './libs/module.functions.ts';
```
!> **Time** There is a known issue with multi-layered dependency resolution and the issue is posted. This means only 1 level of dependencies in client file is allowed.
Review the full example provided to see how the server and client code communicate using ```AIO.AddHandlers``` and the ```AIO.Handle``` methods.
[Slot Machine Full Example](../examples/aio.slotmachine.md)

View File

@@ -0,0 +1,316 @@
## Slot Machine Example
This example demonstrates how to use AIO alongside the WoW API to build AIO UI elements into the client without requiring user patching or installing plugins.
**Server-side Script (aio.server.ts):**
```typescript
/** @ts-expect-error */
let aio: AIO = {};
/**
* Gambler - Slot Machine
* This is the server side code used to add gambling games to the server.
*/
/**
* Game OBject that will start the slot machine up
*/
const SLOT_GAME_OBJECT = 750001;
const ShowGambler: player_event_on_command = (event: number,player: Player, command: string): boolean => {
if(command == 'gamble') {
aio.Handle(player, 'GamblerMain', 'ShowFrame');
return false;
}
return true;
};
/**
* @noSelf
*/
function PayForSpin(this:void, player: Player, cost: number): void {
const money = player.GetCoinage();
if(money >= cost) {
player.ModifyMoney(cost * -1);
aio.Handle(player, 'GamblerMain', 'StartSpin');
} else {
player.SendNotification("You don't have enough money to spin the slots!");
player.PlayDirectSound(8959, player);
}
}
function AwardSlotWin(this:void, player: Player, gold: number, tokens: number): void {
player.ModifyMoney(gold*10000);
if(tokens > 0) {
player.AddItem(910001, tokens);
}
if(tokens > 75) {
player.SendChatMessageToPlayer(ChatMsg.CHAT_MSG_GUILD, 0, `|cff1eff00I HIT THE JACKPOT! I won ${gold} gold and ${tokens} tokens!`, player);
} else {
if(tokens > 0) {
player.SendChatMessageToPlayer(ChatMsg.CHAT_MSG_GUILD, 0, `|cff1eff00I won ${gold} gold and ${tokens} tokens!`, player);
} else {
player.SendChatMessageToPlayer(ChatMsg.CHAT_MSG_GUILD, 0, `|cff1eff00I won ${gold} gold`, player);
}
}
}
const SendSlotStart: gameobject_event_on_use = (event: number, gameobject: GameObject, player: Player): boolean => {
aio.Handle(player, 'GamblerMain', 'ShowFrame');
return true;
}
const gamblerHandlers = aio.AddHandlers('GamblerMain', {
PayForSpin,
AwardSlotWin
});
RegisterPlayerEvent(
PlayerEvents.PLAYER_EVENT_ON_COMMAND,
(...args) => ShowGambler(...args)
);
RegisterGameObjectEvent(SLOT_GAME_OBJECT, GameObjectEvents.GAMEOBJECT_EVENT_ON_USE, (...args) => SendSlotStart(...args));
```
**Client-side Script (aio.client.ts)**
```typescript
/** @ts-expect-error */
let aio: AIO = {};
if(!aio.AddAddon()) {
const gamblerHandlers = aio.AddHandlers('GamblerMain', {});
const classImages = [
"Interface/Gambler/druid",
"Interface/Gambler/deathknight",
"Interface/Gambler/hunter",
"Interface/Gambler/mage",
"Interface/Gambler/paladin",
"Interface/Gambler/priest",
"Interface/Gambler/rogue",
"Interface/Gambler/shaman",
"Interface/Gambler/warlock",
"Interface/Gambler/warrior",
];
let slotSpin = [];
let multiplier = 1;
// this function will randomly select a class image from the array above
function getRandomClassImage() {
const spinIndex = Math.floor(Math.random() * classImages.length);
slotSpin.push(spinIndex);
return classImages[spinIndex];
}
// reset the spin
function resetSpin () {
slotSpin = [];
}
function determineWin(): number {
let win = 0;
let gold = 0;
let tokens = 0;
// Jackpot is all 3 slots as deathknight arthas
if(slotSpin[0] == 1 && slotSpin[1] == 1 && slotSpin[2] == 1) {
if(multiplier == 3) {
tokens = 100;
}
gold = multiplier * 2000;
win = 2;
}
if(slotSpin[0] == slotSpin[1] && slotSpin[1] == slotSpin[2]) {
if(multiplier == 3) {
tokens = 50;
}
gold = multiplier * 500;
win = 1;
}
// Deathknights are considered wild cards
if(
(slotSpin[0] == slotSpin[1] && slotSpin[2] === 1) ||
(slotSpin[0] == slotSpin[2] && slotSpin[1] === 1) ||
(slotSpin[1] == slotSpin[2] && slotSpin[0] === 1) ||
(slotSpin[0] == 1 && slotSpin[1] === 1) ||
(slotSpin[0] == 1 && slotSpin[2] === 1) ||
(slotSpin[1] == 1 && slotSpin[2] === 1)
) {
if(multiplier == 3) {
tokens = 3;
}
gold = multiplier * 300;
win = 1;
}
// handle two of the same class in a row
if((slotSpin[0] == slotSpin[1]) && win == 0) {
gold = multiplier * 150;
win = 1;
if(slotSpin[1] == 1) {
if(multiplier == 3) {
tokens = 3;
}
gold = multiplier * 150;
win = 1;
}
}
// Return money on any lich king wild
if((slotSpin[0] == 1 || slotSpin[1] == 1 || slotSpin[2] == 1) && win == 0) {
if(multiplier == 3) {
tokens = 0;
gold = 100;
} else {
tokens = 0;
gold = 20;
}
win = 1;
}
if(win > 0) {
PlaySoundFile("Sound\\Interface\\LootCoinLarge.wav", "Master");
aio.Handle("GamblerMain", "AwardSlotWin", gold, tokens);
}
return win;
}
function SpinSlots(SlotFrame: WoWAPI.Frame, Slot: WoWAPI.Texture[]) {
let timer = 1;
let counter = 1;
PlaySoundFile("Sound\\Doodad\\GnomeMachine02StandLoop.wav", "Master");
SlotFrame.SetScript("OnUpdate", (frame, elapsed) => {
timer = timer + elapsed;
if(timer > 0.20) {
counter = counter + 1;
resetSpin();
timer = 0;
Slot[0].SetTexture(getRandomClassImage());
Slot[1].SetTexture(getRandomClassImage());
Slot[2].SetTexture(getRandomClassImage());
if(counter > 22) {
frame.SetScript("OnUpdate", null);
determineWin();
}
}
});
}
function ShowSlots(player: Player) {
const GamblerMainFrame = CreateFrame("Frame", "GamblerMainFrame", UIParent, "UIPanelDialogTemplate");
GamblerMainFrame.SetSize(512,324);
GamblerMainFrame.SetMovable(false);
GamblerMainFrame.SetPoint("CENTER");
GamblerMainFrame.EnableMouse(true);
GamblerMainFrame.EnableKeyboard(true);
GamblerMainFrame.Hide();
const Title = GamblerMainFrame.CreateFontString("TitleFrame", "OVERLAY", "GameFontHighlight");
Title.SetPoint("TOPLEFT", 15, -10);
Title.SetText("Heros Slots");
Title.SetFont("Fonts\\FRIZQT__.TTF", 10);
// Slots Display Window
const Slots = CreateFrame("Frame", "SlotsFrame", GamblerMainFrame);
Slots.SetSize(420,160);
Slots.SetPoint("CENTER", 0, 25);
Slots.SetFrameLevel(1);
Slots.SetBackdrop({
bgFile: "Interface/DialogFrame/UI-DialogBox-Background",
edgeFile: "Interface/DialogFrame/UI-DialogBox-Border",
tile: true,
tileSize: 32,
edgeSize: 32,
insets: {
left: 11,
right: 12,
top: 12,
bottom: 11
}
});
// Slot Columns 1 - 3
const Slot1 = Slots.CreateTexture("Slot1Texture", null, Slots);
Slot1.SetSize(128,128);
Slot1.SetAlpha(0.85);
Slot1.SetPoint("TOPLEFT", 13, -16);
Slot1.SetTexture(getRandomClassImage());
let [ Slot1Point, Slot1Region, Slot1RelPoint, x1offset, y1offset ] = Slot1.GetPoint();
const Slot2 = Slots.CreateTexture("Slot2Texture", null, Slots);
Slot2.SetSize(128,128);
Slot2.SetAlpha(0.85);
Slot2.SetPoint("TOPLEFT", Slot1Region, Slot1RelPoint, x1offset + 128 + 5, y1offset);
Slot2.SetTexture(getRandomClassImage());
let [ Slot2Point, Slot2Region, Slot2RelPoint, x2offset, y2offset ] = Slot2.GetPoint();
const Slot3 = Slots.CreateTexture("Slot3Texture", null, Slots);
Slot3.SetSize(128,128);
Slot3.SetAlpha(0.85);
Slot3.SetPoint("TOPLEFT", Slot2Region, Slot2RelPoint, x2offset + 128 + 5, y2offset);
Slot3.SetTexture(getRandomClassImage());
// Low bet button.
const SpinButton = CreateFrame("Button", "SpinButtonLow", GamblerMainFrame, "UIPanelButtonTemplate");
SpinButton.SetSize(128,32);
SpinButton.SetPoint("CENTER", -80, -80);
SpinButton.SetText("Bet 20g Spin");
SpinButton.SetFrameLevel(2);
SpinButton.SetScript("OnClick", (frame, mouse, button) => {
resetSpin();
multiplier = 1;
aio.Handle("GamblerMain", "PayForSpin", 20*10000);
});
const SpinButtonHigh = CreateFrame("Button", "SpinButtonHigh", GamblerMainFrame, "UIPanelButtonTemplate");
SpinButtonHigh.SetSize(128,32);
SpinButtonHigh.SetPoint("CENTER", 80, -80);
SpinButtonHigh.SetText("Bet 100g Spin");
SpinButtonHigh.SetFrameLevel(2);
SpinButtonHigh.SetScript("OnClick", (frame, mouse, button) => {
resetSpin();
multiplier = 3;
aio.Handle("GamblerMain", "PayForSpin", 100*10000);
});
gamblerHandlers.StartSpin = (player: Player) => {
SpinSlots(Slots, [Slot1, Slot2, Slot3]);
}
GamblerMainFrame.Show();
return GamblerMainFrame;
}
gamblerHandlers.ShowFrame = (player: Player) => {
ShowSlots(player);
}
}
```
1. This client script create a new frame and displays 3 images randomly chosen.
2. On bet, money is removed from the player and a 24 frame animation is started changing the images out.
3. At the same time a doodad sound effect that lasts the same amount of time as the 24 frames is played.
4. Upon completion of the frame spin determinations of wins is made and money rewarded.