Add all modules

This commit is contained in:
Ben
2024-01-12 15:59:04 -05:00
commit 663503fe7f
39 changed files with 6329 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
ets.env
node_modules
dist
.vscode
wow-wotlk-declarations

View File

@@ -0,0 +1,3 @@
DELETE FROM `gameobject_template` WHERE (`entry` = 750001);
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
(750001, 1, 2373, 'Slot Machine', 'Interact', '', '', 0.6, 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);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
-- TABLE FOR PLAYER STATS AND SETTINGS
CREATE TABLE `acore_characters`.`player_stats` (
`id` int NOT NULL, -- GUID of player
`name` varchar(150) COLLATE utf8mb4_general_ci NOT NULL,
`value` int DEFAULT '0',
`updated` int DEFAULT NULL,
PRIMARY KEY (`id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

View File

@@ -0,0 +1,13 @@
-- Token Chest
DELETE FROM `gameobject_template` WHERE (`entry` = 110000);
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
(110000, 3, 259, 'Token Chest', '', '', '', 1, 57, 110000, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, '', '', 0);
DELETE FROM `gameobject_template_addon` WHERE (`entry` = 110000);
INSERT INTO `gameobject_template_addon` (`entry`, `faction`, `flags`, `mingold`, `maxgold`, `artkit0`, `artkit1`, `artkit2`, `artkit3`) VALUES
(110000, 34, 0, 0, 0, 0, 0, 0, 0);
DELETE FROM `gameobject_loot_template` WHERE (`Entry` = 110000);
INSERT INTO `gameobject_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES
(110000, 910001, 0, 100, 0, 1, 0, 1, 1, 'Token Chest - Araxia Token');

View File

@@ -0,0 +1,5 @@
-- Item Araxia Token
DELETE FROM `item_template` WHERE (`entry` = 910001);
INSERT INTO `item_template` (`entry`, `class`, `subclass`, `SoundOverrideSubclass`, `name`, `displayid`, `Quality`, `Flags`, `FlagsExtra`, `BuyCount`, `BuyPrice`, `SellPrice`, `InventoryType`, `AllowableClass`, `AllowableRace`, `ItemLevel`, `RequiredLevel`, `RequiredSkill`, `RequiredSkillRank`, `requiredspell`, `requiredhonorrank`, `RequiredCityRank`, `RequiredReputationFaction`, `RequiredReputationRank`, `maxcount`, `stackable`, `ContainerSlots`, `StatsCount`, `stat_type1`, `stat_value1`, `stat_type2`, `stat_value2`, `stat_type3`, `stat_value3`, `stat_type4`, `stat_value4`, `stat_type5`, `stat_value5`, `stat_type6`, `stat_value6`, `stat_type7`, `stat_value7`, `stat_type8`, `stat_value8`, `stat_type9`, `stat_value9`, `stat_type10`, `stat_value10`, `ScalingStatDistribution`, `ScalingStatValue`, `dmg_min1`, `dmg_max1`, `dmg_type1`, `dmg_min2`, `dmg_max2`, `dmg_type2`, `armor`, `holy_res`, `fire_res`, `nature_res`, `frost_res`, `shadow_res`, `arcane_res`, `delay`, `ammo_type`, `RangedModRange`, `spellid_1`, `spelltrigger_1`, `spellcharges_1`, `spellppmRate_1`, `spellcooldown_1`, `spellcategory_1`, `spellcategorycooldown_1`, `spellid_2`, `spelltrigger_2`, `spellcharges_2`, `spellppmRate_2`, `spellcooldown_2`, `spellcategory_2`, `spellcategorycooldown_2`, `spellid_3`, `spelltrigger_3`, `spellcharges_3`, `spellppmRate_3`, `spellcooldown_3`, `spellcategory_3`, `spellcategorycooldown_3`, `spellid_4`, `spelltrigger_4`, `spellcharges_4`, `spellppmRate_4`, `spellcooldown_4`, `spellcategory_4`, `spellcategorycooldown_4`, `spellid_5`, `spelltrigger_5`, `spellcharges_5`, `spellppmRate_5`, `spellcooldown_5`, `spellcategory_5`, `spellcategorycooldown_5`, `bonding`, `description`, `PageText`, `LanguageID`, `PageMaterial`, `startquest`, `lockid`, `Material`, `sheath`, `RandomProperty`, `RandomSuffix`, `block`, `itemset`, `MaxDurability`, `area`, `Map`, `BagFamily`, `TotemCategory`, `socketColor_1`, `socketContent_1`, `socketColor_2`, `socketContent_2`, `socketColor_3`, `socketContent_3`, `socketBonus`, `GemProperties`, `RequiredDisenchantSkill`, `ArmorDamageModifier`, `duration`, `ItemLimitCategory`, `HolidayId`, `ScriptName`, `DisenchantID`, `FoodType`, `minMoneyLoot`, `maxMoneyLoot`, `flagsCustom`, `VerifiedBuild`) VALUES
(910001, 15, 4, -1, 'Araxia Token', 32278, 4, 0, 0, 1, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 100, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1000, 0, 0, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 1, 'Use to buy the rarest goods in the realm', 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 8192, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, '', 0, 0, 0, 0, 0, 0);

View File

@@ -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);

View File

@@ -0,0 +1,5 @@
-- LEGENDARY DIAMOND PICKAXE GIVES BONUS ORE ON MINE
DELETE FROM `item_template` WHERE (`entry` = 910000);
INSERT INTO `item_template` (`entry`, `class`, `subclass`, `SoundOverrideSubclass`, `name`, `displayid`, `Quality`, `Flags`, `FlagsExtra`, `BuyCount`, `BuyPrice`, `SellPrice`, `InventoryType`, `AllowableClass`, `AllowableRace`, `ItemLevel`, `RequiredLevel`, `RequiredSkill`, `RequiredSkillRank`, `requiredspell`, `requiredhonorrank`, `RequiredCityRank`, `RequiredReputationFaction`, `RequiredReputationRank`, `maxcount`, `stackable`, `ContainerSlots`, `StatsCount`, `stat_type1`, `stat_value1`, `stat_type2`, `stat_value2`, `stat_type3`, `stat_value3`, `stat_type4`, `stat_value4`, `stat_type5`, `stat_value5`, `stat_type6`, `stat_value6`, `stat_type7`, `stat_value7`, `stat_type8`, `stat_value8`, `stat_type9`, `stat_value9`, `stat_type10`, `stat_value10`, `ScalingStatDistribution`, `ScalingStatValue`, `dmg_min1`, `dmg_max1`, `dmg_type1`, `dmg_min2`, `dmg_max2`, `dmg_type2`, `armor`, `holy_res`, `fire_res`, `nature_res`, `frost_res`, `shadow_res`, `arcane_res`, `delay`, `ammo_type`, `RangedModRange`, `spellid_1`, `spelltrigger_1`, `spellcharges_1`, `spellppmRate_1`, `spellcooldown_1`, `spellcategory_1`, `spellcategorycooldown_1`, `spellid_2`, `spelltrigger_2`, `spellcharges_2`, `spellppmRate_2`, `spellcooldown_2`, `spellcategory_2`, `spellcategorycooldown_2`, `spellid_3`, `spelltrigger_3`, `spellcharges_3`, `spellppmRate_3`, `spellcooldown_3`, `spellcategory_3`, `spellcategorycooldown_3`, `spellid_4`, `spelltrigger_4`, `spellcharges_4`, `spellppmRate_4`, `spellcooldown_4`, `spellcategory_4`, `spellcategorycooldown_4`, `spellid_5`, `spelltrigger_5`, `spellcharges_5`, `spellppmRate_5`, `spellcooldown_5`, `spellcategory_5`, `spellcategorycooldown_5`, `bonding`, `description`, `PageText`, `LanguageID`, `PageMaterial`, `startquest`, `lockid`, `Material`, `sheath`, `RandomProperty`, `RandomSuffix`, `block`, `itemset`, `MaxDurability`, `area`, `Map`, `BagFamily`, `TotemCategory`, `socketColor_1`, `socketContent_1`, `socketColor_2`, `socketContent_2`, `socketColor_3`, `socketContent_3`, `socketBonus`, `GemProperties`, `RequiredDisenchantSkill`, `ArmorDamageModifier`, `duration`, `ItemLimitCategory`, `HolidayId`, `ScriptName`, `DisenchantID`, `FoodType`, `minMoneyLoot`, `maxMoneyLoot`, `flagsCustom`, `VerifiedBuild`) VALUES
(910000, 2, 14, -1, 'Diamond Pickaxe', 36594, 5, 0, 0, 1, 8100000, 16, 21, -1, -1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2000, 0, 0, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0, 0, -1, 0, -1, 1, 'Steve loaned this to me!', 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1024, 165, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, '', 0, 0, 0, 0, 0, 12340);

View File

@@ -0,0 +1,168 @@
local ____lualib = require("lualib_bundle")
local __TS__New = ____lualib.__TS__New
local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
local __TS__StringIncludes = ____lualib.__TS__StringIncludes
local ____exports = {}
local ____money = require("classes.money")
local ToCopper = ____money.ToCopper
local GetPlayerTax = ____money.GetPlayerTax
local ____account = require("classes.account")
local AccountInfo = ____account.AccountInfo
local spawned = {}
local NPCS = {
GOTHUK = 9000003,
BERNIE = 9000004,
EDWARD = 9000005,
LUNA = 9000006,
BOB_B = 9000007,
SHIVA = 9000008
}
local selectedItem = {}
local function GossipHello(____, event, player, creature)
local accountId = player:GetAccountId()
local bernieCost = ToCopper(nil, 1000) + GetPlayerTax(nil, player, 5)
player:GossipClearMenu()
do
local i = 23
while i <= 38 do
local item = player:GetItemByPos(255, i)
if item ~= nil then
print(item:GetItemLink())
if item:IsSoulBound() then
local quality = item:GetQuality()
if quality > 2 then
player:GossipMenuAddItem(
1,
"Item: " .. item:GetItemLink(),
1,
item:GetGUIDLow(),
nil,
nil
)
end
end
end
i = i + 1
end
end
player:GossipSendMenu(NPCS.GOTHUK, creature, 10000)
return true
end
local function GossipSelect(____, event, player, creature, selection, action, code, menuId)
PrintInfo("selection: " .. tostring(selection))
print("action " .. tostring(action))
local account = __TS__New(
AccountInfo,
player:GetAccountId()
)
local characters = account:GetCharacters()
if action > 15 then
do
local numC = 0
while numC < #characters do
local name = characters[numC + 1].name
if name ~= player:GetName() then
player:GossipMenuAddItem(
2,
"Send to: " .. name,
2,
numC + 1,
nil
)
end
numC = numC + 1
end
end
selectedItem[player:GetName()] = action
player:GossipSendMenu(NPCS.GOTHUK, creature, 10000)
end
if action <= 15 then
local itemToChange = selectedItem[player:GetName()]
local itemGuid = GetItemGUID(itemToChange)
local PlayerItem = player:GetItemByGUID(itemGuid)
print((("Item Info: " .. PlayerItem:GetOwner():GetName()) .. " owns ") .. PlayerItem:GetName())
print("To Name is " .. characters[action].name)
local newItemGuid = SendMail(
"Item Soulswap " .. PlayerItem:GetName(),
"Soulbinder has sent you a gift " .. PlayerItem:GetName(),
characters[action].guid,
player:GetGUIDLow(),
41,
100,
0,
0,
PlayerItem:GetEntry(),
1
)
print((("send new item " .. tostring(newItemGuid)) .. " to ") .. characters[action].name)
player:GossipComplete()
end
return true
end
--- This will load NPCs that shoud be loaded based on purchased guild benefits
-- and if system is enabled.
local function LoadNpcOnStart(____, event)
local npcs = {
9000003,
9000004,
9000005,
9000006,
9000007,
9000008
}
local result = WorldDBQuery("SELECT * from guild_elite_benefits")
do
local i = 0
while i < result:GetRowCount() do
local benefit = result:GetRow()
local entry = benefit.creature_entry
if benefit.purchased == 1 and not __TS__ArrayIncludes(spawned, entry) then
PerformIngameSpawn(
1,
entry,
1,
0,
benefit.x,
benefit.y,
benefit.z,
benefit.o,
false
)
PrintInfo("benefit.benefit,'was purchased!")
else
PrintInfo("benefit.benefit,'was NOT purchased!'")
end
result:NextRow()
i = i + 1
end
end
end
RegisterCreatureGossipEvent(
9000003,
1,
function(...) return GossipHello(nil, ...) end
)
RegisterCreatureGossipEvent(
9000003,
2,
function(...) return GossipSelect(nil, ...) end
)
local function seeItems(____, event, player, command)
if __TS__StringIncludes(command, "backpack") then
do
local i = 23
while i <= 38 do
local item = player:GetItemByPos(255, i)
print(item:GetName())
print(item:GetItemLink())
i = i + 1
end
end
end
return true
end
RegisterPlayerEvent(
42,
function(...) return seeItems(nil, ...) end
)
return ____exports

View File

@@ -0,0 +1,245 @@
import { ToGold, ToCopper, GetPlayerTax } from "../modules/classes/money";
import { AccountInfo } from "../modules/classes/account";
const spawned:Array<number> = [];
const NPCS = {
GOTHUK: 9000003,
BERNIE: 9000004,
EDWARD: 9000005,
LUNA: 9000006,
BOB_B: 9000007,
SHIVA: 9000008
};
const selectedItem: Record<string, number> = {};
const GossipHello : gossip_event_on_hello = (event: number, player: Player, creature: EObject) => {
const accountId = player.GetAccountId();
// NPC Hire Costs
const bernieCost = ToCopper(1000) + GetPlayerTax(player,5);
player.GossipClearMenu();
let items = 0;
for(let i=23; i <= 38; i++ ) {
let item = player.GetItemByPos(255, i);
if(item != undefined) {
print(item.GetItemLink());
if(item.IsSoulBound() ) {
const quality = item.GetQuality();
if(quality > 2) {
items += 1;
player.GossipMenuAddItem(1,`Item: ${item.GetItemLink()}`,1,item.GetGUIDLow(), undefined, undefined);
}
}
}
// print(item.GetName());
// print(item.GetItemLink());
}
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.GossipMenuAddItem(1,`Hire Grandmaster Smith - (Reset Timers) 200g`,1,10,undefined,undefined,10000*200);
// player.GossipMenuAddItem(1,`Hire Grandmaster Tailor - (Reset Timers) 200g`,1,20,undefined,undefined,10000*200);
// // // player.GossipMenuAddItem(1,`Hire Grandmaster Leatherworker - (Reset Timers) 200g`, 1,30, undefined, undefined, 10000*200);
// // player.GossipMenuAddItem(1,`Hire Grandmaster Jewelcrafter - (Reset Timers) 200g`, 1,40, undefined, undefined, 10000*200);
// player.GossipMenuAddItem(1,`Hire Bernie, Leather Trader - ${ToGold(bernieCost)}g`, NPCS.BERNIE,50, undefined, undefined, bernieCost);
// // player.GossipMenuAddItem(1,`Epic Tradeskill Vendor - 650g`, 1,60, undefined, undefined, 10000*650);
// // player.GossipMenuAddItem(1,`Secret Goods Vendor 1300g`, 1,80, undefined, undefined, 10000*1300);
// player.GossipMenuAddItem(0, `I will leave you alone`,1,2);
player.GossipSendMenu(NPCS.GOTHUK, creature, 10000);
return true;
}
const GossipSelect: gossip_event_on_select = (event: number, player: Player, creature: any, selection, action, code, menuId) => {
PrintInfo(`selection: ${selection}`);
print(`action ${action}`);
// const cost = 100000;
// const inGold = cost / 10000;
const account = new AccountInfo(player.GetAccountId());
const characters = account.GetCharacters();
if(action === 50500) {
player.GossipClearMenu();
player.GossipComplete();
return true;
}
// player.GossipClearMenu();
if(action > 15) {
for(let numC = 0; numC < characters.length; numC++) {
let name = characters[numC].name;
if(name != player.GetName()) {
// player.GossipMenuAddItem(2, `Send to: ${name}`, 2, numC+1, undefined, `Are you sure you will to rebind this item to ${name}?`, 10000);
player.GossipMenuAddItem(2, `Send to: ${name}`, 2, numC+1, undefined);
}
}
selectedItem[player.GetName()] = action;
player.GossipSendMenu(NPCS.GOTHUK, creature, 10000);
}
if(action <= 15) {
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
);
print(`To Name is ${characters[action-1].name}`);
player.RemoveItem(PlayerItem, PlayerItem.GetEntry(), 1);
print(`send new item ${newItemGuid} to ${characters[action-1].name}`);
player.GossipClearMenu();
player.GossipComplete();
}
return true;
}
// const MapLog: map_event_on_player_enter = (event: number, map: EMap, player:Player) => {
// PrintInfo(map.GetName());
// PrintInfo(`${map.GetInstanceId()}`);
// PrintInfo(`${player.GetZoneId()}`);
// PrintInfo(`${GetGameTime()}`);
// if(player.GetZoneId() == 876) {
// WorldDBExecute(`insert into guild_house_log VALUES(null,'${player.GetGUID()}','${player.GetName()} entered the guild house', CURTIME())`);
// }
// return true;
// }
/**
* This will load NPCs that shoud be loaded based on purchased guild benefits
* and if system is enabled.
*/
const LoadNpcOnStart: eluna_event_on_lua_state_open = (event: number) => {
const npcs = [
9000003, // Gothuk
9000004, // Bernie
9000005, // Edward
9000006, // Luna
9000007, // Bob B
9000008 // Shiva
];
const result = WorldDBQuery("SELECT * from guild_elite_benefits");
for(let i =0; i < result.GetRowCount(); i++ ) {
let benefit = result.GetRow();
let entry = benefit.creature_entry as number;
if(benefit.purchased === 1 && !spawned.includes(entry)) {
PerformIngameSpawn(1,entry,1,0,
// location data
benefit.x as number,
benefit.y as number,
benefit.z as number,
benefit.o as number,
false);
PrintInfo(`benefit.benefit,'was purchased!`);
} else {
PrintInfo(`benefit.benefit,'was NOT purchased!'`);
}
result.NextRow();
}
// for(const npcId of npcs) {
// Get
// }
}
// RegisterServerEvent(
// ServerEvents.ELUNA_EVENT_ON_LUA_STATE_OPEN,
// (...args) => {
// let spawned = [];
// LoadNpcOnStart(...args)
// }
// );
// PerformIngameSpawn(1,9000003,1,0,16221.8,16278,20.9032,4.70345,false);
// const object = GetObjectGUID(3110516,9000003);
RegisterCreatureGossipEvent(
9000003,
GossipEvents.GOSSIP_EVENT_ON_HELLO,
(...args) => GossipHello(...args)
);
RegisterCreatureGossipEvent(
9000003,
GossipEvents.GOSSIP_EVENT_ON_SELECT,
(...args) => GossipSelect(...args)
);
RegisterGameObjectGossipEvent(
750000,
GossipEvents.GOSSIP_EVENT_ON_HELLO,
(...args) => GossipHello(...args)
);
RegisterGameObjectGossipEvent(
750000,
GossipEvents.GOSSIP_EVENT_ON_SELECT,
(...args) => GossipSelect(...args)
);
// RegisterServerEvent(
// ServerEvents.MAP_EVENT_ON_PLAYER_ENTER,
// (...args) => MapLog(...args)
// )
const seeItems: player_event_on_command = (event: number, player: Player, command: string): boolean => {
if(command.includes('backpack')) {
for(let i=23; i <= 38; i++ ) {
let item = player.GetItemByPos(255, i);
print(item.GetName());
print(item.GetItemLink());
}
}
return true;
}
RegisterPlayerEvent(PlayerEvents.PLAYER_EVENT_ON_COMMAND, (...args) => seeItems(...args));

View File

@@ -0,0 +1,28 @@
class Command {
onCommand : player_event_on_command = (
event: number,
player: Player,
command: string
) => {
if(command == "doit") {
const message = "A Command from the script has been entered" + command;
print(message);
SendWorldMessage(message);
}
print("debug: " + command);
return true;
}
}
const commandHander = new Command();
RegisterPlayerEvent(
PlayerEvents.PLAYER_EVENT_ON_COMMAND,
(...args) => commandHander.onCommand(...args)
)

View File

@@ -0,0 +1,56 @@
/** @ts-expect-error */
let aio: AIO = {};
if(!aio.AddAddon()) {
const myHandlers = aio.AddHandlers('AIOTest', {});
const FrameTest = CreateFrame("Frame", "FrameTest2", UIParent, "UIPanelDialogTemplate");
let frame = FrameTest;
frame.SetSize(400,300);
frame.SetMovable(true);
frame.RegisterForDrag("LeftButton");
frame.SetPoint("CENTER");
frame.EnableMouse(true);
frame.Hide();
frame.SetHyperlinksEnabled(true);
frame.SetScript("OnHyperlinkClick")
frame.SetScript("OnDragStart", frame.StartMoving);
frame.SetScript("OnHide", frame.StopMovingOrSizing);
frame.SetScript("OnDragStop", frame.StopMovingOrSizing);
let increment = -40;
frame.SetScript("OnEnter", (frame: WoWAPI.Frame) => {
if(CursorHasItem()) {
let [objectType, objectId, link] = GetCursorInfo();
const text = frame.CreateFontString('itemdragged', "OVERLAY", "GameFontHighlight");
text.SetPoint("TOPLEFT", 10,increment);
text.SetText(link);
increment = increment - 15;
}
// if(CursorHasItem()) {
// print(type);
// print(GetCursorInfo());
// print(details);
// }
// print(CursorHasItem());
});
myHandlers.ShowFrame = (player: Player) => {
frame.Show();
}
frame.Show();
}

View File

@@ -0,0 +1,21 @@
/** @ts-expect-error */
let aio: AIO = {};
const myHandlers = aio.AddHandlers('AIOTest', {});
myHandlers.print = (...args) => {
print(args);
}
const ShowWindow: player_event_on_command = (event: number,player: Player, command: string): boolean => {
if(command == 'testwin') {
aio.Handle(player, 'AIOTest', 'ShowFrame');
return false;
}
return true;
};
RegisterPlayerEvent(
PlayerEvents.PLAYER_EVENT_ON_COMMAND,
(...args) => ShowWindow(...args)
);

View File

@@ -0,0 +1,41 @@
const COPPER_ORE = 2770;
const HandleSpell: player_event_on_spell_cast = (event: number, player: Player, spell: Spell, skipCheck: boolean) => {
print(spell.GetEntry());
print(skipCheck);
return;
}
// State Changes
// 1 - Ready / Deactivated
// 2 - Activated
// 3 - Looting
const HandleMiningLoot: gameobject_event_on_loot_state_change = (event: number, gameObj: GameObject, state: number) => {
print(gameObj.GetName());
print(`State change: ${state}`);
let player = gameObj.GetNearestPlayer();
print(player.GetName());
// Is Mining and has special pick
if(state === 3) {
if(player.HasItem(910000)) {
let quantity = Math.ceil(Math.random() * 3);
player.AddItem(COPPER_ORE,quantity);
player.SendChatMessageToPlayer(ChatMsg.CHAT_MSG_LOOT, Language.LANG_COMMON, `Your Diamond Axe grants you gifts from beyond!`, player);
}
}
return;
}
RegisterPlayerEvent(
PlayerEvents.PLAYER_EVENT_ON_SPELL_CAST,
(...args) => HandleSpell(...args)
);
RegisterGameObjectEvent(
1731,
GameObjectEvents.GAMEOBJECT_EVENT_ON_LOOT_STATE_CHANGE,
(...args) => HandleMiningLoot(...args)
)

View File

@@ -0,0 +1,59 @@
/** @ts-expect-error */
let aio: AIO = {};
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);
let increment = -40;
const ImgFrame = CreateFrame("Frame", "ImgFrame", frame);
ImgFrame.SetSize(800,600);
ImgFrame.SetPoint("CENTER", 0, 20 );
ImgFrame.SetFrameLevel(1);
const PageFrame = MainFrame.CreateTexture("MainFrameImgTexture", null, ImgFrame);
PageFrame.SetSize(512,512);
PageFrame.SetPoint("CENTER", 0, -15);
PageFrame.SetTexture("Interface/Comics/Comic_Page3");
frame.SetScript("OnEnter", (frame) => {
if(CursorHasItem()) {
let [objectType, objectId, link] = GetCursorInfo();
const text = frame.CreateFontString('itemdragged', "OVERLAY", "GameFontHighlight");
text.SetPoint("TOPLEFT", 10,increment);
text.SetText(link);
increment = increment - 15;
}
// if(CursorHasItem()) {
// print(type);
// print(GetCursorInfo());
// print(details);
// }
// print(CursorHasItem());
});
myHandlers.ShowFrame = (player: Player) => {
frame.Show();
}
}

View File

@@ -0,0 +1,23 @@
/** @ts-expect-error */
let aio: AIO = {};
const myHandlers = aio.AddHandlers('AIOTest', {});
myHandlers.print = (...args) => {
print(args);
}
const frame = CreateFrame
const ShowWindow: player_event_on_command = (event: number,player: Player, command: string): boolean => {
if(command == 'testwin') {
aio.Handle(player, 'AIOTest', 'ShowFrame');
return false;
}
return true;
};
RegisterPlayerEvent(
PlayerEvents.PLAYER_EVENT_ON_COMMAND,
(...args) => ShowWindow(...args)
);

View File

@@ -0,0 +1,232 @@
/** @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 * 5000;
win = 2;
}
if(slotSpin[0] == slotSpin[1] && slotSpin[1] == slotSpin[2]) {
if(multiplier == 3) {
tokens = 50;
}
gold = multiplier * 1000;
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 = 20;
}
gold = multiplier * 500;
win = 1;
}
// handle two of the same class in a row
if((slotSpin[0] == slotSpin[1]) && win == 0) {
gold = multiplier * 250;
win = 1;
if(slotSpin[1] == 1) {
if(multiplier == 3) {
tokens = 3;
}
gold = multiplier * 250;
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);
}
}

View File

@@ -0,0 +1,71 @@
/** @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));

View File

@@ -0,0 +1,35 @@
/**
* Class for find out information about an account
*/
export type BasicCharacter = {
guid: number,
name: string
}
export class AccountInfo {
private accountId: number;
constructor(accountId: number) {
this.accountId = accountId;
}
GetAccountMoney(): number {
const result = CharDBQuery(`SELECT SUM(Money) as AccountMoney from acore_characters.characters WHERE account = ${this.accountId}`);
const row = result.GetRow() as Record<string, number>;
return row.AccountMoney;
}
GetCharacters(): BasicCharacter[] {
const result = CharDBQuery(`SELECT guid, name from characters WHERE account = ${this.accountId}`);
const characters: BasicCharacter[] = [];
for(let i=0; i < result.GetRowCount(); i++) {
const row = result.GetRow();
characters.push({ guid: row.guid as number, name: row.name as string });
result.NextRow();
}
return characters;
}
}

32
modules/classes/money.ts Normal file
View File

@@ -0,0 +1,32 @@
import { AccountInfo } from "./account";
export const GOLD_TO_COPPER = 10000;
/**
* Converts a copper cost to gold
* @param cost <number> Cost of item in copper
* @returns number
*/
export function ToGold(cost: number) : number {
return Math.floor(cost / GOLD_TO_COPPER);
}
/**
* Converts a gold cost to copper
* @param gold <number> Cost of item in gold
* @returns number
*/
export function ToCopper(gold: number) : number {
return gold*GOLD_TO_COPPER;
}
/**
* Gets a scaling tax for players to help with balancing the economy for guild features.
* @param player Player
* @param tax amount of tax against player to levy number (0-100)
* @returns number result in copper
*/
export function GetPlayerTax(player: Player, tax: number) : number {
const account = new AccountInfo(player.GetAccountId());
return (tax/100) * account.GetAccountMoney();
}

132
modules/classes/stats.ts Normal file
View File

@@ -0,0 +1,132 @@
declare function GetGameTime(): number;
const PLAYER_TYPE = 'player';
export const StatEvents = {
TOKEN_CREATED: 'token_created',
TICKETS_AWARDED: 'darkmoon_tickets_awarded',
};
export class Stats {
stats = new Map<string, Stat>();
entity: StatEntity;
constructor(entity: StatEntity) {
this.entity = entity;
this.load();
}
static GetStatsByType(type: string, name: string) : Map<number, number> {
const result = CharDBQuery(`SELECT id, name, value, updated FROM ${type}_stats WHERE name = '${name}'`);
const stats = new Map<number, number>();
if(!result) {
return stats;
}
for(let i=0; i < result.GetRowCount(); i++) {
const row = result.GetRow();
stats.set(row.id as number, row.value as number);
result.NextRow();
}
return stats;
};
load() : boolean {
const result = CharDBQuery(`SELECT id, name, value, updated FROM ${this.entity.type}_stats WHERE id = ${this.entity.id}`);
if(!result) {
return false;
}
for(let i=0; i < result.GetRowCount(); i++) {
const row = result.GetRow();
const stat: Stat = {
name: row.name as string,
type: this.entity.type,
value: row.value as number,
updated: row.updated as number,
loaded: true
}
this.stats.set(stat.name, stat);
result.NextRow();
}
return true;
}
save() : void {
for(const stat of this.stats.values()) {
if(!stat.loaded) {
CharDBExecute(`INSERT INTO ${this.entity.type}_stats (id, name, value, updated) VALUES (${this.entity.id}, '${stat.name}', ${stat.value}, ${stat.updated})`);
PrintDebug(`Inserted ${stat.name} for ${this.entity.type} ${this.entity.id} with value ${stat.value}`);
} else {
CharDBExecute(`UPDATE ${this.entity.type}_stats SET value = ${stat.value}, updated = ${stat.updated} WHERE id = ${this.entity.id} AND name = '${stat.name}'`);
PrintDebug(`Updated ${stat.name} for ${this.entity.type} ${this.entity.id} to ${stat.value}`);
}
}
}
getStat(name: string) : Stat | undefined {
return this.stats.get(name);
}
setStat(name: string, value: number) : void {
const stat = this.stats.get(name);
if(stat) {
stat.value = value;
stat.updated = GetGameTime();
} else {
this.stats.set(name, {
name: name,
type: PLAYER_TYPE,
value: value,
updated: GetGameTime(),
loaded: false
});
}
}
increment(name: string, amount: number = 1) : void {
const stat = this.stats.get(name);
if(stat) {
stat.value += amount;
stat.updated = GetGameTime();
} else {
this.stats.set(name, {
name: name,
type: PLAYER_TYPE,
value: 0,
updated: GetGameTime(),
loaded: false
});
}
}
}
/**
* Custom player stats that will be
*/
export class PlayerStats extends Stats {
player: Player;
playerStats: Stat[] = [];
constructor(player: Player) {
super({
id: player.GetGUID(),
type: PLAYER_TYPE
});
this.player = player;
}
}
interface StatEntity {
type: string,
id: number
}
interface Stat {
type: string,
name: string,
value: number,
updated: number,
loaded: boolean
}

View File

@@ -0,0 +1,37 @@
/* @noSelfInFile */
type TriggerInput = {
triggerName: string,
characterGuid: number,
isSet: boolean
}
/**
* Sets a player trigger boolean that can be retieved later as needed
* @param charTrigger TriggerInput
*/
export function SetTrigger(charTrigger: TriggerInput) {
let sql = `INSERT INTO player_trigger (triggerName, characterGuid, isSet) `+
`VALUES ("${charTrigger.triggerName}", ${charTrigger.characterGuid}, ${charTrigger.isSet})`+
`ON DUPLICATE KEY UPDATE isSet=${charTrigger.isSet}`;
print(sql);
CharDBExecute(sql);
}
/**
* Will return the value of the trigger if it exists, otherwise it will return false
* @param charGuid number
* @param triggerName string
* @returns boolean
*/
export function GetTrigger(charGuid: number, triggerName: string) {
let sql = `SELECT isSet from player_trigger WHERE triggerName="${triggerName}" and characterGuid=${charGuid}`;
const result = CharDBQuery(sql);
if(result && result.GetRowCount() > 0) {
return result.GetBool(0)
} else {
return false;
}
}

View File

@@ -0,0 +1,18 @@
export function colors(name: string) {
const colors = {
GREY: "|cff999999",
RED: "|cffff0000",
WHITE: "|cffFFFFFF",
GREEN: "|cff1eff00",
PURPLE: "|cff9F3FFF",
BLUE: "|cff0070dd",
ORANGE: "|cffFF8400",
};
const keyName = name.toUpperCase();
if(colors[keyName]) {
return colors[keyName];
} else {
return colors.WHITE;
}
}

View File

@@ -0,0 +1,116 @@
/**
* @file set-xp-rate.ts
* @date 2023-11-15
* @author ben-of-codecraft
*
* Type: Command
* Adds a command that allows players to set their own XP rate for their character up to 5x normal xp rate.
*
*/
/**
* Configuration options
*/
const MAX_XP_RATE = 5;
// Command to show the current xp rate
const xpCmd = "#xprate";
const showXPcmd = "#xprate show";
const setXPcmd = "#xprate set";
const XP_RATE_SETTING = "xp_rate";
import { PlayerStats } from "../classes/stats";
let xpRateCache = new Map<number, number>();
const XPRateHandler: player_event_on_chat = (event: number, player: Player, message: string) => {
if(message.includes(xpCmd)) {
const args = message.split(" ");
const cmd = args[1];
const playerCustom = new PlayerStats(player);
playerCustom.load();
if(cmd == "show") {
const xpRate = xpRateCache.get(player.GetGUIDLow());
if(xpRate != undefined) {
player.SendBroadcastMessage(`Your current XP rate is ${xpRate}x`);
} else {
player.SendBroadcastMessage(`Your current XP rate is 1x`);
}
} else if(cmd == "set") {
const rate = args[2];
const rateNum = parseInt(rate);
if(rateNum > MAX_XP_RATE) {
player.SendNotification(`You cannot set your XP rate higher then ${MAX_XP_RATE}x`);
return false;
}
playerCustom.setStat(XP_RATE_SETTING, rateNum);
playerCustom.save();
xpRateCache.set(player.GetGUIDLow(), rateNum);
player.SendBroadcastMessage(`Your XP rate has been set to ${rateNum}x`);
}
else {
player.SendBroadcastMessage(`Usage: ${xpCmd} [show|set] [rate]`);
}
return false;
}
return true;
};
/**
* Gives players extra XP based on their rate
* @param event \
* @param player
* @param amount
* @param victim
*/
const XPBonus: player_event_on_give_xp = (event: number, player: Player, amount: number, victim: Unit) => {
const xpRate = xpRateCache.get(player.GetGUIDLow());
if(xpRate && xpRate > 1) {
player.GiveXP(amount * xpRate);
}
}
const XPRateLoader: player_event_on_login = (event: number, player: Player) => {
const playerCustom = new PlayerStats(player);
playerCustom.load();
const xpRate = playerCustom.getStat(XP_RATE_SETTING);
if(xpRate) {
xpRateCache.set(player.GetGUIDLow(), xpRate.value);
} else {
xpRateCache.set(player.GetGUIDLow(), 1);
}
};
// Grants players extra XP Based on their rate
RegisterPlayerEvent(PlayerEvents.PLAYER_EVENT_ON_GIVE_XP, (...args) => XPBonus(...args));
// Register the command
RegisterPlayerEvent(PlayerEvents.PLAYER_EVENT_ON_CHAT,
(...args) => XPRateHandler(...args)
);
// Loads the cache of seetings on login
RegisterPlayerEvent(PlayerEvents.PLAYER_EVENT_ON_LOGIN, (...args) => XPRateLoader(...args));
// reloads the cache of settings when the lua state is opened
RegisterServerEvent(ServerEvents.ELUNA_EVENT_ON_LUA_STATE_OPEN, (...args) => {
xpRateCache = PlayerStats.GetStatsByType('player', XP_RATE_SETTING);
});

View File

@@ -0,0 +1,57 @@
/**
* CONFIG OPTIONS
* @returns
*/
// Reward 1 token per every 5 achievement points earned.
const AWARD_RATE = 5;
// Token Id of the currency you want to aware the players.
const TOKEN_ID = 910001;
// Character GUID of the character that will be sending the tokens vai mail
const REWARD_CHAR_GUID = 2506;
/**
* On Achivement complete the system will reward the player with tokens based on the AWARD_RATE
* set above
* Default is 5 Achievement Points = 1 Token
*
* IE)
* 10 Achievement Points = 2 Tokens
* 50 Achievement Points = 10 Tokens
*
* @param event : number
* @param player : Player
* @param achievement : Achievement
* @returns boolean
*/
const achievementComplete: player_event_on_achievement_complete = (event, player, achievement) => {
const id = achievement.GetId();
const query = WorldDBQuery(`SELECT Points from achievements where ID=${id}`);
const points = query.GetUInt32(0);
if(points != undefined) {
const tokens = Math.ceil(points / AWARD_RATE);
SendMail(
`Your achievement token reward!`,
`You earned it now spend it!`,
player.GetGUIDLow(),
REWARD_CHAR_GUID,
MailStationery.MAIL_STATIONERY_DEFAULT,
0,
0,
0,
TOKEN_ID,
tokens
);
}
return true;
};
RegisterPlayerEvent(
PlayerEvents.PLAYER_EVENT_ON_ACHIEVEMENT_COMPLETE,
(...args) => achievementComplete(...args)
);

View File

@@ -0,0 +1,23 @@
import { SetTrigger, GetTrigger } from "../classes/triggers";
/**
* Show the Burning Crusade moving on first login.
*
* @param event
* @param player
*/
const ShowBCMovie: player_event_on_login = (event: number, player: Player) => {
const movieShown = GetTrigger(player.GetGUIDLow(), "tbc_movie_shown");
if(movieShown === false) {
player.SendMovieStart(1);
SetTrigger({
triggerName: "tbc_movie_shown",
characterGuid: player.GetGUIDLow(),
isSet: true
});
}
}
RegisterPlayerEvent(PlayerEvents.PLAYER_EVENT_ON_LOGIN,
(...args) => ShowBCMovie(...args)
);

View File

@@ -0,0 +1,24 @@
import { ToCopper } from "../classes/money";
RegisterPlayerEvent(PlayerEvents.PLAYER_EVENT_ON_FIRST_LOGIN, (event, player) => {
// Worgren get additional racials and start at level 20 in Duskwood
if(player.GetRace() == 12 ) {
// Since we do not have all Worgren spells will give them another racial fitting of their class.
player.LearnSpell(20577); // Cannibalize
// learn spell blood fury
player.LearnSpell(33697);
player.SetLevel(20);
player.Teleport(0, -10728.057617, -1131.120850, 27.594067, 1.180833);
player.ModifyMoney(ToCopper(100));
}
// If we are a goblin then start off at level 20 in Ratchet
if(player.GetRace() == 9) {
player.SetLevel(20);
player.ModifyMoney(ToCopper(100));
player.Teleport(1, -1049.596, -3645.963, 23.878, 4.468);
}
});

View File

@@ -0,0 +1,28 @@
/**
* Badge Of Justice Multiplier
* Increases the number of Badges of Justice a player will receive.
*/
/**
* Config
*/
const BADGE_OF_JUSTICE_BONUS = 1;
const HEROIC_FOCUS_AURA = 95000;
const BADGE_OF_JUSTICE_ENTRY = 29434;
const LootToken: player_event_on_loot_item = (event: number, player: Player, item: Item) => {
if(item.GetEntry() == BADGE_OF_JUSTICE_ENTRY) {
player.AddItem(BADGE_OF_JUSTICE_ENTRY, BADGE_OF_JUSTICE_BONUS);
if(player.HasAura(HEROIC_FOCUS_AURA)) {
const randomNumber = Math.floor(Math.random() * 3) + 1;
player.AddItem(BADGE_OF_JUSTICE_ENTRY, randomNumber);
}
}
}
RegisterPlayerEvent(PlayerEvents.PLAYER_EVENT_ON_LOOT_ITEM, (...args) => LootToken(...args));

View File

@@ -0,0 +1,27 @@
/**
* Item that will enable a user to teleport to a new location in azeroth loaded from
* existing waypoints set in the database.
* @date 2023-12-01
* @author ben-of-codecraft
*/
/**
* Configuration options
*/
const TELEPORT_ITEM_ENTRY = 910006;
const TeleportHandler: item_event_on_use = (event: number, player: Player, item: Item, target: Unit) => {
if(player.IsHorde()) {
const master = PerformIngameSpawn(1, 2851, player.GetMapId(), player.GetInstanceId(), player.GetX(), player.GetY(), player.GetZ(), player.GetO(), false, 1, 0) as Creature;
player.SendTaxiMenu(master);
} else {
const master = player.SpawnCreature(1571, player.GetX(), player.GetY(), player.GetZ(), player.GetO(), TempSummonType.TEMPSUMMON_MANUAL_DESPAWN);
player.SendTaxiMenu(master);
master.DespawnOrUnsummon(60*1000);
}
return true;
};
RegisterItemEvent(TELEPORT_ITEM_ENTRY, ItemEvents.ITEM_EVENT_ON_USE, (...args) => TeleportHandler(...args));

76
modules/items/darkmoon.ts Normal file
View File

@@ -0,0 +1,76 @@
import { PlayerStats, StatEvents } from "../classes/stats";
const NORMAL_TICKET_CHANCE = 2;
const BOSS_TICKET_CHANCE = 10;
/**
* Conditions to get a potential token drop are as follows
* Helps with giving players more tickets during darkmoon fair month.
* If
*/
const TicketKill: player_event_on_kill_creature = (event: number, player: Player, creature: Creature ) => {
// let chance: number;
// if( creature.IsDungeonBoss() ) {
// chance = BOSS_TICKET_CHANCE;
// } else if(!creature.IsWorldBoss() ) {
// chance = 100;
// } else {
// chance = NORMAL_TICKET_CHANCE;
// }
// // player level is greater then 7 levels than creature level
// const clevel = creature.GetLevel();
// const plevel = player.GetLevel();
// if(plevel >= (clevel + 7)) {
// return;
// }
// let darkmoon = [];
// const result = WorldDBQuery('SELECT eventEntry from game_event WHERE description like "%Darkmoon Faire%"');
// // A Darkmoon event has to be active.
// const events = GetActiveGameEvents();
// for(let i=0; i < result.GetRowCount(); i++) {
// const row = result.GetRow();
// darkmoon.push(row.eventEntry);
// result.NextRow();
// }
// let darkmoonActive = false;
// for(let event of events) {
// if(darkmoon.includes(event)) {
// darkmoonActive = true;
// }
// }
// // Roll for token drop
// let roll = Math.floor(Math.random() * 100);
// const pStats = new PlayerStats(player);
// if(roll <= chance) {
// if(creature.IsWorldBoss()) {
// player.AddItem(19182,100);
// player.SendChatMessageToPlayer(ChatMsg.CHAT_MSG_LOOT, Language.LANG_COMMON, `Congrats here are 100 tickets.`, player);
// pStats.increment(StatEvents.TICKETS_AWARDED,100);
// } else if(creature.IsDungeonBoss()) {
// player.AddItem(19182, 20);
// player.SendChatMessageToPlayer(ChatMsg.CHAT_MSG_LOOT, Language.LANG_COMMON, `You received a 20 Darkmoon tickets, lucky you!`, player);
// pStats.increment(StatEvents.TICKETS_AWARDED,20);
// } else {
// player.AddItem(19182,1);
// player.SendChatMessageToPlayer(ChatMsg.CHAT_MSG_LOOT, Language.LANG_COMMON, `You received a Darkmoon ticket, lucky you!`, player);
// pStats.increment(StatEvents.TICKETS_AWARDED,1);
// }
// }
}
// RegisterPlayerEvent(
// PlayerEvents.PLAYER_EVENT_ON_KILL_CREATURE,
// (...args) => TicketKill(...args)
// );

134
modules/items/tokens.ts Normal file
View File

@@ -0,0 +1,134 @@
import { PlayerStats, StatEvents } from "../classes/stats";
/**
* Configuration options
*/
const TOKEN_ROLL_CHANCE = 5;
const NORMAL_ROLL_CHANCE = 5;
const TOKEN_ROLL_CAP = 2000;
const TOKEN_GROUP_SIZE = 2;
const createChest = (player: Player, creature: Creature, direction: string): void => {
const [x,y,z,o] = creature.GetLocation();
let chestX,chestY,chestZ;
if(direction == 'center') {
chestX = x+0.5;
chestY = y+0.5;
chestZ = z;
} else if(direction == 'left') {
chestX = x+2.5;
chestY = y+2.5;
chestZ = z;
} else {
chestX = x+2.5;
chestY = y-3.5;
chestZ = z;
}
player.PlayDirectSound(7256); // Loud Chime
player.SummonGameObject(110000,chestX,chestY,chestZ,o, 0);
player.SummonGameObject(186246, chestX,chestY,chestZ+0.20,o, 100);
}
/**
* This creates a randomly dropping currency that rewards players who play together.
* It also has a small chance for doing harder content for solo players to get it, but based chance is on
* any other kill is 0.2%
*/
const TokenKillEvent: player_event_on_kill_creature = (event: number, player: Player, creature: Creature ) => {
// if the creature level is much lower then you have 0 chance to get a token.
if(creature.GetLevel() < (player.GetLevel() - 5)) {
return;
}
const map = creature.GetMap();
// Must be a group of real player 3(default) or more with bonus to drop rate for each player after 3, Condition (3)
const group = player.GetGroup();
let groupCount = 0;
if(group != undefined) {
const members = group.GetMembers();
for(let member of members) {
member.GetName();
groupCount += 1;
}
}
// PrintDebug(`Player: ${player.GetName()} is in a group of ${groupCount}`);
// Roll for token drop
let roll = Math.floor(Math.random() * TOKEN_ROLL_CAP);
let rollModifer = NORMAL_ROLL_CHANCE;
// Solo Drop rates
if(groupCount < TOKEN_GROUP_SIZE) {
if(creature.IsWorldBoss() || creature.IsDungeonBoss()) {
rollModifer += 30;
} else {
if(creature.IsElite()) {
rollModifer = rollModifer + TOKEN_ROLL_CHANCE;
}
}
if(map.IsRaid() || map.IsHeroic()) {
rollModifer = rollModifer + 20;
}
if(map.IsDungeon()) {
rollModifer = rollModifer + 5;
}
} else {
// Group Drop Rates
if(creature.IsWorldBoss() || creature.IsDungeonBoss()) {
rollModifer += 300;
} else {
if(creature.IsElite()) {
rollModifer = rollModifer + 30;
}
}
if(map.IsRaid() || map.IsHeroic()) {
rollModifer = rollModifer + (20 * groupCount);
} else {
if(map.IsDungeon()) {
rollModifer = rollModifer + (4 * groupCount);
}
}
}
PrintDebug(`Player: ${player.GetName()} Roll: ${roll} Roll Modifer: ${rollModifer} Chance: ${rollModifer}`)
if(roll <= rollModifer) {
createChest(player, creature, 'center');
// Add player stat they created token
const pStats = new PlayerStats(player);
pStats.increment(StatEvents.TOKEN_CREATED);
pStats.save();
}
// if it is a larger group roll again!
if(groupCount >= TOKEN_GROUP_SIZE) {
if(creature.IsWorldBoss() || creature.IsDungeonBoss()) {
roll = Math.floor(Math.random() * TOKEN_ROLL_CAP);
if(roll <= rollModifer+200 ) {
createChest(player, creature,'left');
}
}
}
}
RegisterPlayerEvent(
PlayerEvents.PLAYER_EVENT_ON_KILL_CREATURE,
(...args) => TokenKillEvent(...args)
);

View File

@@ -0,0 +1,10 @@
const TaintedKill: player_event_on_kill_creature = (event, player, creature) => {
if(creature.GetEntry() == 22009) {
player.AddItem(31088, 1);
}
};
RegisterPlayerEvent(
PlayerEvents.PLAYER_EVENT_ON_KILL_CREATURE,
(...args) => TaintedKill(...args)
);

0
modules/npcs/gambler.ts Normal file
View File

169
modules/npcs/soulswapper.ts Normal file
View File

@@ -0,0 +1,169 @@
import { ToGold, ToCopper, GetPlayerTax } from "../classes/money";
import { AccountInfo } from "../classes/account";
/**
* SoulSwapper GameObject/NPC
* This is a module that allows players to send soulbound items to alts on the same account.
*/
/**
* 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<string, number> = {};
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)
);

2481
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"scripts": {
"clean": "dotenv -e ets.env cross-var rimraf %ETS_BUILD_ROOT%",
"build": "ets build",
"dev": "npm run clean && ets build && npm run dev-copy",
"dev-copy": "dotenv -e ets.env cross-var ncp %ETS_BUILD_ROOT% %DEV_MODULE_PATH%",
"dev:watch": "ets build -w && npm run dev-copy"
},
"devDependencies": {
"cross-var": "^1.1.0",
"dotenv-cli": "^7.3.0",
"ncp": "^2.0.0",
"rimraf": "^5.0.5",
"typescript": "^5.2.2"
},
"dependencies": {
"ts-node": "^10.9.1",
"wow-eluna-ts-module": "^1.6.7"
}
}

29
plugins/aio-plugin.ts Normal file
View File

@@ -0,0 +1,29 @@
import * as ts from "typescript";
import * as tstl from "typescript-to-lua";
/**
* This plugin will add AIO to the transpile process from TS assuming
* there is a Global installation of AIO installed in the directory, which is
* that follows Rochet2's instructions.
*/
const plugin: tstl.Plugin = {
beforeEmit(
program: ts.Program,
options: tstl.CompilerOptions,
emitHost: tstl.EmitHost,
result: tstl.EmitFile[],
) {
for (const file of result) {
if(file.code.includes("aio = {}")) {
console.log(`installing AIO for this file ${file.outputPath}`);
file.code = file.code.replace("-- @ts-expect-error", "");
file.code = file.code.replace("aio = {}", "local AIO = AIO or require(\"AIO\")");
file.code = file.code.replace(/aio\./g, "AIO.");
}
}
}
};
export default plugin;

48
tsconfig.json Normal file
View File

@@ -0,0 +1,48 @@
{
"$schema": "./tstl.schema.json",
"compilerOptions": {
"strict": false,
"target": "esnext",
"outDir": "./dist",
"skipLibCheck": true,
"lib": [
"esnext",
"DOM"
],
"moduleResolution": "node",
"types": [
"lua-types/5.2",
"@typescript-to-lua/language-extensions",
"wow-eluna-ts-module",
"@araxiaonline/wow-wotlk-declarations"
],
"typeRoots": [
"./node_modules/wow-eluna-ts-module/types",
"./node_modules",
"./node_modules/@types",
"./node_modules/@araxiaonline/wow-wotlk-declarations"
],
"rootDir": "modules"
},
"include": [
"modules/**/*.ts"
],
"exclude": [
"node_modules",
"**/node_modules",
"test",
"**/*spec.ts",
"**/__tests__",
"**/__mocks__"
],
"tstl": {
"luaTarget": "5.2",
"noHeader": true,
"luaLibImport": "require",
"luaBundleEntry": "./modules/index.ts",
"luaPlugins": [
{ "name": "./plugins/aio-plugin.ts" },
]
}
}

111
tstl.schema.json Normal file
View File

@@ -0,0 +1,111 @@
{
"title": "tsconfig.json with TSTL",
"description": "JSON schema for the TypeScript compiler's configuration file with TSTL",
"$schema": "http://json-schema.org/draft-07/schema",
"allOf": [
{
"$ref": "https://json.schemastore.org/tsconfig"
}
],
"properties": {
"tstl": {
"description": "TypeScriptToLua compiler options.",
"type": "object",
"definitions": {
"//": {
"reference": "https://typescripttolua.github.io/docs/configuration#custom-options"
}
},
"properties": {
"buildMode": {
"description": "Use buildMode: \"library\" to build publishable library packages.",
"type": "string",
"default": "library",
"enum": ["default", "library"]
},
"extension": {
"description": "File extension for the resulting Lua files. Defaults to \".lua\"",
"type": "string"
},
"lua51AllowTryCatchInAsyncAwait": {
"description": "Disable the warning that try/catch is not allowed in async functions in Lua 5.1, in case you are using a patched 5.1 lua version that supports this.",
"type": "boolean",
"default": false
},
"luaBundle": {
"description": "The name of the lua file to bundle output lua to. Requires luaBundleEntry.",
"type": "string",
"default": ""
},
"luaBundleEntry": {
"description": "The entry *.ts file that will be executed when entering the luaBundle. Requires luaBundle.",
"type": "string"
},
"luaLibImport": {
"description": "Specifies how js standard features missing in lua are imported.",
"type": "string",
"default": "require",
"enum": ["none", "inline", "require", "require-minimal"]
},
"luaTarget": {
"description": "Specifies the Lua version you want to generate code for.",
"type": "string",
"default": "universal",
"enum": ["5.0", "universal", "5.1", "5.2", "5.3", "5.4", "JIT"]
},
"noImplicitGlobalVariables": {
"description": "Always declare all root-level variables as local, even if the file is not a module and they would be global in TypeScript.",
"type": "boolean",
"default": false
},
"noImplicitSelf": {
"description": "If true, treats all project files as if they were prefixed with\n/** @noSelfInFile **/.",
"type": "boolean",
"default": false
},
"noHeader": {
"description": "Specify if a header will be added to compiled files.",
"type": "boolean",
"default": false
},
"noResolvePaths": {
"description": "An array of import paths that should not be resolved but copied verbatim to output lua.",
"type": "array"
},
"sourceMapTraceback": {
"description": "Applies the source map to show source TS files and lines in error tracebacks.",
"default": false,
"type": "boolean"
},
"tstlVerbose": {
"description": "Give verbose tstl output, helpful when diagnosing tstl issues.",
"type": "boolean",
"default": false
},
"luaPlugins": {
"description": "List of TypeScriptToLua plugins.",
"type": "array",
"items": {
"description": "Describes TypeScriptToLua plugin",
"type": "object",
"required": ["name"],
"properties": {
"name": {
"description": "Path to the JS file, that contains the plugin code",
"type": "string"
},
"import": {
"type": "string"
}
}
}
},
"measurePerformance": {
"description": "Measure and report performance of the tstl compiler.",
"type": "boolean"
}
}
}
},
"allowTrailingCommas": true
}