Quantcast

Moved files back to top level directory

Christopher Tse [03-31-20 - 11:41]
Moved files back to top level directory
Filename
BlizzChatIntegration.lua
ChatBubblePool.lua
MainFrame.lua
MainFrame.xml
RoleplayChatBubbles.toc
TotalRP3.lua
diff --git a/BlizzChatIntegration.lua b/BlizzChatIntegration.lua
new file mode 100644
index 0000000..a175441
--- /dev/null
+++ b/BlizzChatIntegration.lua
@@ -0,0 +1,183 @@
+-- Author      : Chrono
+-- Create Date : 3/30/2020 8:27:47 PM
+
+local ADDON_NAME, Import = ...
+
+--This is an invisible frame that is created to receive OnUpdate calls
+--Attached to the WorldFrame so it receives events even when the UI is hidden
+local Timer = CreateFrame("Frame","RPChatBubble-Timer",WorldFrame)
+Timer:SetFrameStrata("TOOLTIP") -- higher strata is called last
+
+--Alias functions
+Timer.Start = Timer.Show
+Timer.Stop = function(self)
+	Timer:Hide()
+	Timer.elapsed = 0
+end
+
+Timer:Stop()
+
+local numBubbles = 0;
+local messageToSender = {};
+
+local MANAGED_CHANNELS = {
+	"CHAT_MSG_SAY", "CHAT_MSG_YELL", "CHAT_MSG_MONSTER_SAY", "CHAT_MSG_MONSTER_YELL"
+};
+
+local function getPadding(numSpaces)
+	local str = ">";
+	for i=1,numSpaces,1 do
+		str = "-" .. str
+	end
+	return str
+end
+
+local function printTable(t, depth)
+	local padding = getPadding(depth)
+	for key, value in pairs(t) do
+		if type(value) == "table" then
+			print(padding .. key .. " = (table):");
+			printTable(value, depth + 1);
+		else
+			print(padding .. key .. " ("..type(value)..") = " .. tostring(value) );
+		end
+	end
+end
+
+local function getChatBubbleText(chatBubble)
+	for i = 1, chatBubble:GetNumRegions() do
+		local region = select(i, chatBubble:GetRegions())
+		if region:GetObjectType() == "FontString" then
+			return region:GetText()
+		end
+	end
+end
+
+local function getNamedPoint(chatBubble,pointName)
+	for i = 1, chatBubble:GetNumPoints() do
+		local point, relativeTo, relativePoint, xOfs, yOfs = chatBubble:GetPoint(i);
+		if point == pointName then
+			return relativeTo, relativePoint, xOfs, yOfs;
+		end
+	end
+end
+
+local function skinBubble(chatBubble)
+	local message = getChatBubbleText(chatBubble);
+	local name = messageToSender[message]
+
+	local NameText = CreateFrame("EditBox","BlizzBoxNameText",chatBubble);
+	NameText:SetFrameStrata("MEDIUM"); --This is the default but better to be explicit
+	--NameText:SetMultiLine(true);
+	NameText:SetAutoFocus(false);
+	--NameText:EnableMouse(false);
+	NameText:SetSize(700,11);
+	--NameText:SetPoint("CENTER");
+	NameText:SetPoint("BOTTOMLEFT",chatBubble,"TOPLEFT",13,2);
+	NameText:SetFontObject("GameFontNormal");
+	NameText:SetText(name);
+	--local tex = NameText:CreateTexture(nil,"ARTWORK");
+	--tex:SetAllPoints()
+	--tex:SetTexture(255,255,255);
+	NameText.stringMeasure = NameText:CreateFontString(nil,"OVERLAY","GameFontNormal");
+	NameText.stringMeasure:SetText(name);
+
+	local NameBg = CreateFrame("Frame","BlizzBubbleNameBG",NameText);
+	NameBg:SetPoint("TOPLEFT",-1,14);
+	NameBg:SetPoint("BOTTOMLEFT",-1,-2);
+	NameBg:SetWidth(NameText.stringMeasure:GetStringWidth());
+	NameBg:SetFrameStrata("BACKGROUND");
+
+
+	local midTex = NameBg:CreateTexture("nameBoxBackgroundTex-middle","BACKGROUND");
+	midTex:SetTexture("Interface/CHATFRAME/ChatFrameTab-BGMid.blp");
+	midTex:SetPoint("TOPLEFT",8,0);
+	midTex:SetPoint("BOTTOMRIGHT",-7,0);
+	local leftTex = NameBg:CreateTexture("nameBoxBackgroundTex-left","BACKGROUND");
+	leftTex:SetTexture("Interface/CHATFRAME/ChatFrameTab-BGLeft.blp");
+	leftTex:SetPoint("TOPRIGHT",midTex,"TOPLEFT");
+	leftTex:SetPoint("BOTTOMRIGHT",midTex,"BOTTOMLEFT");
+	local rightTex = NameBg:CreateTexture("nameBoxBackgroundTex-right","BACKGROUND");
+	rightTex:SetTexture("Interface/CHATFRAME/ChatFrameTab-BGRight.blp");
+	rightTex:SetPoint("TOPLEFT",midTex,"TOPRIGHT");
+	rightTex:SetPoint("BOTTOMLEFT",midTex,"BOTTOMRIGHT");
+
+	local relativeTo, relativePoint, xOfs, yOfs = getNamedPoint(chatBubble,"BOTTOMRIGHT");
+	chatBubble.string = relativeTo;
+	chatBubble.defaultXOfs = xOfs;
+	chatBubble.fixWidth = function(self)
+		local nameWidth = NameText.stringMeasure:GetWidth();
+		NameBg:SetWidth(nameWidth);
+		local stringWidth = self.string:GetStringWidth();
+		local expectedWidth = stringWidth + 32;
+		local requiredWidthForName = nameWidth + 14 + 2 + 16;
+		local defaultXOfs = self.defaultXOfs;
+		local relativeTo, relativePoint, xOfs, yOfs = getNamedPoint(self,"BOTTOMRIGHT");
+		local currHeight = self:GetHeight();
+		if ( expectedWidth < requiredWidthForName ) then
+			local diff = requiredWidthForName - expectedWidth;
+			self:SetPoint("BOTTOMRIGHT",relativeTo,relativePoint,defaultXOfs+diff,yOfs);
+		else
+			self:SetPoint("BOTTOMRIGHT",relativeTo,relativePoint,defaultXOfs,yOfs);
+		end
+	end
+	chatBubble:fixWidth();
+
+	chatBubble.nameText = NameText;
+	chatBubble.SetName = function(self,text)
+		NameText:SetText(text)
+		NameText.stringMeasure:SetText(text);
+		self:fixWidth();
+	end;
+	chatBubble.rpSkinned = true;
+	numBubbles = numBubbles + 1;
+end
+
+local function checkBubbles(chatBubbles)
+	--chatBubbles is an indexed array of frames
+	for _, chatBubble in pairs(chatBubbles) do
+		if not chatBubble.rpSkinned then
+			skinBubble(chatBubble)
+		else
+			local message = getChatBubbleText(chatBubble)
+			chatBubble:SetName(messageToSender[message])
+		end
+	end
+end
+
+Timer:SetScript("OnUpdate", function(self, elapsed)
+	self.elapsed = self.elapsed + elapsed
+	-- 0.01 Seconds after the chat message happened...
+	if self.elapsed > 0.01 then
+		self:Stop();
+		--This returns all chat bubbles created through default Blizz's UI. Custom chat bubbles aren't seen here
+		chatBubbles = C_ChatBubbles:GetAllChatBubbles()
+		checkBubbles(chatBubbles)
+	end
+end)
+
+local function onChatMessage(_, event, message, sender, ...)
+	local name = GetColoredName(event, message, sender, ...);
+	messageToSender[message] = name;
+	--At the time of the chat event, the chat bubble hasn't been created yet. So we'll wait 0.01 seconds before looking for chat bubbles to skin.
+	Timer:Start();
+	return false, message, sender, ...
+end
+
+local function resetChatHandler(self)
+	for _, channel in pairs(MANAGED_CHANNELS) do
+		ChatFrame_RemoveMessageEventFilter(channel, onChatMessage)
+		ChatFrame_AddMessageEventFilter(channel, onChatMessage);
+	end
+end
+
+local function onStart(self)
+	for _, channel in pairs(MANAGED_CHANNELS) do
+		ChatFrame_AddMessageEventFilter(channel, onChatMessage);
+	end
+end
+
+Import.modules.BlizzChatIntegration = {};
+Import.modules.BlizzChatIntegration.name = "BlizzChatIntegration";
+Import.modules.BlizzChatIntegration.OnStart = onStart;
+Import.modules.BlizzChatIntegration.ResetChatHandler = resetChatHandler
\ No newline at end of file
diff --git a/ChatBubblePool.lua b/ChatBubblePool.lua
new file mode 100644
index 0000000..e6ffcf6
--- /dev/null
+++ b/ChatBubblePool.lua
@@ -0,0 +1,240 @@
+-- Author      : Christopher Tse
+-- Create Date : 3/28/2020 1:37:28 PM
+
+local ADDON_NAME, Import = ...;
+
+local pool = {}
+
+Import.ChatBubblePool = {};
+local ChatBubblePool = Import.ChatBubblePool
+
+local function adjustChatBubbleWidth(chatBubble)
+	local editBox = chatBubble.editBox;
+	local strWidth = editBox.stringMeasure:GetStringWidth();
+	local bg = editBox.background;
+	local padding = bg.padding;
+	local nameBox = chatBubble.nameBox
+	local nameBoxWidth = nameBox:GetFullWidth();
+	local minWidth = 64;
+	if ( nameBoxWidth ~= nil) then
+		local nameBoxMargin = nameBox.margin.L + nameBox.margin.R;
+		minWidth = max(64, nameBoxWidth + nameBoxMargin);
+	end
+	local maxWidth = chatBubble:GetWidth()
+	if ( strWidth < minWidth ) then
+		bg:SetWidth(minWidth + padding)
+	elseif ( minWidth < strWidth and strWidth < maxWidth ) then
+		bg:SetWidth(strWidth + padding)
+	else
+		bg:SetWidth(maxWidth + padding )
+	end
+end
+
+local function adjustNameBoxWidth(chatBubble)
+	local nameBox = chatBubble.nameBox;
+	local nameBoxBg = nameBox.background;
+	local strWidth = nameBox.stringMeasure:GetStringWidth();
+	local minWidth = 32;
+	local padding = nameBox.padding.L + nameBox.padding.R;
+	--The max width usually won't be reached because of the character limit on the name box
+	local maxWidth = chatBubble:GetWidth() - padding - nameBox.margin.L
+	if ( strWidth < minWidth ) then
+		nameBoxBg:SetWidth(minWidth + padding);
+	elseif ( minWidth < strWidth and strWidth < maxWidth ) then
+		nameBoxBg:SetWidth(strWidth + padding);
+	else
+		nameBoxBg:SetWidth(maxWidth + padding);
+	end
+end
+
+local function pickNameColor(chatBubble)
+	local r, g, b = chatBubble:GetNameColor()
+	ColorPickerFrame:SetColorRGB(r,g,b);
+	ColorPickerFrame.hasOpacity = false;
+	ColorPickerFrame.func = function(self) chatBubble:SetNameColor(ColorPickerFrame:GetColorRGB()) end;
+	ColorPickerFrame.cancelFunc = function(self) chatBubble:SetNameColor(r,g,b) end;
+	ColorPickerFrame:Show();
+end
+
+local function closeBubble(chatBubble)
+	chatBubble:Hide();
+	chatBubble:SetMessage("");
+	chatBubble:SetName("");
+	chatBubble.nameBox:SetAlpha(0.01)
+	chatBubble:ClearAllPoints();
+	chatBubble:SetPoint("TOPLEFT",WorldFrame,"CENTER",-chatBubble.center.x,-chatBubble.center.y);
+	chatBubble.isAvailable = true;
+end
+
+function ChatBubblePool.getChatBubble()
+	for index, chatBubble in ipairs(pool) do
+		if chatBubble.isAvailable then
+			chatBubble:Show()
+			chatBubble.isAvailable = false;
+			return chatBubble
+		end
+	end
+
+	-- If we got here, there isn't any available chat bubble so create a new one
+	local frameName = "RPChatBubble" .. #pool
+
+	local newChatBubble = CreateFrame("Frame",frameName,nil)
+	newChatBubble:SetWidth(300)
+	newChatBubble:SetHeight(300)
+	newChatBubble:SetMovable(true)
+	newChatBubble:SetFrameStrata("LOW")
+	newChatBubble.isAvailable = false
+	table.insert(pool, newChatBubble);
+
+	--chatBubbleTail:EnableMouse(true)
+	--chatBubbleTail:SetMovable(true)
+	--chatBubbleTail:RegisterForDrag("LeftButton")
+	--chatBubbleTail:SetScript("OnDragStart", function(self) self:StartMoving() end)
+	--chatBubbleTail:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() end)
+
+	local editBox = CreateFrame("EditBox",frameName.."-EditBox",newChatBubble);
+	editBox:SetPoint("TOPLEFT",newChatBubble);
+	editBox:SetPoint("TOPRIGHT",newChatBubble);
+	editBox:SetMultiLine(true);
+	editBox:SetAutoFocus(false);
+	editBox:SetFontObject("ChatBubbleFont");
+	editBox:SetScript("OnEnterPressed", function(self) self:ClearFocus() end);
+	editBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end);
+	--Apparently, the below code stops the user from being able to change the cursor location
+	--editBox:EnableMouse(true)
+	--editBox:SetScript("OnMouseDown", function(self) newChatBubble:StartMoving() end )
+	--editBox:SetScript("OnMouseUp", function(self) newChatBubble:StopMovingOrSizing() end )
+
+	newChatBubble.editBox = editBox;
+	--This is a hack that centers the newChatBubble using the center of the editbox
+	newChatBubble.center = { x=editBox:GetWidth()/2, y=editBox:GetHeight()/2 };
+	newChatBubble:SetPoint("TOPLEFT",WorldFrame,"CENTER",-newChatBubble.center.x,-newChatBubble.center.y);
+
+	local chatBubbleBackground = CreateFrame("Frame",frameName.."Background",editBox);
+	chatBubbleBackground:SetBackdrop({
+		bgFile="Interface\\Tooltips\\CHATBUBBLE-BACKGROUND.BLP",
+		edgeFile="Interface\\Tooltips\\CHATBUBBLE-BACKDROP.BLP",
+		tile=true, tileSize=16, edgeSize=16,
+		insets={left=16, right=16, top=16, bottom=16}
+	})
+	chatBubbleBackground:EnableMouse(true)
+	chatBubbleBackground:SetPoint("TOPLEFT",editBox,"TOPLEFT",-16,16)
+	chatBubbleBackground:SetPoint("BOTTOMLEFT",editBox,"BOTTOMLEFT",-16,-16)
+	chatBubbleBackground.padding = 32;
+	chatBubbleBackground:SetWidth(64 + chatBubbleBackground.padding)
+	chatBubbleBackground:SetFrameStrata("BACKGROUND")
+	chatBubbleBackground:EnableMouse(true)
+	chatBubbleBackground:SetScript("OnMouseDown", function(self) newChatBubble:StartMoving() end )
+	chatBubbleBackground:SetScript("OnMouseUp", function(self) newChatBubble:StopMovingOrSizing() end )
+	editBox.background = chatBubbleBackground;
+
+	--This part of the code makes the editbox and the background grow up to 300px as the text grows.
+	--We use an invisible FontString to measure the length of the text inside the edit box.
+	editBox.stringMeasure = editBox:CreateFontString(nil,"OVERLAY","ChatBubbleFont");
+	editBox.stringMeasure:SetAlpha(0);
+	editBox:SetScript("OnTextChanged", function(self)
+	    editBox.stringMeasure:SetText(self:GetText());
+		adjustChatBubbleWidth(newChatBubble);
+	end)
+
+	local closeButton = CreateFrame("Button",frameName.."-CloseButton",chatBubbleBackground,"UIPanelCloseButton")
+	closeButton:SetPoint("CENTER",chatBubbleBackground,"TOPRIGHT",-4,-4);
+	closeButton:SetScript("OnClick",function(self) closeBubble(newChatBubble) end);
+	closeButton:SetScript("OnEnter",function(self) closeButton:SetAlpha(1) end);
+	closeButton:SetScript("OnLeave",function(self) closeButton:SetAlpha(0.1) end);
+	closeButton:SetAlpha(0.1);
+
+	local nameBoxFrame = CreateFrame("Frame",frameName.."-NameBoxFrame",newChatBubble)
+	nameBoxFrame:SetSize(250,18);
+	nameBoxFrame:SetPoint("BOTTOMLEFT",chatBubbleBackground,"TOPLEFT");
+
+	local nameBox = CreateFrame("EditBox",frameName.."-NameBox",nameBoxFrame);
+	nameBox:SetFontObject("GameFontNormal");
+	nameBox:SetMaxLetters(25);
+	nameBox.margin = {L=10, R=0, T=4, D=4};
+	nameBox.padding = {L=10, R=10};
+	nameBox:SetPoint("TOPLEFT",nameBoxFrame,nameBox.margin.L,-nameBox.margin.T);
+	nameBox:SetPoint("BOTTOMRIGHT",nameBoxFrame,-nameBox.margin.R,nameBox.margin.D);
+	nameBox:SetAutoFocus(false);
+	nameBox:SetMultiLine(true); --It's not actually multiline, but this stops the name from scrolling off if the user selects too much of the text.
+								--The max letters should prevent the edit box from ever reaching more than one line
+	nameBox:SetScript("OnEnterPressed", function(self) self:ClearFocus() end);
+	nameBox:SetScript("OnTabPressed", function(self) editBox:SetFocus() end);
+	nameBox:SetAlpha(0);
+	nameBox:SetScript("OnEditFocusGained", function(self) self:SetAlpha(1) end);
+	nameBox:SetScript("OnEditFocusLost", function(self) if self:GetText() == "" then self:SetAlpha(0.01) end end);
+	newChatBubble.nameBox = nameBox;
+
+	local nameBoxBackground = CreateFrame("Frame",frameName.."-NameBoxBackground",nameBox);
+	local paddingL = nameBox.padding.L;
+	nameBoxBackground:SetPoint("BOTTOMLEFT",nameBox,"BOTTOMLEFT",-paddingL,-nameBox.margin.D)
+	nameBoxBackground:SetPoint("TOPLEFT",nameBox,"TOPLEFT",-paddingL,nameBox.margin.T + 12);
+	nameBoxBackground:SetWidth(32);
+	nameBoxBackground:SetFrameStrata("BACKGROUND");
+	nameBox.background = nameBoxBackground
+
+	local nameBoxMouseCatcher = CreateFrame("Button",frameName.."-NameBoxMouseCatcher",nameBox);
+	nameBoxMouseCatcher:SetPoint("BOTTOMLEFT",nameBoxBackground);
+	nameBoxMouseCatcher:SetPoint("TOPRIGHT",nameBoxBackground);
+	nameBoxMouseCatcher:SetScript("OnEnter", function(self) if nameBox:GetText() == "" and not nameBox:HasFocus() then nameBox:SetAlpha(0.5) end end);
+	nameBoxMouseCatcher:SetScript("OnLeave", function(self) if nameBox:GetText() == "" and not nameBox:HasFocus() then nameBox:SetAlpha(0) end end);
+	nameBoxMouseCatcher:SetScript("OnClick", function(self) nameBox:SetFocus() end);
+	nameBoxMouseCatcher:SetScript("OnMouseDown", function(self) newChatBubble:StartMoving() end )
+	nameBoxMouseCatcher:SetScript("OnMouseUp", function(self) newChatBubble:StopMovingOrSizing() end )
+
+	local nameBoxColorPicker = CreateFrame("Button",frameName.."-ColorPickerButton",newChatBubble);
+	nameBoxColorPicker:SetSize(16,16);
+	nameBoxColorPicker:SetFrameStrata("MEDIUM") -- Needs to be higher than the EditBox to override it
+	nameBox.colorPickerTex = nameBoxColorPicker:CreateTexture(frameName.."-ColorPickerButton-color","ARTWORK")
+	nameBox.colorPickerTex:SetPoint("TOPLEFT",2,-2);
+	nameBox.colorPickerTex:SetPoint("BOTTOMRIGHT",-2,2);
+	nameBox.colorPickerTex:SetColorTexture(nameBox:GetTextColor());
+	local cpBorderTex = nameBoxColorPicker:CreateTexture(frameName.."-ColorPickerButton-border","BORDER");
+	cpBorderTex:SetAllPoints();
+	cpBorderTex:SetColorTexture(0.1,0.1,0.1);
+	nameBoxColorPicker:SetPoint("BOTTOMLEFT",nameBoxBackground,"BOTTOMRIGHT");
+	nameBoxColorPicker:SetAlpha(0.01);
+	nameBoxColorPicker:EnableMouse(true);
+	nameBoxColorPicker:SetScript("OnEnter", function(self) if nameBox:GetText() ~= "" then self:SetAlpha(1); end; end);
+	nameBoxColorPicker:SetScript("OnLeave", function(self) self:SetAlpha(0.01) end);
+	nameBoxColorPicker:SetScript("OnClick", function(self) pickNameColor(newChatBubble) end);
+
+	nameBox.stringMeasure = nameBox:CreateFontString(nil,"OVERLAY","GameFontNormal");
+	--nameBox.stringMeasure:SetAlpha(0);
+	nameBox.GetFullWidth = function(self)  return nameBoxBackground:GetWidth() end;
+	nameBox:SetScript("OnTextChanged", function(self)
+		nameBox.stringMeasure:SetText(self:GetText());
+		adjustNameBoxWidth(newChatBubble)
+		adjustChatBubbleWidth(newChatBubble)
+	end);
+
+	local midTex = nameBoxBackground:CreateTexture("nameBoxBackgroundTex-middle","BACKGROUND");
+	midTex:SetTexture("Interface/CHATFRAME/ChatFrameTab-BGMid.blp");
+	midTex:SetPoint("TOPLEFT",16,0);
+	midTex:SetPoint("BOTTOMRIGHT",-16,0);
+	local leftTex = nameBoxBackground:CreateTexture("nameBoxBackgroundTex-left","BACKGROUND");
+	leftTex:SetTexture("Interface/CHATFRAME/ChatFrameTab-BGLeft.blp");
+	leftTex:SetPoint("TOPRIGHT",midTex,"TOPLEFT");
+	leftTex:SetPoint("BOTTOMRIGHT",midTex,"BOTTOMLEFT");
+	local rightTex = nameBoxBackground:CreateTexture("nameBoxBackgroundTex-right","BACKGROUND");
+	rightTex:SetTexture("Interface/CHATFRAME/ChatFrameTab-BGRight.blp");
+	rightTex:SetPoint("TOPLEFT",midTex,"TOPRIGHT");
+	rightTex:SetPoint("BOTTOMLEFT",midTex,"BOTTOMRIGHT");
+
+	local chatBubbleTail = CreateFrame("Frame",frameName.."-tail",chatBubbleBackground)
+	chatBubbleTail:SetBackdrop({
+		bgFile="Interface\\Tooltips\\CHATBUBBLE-TAIL.BLP"
+	})
+	chatBubbleTail:SetSize(16,16)
+	chatBubbleTail:SetPoint("TOPLEFT",chatBubbleBackground,"BOTTOMLEFT",8,3)
+
+	--Functions for outside use
+	newChatBubble.GetName = nameBox.GetText;
+	newChatBubble.SetName = function(self,name) nameBox:SetText(name); if (name ~= "" ) then nameBox:SetAlpha(1); end; end;
+	newChatBubble.GetMessage = editBox.GetText;
+	newChatBubble.SetMessage = function(self,message) editBox:SetText(message) end;
+	newChatBubble.GetNameColor = function(self) return nameBox:GetTextColor() end;
+	newChatBubble.SetNameColor = function(self,r,g,b) nameBox:SetTextColor(r,g,b) nameBox.colorPickerTex:SetColorTexture(r,g,b) end;
+
+	return newChatBubble
+end
\ No newline at end of file
diff --git a/MainFrame.lua b/MainFrame.lua
new file mode 100644
index 0000000..68547f9
--- /dev/null
+++ b/MainFrame.lua
@@ -0,0 +1,33 @@
+-- Author      : Christopher Tse
+-- Create Date : 3/28/2020 11:43:45 AM
+
+local ADDON_NAME, Import = ...;
+
+local mainFrame;
+local ChatBubblePool = Import.ChatBubblePool
+
+function RPChatBubbles_createChatBubble()
+	return ChatBubblePool.getChatBubble()
+end
+
+function RPChatBubbles_OnLoad(self, event,...)
+	self:SetClampedToScreen(true);
+    self:RegisterEvent("ADDON_LOADED");
+end
+
+function RPChatBubbles_OnEvent(self, event, ...)
+     if event == "ADDON_LOADED" and ... == ADDON_NAME then
+		self:RegisterForDrag("LeftButton");
+		self:SetScript("OnDragStart", function(self)
+			self:StartMoving();
+		end);
+		self:SetScript("OnDragStop", function(self)
+			self:StopMovingOrSizing();
+		end);
+		for moduleName, moduleStructure in pairs(Import.modules) do
+			moduleStructure:OnStart();
+		end
+	end
+end
+
+Import.modules = {};
\ No newline at end of file
diff --git a/MainFrame.xml b/MainFrame.xml
new file mode 100644
index 0000000..45e7653
--- /dev/null
+++ b/MainFrame.xml
@@ -0,0 +1,51 @@
+<Ui xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.blizzard.com/wow/ui/">
+	<Script file="MainFrame.lua" />
+	<Script file="BlizzChatIntegration.lua" />
+	<Script file="TotalRP3.lua" />
+	<Frame name="MainFrame" parent="UIParent" toplevel="true" movable="true" enableMouse="true">
+		<Size x="145" y="76" />
+		<Anchors>
+			<Anchor point="CENTER" x="-28" y="29" />
+		</Anchors>
+		<Backdrop bgFile="Interface\DialogFrame\UI-DialogBox-Background" edgeFile="Interface\DialogFrame\UI-DialogBox-Border" tile="true">
+			<BackgroundInsets>
+				<AbsInset left="11" right="12" top="12" bottom="11" />
+			</BackgroundInsets>
+			<TileSize>
+				<AbsValue val="32" />
+			</TileSize>
+			<EdgeSize>
+				<AbsValue val="32" />
+			</EdgeSize>
+		</Backdrop>
+		<Layers>
+			<Layer level="OVERLAY">
+				<FontString inherits="GameFontNormal" text="RP Chat Bubbles">
+					<Size x="112" y="20" />
+					<Anchors>
+						<Anchor point="TOPLEFT" x="18" y="-14" />
+					</Anchors>
+				</FontString>
+			</Layer>
+		</Layers>
+		<Frames>
+			<Button inherits="UIPanelButtonTemplate" text="Create">
+				<Scripts>
+					<onClick function="RPChatBubbles_createChatBubble" />
+				</Scripts>
+				<Size x="115" y="23" />
+				<Anchors>
+					<Anchor point="TOPLEFT" x="15" y="-35" />
+				</Anchors>
+			</Button>
+		</Frames>
+		<Scripts>
+			<OnLoad function="RPChatBubbles_OnLoad">
+
+			</OnLoad>
+			<OnEvent function="RPChatBubbles_OnEvent">
+
+			</OnEvent>
+		</Scripts>
+	</Frame>
+</Ui>
\ No newline at end of file
diff --git a/RoleplayChatBubbles.toc b/RoleplayChatBubbles.toc
new file mode 100644
index 0000000..6d40004
--- /dev/null
+++ b/RoleplayChatBubbles.toc
@@ -0,0 +1,8 @@
+## Title: RoleplayChatBubbles
+## Version: 1.0
+## Author: Christopher Tse
+## Interface: 80300
+## OptionalDeps: totalRP3
+
+ChatBubblePool.lua
+MainFrame.xml
diff --git a/TotalRP3.lua b/TotalRP3.lua
new file mode 100644
index 0000000..125fe11
--- /dev/null
+++ b/TotalRP3.lua
@@ -0,0 +1,108 @@
+-- Author      : Chrono
+-- Create Date : 3/29/2020 4:39:46 PM
+
+
+local _, Import = ...;
+
+local ellyb, loc, Color, ColorManager;
+local NPC_TALK_PATTERNS;
+
+
+local function makeBubbleForNPCChat(_, event, message, ...)
+	if event == "CHAT_MSG_EMOTE" then
+		local npcName = TRP3_API.chat.getNPCMessageName()
+		if npcName then
+			for talkType, talkChannel in pairs(NPC_TALK_PATTERNS) do
+				if message:find(talkType) then
+					local color;
+					local myMessage = message;
+					local normalColor = ColorManager.getChatColorForChannel(talkChannel);
+					local normalColorAsString = normalColor:GetColorCodeStartSequence();
+					local nameColor;
+
+					--Detect colour alterations
+					if myMessage:sub(1,2) == "|c" then
+						color = myMessage:sub(1,10); --Save this to prepend back later
+						myMessage = myMessage:sub(11);
+					end
+
+					--If the name is in the default color scheme, remove it for titling emphasis
+					if npcName:sub(1,10) ~= normalColorAsString then
+						nameColor = Color.static.CreateFromHexa(npcName:sub(1,10));
+					end
+					npcName = npcName:sub(11);
+
+					local len = talkType:len();
+					--Remove the "says:" from the beginning of the message.
+					if myMessage:sub(1, len) == talkType then
+						local actualMessage = myMessage:sub(len+1);
+
+						--Remove leading spaces if any
+						if actualMessage:sub(1,1) == " " then
+							actualMessage = actualMessage:sub(2);
+						end
+
+						actualMessage = color .. actualMessage;
+						local chatBubble = RPChatBubbles_createChatBubble()
+						print("NPC Talk Found! npcName="..npcName);
+						chatBubble:SetName(npcName);
+						chatBubble:SetMessage(actualMessage);
+						if nameColor then
+							chatBubble:SetNameColor(nameColor:GetRGBA())
+						end
+					end
+					break;
+				end
+			end
+		end
+	end
+	return false, message, ...
+end
+
+function initTRP3Vars(self)
+	ellyb = TRP3_API.Ellyb;
+	loc = TRP3_API.loc;
+	Color = ellyb.Color;
+	ColorManager = ellyb.ColorManager;
+	NPC_TALK_PATTERNS = {
+		[loc.NPC_TALK_SAY_PATTERN] = "MONSTER_SAY",
+		[loc.NPC_TALK_YELL_PATTERN] = "MONSTER_YELL",
+		[loc.NPC_TALK_WHISPER_PATTERN] = "MONSTER_WHISPER",
+	};
+end
+
+function TotalRP3_onStart(self)
+	if TRP3_API then
+		initTRP3Vars();
+		for _, channel in pairs(POSSIBLE_CHANNELS) do
+			ChatFrame_RemoveMessageEventFilter(channel, makeBubbleForNPCChat);
+			ChatFrame_AddMessageEventFilter(channel, makeBubbleForNPCChat);
+		end
+		Import.modules.BlizzChatIntegration:ResetChatHandler()
+	end
+end
+
+--Import.modules["TotalRP3"] = {
+--	name="TotalRP3",
+--	onStart = TotalRP3_onStart;
+--}
+
+POSSIBLE_CHANNELS = {
+	"CHAT_MSG_SAY", "CHAT_MSG_YELL", "CHAT_MSG_EMOTE", "CHAT_MSG_TEXT_EMOTE",
+	"CHAT_MSG_PARTY", "CHAT_MSG_PARTY_LEADER", "CHAT_MSG_RAID", "CHAT_MSG_RAID_LEADER",
+	"CHAT_MSG_GUILD", "CHAT_MSG_OFFICER", "CHAT_MSG_WHISPER", "CHAT_MSG_WHISPER_INFORM"
+};
+
+
+local MODULE_STRUCTURE = {
+	["name"] = "Roleplay Chat Bubbles",
+	["description"] = "Module for integrating TotalRP3's chatframe system with Roleplay Chat Bubbles.",
+	["version"] = 1.000,
+	["id"] = "rp_chatBubbles",
+	["onStart"] = TotalRP3_onStart,
+	["minVersion"] = 3,
+};
+
+if TRP3_API then
+	TRP3_API.module.registerModule(MODULE_STRUCTURE);
+end