diff --git a/TitanExampleLDB/Artwork/Starter.tga b/TitanExampleLDB/Artwork/Starter.tga new file mode 100644 index 0000000..6a7ddda Binary files /dev/null and b/TitanExampleLDB/Artwork/Starter.tga differ diff --git a/TitanExampleLDB/Starter.tga b/TitanExampleLDB/Starter.tga new file mode 100644 index 0000000..6a7ddda Binary files /dev/null and b/TitanExampleLDB/Starter.tga differ diff --git a/TitanExampleLDB/StarterLDB.lua b/TitanExampleLDB/StarterLDB.lua new file mode 100644 index 0000000..a9e5e1c --- /dev/null +++ b/TitanExampleLDB/StarterLDB.lua @@ -0,0 +1,288 @@ +--[[ +StarterLDB.lua +This is a simplistic example of a LDB (LibDataBroker) Addon. +It is assumed that you understand the Blizzard addon basics. +It is based loosely on the Titan Bag addon but is not Titan specific. + +It is not required that a display addon exist for your addon to run; Such as Titan :-) +However your addon will need to enable command line commands or the user will not be able to see or do anything. + +Enjoy! + +By: The Titan Development Team +--]] + +--[[ Folder Structure +NOTE: Before running this addon, the folder and file prefix must be the same to be considered for loading into WoW! +For example, to just run this as is, remove the 'Example' from the folder name then start or reload WoW. +This is explained in more detail below. + + +This plugin folder must be added to the Addon folder to be considered for loading into WoW. +Inside this folder you will notice : +- three .toc files +- one .lua file +- one .tga file. + +There are sites (wowhead or wow wiki as examples) that have deeper explainations on addon development. +Please use these sites for more general addon information. + +=== .toc +The folder and the .toc files MUST have the same name! +Sort of... the name prior to the underscore(_) must be the same. The name after that (postfix) has meaning to WoW. +WoW has three versions represented by the three postix values. +_Mainline : current retail version +_Wrath : Wrath of the Lich King version. +_Vanilla : Classic Era version +These values may change as the versions evolve, say Cata is added to Wrath. +Or they may not :). Years from now we may wonder why Wrath represents Dragonflight! + +If your plugin is only for Classic Era then delete or rename the 'mainline' and 'wrath' .toc files. +Changing the filename will prevent WoW from loading the addon into that version of the game. + +Titan uses this method. Notice Titan folder has no 'wrath' or 'vanilla' .toc. +TitanClassic has has both 'wrath' and 'vanilla' .toc but no 'mainline' .toc. +This allows Titan plugins intended for Classic versions to run without change. + +NOTE: The ## Interface value should match the current interface value of the coorsponding WoW version. +In BattleNet this typcially shown below the 'Play' button. +DragonFlight 10.02.05 is represented without dots - 100205 - in the .toc. + +If the interface value is close (but lower) WoW will complain that you are running 'older' addons. +If WoW finds a Classic Era value (say 11500) in a 'mainline' .toc, it will just ignore it (not load). +The reverse (a higher value) is true as well. + +=== .lua +This is the code for the plugin - and this file. + +=== .tga +This file is the icon used by the plugin. +It is specified in the .obj created for the LDB init routine. +WoW can use several different types of icons. This discussion is outside the scope of this example. + +=== libs +This file implementing the LDB functions. +See https://github.com/tekkub/libdatabroker-1-1/wiki/ for an API description. +LibDataBroker-1.1.lua at https://github.com/tekkub/libdatabroker-1-1/ +It *should* be included in the LDB compliant display addon. +It *should* included in the display addon. + + +Anyone can extract the code and art from WoW. This can be handy to get code examples. +And to grab icons to use for a plugin. My undestanding is any icon can be used within WoW without violating the ToS. +WoW icons tend to be .blp files. These files are NOT easy to look at or manipulate!! +You will need to research third party tools to manipulate .blp files. +--]] + +-- ******************************** Constants ******************************* +local ADDON_NAME = ... +-- Set the name we want in the global name space. Ensure the name is unique across all addons. +StarterLDB = {} + +local id = "LDBStarter"; -- What the user will see as the name +local addon = ADDON_NAME -- addon name / folder name / toc name + +-- Localized strings are outside the scope of this example. + +--[[ +The artwork path must start with Interface\\AddOns +Then the name of the plugin +Then any additional folder(s) to your artwork / icons. +--]] +local artwork_path = "Interface\\AddOns\\TitanLDB\\" + +-- Get data from the TOC file. +local version = tostring(GetAddOnMetadata(addon, "Version")) or "Unknown" +local author = GetAddOnMetadata(addon, "Author") or "Unknown" +-- NOTE: GetAddOnMetadata expects the addon name : +-- The addon folder name or .toc name needs to be the same. +-- ******************************** Variables ******************************* +local trace = false -- toggle to show / hide debug statements in this addon + +-- ******************************** Functions ******************************* +local function Debug(debug_message, debug_type) + if trace then + local dtype = "" + local time_stamp = "" + local msg = "" + if debug_type == "error" then + dtype = "Error: " + elseif debug_type == "warning" then + dtype = "Warning: " + end + if debug_type == "normal" then + time_stamp = "" + else + time_stamp = date("%H:%M:%S")..": " + end + + msg = + "Debug".." " + ..time_stamp + ..dtype + ..debug_message + + _G["DEFAULT_CHAT_FRAME"]:AddMessage(msg) + else + -- not requested + end + --date("%m/%d/%y %H:%M:%S") +end + +-- Calculate bag space then return text and icon to display +local function GetBagSlotInfo() + local totalSlots, usedSlots, availableSlots, icon + totalSlots = 0; + usedSlots = 0; + for bag = 0, 4 do + local size = C_Container.GetContainerNumSlots(bag); + if (size and size > 0) then + totalSlots = totalSlots + size; + local free = C_Container.GetContainerNumFreeSlots(bag) + local used = size - free + usedSlots = usedSlots + used; + end + end + availableSlots = totalSlots - usedSlots; + + local i,r = math.modf(availableSlots/2) + if (r == 0) then + icon = artwork_path.."Starter.tga" + else + icon = "Interface\\PetPaperDollFrame\\UI-PetHappiness" --PET_DISMISS_TEXTURE + end + + local bagText + bagText = format("%d/%d", availableSlots, totalSlots); + + bagText = HIGHLIGHT_FONT_COLOR_CODE..bagText..FONT_COLOR_CODE_CLOSE + + return bagText, icon +--]] +end + +-- Create the tooltip +local function LDB_OnTooltipShow(tooltip) + tooltip = tooltip or GameTooltip + local tt_str = "" + + tt_str = + GREEN_FONT_COLOR_CODE + ..id.." Info" + ..FONT_COLOR_CODE_CLOSE + tooltip:AddLine(tt_str) + + local text, icon = GetBagSlotInfo() + tt_str = "Available bag slots" + .." "..text.."\n" + .."\n".."Hint: Left-click to open all bags." + + tooltip:AddLine(tt_str) +end + +local function LDB_Init(LDB_frame) + Debug(id.." Init ..."); + --[[ + Initialize the Data Broker 'button'. + This is the heart of a LDB plugin. It determines how the display addon is to treat this addon. + + Setting the type is required so the LDB lib and display addon know what to do. See the LDB spec. + --]] + -- The .obj is the key link to the display addon!! + LDB_frame.obj = + LibStub("LibDataBroker-1.1"):NewDataObject(id, { + type = "data source", -- required + -- SDK: The two options are: + -- "data source" - A data source is expected to show some type of info + -- "launcher" - Expected to open another window or perform some action + + -- For some unknown reason, Classic Era shows a tiny icon... Wrath and Retail show fine... + icon = artwork_path.."Starter", -- The icon to display on the display addon + label = id, -- label is the text the user will use to find this addon in the display addon. + text = "nyl", -- will be updated later + OnTooltipShow = function(tooltip) + LDB_OnTooltipShow(tooltip) + end, + OnClick = function(self, button) + if ( button == "LeftButton" ) then + -- Just a simple action to illustrate an LDB addon. + ToggleAllBags(); + elseif ( button == "RightButton" ) then + -- There is no action to take in this example. + --[[ Add code here if your addon needs to do something on right click. + Typically an options menu which is outside the scope of this example. + --]] + end + end, + }) + Debug(id.." Init fini."); +--]===] +end + +-- Update the Bags Data Broker 'button' +local function LDB_Update(LDB_frame) + local text, icon = GetBagSlotInfo() + LDB_frame.obj.text = text + LDB_frame.obj.icon = icon +end + +-- Parse events registered to plugin and act on them +local function Button_OnEvent(self, event, ...) + Debug("OnEvent" + .." "..tostring(event).."" + ) + if (event == "PLAYER_ENTERING_WORLD") then + -- Do any additional set up needed + -- + print("" + .." "..tostring(id).."" + .." "..tostring(version).."" + .." by "..tostring(author).."" + ) + + -- Now that events have settled, register the one(s) we really want. + -- This may not be needed but it could reduce churn and possible timing issues. + self:RegisterEvent("BAG_UPDATE"); + + -- Unregister events no longer needed. + -- Good practice although this event is only fired on login + self:UnregisterEvent("PLAYER_ENTERING_WORLD"); + end + if event == "BAG_UPDATE" then + LDB_Update(self) + end +end + +-- ====== Create needed frames +local function Create_Frames() + -- general container frame + -- The frame pointer is passed as a parameter rather than an addon local. + local window = CreateFrame("Frame", "StarterLDBExample", UIParent) +-- window:Hide() + + -- Set strata as desired + window:SetFrameStrata("FULLSCREEN") + -- Using SetScript("OnLoad", does not work + + window:SetScript("OnEvent", function(self, event, ...) + Button_OnEvent(self, event, ...) + end) + + -- Tell Blizzard this frame needs player entering world event. + window:RegisterEvent("PLAYER_ENTERING_WORLD"); + + -- Any other addon specific "on load" code here + LDB_Init(window) + -- Update the text (bag numbers) + LDB_Update(window) + + -- shamelessly print a load message to chat window + DEFAULT_CHAT_FRAME:AddMessage( + GREEN_FONT_COLOR_CODE + ..addon..id.." "..version + .." by " + ..FONT_COLOR_CODE_CLOSE + .."|cFFFFFF00"..author..FONT_COLOR_CODE_CLOSE); +end + +Create_Frames() -- do the work diff --git a/TitanExampleLDB/TitanLDB_Mainline.toc b/TitanExampleLDB/TitanLDB_Mainline.toc new file mode 100644 index 0000000..44af1ac --- /dev/null +++ b/TitanExampleLDB/TitanLDB_Mainline.toc @@ -0,0 +1,12 @@ +## Interface: 100205 +## Title: StarterLDB +## Notes: LDB example +## Author: Titan Dev Team +## DefaultState: Enabled +## SavedVariables: +## OptionalDeps: +## Dependencies: +## Version: 1.1 +## X-Child-Of: +libs/LibDataBroker-1.1.lua +StarterLDB.lua diff --git a/TitanExampleLDB/TitanLDB_Vanilla.toc b/TitanExampleLDB/TitanLDB_Vanilla.toc new file mode 100644 index 0000000..a294486 --- /dev/null +++ b/TitanExampleLDB/TitanLDB_Vanilla.toc @@ -0,0 +1,12 @@ +## Interface: 11500 +## Title: StarterLDB +## Notes: LDB example +## Author: Titan Dev Team +## DefaultState: Enabled +## SavedVariables: +## OptionalDeps: +## Dependencies: +## Version: 1.1 +## X-Child-Of: +libs/LibDataBroker-1.1.lua +StarterLDB.lua diff --git a/TitanExampleLDB/TitanLDB_Wrath.toc b/TitanExampleLDB/TitanLDB_Wrath.toc new file mode 100644 index 0000000..40aa326 --- /dev/null +++ b/TitanExampleLDB/TitanLDB_Wrath.toc @@ -0,0 +1,12 @@ +## Interface: 30403 +## Title: StarterLDB +## Notes: LDB example +## Author: Titan Dev Team +## DefaultState: Enabled +## SavedVariables: +## OptionalDeps: +## Dependencies: +## Version: 1.1 +## X-Child-Of: +libs/LibDataBroker-1.1.lua +StarterLDB.lua diff --git a/TitanExampleLDB/libs/LibDataBroker-1.1.lua b/TitanExampleLDB/libs/LibDataBroker-1.1.lua new file mode 100644 index 0000000..f47c0cd --- /dev/null +++ b/TitanExampleLDB/libs/LibDataBroker-1.1.lua @@ -0,0 +1,90 @@ + +assert(LibStub, "LibDataBroker-1.1 requires LibStub") +assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0") + +local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4) +if not lib then return end +oldminor = oldminor or 0 + + +lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib) +lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {} +local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks + +if oldminor < 2 then + lib.domt = { + __metatable = "access denied", + __index = function(self, key) return attributestorage[self] and attributestorage[self][key] end, + } +end + +if oldminor < 3 then + lib.domt.__newindex = function(self, key, value) + if not attributestorage[self] then attributestorage[self] = {} end + if attributestorage[self][key] == value then return end + attributestorage[self][key] = value + local name = namestorage[self] + if not name then return end + callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self) + callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self) + callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self) + callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self) + end +end + +if oldminor < 2 then + function lib:NewDataObject(name, dataobj) + if self.proxystorage[name] then return end + + if dataobj then + assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table") + self.attributestorage[dataobj] = {} + for i,v in pairs(dataobj) do + self.attributestorage[dataobj][i] = v + dataobj[i] = nil + end + end + dataobj = setmetatable(dataobj or {}, self.domt) + self.proxystorage[name], self.namestorage[dataobj] = dataobj, name + self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj) + return dataobj + end +end + +if oldminor < 1 then + function lib:DataObjectIterator() + return pairs(self.proxystorage) + end + + function lib:GetDataObjectByName(dataobjectname) + return self.proxystorage[dataobjectname] + end + + function lib:GetNameByDataObject(dataobject) + return self.namestorage[dataobject] + end +end + +if oldminor < 4 then + local next = pairs(attributestorage) + function lib:pairs(dataobject_or_name) + local t = type(dataobject_or_name) + assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)") + + local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name + assert(attributestorage[dataobj], "Data object not found") + + return next, attributestorage[dataobj], nil + end + + local ipairs_iter = ipairs(attributestorage) + function lib:ipairs(dataobject_or_name) + local t = type(dataobject_or_name) + assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)") + + local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name + assert(attributestorage[dataobj], "Data object not found") + + return ipairs_iter, attributestorage[dataobj], 0 + end +end diff --git a/TitanExamplePlugin/TitanPlugin_Mainline.toc b/TitanExamplePlugin/TitanPlugin_Mainline.toc new file mode 100644 index 0000000..2b4dc88 --- /dev/null +++ b/TitanExamplePlugin/TitanPlugin_Mainline.toc @@ -0,0 +1,8 @@ +## Interface: 100205 +## Title: Titan Starter +## Version: 1.0.0 +## Notes: Adds bag and free slot information to Titan Panel +## Author: Titan Panel Development Team (http://www.titanpanel.org) +## SavedVariables: +## Dependencies: Titan +TitanStarter.lua diff --git a/TitanExamplePlugin/TitanPlugin_Vanilla.toc b/TitanExamplePlugin/TitanPlugin_Vanilla.toc new file mode 100644 index 0000000..d781ee4 --- /dev/null +++ b/TitanExamplePlugin/TitanPlugin_Vanilla.toc @@ -0,0 +1,9 @@ +## Interface: 11500 +## Title: TitanStarter +## Version: 1.0.0 +## Notes: Adds bag and free slot information to Titan Panel +## Author: Titan Panel Development Team (http://www.titanpanel.org) +## SavedVariables: +## OptionalDeps: +## Dependencies: TitanClassic +TitanStarter.lua diff --git a/TitanExamplePlugin/TitanPlugin_Wrath.toc b/TitanExamplePlugin/TitanPlugin_Wrath.toc new file mode 100644 index 0000000..4f74b27 --- /dev/null +++ b/TitanExamplePlugin/TitanPlugin_Wrath.toc @@ -0,0 +1,9 @@ +## Interface: 30403 +## Title: Titan Starter +## Version: 1.0.0 +## Notes: Adds bag and free slot information to Titan Panel +## Author: Titan Panel Development Team (http://www.titanpanel.org) +## SavedVariables: +## OptionalDeps: +## Dependencies: TitanClassic +TitanStarter.lua diff --git a/TitanExamplePlugin/TitanStarter.lua b/TitanExamplePlugin/TitanStarter.lua new file mode 100644 index 0000000..a46b79a --- /dev/null +++ b/TitanExamplePlugin/TitanStarter.lua @@ -0,0 +1,839 @@ +--[[ +-- ************************************************************************** +-- * TitanStarter.lua +-- * +-- * By: The Titan Panel Development Team +-- ************************************************************************** +--]] +--[[ Example +This is an example Titan (Titen Panel) plugin based on Titan Bag. +It is intended to introduce Titan plugin essentials by using a basic addon +with a lot of comments. This can be run as is then changed to implement your great idea! + +NOTE: The terms addon and plugin are essentially the same. +Here plugin is used when the addon is displayed by Titan. +--]] + +--[[ Folder Structure +NOTE: Before running this addon, the folder and file prefix must be the same to be considered for loading into WoW! +For example to just run this as is, remove the 'Example' from the folder name then start or reload WoW. +This is explained in more detail below. + + +This plugin folder must be added to the Addon folder to be considered for loading into WoW. +Inside this folder you will notice : +- three .toc files +- one .lua file +- one .tga file. + +There are sites (wowhead or wow wiki as examples) that have deeper explainations on addon development. +Please use these sites for more general addon information. + +=== .toc +The folder and the .toc files MUST have the same name! +Sort of... the name prior to the underscore(_) must be the same. The name after that (postfix) has meaning to WoW. +WoW has three versions represented by the three postix values. +_Mainline : current retail version +_Wrath : Wrath of the Lich King version. +_Vanilla : Classic Era version +These values may change as the versions evolve, say Cata is added to Wrath. +Or they may not :). Years from now we may wonder why Wrath represents Dragonflight! + +If your plugin is only for Classic Era then delete or rename the 'mainline' and 'wrath' .toc files. +Changing the filename will prevent WoW from loading the addon into that version of the game. + +Titan uses this method. Notice Titan folder has no 'wrath' or 'vanilla' .toc. +TitanClassic has has both 'wrath' and 'vanilla' .toc but no 'mainline' .toc. +This allows Titan plugins intended for Classic versions to run without change. + +NOTE: The ## Interface value should match the current interface value of the coorsponding WoW version. +In BattleNet this typcially shown below the 'Play' button. +DragonFlight 10.02.05 is represented without dots - 100205 - in the .toc. + +If the interface value is close (but lower) WoW will complain that you are running 'older' addons. +If WoW finds a Classic Era value (say 11500) in a 'mainline' .toc, it will just ignore it (not load). +The reverse (a higher value) is true as well. + +=== .lua +This is the code for the plugin - and this file. + +=== .tga +This file is the icon used by the plugin. +It is specified in the .registry used by Titan. +WoW can use several different types of icons. This discussion is outside the scope of this example. + +Anyone can extract the code and art from WoW. This can be handy to get code examples. +And to grab icons to use for a plugin. My undestanding is any icon can be used within WoW without violating the ToS. +WoW icons tend to be .blp files. These files are NOT easy to look at or manipulate!! +You will need to research third party tools to manipulate .blp files. +--]] + +--[[ Editting +This example may seem daunting at first. We decided to leave Bag as is to give plenty of coding examples. + +GetBagData will be replaced by your code. It uses IsProfessionBagID. +ToggleBags is called from OnClick. This will be replaced by your code. + +TitanPanelRightClickMenu_PrepareStarterMenu will need its name changed. + +The other routines will be modified to impement your idea. +--]] + +--[[ Code flow +NOTE: The .toc states Titan is required [## Dependencies: Titan]. +WoW insures Titan is loaded BEFORE this addon. + +First step: ==== Starting WoW +Wow will 'load' this addon. Any code outside the Lua functions will be run. +- local VERSION = GetAddOnMetadata(add_on, "Version") will run GetAddOnMetadata +- bag_info will be populated +- ... +- Create_Frames is called + +Create_Frames will create the addon frame - TITAN_BUTTON - and call local OnLoad. +Create_Frames also sets the event scripts for the addon. +This is how WoW and Titan interact with this addon. +NOTE: The frame is created using a Titan template. This template handles some frame processing. + +OnLoad does two important things +1) Sets the .registry of the addon - See the .registry comment block +2) Registers for PLAYER_ENTERING_WORLD +NOTE: OnLoad should be small and not assume data is ready yet. + +Next: ==== Waiting for WoW +WoW fires a bunch of events as this and other addons are loaded. + +Eventually the game and all addons are loaded and this plugin receives the +PLAYER_ENTERING_WORLD event via OnEvent. +When processing PLAYER_ENTERING_WORLD only register for events and access local data. + +Titan also receives the PLAYER_ENTERING_WORLD event. There is NO guarantee this plugin +receives the event before or after Titan! +Titan then processes its own data and saved variables. +Titan will process and set any saved variables specified in the .registry of this plugin! +Then Titan starts registering plugins using each addon .registry. +It also loads any LDB plugins found. + +Next: ==== Still waiting for WoW +Titan shows the user requested bars with plugins. +OnShow is called by Titan when placing this plugin on a bar or the user requests the plugin to be shown. +Technically Titan calls the now registered frame - TITAN_BUTTON - :Show +then WoW calls the frame script :OnShow eventually calling OnShow in this addon set by Create_Frames. + +NOTE: Do not assume any saved variables in .registry are ready until OnShow! + +OnShow now does all processing to set up this plugin properly. Once done it calls TitanPanelButton_UpdateButton. +Now the plugin is ready for the user. + +Next: ==== Ready to play WoW +The plugin is now 'idle' until : +- Titan calls TitanPanelButton_UpdateButton(TITAN_PLUGIN) to (re)display this plugin +- Any registered event is received +- A timer or callback calls the routine - via Titan or this plugin code + +TitanPanelButton_UpdateButton(TITAN_PLUGIN) called when: +- User does a reload or user action - via Titan +- User changes a plugin option using Titan menu +- User changes a plugin option using Titan configuration +- A Titan timer expires such as auto hide bar is active or pet battle or ... + +Events occur when: +- User clicks on this plugin to take an action or show menu - OnClick +- User mouses over this plugin to display tool tip - OnEnter +- Any registered event is received - OnEvent +- Your timer expires - be very careful here! :) +- Your callback happens such as waiting for WoW server info + +OnClick is the TITAN_BUTTON frame script. +Left click is procssed by this plugin. +Right click is handled by the Titan template to invoke plugin menu (TitanPanelRightClickMenu_PrepareStarterMenu) + +OnEnter and OnLeave are the TITAN_BUTTON frame scripts. +OnEnter is handled by the Titan template to show the tool tip (.registry.tooltipTextFunction). +OnLeave is handled by the Titan template to hide the tool tip. + +Next: ==== +The above step continues until: +- User hides the plugin +- The user logs out the character or exits WoW + +Either action causes OnHide to be called. +OnHide should : +- Do any cleanup +- Stop any timers +- Unregister any events +These steps keep processing to a minimum and reduces the chance of errors to the user. + +NOTE: Any saved variables - in .registry or specific to this plugin - are saved by WoW on logout or exit. +No specific actions are required. + +--]] + +--[[ Structure +The plugin starts with local constants used throughout. +Then local variables; then functions. + +The design uses local functions sa much as practical. Mainly for clarity and a small speed increase. +Titan specific details will be noted and explained. +The comments will specify where names are important. +Any local routine or variable with 'bag' in the name can be changed to your design or deleted. + +Suggested path is start with Create_Frames at the bottom of the file. It is where the magic starts. + +Note: Feel free to look at Titan code. Pay attention to the comments. Any routine labeled as API +can be used by a plugin. API routines will be maintained and the functionality expected to remain stable. +Any parameter or functionality changes to API will be 'broadcast' on delivery sites and our sites (Discord, our web page). + +Of course feel free to reach out on our Discord site with any questions or suggestions! +--]] + +--[[ .registry +This is the GUTS of a Titan plugin. The .registry on the frame contains all +the info to register / create a plugin. + +Every registry with an id should appear in Titan > Configuration > Attempts. +Info is shown there along with pass / fail. +If the plugin failed to register, the error is shown there. + +Attributes: +.id : Required : must be unique across plugins. If there are duplicates, the first one 'wins'. +.category : The list is in TITAN_PANEL_BUTTONS_PLUGIN_CATEGORY (TitanGlobal.lua) + "Built-ins" is reserved for plugins that Titan releases. +.version : plugin version sown in menus and config. +.menuText : Used by as the title for the right click menu. + NOTE : Titan builds the function name rather than it being listed in the registery. + It is expected to be global and named "TitanPanelRightClickMenu_Prepare"<id>"Menu" + TitanPanelRightClickMenu_PrepareStarterMenu in this example. +.buttonTextFunction : This is called whenever the button is to be updated - TitanPanelButton_UpdateButton(TITAN_PLUGIN) + This is called from within the the plugin and from Titan core. + The specified routine is expected to be global. + It is called securely (pcall) so a plugin will not crash Titan (hopefully). + Titan will usually show "<?>" if the routine dies. + If this occurs and you need to see the error, search for this attribute in Titan folder and + uncomment the print of the error message. +.tooltipTitle : Used as the title for the tool tip +.tooltipTextFunction : This is called when OnEnter of the plugin frame is triggered. + It is expected to be global. + It is called securely (pcall) so a plugin will not crash Titan (hopefully). + Titan will usually show part of the error in the tool tip if the routine dies. +.icon : Allowed path to the icon to be used. It is recommended to store the icon in the plugin folder, + even if copied from WoW folder. +.iconWidth : Best left at 16... +.notes : This is shown in Titan > Config > Plugins when this plugin is selected. +.controlVariables : This list is used by TitanPanelRightClickMenu_AddControlVars(TITAN_PLUGIN) + true : Will be included in the menu + false : Will not be included in the menu +.savedVariables : These are the variables stored in Titan saved variables. + The initial values are used only if that particular is 'new' to that character (new Titan install, new character). + If a value is removed then it is removed from the saved variables as Titan is run for each character. + +--]] + +-- ******************************** Constants ******************************* +local add_on = ... +local _G = getfenv(0); +local artwork_path = "Interface\\AddOns\\TitanPlugin\\" +-- NOTE: This is the plugin id which should be unique across Titan plugins +-- It does not need to match the addon id. +local TITAN_PLUGIN = "Starter" +-- NOTE: The convention is TitanPanel <id> Button +local TITAN_BUTTON = "TitanPanel"..TITAN_PLUGIN.."Button" +local VERSION = GetAddOnMetadata(add_on, "Version") + +local TITAN_BAG_THRESHOLD_TABLE = { + Values = { 0.5, 0.75, 0.9 }, + Colors = { HIGHLIGHT_FONT_COLOR, NORMAL_FONT_COLOR, ORANGE_FONT_COLOR, RED_FONT_COLOR }, +} +local updateTable = {TITAN_PLUGIN, TITAN_PANEL_UPDATE_BUTTON}; +-- ******************************** Variables ******************************* +local AceTimer = LibStub("AceTimer-3.0") +local L = LibStub("AceLocale-3.0"):GetLocale(TITAN_ID, true) +local BagTimer + +local bag_info = { -- itemType : warcraft.wiki.gg/wiki/itemType + [1] = -- Soul bag + { color = {r=0.96,g=0.55,b=0.73}}, -- PINK + [2] = -- HERBALISM = + { color = {r=0,g=1,b=0}}, -- GREEN + [3] = -- ENCHANTING = + { color = {r=0,g=0,b=1}}, -- BLUE + [4] = -- ENGINEERING = + { color = {r=1,g=0.49,b=0.04}}, -- ORANGE + [5] = -- JEWELCRAFTING = + { color = {r=1,g=0,b=0}}, -- RED + [6] = -- MINING = + { color = {r=1,g=1,b=1}}, -- WHITE + [7] = -- LEATHERWORKING = + { color = {r=0.78,g=0.61,b=0.43}}, -- TAN + [8] = -- INSCRIPTION = + { color = {r=0.58,g=0.51,b=0.79}}, -- PURPLE + [9] = -- FISHING = + { color = {r=0.41,g=0.8,b=0.94}}, -- LIGHT_BLUE + [10] = -- COOKING = + { color = {r=0.96,g=0.55,b=0.73}}, -- PINK + -- These are Classic arrow or bullet bags + [22] = -- Classic arrow + { color = {r=1,g=.4,b=0}}, -- copper + [23] = -- Classic bullet + { color = {r=0.8,g=0.8,b=0.8}}, -- silver +} + +local trace = false -- true / false Make true when debug output is needed. + +local MIN_BAGS = 0 +local MAX_BAGS = Constants.InventoryConstants.NumBagSlots +local bag_data = {} -- to hold the user bag data + +-- ******************************** Functions ******************************* +local function IsProfessionBagID(slot) + -- The info needed is available using GetItemInfoInstant; only the bag / item id is required + -- itemType : warcraft.wiki.gg/wiki/itemType + local res = false + local style = "" + local info, itemId, itemType, itemSubType, itemEquipLoc, itemTexture, classID, subclassID + local inv_id = C_Container.ContainerIDToInventoryID(slot) + + if inv_id == nil then + -- Only works on bag and bank bags NOT backpack! + else + info = GetInventoryItemLink("player", inv_id) + itemId, itemType, itemSubType, itemEquipLoc, itemTexture, classID, subclassID = GetItemInfoInstant(info) + style = subclassID +--[[ +TitanDebug("T isP 0:" + .." "..tostring(slot).."" + .." "..tostring(itemId).."" + .." '"..tostring(itemType).."'" + .." '"..tostring(itemSubType).."'" + .." "..tostring(itemEquipLoc).."" + .." '"..tostring(itemTexture).."'" + .." "..tostring(classID).."" + .." "..tostring(subclassID).."" + ) +--]] + if classID == 1 then -- is a container / bag + if subclassID >= 1 then + -- profession bag of some type [2 - 10] Jan 2024 (DragonFlight / Wrath / Classic Era) + -- OR soul bag [1] + res = true + else + -- is a arrow or bullet; only two options + end + elseif classID == 6 then -- is a 'projectile' holder + res = true + -- is a ammo bag or quiver; only two options + elseif classID == 11 then -- is a 'quiver'; Wrath and CE + res = true + -- is a ammo pouch or quiver; only two options + style = subclassID + 20 -- change to get local color for name + else + -- not a bag + end + end + + if trace then + TitanDebug("T isP:" + .." "..tostring(res).."" + .." "..tostring(style).."" + .." "..tostring(itemId).."" + .." "..tostring(classID).."" + .." "..tostring(subclassID).."" + .." "..tostring(inv_id).."" + ) + end + + return res, style +end + +local function ToggleBags() + if TitanGetVar(TITAN_PLUGIN, "OpenBags") then + ToggleAllBags() + else + end +end + +--[[ +Where the magic happens! +It is good practice - and good memory - to document the 'why' the code does what it does. +And give details that are not obvious to the reader who did not write the code. +--]] +local function GetBagData(id) + --[[ + The bag name is not always available when player entering world + Grabbing the total slots is available though to determine if a bag exists. + The user may see bag name as UNKNOWN until an event triggers a bag check AND the name is available. + --]] + + local total_slots = 0 + local total_free = 0 + local total_used = 0 + + local count_prof = TitanGetVar(TITAN_PLUGIN, "CountProfBagSlots") + + for bag_slot = MIN_BAGS, MAX_BAGS do -- assuming 0 (Backpack) will not be a profession bag + local slots = C_Container.GetContainerNumSlots(bag_slot) + + -- Ensure a blank structure exists + bag_data[bag_slot] = { + has_bag = false, + name = "", + maxi_slots = 0, + free_slots = 0, + used_slots = 0, + style = "", + color = "", + } + + if slots > 0 then + bag_data[bag_slot].has_bag = true + + local bag_name = (C_Container.GetBagName(bag_slot) or UNKNOWN) + bag_data[bag_slot].name = bag_name + bag_data[bag_slot].maxi_slots = slots + + local free = C_Container.GetContainerNumFreeSlots(bag_slot) + local used = slots - free + bag_data[bag_slot].free_slots = free + bag_data[bag_slot].used_slots = used + + -- some info is not known until the name is available... + -- The API requires name to get the bag ID. + local bag_type = "none" + local color = {r=0,g=0,b=0} -- black (should never be used...) + + if bag_name == UNKNOWN then + -- name not available yet + else + end + + -- Jan 2024 : Moved away from named based to an id based. Allows name to come later from server + local is_prof_bag, style = IsProfessionBagID(bag_slot) +--[[ +if trace then +TitanDebug("T Bag...:" +.." "..tostring(bag_slot).."" +.." "..tostring(is_prof_bag).."" +.." '"..tostring(style).."'" +.." "..tostring(itemClassID).."" +.." "..tostring(itemSubClassID).."" +) +end +--]] + if is_prof_bag then + color = bag_info[style].color + bag_type = "profession" + else + bag_type = "normal" + end + bag_data[bag_slot].style = bag_type + bag_data[bag_slot].color = color + + -- add to total + if bag_data[bag_slot].style == "profession" then + if count_prof then + total_slots = total_slots + slots + total_free = total_free + free + total_used = total_used + used + else + -- ignore in totals + end + else + total_slots = total_slots + slots + total_free = total_free + free + total_used = total_used + used + end + else + bag_data[bag_slot].has_bag = false + end + + if trace then + TitanDebug("T info" + .." "..tostring(bag_slot).."" + .." ?:"..tostring(bag_data[bag_slot].has_bag).."" + .." max: "..tostring(bag_data[bag_slot].maxi_slots).."" + .." used: "..tostring(bag_data[bag_slot].used_slots).."" + .." free: "..tostring(bag_data[bag_slot].free_slots).."" + .." type: "..tostring(bag_data[bag_slot].style).."" + .." count: "..tostring(count_prof).."" + .." '"..tostring(bag_data[bag_slot].name).."'" + ) + end + end + + bag_data.total_slots = total_slots + bag_data.total_free = total_free + bag_data.total_used = total_used + + local bagText = "" + if (TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots")) then + bagText = format(L["TITAN_BAG_FORMAT"], total_used, total_slots); + else + bagText = format(L["TITAN_BAG_FORMAT"], total_free, total_slots); + end + + local bagRichText = "" + if ( TitanGetVar(TITAN_PLUGIN, "ShowColoredText") ) then + local color = "" + color = TitanUtils_GetThresholdColor(TITAN_BAG_THRESHOLD_TABLE, total_used / total_slots); + bagRichText = TitanUtils_GetColoredText(bagText, color); + else + bagRichText = TitanUtils_GetHighlightText(bagText); + end + + bagRichText = bagRichText --..bagRichTextProf[1]..bagRichTextProf[2]..bagRichTextProf[3]..bagRichTextProf[4]..bagRichTextProf[5]; + + return L["TITAN_BAG_BUTTON_LABEL"], bagRichText +end + +function TitanPanelBagButton_GetTooltipText() + local totalSlots, usedSlots, availableSlots; + local returnstring = ""; + + if trace then + TitanDebug("TS tool tip" + .." detail "..tostring(TitanGetVar(TITAN_PLUGIN, "ShowDetailedInfo")).."" + .." used "..tostring(TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots")).."" + .." prof "..tostring(TitanGetVar(TITAN_PLUGIN, "CountProfBagSlots")).."" + .." color "..tostring(TitanGetVar(TITAN_PLUGIN, "ShowColoredText")).."" + .." open "..tostring(TitanGetVar(TITAN_PLUGIN, "OpenBags")).."" + ) + end + + if TitanGetVar(TITAN_PLUGIN, "ShowDetailedInfo") then + returnstring = "\n"; + if TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots") then + returnstring = returnstring..TitanUtils_GetNormalText(L["TITAN_BAG_MENU_TEXT"]) + ..":\t"..TitanUtils_GetNormalText(L["TITAN_BAG_USED_SLOTS"])..":\n"; + else + returnstring = returnstring..TitanUtils_GetNormalText(L["TITAN_BAG_MENU_TEXT"]) + ..":\t"..TitanUtils_GetNormalText(L["TITAN_BAG_FREE_SLOTS"])..":\n"; + end + + for bag = MIN_BAGS, MAX_BAGS do + local bagText, bagRichText, color; +--[[ +TitanDebug("T Bag: TT" +.." "..tostring(bag).."" +.." "..tostring(bag_data[bag].has_bag).."" +.." "..tostring(bag_data[bag].name).."" +.." "..tostring(bag_data[bag].maxi_slots).."" +.." "..tostring(bag_data[bag].used_slots).."" +.." "..tostring(bag_data[bag].free_slots).."" +) +--]] + if bag_data[bag] and bag_data[bag].has_bag then + if (TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots")) then + bagText = format(L["TITAN_BAG_FORMAT"], bag_data[bag].used_slots, bag_data[bag].maxi_slots); + else + bagText = format(L["TITAN_BAG_FORMAT"], bag_data[bag].free_slots, bag_data[bag].maxi_slots); + end + + if bag_data[bag].style == "profession" + and not TitanGetVar(TITAN_PLUGIN, "CountProfBagSlots") + then + bagRichText = "|cffa0a0a0"..bagText.."|r" -- show as gray + elseif ( TitanGetVar(TITAN_PLUGIN, "ShowColoredText") ) then + if bag_data[bag].maxi_slots == 0 then + color = TitanUtils_GetThresholdColor(TITAN_BAG_THRESHOLD_TABLE, 1 ); + else + color = TitanUtils_GetThresholdColor(TITAN_BAG_THRESHOLD_TABLE, bag_data[bag].used_slots / bag_data[bag].maxi_slots); + end + bagRichText = TitanUtils_GetColoredText(bagText, color); + else + -- use without color + bagRichText = TitanUtils_GetHighlightText(bagText); + end + + local name_text = bag_data[bag].name + if bag_data[bag].style == "profession" + then + name_text = TitanUtils_GetColoredText(name_text, bag_data[bag].color) + else + -- use without color + end + returnstring = returnstring..name_text.."\t"..bagRichText.."\n"; + else + returnstring = returnstring..NONE.."\n"; + end + end + returnstring = returnstring.."\n"; + end + + if TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots") then + local xofy = ""..tostring(bag_data.total_used) + .."/"..tostring(bag_data.total_slots).."\n" + returnstring = returnstring..TitanUtils_GetNormalText(L["TITAN_BAG_USED_SLOTS"]) + ..":\t"..xofy + else + local xofy = ""..tostring(bag_data.total_free) + .."/"..tostring(bag_data.total_slots).."\n" + returnstring = returnstring..TitanUtils_GetNormalText(L["TITAN_BAG_USED_SLOTS"]) + ..":\t"..xofy + end + + -- Add Hint + if TitanGetVar(TITAN_PLUGIN, "OpenBags") then + returnstring = returnstring..TitanUtils_GetGreenText(L["TITAN_BAG_TOOLTIP_HINTS"]) + else + -- nop + end + return returnstring +end + +local function OnLoad(self) + local notes = "" + .."Adds bag and free slot information to Titan Panel.\n" +-- .."- xxx.\n" + self.registry = { + id = TITAN_PLUGIN, + category = "Information", + version = VERSION, + menuText = L["TITAN_BAG_MENU_TEXT"], + buttonTextFunction = "TitanPanelBagButton_GetButtonText", + tooltipTitle = L["TITAN_BAG_TOOLTIP"], + tooltipTextFunction = "TitanPanelBagButton_GetTooltipText", + icon = artwork_path.."TitanStarter", + iconWidth = 16, + notes = notes, + controlVariables = { + ShowIcon = true, + ShowLabelText = true, + ShowColoredText = true, + DisplayOnRightSide = true, + }, + savedVariables = { + ShowUsedSlots = 1, + ShowDetailedInfo = false, + CountProfBagSlots = false, + ShowIcon = 1, + ShowLabelText = 1, + ShowColoredText = 1, + DisplayOnRightSide = false, + OpenBags = false, + OpenBagsClassic = "new_install", + } + }; + if TITAN_ID == "Titan" then -- 10.* / Retail + -- for taint issue + self.registry.savedVariables.OpenBags = false + else + -- does not taint so default to open bags on click + self.registry.savedVariables.OpenBags = true + end + + self:RegisterEvent("PLAYER_ENTERING_WORLD"); + + if trace then + TitanDebug("TS OnLoad" + .." complete" + ) + end +end + +local function OnEvent(self, event, a1, a2, ...) + if trace then + TitanDebug("TS event" + .." "..tostring(event).."" + .." "..tostring(a1).."" + ) + end + + if event == "PLAYER_ENTERING_WORLD" then + if a1 == true and TITAN_ID == "Titan" then -- 10.* / Retail + -- initial login + + TitanPrint(L["TITAN_BAG_TAINT_TEXT"], "warning") + else -- either Classic version + local open = TitanGetVar(TITAN_PLUGIN, "OpenBagsClassic") + if open == "new_install" then -- + -- set to default behavior of opening bag on left click + TitanSetVar(TITAN_PLUGIN, "OpenBags", true) + TitanSetVar(TITAN_PLUGIN, "OpenBagsClassic", "processed") -- don't do again + else + -- already processed... + end + end + end + + if event == "BAG_UPDATE" then + -- update the plugin text + TitanPanelButton_UpdateButton(TITAN_PLUGIN); + end +end + +local function OnClick(self, button) + if trace then + TitanDebug("TS click" + .." "..tostring(button).."" + ) + end + + if (button == "LeftButton") then + ToggleBags(); + end +end + +function TitanPanelBagButton_GetButtonText(id) + local strA, strB = GetBagData(id) + return strA, strB +end + +function TitanPanelRightClickMenu_PrepareStarterMenu() + if trace then + TitanDebug("TS event" + .." "..tostring(TitanPanelRightClickMenu_GetDropdownLevel()).."" + .." '"..tostring(TitanPanelRightClickMenu_GetDropdMenuValue()).."'" + ) + end + local info + -- level 2 + if TitanPanelRightClickMenu_GetDropdownLevel() == 2 then + if TitanPanelRightClickMenu_GetDropdMenuValue() == "Options" then + TitanPanelRightClickMenu_AddTitle(L["TITAN_PANEL_OPTIONS"], TitanPanelRightClickMenu_GetDropdownLevel()) + info = {}; + info.text = L["TITAN_BAG_MENU_SHOW_USED_SLOTS"]; + info.func = function() + TitanSetVar(TITAN_PLUGIN, "ShowUsedSlots", 1); + TitanPanelButton_UpdateButton(TITAN_PLUGIN); + end + info.checked = TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots"); + TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel()); + + info = {}; + info.text = L["TITAN_BAG_MENU_SHOW_AVAILABLE_SLOTS"]; + info.func = function() + TitanSetVar(TITAN_PLUGIN, "ShowUsedSlots", nil); + TitanPanelButton_UpdateButton(TITAN_PLUGIN); + end + info.checked = TitanUtils_Toggle(TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots")); + TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel()); + + info = {}; + info.text = L["TITAN_BAG_MENU_SHOW_DETAILED"]; + info.func = function() + TitanToggleVar(TITAN_PLUGIN, "ShowDetailedInfo"); + end + info.checked = TitanGetVar(TITAN_PLUGIN, "ShowDetailedInfo"); + TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel()); + + info = {}; + info.text = L["TITAN_BAG_MENU_OPEN_BAGS"] + info.func = function() + TitanToggleVar(TITAN_PLUGIN, "OpenBags") + end + info.checked = TitanGetVar(TITAN_PLUGIN, "OpenBags"); + TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel()); + end + return + end + + -- level 1 + TitanPanelRightClickMenu_AddTitle(TitanPlugins[TITAN_PLUGIN].menuText); + + info = {}; + info.notCheckable = true + info.text = L["TITAN_PANEL_OPTIONS"]; + info.value = "Options" + info.hasArrow = 1; + TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel()); + + TitanPanelRightClickMenu_AddSpacer(); + info = {}; + info.text = L["TITAN_BAG_MENU_IGNORE_PROF_BAGS_SLOTS"]; + info.func = function() + TitanToggleVar(TITAN_PLUGIN, "CountProfBagSlots"); + TitanPanelButton_UpdateButton(TITAN_PLUGIN); + end + info.checked = not TitanGetVar(TITAN_PLUGIN, "CountProfBagSlots") + TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel()); + + TitanPanelRightClickMenu_AddControlVars(TITAN_PLUGIN) +end + +local function OnShow(self) + if trace then + TitanDebug("TS OnShow" + .." register" + ) + end + -- Register for bag updates and update the plugin text + self:RegisterEvent("BAG_UPDATE") + TitanPanelButton_UpdateButton(TITAN_PLUGIN); +end + +local function OnHide(self) + if trace then + TitanDebug("TS OnShow" + .." unregister" + ) + end + self:UnregisterEvent("BAG_UPDATE") +end + +-- ====== Create needed frames +local function Create_Frames() + if _G[TITAN_BUTTON] then + return -- if already created + end + + if trace then + TitanDebug("TS frames" + .." '"..tostring(TITAN_BUTTON).."'" + ) + end + + -- general container frame + local f = CreateFrame("Frame", nil, UIParent) +-- f:Hide() + + -- Titan plugin button + --[[ + The plugin frame is created here. The typical plugin is a 'combo' which includes + - an icon (can be shown or hidden) + - label - value pair where the label can be turned off + There can be multiple label - value pairs; TitanPerformance uses this scheme. + + The frame is 'forever' as are most of WoW game frames. + --]] + local window = CreateFrame("Button", TITAN_BUTTON, f, "TitanPanelComboTemplate") + window:SetFrameStrata("FULLSCREEN") + -- Using SetScript to set "OnLoad" does not work + -- + -- This routine sets the guts of the plugin - the .registry + OnLoad(window); + + --[[ + Below are the frame 'events' that need to be processed. + A couple have Titan routines that ensure the plugin is properly on / off a Titan bar. + + The combined Titan changed design to register for events when the user places the plugin + on the bar (OnShow) and unregister events when the user hids the plugin (OnHide). + This reduces cycles the plugin uses when the user does not want the plugin. + + NOTE: If a Titan bar is hidden, the plugins on it will still run. + NOTE: Titan plugins are NOT child frames!! Meaning plugins are not automatically hidden when the + bar they visible on is hidden! + --]] + window:SetScript("OnShow", function(self) + OnShow(self) + -- This routine ensures the plugin is put where the user requested it. + -- Titan saves the bar the plugin was on. It does not save the relative order. + TitanPanelButton_OnShow(self); + end) + window:SetScript("OnHide", function(self) + OnHide(self) + -- We use the Blizzard frame hide to visually remove the frame + end) + window:SetScript("OnEvent", function(self, event, ...) + -- Handle any events the plugin is interested in + OnEvent(self, event, ...) + end) + window:SetScript("OnClick", function(self, button) + -- Typically this routine handles actions on left click + OnClick(self, button); + -- Typically this routine handles the menu creation on right click + TitanPanelButton_OnClick(self, button); + end) +end + +Create_Frames() -- do the work diff --git a/TitanExamplePlugin/TitanStarter.tga b/TitanExamplePlugin/TitanStarter.tga new file mode 100644 index 0000000..6a7ddda Binary files /dev/null and b/TitanExamplePlugin/TitanStarter.tga differ