The Forums Are Now Closed!

The content will remain as a historical reference, thank you.

squelch mod - need a code review

By on June 12, 2011 4:29:56 PM from Demigod Forums Demigod Forums

pacov

Join Date 02/2008
+182

So, I decided to create a mod that will enable you to squelch certain individuals in advance.  This will be helpful to folks that get harassed a bit or just generally have bad experiences with certain folks.  The user just has to update the lua code with the user name they want to block.  During testing, I'll see if its possible to manually unsquelch a person in game - i assume it is.  This mod's only purpose is so that folks can automatically block communication with certain players in advance.

Anyway, I just copied the squelch fix mod (no longer needed as the chat is now fixed) and added in a string to block specific individuals.  My question is, what do I really need in this mod?  I have everything including a fix that is no longer needed.  Any guidance would be appreciated.

Here's the bit of the code I modified:

Code: c++
  1. function SetupIgnoreList()
  2.     local armies = GetArmiesTable()
  3.     local focus = armies.focusArmy
  4.     for id, army in armies.armiesTable do
  5.         -- Can't ignore yourself, as much as you may want to
  6.         if id != focus and army.human then
  7.             ignoredPlayers[string.lower(army.nickname)] = army.nickname=='poopy' or army.nickname=='Yomothua'  --added by pacov
  8.         end
  9.     end
  10. end

And here's the full code of the mod in its entirety:

Code: c++
  1. #*****************************************************************************
  2. #* File: lua/modules/ui/game/chat.lua
  3. #* Summary: In game chat ui
  4. #*
  5. #* Copyright © 2008 Gas Powered Games, Inc.  All rights reserved.
  6. #*****************************************************************************
  7. local UIMain = import('/lua/ui/uimain.lua')
  8. local UIUtil = import('/lua/ui/uiutil.lua')
  9. local LayoutHelpers = import('/lua/maui/layouthelpers.lua')
  10. local EffectHelpers = import('/lua/maui/effecthelpers.lua')
  11. local Group = import('/lua/maui/group.lua').Group
  12. local Button = import('/lua/maui/button.lua').Button
  13. local Checkbox = import('/lua/maui/checkbox.lua').Checkbox
  14. local Text = import('/lua/maui/text.lua').Text
  15. local Edit = import('/lua/maui/edit.lua').Edit
  16. local Bitmap = import('/lua/maui/bitmap.lua').Bitmap
  17. local ItemList = import('/lua/maui/itemlist.lua').ItemList
  18. local Window = import('/lua/ui/controls/window.lua').Window
  19. local BitmapCombo = import('/lua/ui/controls/combo.lua').BitmapCombo
  20. local IntegerSlider = import('/lua/maui/slider.lua').IntegerSlider
  21. local Prefs = import('/lua/ui/prefs.lua')
  22. local Dragger = import('/lua/maui/dragger.lua').Dragger
  23. local InGameUI = import('/lua/ui/game/InGameUI.lua')
  24. --[[ LOC Strings
  25. <LOC chat_win_0001>To %s:
  26. <LOC chat_win_0002>Chat (%d - %d of %d lines)
  27. --]]
  28. local CHAT_INACTIVITY_TIMEOUT = 5  -- in seconds
  29. local savedParent = false
  30. local commandhistory = {}
  31. local ignoredPlayers = {}
  32. local bg = false
  33. function HandleIgnore(param)
  34.     if param != "" then
  35.         playerName = string.lower(param)
  36.         if ignoredPlayers[playerName] != nil then
  37.             ignoredPlayers[playerName] = not ignoredPlayers[playerName]
  38.             if ignoredPlayers[playerName] then
  39.                 bg.history:AddItem(LOCF("<LOC chatui_0005>Ignoring %s", playerName))
  40.             else
  41.                 bg.history:AddItem(LOCF("<LOC chatui_0006>No longer ignoring %s", playerName))
  42.             end
  43.         else
  44.             bg.history:AddItem(LOCF("<LOC chatui_0007>Player not found: %s", playerName))
  45.         end
  46.         if bg:IsHidden() then
  47.             ShowHistory()
  48.         end
  49.     end
  50. end
  51. local specialCommands = {
  52.     {
  53.         key = "ignore",
  54.         action = HandleIgnore,
  55.     },
  56.     {
  57.         key = "squelch",
  58.         action = HandleIgnore,
  59.     },
  60. }
  61. local ChatOptions = { all_color = 1,
  62.         allies_color = 2,
  63.         priv_color = 3,
  64.         link_color = 4,
  65.         font_size = 14,
  66.         fade_time = 15,
  67.         win_alpha = 1}
  68. local chatTo = 'allies'
  69. function FindClients(id)
  70.     local t = GetArmiesTable()
  71.     local focus = t.focusArmy
  72.     local result = {}
  73.     if focus == -1 then
  74.         for index,client in GetSessionClients() do
  75.             if table.getn(client.authorizedCommandSources) == 0 then
  76.                 table.insert(result, index)
  77.             end
  78.         end
  79.     else
  80.         local srcs = {}
  81.         for army,info in t.armiesTable do
  82.             if id then
  83.                 if army == id then
  84.                     for k,cmdsrc in info.authorizedCommandSources do
  85.                         srcs[cmdsrc] = true
  86.                     end
  87.                     break
  88.                 end
  89.             else
  90.                 if IsAlly(focus, army) then
  91.                     for k,cmdsrc in info.authorizedCommandSources do
  92.                         srcs[cmdsrc] = true
  93.                     end
  94.                 end
  95.             end
  96.         end
  97.         for index,client in GetSessionClients() do
  98.             for k,cmdsrc in client.authorizedCommandSources do
  99.                 if srcs[cmdsrc] then
  100.                     table.insert(result, index)
  101.                     break
  102.                 end
  103.             end
  104.         end
  105.     end
  106.     return result
  107. end
  108. -- Setup the ignore list with all of the player armies in the game.
  109. function SetupIgnoreList()
  110.     local armies = GetArmiesTable()
  111.     local focus = armies.focusArmy
  112.     for id, army in armies.armiesTable do
  113.         -- Can't ignore yourself, as much as you may want to
  114.         if id != focus and army.human then
  115.             ignoredPlayers[string.lower(army.nickname)] = army.nickname=='poopy' or army.nickname=='Yomothua'  --added by pacov
  116.         end
  117.     end
  118. end
  119. function ChatPageUp(mod)
  120. end
  121. function ChatPageDown(mod)
  122. end
  123. function Create()
  124.     savedParent = GetFrame(0)
  125.     CreateChat()
  126. end
  127. function CreateChat()
  128.     bg = Bitmap(savedParent)
  129.     #bg:SetSolidColor('cc000000')
  130.     bg.Height:Set(220)
  131.     bg.Width:Set(400)
  132.     #bg.Left:Set(savedParent.Left)
  133.     #bg.Bottom:Set(function() return savedParent.Bottom() - 150 end)
  134.     LayoutHelpers.AtLeftIn(bg, GetFrame(0), 10)
  135.     LayoutHelpers.AtVerticalCenterIn(bg, GetFrame(0), -50)
  136.     bg.Depth:Set(15000)
  137.     bg.history = ItemList(bg)
  138.     bg.history.Configs = {}
  139.     bg.history:SetFont(UIUtil.bodyFont, 18)
  140.     bg.history:SetColors("ffffffff", "00000000", 'black',  UIUtil.highlightColor, "ffbcfffe", '4f4f5f')
  141.     bg.history:ShowMouseoverItem(false)
  142.     bg.history:SetDropShadow(true)
  143.     LayoutHelpers.AtLeftIn(bg.history, bg, 5)
  144.     LayoutHelpers.AtTopIn(bg.history, bg, 5)
  145.     bg.history.Width:Set(360)
  146.     bg.history.Height:Set(250)
  147.     bg.history.Depth:Set(function() return bg.Depth() + 2 end)
  148.     bg.historyBackLeft = Bitmap(bg, '/textures/ui/hud/hud_chat_bg_left.dds')
  149.     local width, height = GetTextureDimensions('/textures/ui/hud/hud_chat_bg_left.dds')
  150.     bg.historyBackLeft.Width:Set(width)
  151.     bg.historyBackLeft.Height:Set(height)
  152.     LayoutHelpers.AtLeftIn(bg.historyBackLeft, bg, -15)
  153.     LayoutHelpers.AtTopIn(bg.historyBackLeft, bg, -20)
  154.     bg.historyBackLeft:DisableHitTest(true)
  155.     bg.historyBackLeft.Depth:Set(function() return bg.history.Depth() - 1 end)
  156.     bg.historyBackMid = Bitmap(bg, '/textures/ui/hud/hud_chat_bg_middle.dds')
  157.     width, height = GetTextureDimensions('/textures/ui/hud/hud_chat_bg_middle.dds')
  158.     bg.historyBackMid.Width:Set(width)
  159.     bg.historyBackMid.Height:Set(height)
  160.     LayoutHelpers.AtTopIn(bg.historyBackMid, bg, -20)
  161.     bg.historyBackMid.Left:Set( function() return bg.historyBackLeft.Right() end )
  162.     bg.historyBackMid:DisableHitTest(true)
  163.     bg.historyBackMid.Depth:Set(function() return bg.history.Depth() - 1 end)
  164.     bg.historyBackRight = Bitmap(bg, '/textures/ui/hud/hud_chat_bg_right.dds')
  165.     width, height = GetTextureDimensions('/textures/ui/hud/hud_chat_bg_right.dds')
  166.     bg.historyBackRight.Width:Set(width)
  167.     bg.historyBackRight.Height:Set(height)
  168.     LayoutHelpers.AtTopIn(bg.historyBackRight, bg, -20)
  169.     bg.historyBackRight:DisableHitTest(true)
  170.     bg.historyBackRight.Left:Set( function() return bg.historyBackMid.Right() end )
  171.     bg.historyBackRight.Depth:Set(function() return bg.history.Depth() - 1 end)
  172.     #bg.historyScroll = UIUtil.CreateVertScrollbarFor(bg.history)
  173.     #bg.historyScroll:SetParent(bg)
  174.     --[[
  175.     bg.edit = Edit(bg)
  176.     bg.edit.Left:Set(function() return bg.toText.Right() + 5 end)
  177.     bg.edit.Right:Set(function() return bg.Right() - 5 end)
  178.     bg.edit.Bottom:Set(function() return bg.Bottom() - 5 end)
  179.     bg.edit.Height:Set(function() return bg.edit:GetFontHeight() + 2 end)
  180.     UIUtil.SetupEditStd(bg.edit, "ff00ff00", nil, "blue", UIUtil.highlightColor, UIUtil.bodyFont, ChatOptions.font_size, 200)
  181.     bg.edit:SetDropShadow(true)
  182.     bg.edit:ShowBackground(true)
  183.     ]]--
  184.     bg.edit = Edit(bg)
  185.     LayoutHelpers.Above(bg.edit, InGameUI.HUDParent, 50)
  186.     LayoutHelpers.AtHorizontalCenterIn(bg.edit, GetFrame(0), 40)
  187.     bg.edit.Width:Set(350)
  188.     bg.edit.Height:Set(function() return bg.edit:GetFontHeight() + 2 end)
  189.     UIUtil.SetupEditStd(bg.edit, 'ffffffff', '00000000', 'blue', UIUtil.highlightColor, UIUtil.bodyFont, 20, 200)
  190.     bg.edit:SetDropShadow(true)
  191.     bg.edit:SetText('')
  192.     bg.imgleft = Bitmap(savedParent, UIUtil.UIFile('/textboxes/edit_left.dds'))
  193.     bg.imgleft.Width:Set(10)
  194.     bg.imgleft.Height:Set(45)
  195.     LayoutHelpers.AtLeftTopIn(bg.imgleft, bg.edit, -60, -9)
  196.     bg.imgleft:Hide()
  197.     bg.imgleft.Depth:Set(15000)
  198.     bg.imgmid = Bitmap(savedParent, UIUtil.UIFile('/textboxes/edit_mid.dds'))
  199.     bg.imgmid.Width:Set(410)
  200.     bg.imgmid.Height:Set(45)
  201.     LayoutHelpers.AnchorToRight(bg.imgmid, bg.imgleft, -1)
  202.     LayoutHelpers.AtTopIn(bg.imgmid, bg.imgleft)
  203.     bg.imgmid:Hide()
  204.     bg.imgmid.Depth:Set(15000)
  205.     bg.imgright = Bitmap(savedParent, UIUtil.UIFile('/textboxes/edit_right.dds'))
  206.     bg.imgright.Width:Set(10)
  207.     bg.imgright.Height:Set(45)
  208.     LayoutHelpers.AnchorToRight(bg.imgright, bg.imgmid)
  209.     LayoutHelpers.AtTopIn(bg.imgright, bg.imgmid)
  210.     bg.imgright:Hide()
  211.     bg.imgright.Depth:Set(15000)
  212.     bg.toText = UIUtil.CreateText(bg.edit, '<LOC chatui_0000>Allies:', 20, UIUtil.bodyFont)
  213.     bg.toText:SetColor(UIUtil.specialFontColor)
  214.     LayoutHelpers.AnchorToLeft(bg.toText, bg.edit, 5)
  215.     LayoutHelpers.AtVerticalCenterIn(bg.toText, bg.edit, -1)
  216.     bg.toText:SetDropShadow(true)
  217.     bg.edit.OnNonTextKeyPressed = function(self, charcode, event)
  218.         local function RecallCommand(entryNumber)
  219.             self:SetText(commandhistory[self.recallEntry].text)
  220.         end
  221.         if charcode == UIUtil.VK_UP then
  222.             if table.getsize(commandhistory) > 0 then
  223.                 if self.recallEntry then
  224.                     self.recallEntry = math.max(self.recallEntry-1, 1)
  225.                 else
  226.                     self.recallEntry = table.getsize(commandhistory)
  227.                 end
  228.                 RecallCommand(self.recallEntry)
  229.             end
  230.         elseif charcode == UIUtil.VK_DOWN then
  231.             if table.getsize(commandhistory) > 0 then
  232.                 if self.recallEntry then
  233.                     self.recallEntry = math.min(self.recallEntry+1, table.getsize(commandhistory))
  234.                     RecallCommand(self.recallEntry)
  235.                     if self.recallEntry == table.getsize(commandhistory) then
  236.                         self.recallEntry = nil
  237.                     end
  238.                 else
  239.                     self:SetText('')
  240.                 end
  241.             end
  242.         end
  243.     end
  244.     bg.edit.OnCharPressed = function(self, charcode)
  245.         local charLim = self:GetMaxChars()
  246.         if STR_Utf8Len(self:GetText()) >= charLim then
  247.             # Play Sound when charlimit reached
  248.             PlaySound('Forge/Lobby/snd_ui_lobby_lobby_unique_fail')
  249.         end
  250.     end
  251.     bg.edit.OnEnterPressed = function(self, text)
  252.         if text ~= "" then
  253.             if string.sub(text, 1, 1) == '/' then
  254.                 local spaceStart = string.find(text, " ") or string.len(text)
  255.                 local comKey = string.sub(text, 2, spaceStart - 1)
  256.                 local param = string.sub(text, spaceStart + 1)
  257.                 local found = false
  258.                 for i, command in specialCommands do
  259.                     if command.key == string.lower(comKey) then
  260.                         command.action(param)
  261.                         found = true
  262.                         break
  263.                     end
  264.                 end
  265.                 if not found then
  266.                     bg.history:AddItem(LOCF("<LOC chatui_0008>Unknown command: %s", comKey))
  267.                     if bg:IsHidden() then
  268.                         ShowHistory()
  269.                     end
  270.                 end
  271.             else
  272.                 msg = { to = chatTo, text = text }
  273.                 # Play Sound when chat text is submitted
  274.                 #PlaySound('Forge/UI/Lobby/snd_ui_lobby_chatbox_submit_text' )
  275.                 if chatTo == 'allies' then
  276.                     SessionSendChatMessage(FindClients(), msg)
  277.                 else
  278.                     SessionSendChatMessage(msg)
  279.                 end
  280.                 table.insert(commandhistory, msg)
  281.             end
  282.         end
  283.         # always close the chat window
  284.         ToggleChat()
  285.     end
  286.     bg.edit.OnEscapePressed = function(self, text)
  287.         # if the chat text box is currently shown, ESC should close it
  288.         if not bg:IsHidden() then
  289.             ToggleChat()
  290.         end
  291.     end
  292.    
  293.     SetupIgnoreList()
  294.     bg:Hide()
  295. end
  296. function ReceiveChat(sender, msg)
  297.     sender = sender or "nil sender"
  298.     if type(msg) == 'string' then
  299.         msg = { text = msg }
  300.     elseif type(msg) != 'table' then
  301.         msg = { text = repr(msg) }
  302.     end
  303.     if(msg.to) then
  304.         if(msg.to == 'allies') then
  305.             msg.to = '<LOC chatui_0002>Allies'
  306.         elseif(msg.to == 'all') then
  307.             msg.to = '<LOC chatui_0001>All'
  308.         end
  309.     end
  310.    
  311.     #if ignoredPlayers[sender] then return end
  312.     -----------------------------------------
  313.     if ignoredPlayers[string.lower(sender)] then return end
  314.     -----------------------------------------
  315.    
  316.     local textBoxWidth = bg.history.Right() - bg.history.Left()
  317.     local wrappedText = import('/lua/maui/text.lua').WrapText(LOCF("<LOC chatui_0003>%s (to %s): %s", sender, msg.to, msg.text), textBoxWidth,
  318.     function(text)
  319.         return bg.history:GetStringAdvance(text)
  320.     end)
  321.     for i, textLine in wrappedText do
  322.         bg.history:AddItem(textLine)
  323.     end
  324.     bg.history:ScrollToBottom()
  325.     local numItems = bg.history:GetItemCount()
  326.     local longest = 0
  327.     for i = numItems - 10, numItems do
  328.         if i < 0 or i >= numItems then
  329.             continue
  330.         end
  331.         local textLine = bg.history:GetItem(i)
  332.         local length = import('/lua/maui/text.lua').GetTextWidth(textLine, function(text) return bg.history:GetStringAdvance(text) end)
  333.         if length > longest then
  334.             longest = length
  335.         end
  336.     end
  337.     bg.historyBackMid.Width:Set(longest - 76)
  338.     if bg:IsHidden() then
  339.         ShowHistory()
  340.          #Play Sound when receiving chat message
  341.                 PlaySound( 'Forge/UI/HUD/snd_ui_hud_chatbox_receive_message' )
  342.     end
  343. end
  344. function ActivateChat(mods)
  345.     if mods.Shift then
  346.         chatTo = 'all'
  347.         bg.toText:SetText(LOC('<LOC chatui_0004>All:'))
  348.     else
  349.         chatTo = 'allies'
  350.         bg.toText:SetText(LOC('<LOC chatui_0000>Allies:'))
  351.     end
  352.     ToggleChat()
  353. end
  354. function ShowHistory()
  355.     bg.history:Show()
  356.     bg.history:SetNeedsFrameUpdate(true)
  357.     bg.history.time = 0
  358.     bg.history:DisableHitTest()
  359.     if bg.history:GetItemCount() > 0 then
  360.         bg.historyBackLeft:Show()
  361.         bg.historyBackMid:Show()
  362.         bg.historyBackRight:Show()
  363.         bg.historyBackLeft:DisableHitTest()
  364.         bg.historyBackMid:DisableHitTest()
  365.         bg.historyBackRight:DisableHitTest()
  366.     end
  367.     bg.history.OnFrame = function(self, delta)
  368.         self.time = self.time + delta
  369.         if self.time > CHAT_INACTIVITY_TIMEOUT then
  370.             self:SetNeedsFrameUpdate(false)
  371.             self:Hide()
  372.             self:GetParent().historyBackLeft:Hide()
  373.             self:GetParent().historyBackMid:Hide()
  374.             self:GetParent().historyBackRight:Hide()
  375.         end
  376.     end
  377. end
  378. function ToggleChat()
  379.     bg:SetHidden(not bg:IsHidden())
  380.     if bg.history:GetItemCount() <= 0 then
  381.         bg.historyBackLeft:Hide()
  382.         bg.historyBackMid:Hide()
  383.         bg.historyBackRight:Hide()
  384.     end
  385.     if bg:IsHidden() then
  386.         bg.edit:AbandonFocus()
  387.         bg.imgright:Hide()
  388.         bg.imgleft:Hide()
  389.         bg.imgmid:Hide()
  390.         bg.history.Depth:Set(1)
  391.         UIMain.RemoveEscapeHandler(ToggleChat)
  392.     else
  393.         bg.imgright:Show()
  394.         bg.imgleft:Show()
  395.         bg.imgmid:Show()
  396.         bg.edit:AcquireFocus()
  397.         bg.history:SetNeedsFrameUpdate(false)
  398.         bg.history:EnableHitTest()
  399.         bg.history.Depth:Set(15000)
  400.         UIMain.AddEscapeHandler(ToggleChat, UIUtil.ESCPRI_Commands)
  401.     end
  402. end
Locked Post 24 Replies
Search this post
Subscription Options


Reason for Karma (Optional)
Successfully updated karma reason!
June 12, 2011 5:44:24 PM from Demigod Forums Demigod Forums

NT

Reason for Karma (Optional)
Successfully updated karma reason!
June 12, 2011 7:51:02 PM from Demigod Forums Demigod Forums

Quick notes: 

So I'm just toying and testing with the full code atm.  Decided to add another command so that's its easier to squlech in game.  Added /s <playername>.  This works fine.  Going to include that functionality and provide a quick writeup explaining how squelch works as I don't think many/any use it.  The /s change required little to no work - tests ok.

Code: c++
  1. local specialCommands = {
  2.     {
  3.         key = "ignore",
  4.         action = HandleIgnore,
  5.     },
  6.     {
  7.         key = "s",
  8.         action = HandleIgnore,
  9.     },
  10.     {
  11.         key = "squelch",
  12.         action = HandleIgnore,
  13.     },
  14. }
Reason for Karma (Optional)
Successfully updated karma reason!
June 12, 2011 8:12:30 PM from Demigod Forums Demigod Forums

tested the mod out with doggu.  All functionality seems to work as intended.  Its a UI only mod (so no one else needs to have it enabled).  It has no impact on the lobby (expected) and only impacts in game play.  By adding doggu to the list of ignored players in the lua, I was able to have him automatically squelched when the game started.  Then, by using the /s doggu command, I was able to unsquelch him. 

As far as interesting things, it would be cool if i could figure out how to pass a command through chat that would return a number associated with a player and then mute based on that... something like /list

and that returns

pacov - 1

blah - 2

then i type /s 2 and blah gets muted. 

Just some quick thoughts.  Methinks I'm done playing with this atm, though. 

or perhaps figure out a way to squelch all and unsquelch all.  Not generally what you'd want to do, but it could be useful to just quickly type /sall or /uall. 

 

Reason for Karma (Optional)
Successfully updated karma reason!
June 12, 2011 9:54:40 PM from Demigod Forums Demigod Forums

Going to sit on this for a few days and then just add it in as is as a standalone mod and add to enhanced ui.  Any feedback appreciated on the code mess.  TIA

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 1:20:52 AM from Demigod Forums Demigod Forums

Like I said in your PM, it should be easy enough to modify the squelch system to save to and load from preferences.  I'll take another look at the preferences system, and see what I can come up with.

Until then, I'm willing to help, but I'd really prefer to help you learn to use non-destructive hooking first, as it's very advantageous and fairly simple.  I'd much rather fix and explain a failed attempt at a hook than have to re-write all of someone else's work for them - I'm sure you can understand?

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 10:00:49 AM from Demigod Forums Demigod Forums

Got some time to work in this last night, and a bit this morning.  I could only test it with bots, which have very different rules regarding nicknames, chat, etc and I had to modify a lot of conditions to make the bot squelching and list management work.  So, I don't know if it works with actual human players, but this version has all of the right conditions in, so it should.

If it isn't obvious from the code or the comments, this does a whole lot more than what you asked for, but it wasn't too difficult to add the extra framework/features.  Type /? or /help in the chat for a list of current commands, and see the comments for how to add more.

Features so far:
- Slash commands are saved to the up/down arrow command buffer, same as chat messages
- Issued commands pop up the chat window with normal hide delay so you can see the result of what you entered
- Better display of non-chat text, including window size adjustment, selective wrapping, etc
- New command infrastructure that makes it easy to add new commands and supports help text and multiple aliases for each command
- Commands for adjusting chat font size and window auto-hide time added, both save to the current profile
- Player names you ignore are saved to a persistent ignore list in preferences.  Names in the list are auto-ignored on game start (if present) and the chat window pops up with notifications for any ignored players in the current game, so you don't forget.  This is done on a 5-second timer after chat.lua loads, so slower computers may not get to see this with default chat timeout.
- Limited ignore list management: you can view the whole list including offline players (with players in the current game at the top, denoted by a star), but for now I kept in the limitation to only add/remove player names that are actually in the current game, mostly to prevent mistakes with mistyping names.  I'll add a separate command to add/remove offline players if anyone cares, but you can already do that by (carefully) editing the IgnoreList table it creates in your game.prefs.
- Player index list, with support for ignoring based on index (/pl for the list, /i # to ignore)

I'll probably release this as a standalone mod myself, but I need it tested first - and of course you're welcome to include the finished version in the enhanced UI package.

http://pastebin.com/iYxggZpH

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 10:03:35 AM from Stardock Forums Stardock Forums

Miri - 1st, I'd like to learn everything I can about how to mod in a nondestructive fashion.  I know enough to be able to reverse engineer the code a bit and figure out what's happening, but there are quite a few holes in my logic here and there. 

So just to recap what I have here:  I copied a mod that chirmaya created that fixed an old issue with the squelch/ignore system.  That mod isn't needed at all as the issue was resolved in a later DG patch. I'm guessing I could have started with the original chat.lua file, but I started using chirmaya's version anyway.  Then, I modified the code so that you could explicitly indicate users you want to squelch on a regular basis by editing the lua.  Then I added a change to make it easier to squelch so that instead of type /squelch (or /ignore) you can just type /s + playername.  That's just a minor enhancement that I thought would help people quickly squelch in game.  Anyway, that's the goal and that all works perfectly and tests out fine with how I coded things. 

That said, what I want is to code it proper - only I'm not sure how and I don't think I fully comprehend non-destructive modding.  I don't know precisely what I can exclude and what I need to include - and that's where I'm looking for some assistance.  Any explanations that you can provide to fill in the blanks that I don't get will make things much easier for me to understand (and will help me to learn how to do non-destructive hooking).

Can you perhaps explain how things work a little more?  For instance, without a mod enabled, the game would just check the normal lua location in dgdata.zip, right?  But if you enable a mod, it will check the dgdata.zip 1st, then looks at the modded lua (as long as its named appropriately and in the corresponding folder.  Am I right so far?  When you referred to destructive hooking, that's when I overwrite existing functions etc in my modded lua?  Or what precisely.  Anything you can explain along those lines will help.

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 10:55:09 AM from Demigod Forums Demigod Forums

Every time the engine executes a file from dgdata.zip, it looks in each mod's /hook folder for a file with a corresponding path and name, appends it to the original and executes it as one chunk.  So anything that's in the original file does not need to be included in a hook, as it's already still there.  The mod hooks of the file just add onto the end of the same file, and everything in the original is accessible, including any local variables declared in the main block of the file (outside of any functions, loops, conditional blocks, etc).

This is also why if you have a syntax error in a hooked file, the entire original fails to parse, and also why it only lists the path of the original file and only one line number - it's joining them together before it even tries to parse the code contained therein.  That's also why the line number represents the entire original file plus the number of lines in the hooked file prior to the error (e.g. if the original file is 200 lines, and the log error says line 220, that's likely line 20 of the first hook of that file).

So, like I've said before, always start your hooks from a blank file.  Never copy an existing file.  You want to have the original file open for reference and copy-pasting bits of code, but you only want to hook or override what you absolutely need to.

 

Everything else you can pretty much figure out by comparing original code to existing mod code that hooks it.  Learning to hook functions and classes is pretty simple - it's just a matter of saving a reference to the current version, re-defining it, and calling the original (adding your code in either before or after calling the original, as appropriate).

Sometimes you have to destructively override a function.  It can almost always be avoided when the original function was made with hooking in mind, but obviously GPG was strapped for time, and many of them are not.  With the exception of some inline texture file paths defined as local variables, most UI code can be hooked non-destructively without much effort.

In addition to the xp bar hook that I just did (which tells you everything you need to know about hooking functions, including functions defined within tables/objects by other functions), the UberFix is a good place to start, as I've commented just about everything in it including why I've overridden certain functions.

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 11:05:59 AM from Stardock Forums Stardock Forums

Quoting miriyaka,
appends it to the original and executes it as one chunk.

ah... ok. 

So, what you refer to as destructive is if I completely overwrite something in a mod that exists in the dgdata.zip?  eg if a fuction called bob exists in the dgdata.zip and I reprogram that function in the modded lua (eg I reprogram the function bob by just directly recoding it and calling it bob). then that's destructive?  Am I following you?

And you are saying that if there is a function called bob in dgdata.zip and I need to modify that, what I want to do is create my mod lua, call the function and save it as something else... It think I'm missing some things here.  Anyway, I'll review that xp bar hook today after work.  If you can think of anything else for me to chew on, I'm all ears.  Set me straight, miri!  And thanks.

I guess I'll throw out a few more questions.  Let's say there is a function that I need changed.  I'm just going to copy paste info from the chat.lua.

Code: c++
  1. function ToggleChat()
  2.     bg:SetHidden(not bg:IsHidden())
  3.     if bg.history:GetItemCount() <= 0 then
  4.         bg.historyBackLeft:Hide()
  5.         bg.historyBackMid:Hide()
  6.         bg.historyBackRight:Hide()
  7.     end

Let's say (and it probably is) that the code above is exactly what is in the dgdata.zip.  Let's say that my one goal is to create a mod so that the line bg.historyBackMid:Hide() is removed.  What would be the appropriate way to code that?

 

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 12:53:21 PM from Demigod Forums Demigod Forums

Quoting OMG_pacov,
So, what you refer to as destructive is if I completely overwrite something in a mod that exists in the dgdata.zip? eg if a fuction called bob exists in the dgdata.zip and I reprogram that function in the modded lua (eg I reprogram the function bob by just directly recoding it and calling it bob). then that's destructive? Am I following you?

More or less, yes.  If, for example, your hook is a copy of an original file with a few changes, it's 100% incompatible with any other mod that changes anything else in that file, because everything will be re-defined and the other mod's changes will be overridden with the 'original' code that you're duplicating.  It's also incompatible with any hypothetical future Demigod patch that changes that file, however unlikely that is to happen at this point.

 

Quoting OMG_pacov,
Let's say (and it probably is) that the code above is exactly what is in the dgdata.zip. Let's say that my one goal is to create a mod so that the line bg.historyBackMid:Hide() is removed. What would be the appropriate way to code that?

You can't directly erase functionality from an existing function without completely overriding it.  However, in most cases, it's possible to just change something back.  In this case, you would assign a local variable the current version of that function, re-define the function, run that saved copy, and then do a Show() on whatever you didn't want hidden afterwards.  Just like the XP bar hook does with both the hidden controls and the text position.

As I just mentioned, the only time you're out of luck when it comes to un-doing something is when it's declared locally and used within the same chunk of code, with no external or post-run opportunities to change it.  This is, as far as I'm aware, how most e.g. UI background textures are assigned.  Some control/bitmap types might allow a new SetTexture call afterwards, but in my experience some don't.  In those cases, you have to override the function with an altered copy of the original (but even then only that function - nothing else in the file).

 

Anyway, if you get a chance, test out that chat.lua hook in an actual multiplayer game, and make sure the ignore commands (particularly the auto-ignore and the ignore-by-index) work on human players.

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 1:18:41 PM from Stardock Forums Stardock Forums

Quoting miriyaka,
f, for example, your hook is a copy of an original file with a few changes, it's 100% incompatible with any other mod that changes anything else in that file, because everything will be re-defined and the other mod's changes will be overridden with the 'original' code that you're duplicating.

ah - ok.  So the bottom line is learning to take exactly what you need and only what you need.  I think I get the logic.  There are still some bits I'm missing though, but getting close to a better understanding.

Quoting miriyaka,
You can't directly erase functionality from an existing function without completely overriding it.

When you say overriding that, that's when I'd name a function the exact same thing as its called in the dgdata.zip and program it "again" in the lua?  I have that right?

Quoting miriyaka,
and then do a Show() on whatever you didn't want hidden afterwards. J

I think I see what you are saying here... so you'd just leave the original function alone and create a new one that forced it visible?   That right?

Quoting miriyaka,
Anyway, if you get a chance, test out that chat.lua hook in an actual multiplayer game, and make sure the ignore commands (particularly the auto-ignore and the ignore-by-index) work on human players.

I'm not exactly sure what you mean.  I tested the full mod (eg all of the code - sloppy as it is) in MP and it worked perfectly.  Are you saying you just want me to test a small section of it in MP?  Anyway, sorry to overdo the probably obvious questions, but I'm trying to get a good understanding that should help the silly questions die a bit.

 edit - I just noticed you updated  (or I completely missed reply #6).  I'll look into that.  Thanks!

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 1:25:41 PM from Stardock Forums Stardock Forums

quick question (and like all my questions... brilliant ) - I notice at the start of your code you pull local prefs.  I don't have access to that file from here - why are you importing this in your mod?

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 1:31:44 PM from Demigod Forums Demigod Forums

Quoting OMG_pacov,
When you say overriding that, that's when I'd name a function the exact same thing as its called in the dgdata.zip and program it "again" in the lua? I have that right?

Right.  Technically a hook is also an override, in that you're hijacking the name of the original function, the difference being that you're saving and calling the existing version of that function.

 

Quoting OMG_pacov,
I think I see what you are saying here... so you'd just leave the original function alone and create a new one that forced it visible? That right?

If by 'create a new one' you mean hook the original function, then yes.  But you're not exactly leaving the original alone, you're saving it to another variable and co-opting its name so that whenever it would be called, your code runs before or after the existing code in the copy of the function you saved (which has to be explicitly called in your re-defined version of the function).  Again, look at the XP bar hook - this is the first thing done in the file, and I think I commented it pretty well.

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 1:39:13 PM from Stardock Forums Stardock Forums

Quoting miriyaka,
the difference being that you're saving and calling the existing version of that function.

ok i get it.  thx.

I'm reading xp.lua again - and yes - you did appear to comment it well   thx

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 1:56:09 PM from Stardock Forums Stardock Forums

question about the mod you are working on (and yes, I can test it out for you in MP - np) - with how you've coded it, does simply adding someone to the ignore list in game cause that person to persist over to the next game?  My method required editing the lua itself.  Does yours require that as well or did you come up with a way where you just ignore in game and it persists?

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 2:57:11 PM from Stardock Forums Stardock Forums

And another question- how do know what options are available for a function.  For instance, in the xp mod, you use OnHeroChange(unit) - and also function create(parent) - how do you know what you can use?  I'm referring to whatever appears between ( ).  Is this defined somewhere?  That would fill in a big blank in my understanding.  Thx.

and one other thought about the mod you are working on.  Is there any way you can create some sort of toggle that might capture the in game chat to a txt file?  Even without a toggle, something like that could be fun.

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 7:41:53 PM from Demigod Forums Demigod Forums

abt to do some testing.

-Testing done - I'm thinking I might bundle up a beta version of this so we can get some feedback from more folks.   I tested /?, /s, /pl, then squelching by player list number.  Also tested unsquelching - worked fine. 

Played to games with darkliath.  Muted her in game 1.  Hit end game and hosted another.  Message displayed indicating that she was muted.  Tested and she could hear me - i could not hear her.  So, the ignorelist seems to be propagating perfectly.  Nice work. 

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 8:02:38 PM from Demigod Forums Demigod Forums

Linky to exe: http://www.box.net/shared/lr2k101hj90fax35lugj

I did create an UID in case you want to keep using that for the mod.  Let me know if you need further testing, if you are planning on a new version, etc.  If this is what you are sticking with, please let me know that as well and I'll integrate it into the enhanced ui mod.  Thanks!

Oh - and here's the inno script in case you want full control for your final:

Code: c++
  1. ; Inno Setup script for Demigod mod
  2. ; Instructions for use:
  3. ; 1. Download and install Inno Setup: http://www.jrsoftware.org/isdl.php
  4. ; 2. Edit this file by searching for the ***** comments and following the instructions.
  5. ; 3. Click the compile button above.
  6. ; 4. Make sure you test!
  7. [Setup]
  8. ; ***** The Name of your mod
  9. AppName=Squelch Mod Beta
  10. ; ***** The Name of your mod including version number
  11. AppVerName=Squelch Mod Beta
  12. ; ***** You are the publisher right?
  13. AppPublisher=mithy
  14. ; ***** Version number that will be embedded in the setup.exe
  15. VersionInfoVersion=0.1
  16. ; Looks for the DG install folder
  17. DefaultDirName={reg:HKLM\Software\Stardock\Drengin.net\Demigod,Path|{pf}\Demigod}
  18. DirExistsWarning=no
  19. EnableDirDoesntExistWarning=yes
  20. DisableProgramGroupPage=true
  21. Uninstallable=no
  22. ; ***** Where do you want Inno Setup to put the setup file?
  23. OutputDir=.
  24. ; ***** What do you want to call the setup.exe file? (do NOT put the .exe on the end)
  25. OutputBaseFilename=Squelch Mod Beta
  26. [Files]
  27. ; ***** For SOURCE, point to where your mod files are located.  For DESTDIR, it will put it in the DG\bindata\mods folder.  You just need to put the foldername your mod will exist in within the mods folder.
  28. ; The EXCLUDES line included in this example will exclude .SVN folders (subversion junk), ~ files (GVIM junk), .ISS files (INNOSETUP script files), .EXE files (your setup file!).  Add any other file patterns you want to exclude
  29. Source: C:\Program Files (x86)\Stardock Games\Demigod\bindata\mods\Squelch\*; DestDir: {app}\bindata\mods\Squelch; Excludes: ".svn,*~,*.iss,*.exe"; Flags: ignoreversion overwritereadonly replacesameversion recursesubdirs
  30. [InstallDelete]
  31. Type: filesandordirs; Name: "{app}\bindata\mods\Squelch\*"
  32. ;This code removes the directory if it is empty
  33. Type: dirifempty; Name: "{app}\bindata\mods\Squelch\"
  34.              
  35. [Code]
  36. var DGNotFoundWarned: Boolean;
  37. function InitializeSetup(): Boolean;
  38. begin
  39.   DGNotFoundWarned := False;
  40.   Result := True;
  41. end;
  42. { See if this folder looks like the Demigod folder }
  43. function IsValidDGFolder(const Folder: String): Boolean;
  44. begin
  45.   Result := FileExists(RemoveBackslashUnlessRoot(Folder) + '\bin\demigod.exe');
  46. end;
  47. { Do not show the select dir page if we found the Demigod folder. }
  48. function ShouldSkipPage(PageID: Integer): Boolean;
  49.   var DGPath : String;
  50. begin
  51.   case PageID of
  52.     wpSelectDir:
  53.       begin
  54.         Result := RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\Stardock\Drengin.net\Demigod', 'Path', DGPath);
  55.         If Result Then
  56.           Result := IsValidDGFolder(DGPath);
  57.         If Not Result Then
  58.         begin
  59.           If Not DGNotFoundWarned Then
  60.           begin
  61.             DGNotFoundWarned := True;
  62.             MsgBox('Could not find your Demigod installation.  Please select the main Demigod folder', mbInformation, MB_OK);
  63.           end;
  64.         end;
  65.       end;
  66.   else
  67.     Result := False;
  68.   end;
  69. end;
  70. { Make sure the folder the user selected is correct! }
  71. function NextButtonClick(CurPageID: Integer): Boolean;
  72. begin
  73.   case CurPageID of
  74.     wpSelectDir:
  75.       begin
  76.         Result := IsValidDGFolder(ExpandConstant('{app}'));
  77.         If Not Result Then
  78.           MsgBox('The folder you selected does not look like the main Demigod folder.  Please select the main Demigod folder', mbInformation, MB_OK);
  79.       end;
  80.   else
  81.     Result := True;
  82.   end;
  83. end;
  84. { Remind the user to enable the mod from within the game. }
  85. procedure CurStepChanged(CurStep: TSetupStep);
  86. begin
  87.   case CurStep of
  88.     ssPostInstall:
  89.       MsgBox('Make sure you disable or delete any ui mod included in this package before enabling this mod in the Mod Manager menu!', mbInformation, MB_OK);
  90.   end;
  91. end;
  92. [Dirs]
Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 8:54:46 PM from Demigod Forums Demigod Forums

Thanks for testing it.  Good to hear that it works with players - it took jumping through a lot of hoops to test it with bots, and obviously in a normal game I don't want bots to show up on the player list nor be ignorable.

I'm not sure how much I'll expand it at this point, I guess it depends on how badly people want built-in offline list addition/removal, or what other similar features I or others can think of - chat size and timeout were obvious, because I found both to be really, really annoying at default while testing the mod myself. 
The chat size still kind of needs an adjustment to the line number calculation-- there are 10 history lines at font size 18, but if you make it smaller, there are more than 10 and the auto-width adjustment only adjusts to the last 10 lines which occasionally makes the window narrower than the earliest two or three lines of visible text.

 

Quoting OMG_pacov,
And another question- how do know what options are available for a function. For instance, in the xp mod, you use OnHeroChange(unit) - and also function create(parent) - how do you know what you can use? I'm referring to whatever appears between ( ). Is this defined somewhere? That would fill in a big blank in my understanding. Thx.

They're defined right there, in the function definition itself.  When you define a function, you also define the names of its parameters.  I could call them 'bleeg' and 'flarn' if I wanted to, and they'll still represent the first two parameters passed to any call of that function, and it's the number of them that's important, not their names.  This:

Code: c++
  1. local prevOnHeroChange = OnHeroChange
  2. function OnHeroChange(meep)
  3.     prevOnHeroChange(meep)
  4.     if meep then
  5.         --do stuff with meep, which is the unit
  6.     end
  7. end
..is perfectly valid, and neither the previous version of OnHeroChange nor any other mods that hook my version of the function know or care what I named the parameter variable, only its contents.  I could even add an extra parameter, although in this case, that would do nothing since the function is only being called with one parameter.  Lua is really flexible when it comes to things like this.
You can remove parameters too, which is essentially what happens when OnHeroChange is called in observer mode or with a dead demigod (the parameter passed to it, normally the user unit object for that demigod, is nil, or non-existent-- which is what caused problems for the original mod), but there's pretty much never actually a reason to remove parameters from the function definition itself, especially a function that multiple mods may hook (as that would break all mods that rely on those parameters).

As mentioned, the number of parameters passed to the function in any/all of its calls determine how many it needs in its definition.  Or, more accurately, the reverse, but when you're hooking an existing function, that's how you know for certain what's being passed to it and how many parameters it uses.  In the case of OnHeroChange, it's initially confusing, because the function is never called directly in HUD_xp.lua.  It's added as a callback function via /lua/ui/game/InGameUI.lua, and called by SetFocusArmyHero on line 125 of that file with one parameter - the unit passed to that function by UpdateForNewArmy at line 27, which you can see getting (or failing to get) a handle to the hero unit.

 

Quoting OMG_pacov,
and one other thought about the mod you are working on. Is there any way you can create some sort of toggle that might capture the in game chat to a txt file? Even without a toggle, something like that could be fun.

I'm not sure.  The game only allows lua to write to the preferences file (which has limitations) and the log.  I could make a UI-side mod that outputs all chat messages to the log fairly easily, and gives them a unique tag so that they're easy to search/filter for, but you'd still have to run with the log enabled, and find some program to pick them out of all the other spam.

In fact, it's so easy, it took about 3 minutes: http://pastebin.com/bLnFAHGh

Not tested, but it's pretty simple.  I just hooked ReceiveChat, aped the first part of its sender formatting/localization, prepend [CHAT] and/or *ignored* to the message, and dump it to the log.  No on/off switch, but it's probably not something most people will use / know how to use anyway, so it's best in a standalone mini-mod.  To be clear, this only logs chat received in the UI, and has no more access to enemy team chat than the normal UI.

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 9:00:42 PM from Demigod Forums Demigod Forums

Quoting miriyaka,
n fact, it's so easy, it took about 3 minutes: http://pastebin.com/bLnFAHGh

is there a sample of any output you wouldn't mind posting?  I'm guessing its just a bunch of stuff to wade through.  Curious.

ah nevermind... the words not tested imply you don't have a log

I'll look at this later.  Thanks for all your help!

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 9:06:07 PM from Demigod Forums Demigod Forums

The average log for a full game is about 3000-6000 lines.  So without a tool to pull out lines starting with [CHAT], you're limited to using the log window (/showlog), filtering by that string, and copy-pasting.

Oh, and the game will erase and re-start the log file every time it starts up (not every game, just every app start).  So if you really want to save the chat messages (or anything else in a log), you need to do it right after you exit.

Reason for Karma (Optional)
Successfully updated karma reason!
June 13, 2011 11:23:06 PM from Demigod Forums Demigod Forums

ok - i'll prob take a look.  If its not just a chat file, then I'm not sure I'm interested... but still... if we add it on, it probably won't hurt.

Oh - and if creating a list works fine in game (it does), then I don't think we need an offline component.  I didn't really want folks to have to edit a lua file in my version of the squelch mod, but it was all I knew how to do.  Yours works better and I think removes that need. 

So - let's let it sit for 2 days before we finalize anything.  That work for you?  Do you want to release a finalized version along with a new post/changelog/blahblah or do you want me to take care of that, bundle it up, create the post, etc?  Obviously all credit to you on this mod and thanks for taking the time.  Whatever you want - I just want to finalize so I can add this to the enhanced ui and we'll call it a day. 

Reason for Karma (Optional)
Successfully updated karma reason!
June 14, 2011 1:44:29 AM from Demigod Forums Demigod Forums

Let it sit for a few days at least.  It's right at the top of the forum, and people who are interested can certainly try it out for now.  I wrote it in a few hours and probably didn't test every angle, and I'm not sure whether or not I want to add to it yet.

Reason for Karma (Optional)
Successfully updated karma reason!
June 14, 2011 8:28:09 AM from Stardock Forums Stardock Forums

righto.

Reason for Karma (Optional)
Successfully updated karma reason!
Stardock Forums v1.0.0.0    #108433  walnut3   Server Load Time: 00:00:00.0000672   Page Render Time: