List of commits:
Subject Hash Author Date (UTC)
feat(choir)!: Add alternative section creation 1aac7826a961509021679db26b95071edba77ad8 Vladyslav Bondarenko 2021-11-04 17:36:05
feat(clearcasting): Add death knight indicators 5c42814210b355e6de601faaf8e173222995cfa6 Vladyslav Bondarenko 2021-10-30 12:23:43
feat!: Show spell tooltip on indicator mouseover dd69f46f1522f49ce942d8e0b3756c609471be7a Vladyslav Bondarenko 2021-10-13 01:12:08
feat!: Indicator grouping feature df7e0579a51388dba73bc5d92b3d04369bb0e426 Vladyslav Bondarenko 2021-10-13 00:17:28
feat!: Redo the core functionality 085af1bc403b3a9d2d4b99ec460d67889239fabd Vladyslav Bondarenko 2021-07-09 11:15:31
feat: Track additional auras 958e52f9808d64ebb816663d2aa22a4eb39a2068 Vladyslav Bondarenko 2021-01-22 20:28:13
fix!: No longer crash on unknown spell 65061085f0f55b75910c9ed4d8249b1e8e30f9ee Vladyslav Bondarenko 2020-12-11 17:31:16
fix: Render indicators properly on first login bf102cc749cc23d3769a82f10fe93a05afc5277f Vladyslav Bondarenko 2020-12-11 14:25:11
feat: Add Serendipity and Surge of Light indicators ba1d0f32a4b8f4f5d4e2730f0a4e409e23b52974 Vladyslav Bondarenko 2020-12-10 16:19:55
Initial commit 9b3df418e373218125fec12271084afebb11cfc2 Vladyslav Bondarenko 2020-12-04 10:27:24
Commit 1aac7826a961509021679db26b95071edba77ad8 - feat(choir)!: Add alternative section creation
Add alternative way to produce a filtered subset of unit auras,
that is sorted predictably and clearly. Previous feature could not
be rendered in an ordered fasion and produced too much unmaintainable
code. This is a prototype.

Does not work in combat. Next step would be to fix it to allow correct
rendering in combat.
Author: Vladyslav Bondarenko
Author date (UTC): 2021-11-04 17:36
Committer name: Vladyslav Bondarenko
Committer date (UTC): 2021-11-04 17:36
Parent(s): 5c42814210b355e6de601faaf8e173222995cfa6
Signing key:
Tree: 70517e01b2582f5167684fcbc2d89e2398385d33
File Lines added Lines deleted
clearcasting.lua 433 5
File clearcasting.lua changed (mode: 100644) (index b275cca..9682e8f)
... ... local function tooltipOverlayEventProcessor(tooltipOverlay)
393 393 local indicator = tooltipOverlay:GetParent() local indicator = tooltipOverlay:GetParent()
394 394 assert (indicator ~= nil) assert (indicator ~= nil)
395 395
396 local spellId = indicator.spellId
396 local spell = indicator.spellId or indicator.spellName or indicator:GetAttribute('spell')
397 local _, _, _, spellId = GetSpellInfo(spell)
397 398 if spellId then if spellId then
398 399 assert ('number' == type(spellId)) assert ('number' == type(spellId))
399 local t = GetSpellLink(spellId)
400 local t = GetSpellLink(spell)
400 401 GameTooltip:SetHyperlink(t) GameTooltip:SetHyperlink(t)
401 402 else else
402 403 GameTooltip:SetText('spell description could not be found') GameTooltip:SetText('spell description could not be found')
 
... ... local function createTooltipOverlay(indicator)
414 415 --[[ It is critical to call EnableMouse method on a tooltip overlay frame ]]-- --[[ It is critical to call EnableMouse method on a tooltip overlay frame ]]--
415 416 o:EnableMouse(true) o:EnableMouse(true)
416 417
417 indicator.tooltipOverlay = o
418
419 418 o:SetScript('OnEnter', tooltipOverlayEventProcessor) o:SetScript('OnEnter', tooltipOverlayEventProcessor)
420 419 o:SetScript('OnLeave', function() GameTooltip:Hide(); end) o:SetScript('OnLeave', function() GameTooltip:Hide(); end)
421 420
 
... ... local function createIndicator(parentFrame, target, unitDesignation, filterDescr
469 468 f.spellId = nil f.spellId = nil
470 469 f.spellName = nil f.spellName = nil
471 470
472 createTooltipOverlay(f)
471 f.tooltipOverlay = createTooltipOverlay(f)
473 472
474 473 f:SetScript('OnEvent', indicatorEventProcessor) f:SetScript('OnEvent', indicatorEventProcessor)
475 474 f:SetScript('OnUpdate', indicatorUpdateProcessor) f:SetScript('OnUpdate', indicatorUpdateProcessor)
 
... ... local function createSection(name, parent, width, height)
554 553 return section return section
555 554 end end
556 555
556 local function unpackAuraTable(auraTable)
557 assert(auraTable ~= nil)
558 assert("table" == type(auraTable))
559
560 local spellName = auraTable[1]
561 local spellRank = auraTable[2]
562 local pictureFile = auraTable[3]
563 local stackQuantity = auraTable[4]
564 local category = auraTable[5]
565 local durationSec = auraTable[6]
566 local expirationInstance = auraTable[7]
567 local casterUnitDesignation = auraTable[8]
568 local stealableFlag = auraTable[9]
569 local consolidationFlag = auraTable[10]
570 local spellId = auraTable[11]
571
572 return spellName, spellRank, pictureFile, stackQuantity, category, durationSec, expirationInstance,
573 casterUnitDesignation, stealableFlag, consolidationFlag, spellId
574 end
575
576 local function getAuraWeight(
577 eitherAuraTableOrSpellName,
578 spellRank,
579 pictureFile,
580 stackQuantity,
581 category,
582 durationSec,
583 expirationInstance,
584 casterUnitDesignation,
585 stealableFlag,
586 consolidationFlag,
587 spellId)
588
589 local spellName
590 if "table" == type(eitherAuraTableOrSpellName) then
591 local auraTable = eitherAuraTableOrSpellName
592
593 spellName,
594 spellRank,
595 pictureFile,
596 stackQuantity,
597 category,
598 durationSec,
599 expirationInstance,
600 casterUnitDesignation,
601 stealableFlag,
602 consolidationFlag,
603 spellId = unpackAuraTable(auraTable)
604 elseif "string" == type(eitherAuraTableOrSpellName) then
605 spellName = eitherAuraTableOrSpellName
606 else
607 error("invalid argument")
608 end
609
610 durationSec = durationSec or 1
611 durationSec = math.max(1, durationSec)
612 local now = GetTime()
613 expirationInstance = expirationInstance or now
614 local weight = 0
615 if "player" == casterUnitDesignation then
616 weight = weight + 10000
617 end
618
619 local categoryMap = {["Magic"] = 4000, ["Poison"] = 3000, ["Disease"] = 2000, ["Curse"] = 1000}
620 local categoryWeight = 0
621 if category then
622 categoryWeight = categoryMap[category] or 0
623 end
624 weight = weight + categoryWeight
625
626 --[[ FIXME Sorting by remained duraiton does not work for all spells for some reason ]]--
627 local durationRemainingSec = expirationInstance - now
628 local durationElapsedSec = durationSec - durationRemainingSec
629 local durationWeight = durationElapsedSec - durationSec
630 weight = weight + math.ceil(durationWeight)
631
632 return weight
633 end
634
635
636 local function sortUnitAuraTable(a, b)
637 return getAuraWeight(a) > getAuraWeight(b)
638 end
639
640 local function requestUnitAuraTable(unitDesignation, filterDescriptor)
641 assert (unitDesignation ~= nil)
642 assert ('string' == type(unitDesignation))
643 unitDesignation = strtrim(unitDesignation)
644 assert (string.len(unitDesignation) >= 4)
645 assert (string.len(unitDesignation) <= 64)
646
647 assert (filterDescriptor ~= nil)
648 assert ('string' == type(filterDescriptor))
649 filterDescriptor = strtrim(filterDescriptor)
650 assert (string.len(filterDescriptor) >= 4)
651 assert (string.len(filterDescriptor) <= 128)
652
653 local j = 0
654 local q = 0
655 local e = {}
656 while (j < 144) do
657 j = j + 1
658 local name, rank, pictureFile, stackQuantity, category,
659 duration, expirationInstance,
660 caster, stealableFlag, consolidateFlag, id = UnitAura(unitDesignation, j, filterDescriptor)
661 if name then
662 local auraTable = {name, rank, pictureFile, stackQuantity, category,
663 duration, expirationInstance,
664 caster, stealableFlag, consolidateFlag, id}
665 q = q + 1
666 e[q] = auraTable
667 else
668 break
669 end
670 end
671 table.sort(e, sortUnitAuraTable)
672
673 return e
674 end
675
676 local function subsetEventProcessor(subsetFrame)
677 assert (subsetFrame ~= nil)
678
679 local unitDesignation = subsetFrame.unit or 'player'
680 assert (unitDesignation ~= nil)
681 assert ('string' == type(unitDesignation))
682 unitDesignation = strtrim(unitDesignation)
683 assert (string.len(unitDesignation) >= 4)
684 assert (string.len(unitDesignation) <= 64)
685
686 local filterDescriptor = subsetFrame.filter or 'HELPFUL'
687 assert (filterDescriptor ~= nil)
688 assert ('string' == type(filterDescriptor))
689 filterDescriptor = strtrim(filterDescriptor)
690 assert (string.len(filterDescriptor) >= 4)
691 assert (string.len(filterDescriptor) <= 128)
692
693 local e = requestUnitAuraTable(unitDesignation, filterDescriptor)
694
695 local i = 0
696 local t = {subsetFrame:GetChildren()}
697 while (i < math.min(#e, #t)) do
698 i = i + 1
699 local b = t[i]
700 assert (b ~= nil)
701
702 local auraTable = e[i]
703 assert (auraTable ~= nil)
704
705 local pictureFile = auraTable[3]
706 assert (pictureFile ~= nil)
707 local artwork = b.artwork
708 assert (artwork ~= nil, b:GetName() .. ' requires artwork field')
709 artwork:SetTexture(pictureFile)
710
711 local stackQuantity = auraTable[4]
712 local durationSec = auraTable[6]
713 local expirationInstance = auraTable[7]
714 applyDuration(b, durationSec, expirationInstance, stackQuantity)
715
716 local spellName = auraTable[1]
717 assert (spellName ~= nil)
718 b:SetAttribute('unit', unitDesignation)
719 b:SetAttribute('spell', spellName)
720
721 b.unit = unitDesignation
722 local spellId = auraTable[11]
723 b.spellId = spellId
724 b.spellName = spellName
725
726 b:Show()
727 end
728
729 local k = #e
730 while (k < #t) do
731 k = k + 1
732 local b = t[k]
733 assert (b ~= nil)
734
735 local artwork = b.artwork
736 assert (artwork ~= nil, b:GetName() .. ' requires artwork field')
737 artwork:SetTexture("Interface\\Icons\\spell_nature_wispsplode")
738
739 b:SetAttribute('unit', unitDesignation)
740 b:SetAttribute('spell', nil)
741
742 b.unit = nil
743 b.spellId = nil
744 b.spellName = nil
745
746 b:Hide()
747 end
748 end
749
750 local function subsetButtonUpdateProcessor(subsetButton)
751 local unit = subsetButton.unit or subsetButton:GetAttribute('unit')
752 if not unit then
753 return
754 end
755
756 local spell = subsetButton.spell or subsetButton.spellName or subsetButton:GetAttribute('spell')
757 if not spell then
758 return
759 end
760
761 local filter = subsetButton.filter or subsetButton:GetAttribute('filter')
762
763 local n, _, _, stackQuantity, _, durationSec, expirationInstance = UnitAura(unit, spell, nil, filter)
764 if not n then
765 return
766 end
767
768 applyDuration(subsetButton, durationSec, expirationInstance, stackQuantity)
769 end
770
771 local function createSubsetButtonArtwork(subsetButton)
772 assert (subsetButton ~= nil)
773
774 local buttonWidth = subsetButton:GetWidth()
775 local buttonHeight = subsetButton:GetHeight()
776
777 local marginBottom = math.max(buttonWidth, buttonHeight) - math.min(buttonWidth, buttonHeight)
778 local artwork = subsetButton:CreateTexture(subsetButton:GetName() .. 'Artwork', 'ARTWORK')
779 artwork:SetPoint('TOPLEFT', 0, 0)
780 artwork:SetPoint('TOPRIGHT', 0, 0)
781 artwork:SetPoint('BOTTOMLEFT', 0, marginBottom)
782 artwork:SetPoint('BOTTOMRIGHT', 0, marginBottom)
783 artwork:SetTexture("Interface\\Icons\\spell_nature_wispsplode")
784
785 return artwork
786 end
787
788 local function createSubsetButtonText(subsetButton)
789 assert (subsetButton ~= nil)
790
791 local buttonHeight = subsetButton:GetHeight()
792
793 local t = subsetButton:CreateFontString(subsetButton:GetName() .. 'Text', 'OVERLAY')
794 local fontObject = NumberFont_OutlineThick_Mono_Small
795 assert (fontObject ~= nil)
796 t:SetFontObject(fontObject)
797 t:SetPoint('BOTTOMLEFT', -4, 0)
798 t:SetPoint('BOTTOMRIGHT', 4, 0)
799 t:SetPoint('TOPRIGHT', 4, buttonHeight / -2)
800 t:SetPoint('TOPLEFT', -4, buttonHeight / -2)
801 t:SetText('?')
802
803 return t
804 end
805
806 local function createSubsetButton(subsetFrame, buttonDesignation, unitDesignation, spellName, filterDescriptor,
807 buttonWidth, buttonHeight)
808 assert (buttonDesignation ~= nil)
809 assert ('string' == type(buttonDesignation))
810 buttonDesignation = strtrim(buttonDesignation)
811 assert (string.len(buttonDesignation) >= 4)
812 assert (string.len(buttonDesignation) <= 256)
813
814 assert (subsetFrame ~= nil)
815
816 assert (unitDesignation ~= nil)
817 assert ('string' == type(unitDesignation))
818 unitDesignation = strtrim(unitDesignation)
819 assert (string.len(unitDesignation) >= 4)
820 assert (string.len(unitDesignation) <= 64)
821
822 if spellName then
823 assert ('string' == type(spellName))
824 spellName = strtrim(spellName)
825 assert (string.len(spellName) >= 2)
826 assert (string.len(spellName) <= 256)
827 end
828
829 if filterDescriptor then
830 assert ('string' == type(filterDescriptor))
831 filterDescriptor = strtrim(filterDescriptor)
832 assert (string.len(filterDescriptor) >= 4)
833 assert (string.len(filterDescriptor) <= 128)
834 end
835
836 if not buttonWidth then
837 buttonWidth = 24
838 end
839 buttonWidth = math.ceil(buttonWidth)
840 assert (buttonWidth ~= nil)
841 assert ('number' == type(buttonWidth))
842 assert (buttonWidth >= 8)
843 assert (buttonWidth <= 40)
844
845 if not buttonHeight then
846 buttonHeight = buttonWidth + buttonWidth * 2 / 3
847 end
848 buttonHeight = math.ceil(buttonHeight)
849 assert (buttonHeight ~= nil)
850 assert ('number' == type(buttonHeight))
851 assert (buttonHeight >= 8)
852 assert (buttonHeight <= 40)
853
854 assert (buttonWidth <= buttonHeight)
855
856 local b = CreateFrame('BUTTON', buttonDesignation, subsetFrame, 'SecureActionButtonTemplate')
857 b:SetSize(buttonWidth, buttonHeight)
858
859 b.artwork = createSubsetButtonArtwork(b)
860 b.text = createSubsetButtonText(b)
861
862 b:SetAttribute('type2', 'cancelaura')
863 b:SetAttribute('unit', unitDesignation)
864 b:SetAttribute('spell', spellName)
865 b:SetAttribute('filter', filterDescriptor)
866
867 --[[ TODO Add aura category (magic, disease, physical etc) border color indicator ]]--
868 b.tooltipOverlay = createTooltipOverlay(b)
869
870 b:SetScript('OnUpdate', subsetButtonUpdateProcessor)
871
872 return b
873 end
874
875 local function createSubset(parentFrame, frameDesignation, unitDesignation, filterDescriptor,
876 columnQuantity, rowQuantity,
877 buttonWidth, buttonHeight)
878 assert (frameDesignation ~= nil)
879 assert ('string' == type(frameDesignation))
880 frameDesignation = strtrim(frameDesignation)
881 assert (string.len(frameDesignation) >= 4)
882 assert (string.len(frameDesignation) <= 256)
883
884 assert (parentFrame ~= nil)
885
886 assert (unitDesignation ~= nil)
887 assert ('string' == type(unitDesignation))
888 unitDesignation = strtrim(unitDesignation)
889 assert (string.len(unitDesignation) >= 4)
890 assert (string.len(unitDesignation) <= 64)
891
892 assert (filterDescriptor ~= nil)
893 assert ('string' == type(filterDescriptor))
894 filterDescriptor = strtrim(filterDescriptor)
895 assert (string.len(filterDescriptor) >= 4)
896 assert (string.len(filterDescriptor) <= 128)
897
898 if not buttonWidth then
899 buttonWidth = 24
900 end
901 buttonWidth = math.ceil(buttonWidth)
902 assert (buttonWidth ~= nil)
903 assert ('number' == type(buttonWidth))
904 assert (buttonWidth >= 8)
905 assert (buttonWidth <= 40)
906
907 if not buttonHeight then
908 buttonHeight = buttonWidth + buttonWidth * 2 / 3
909 end
910 buttonHeight = math.ceil(buttonHeight)
911 assert (buttonHeight ~= nil)
912 assert ('number' == type(buttonHeight))
913 assert (buttonHeight >= 8)
914 assert (buttonHeight <= 40)
915
916 if not columnQuantity then
917 columnQuantity = 1
918 end
919 columnQuantity = math.min(math.max(1, math.ceil(columnQuantity)), 12)
920 assert (columnQuantity ~= nil)
921 assert ('number' == type(columnQuantity))
922 assert (columnQuantity >= 1)
923 assert (columnQuantity <= 12)
924
925 if not rowQuantity then
926 rowQuantity = 1
927 end
928 rowQuantity = math.min(math.max(1, math.ceil(rowQuantity)), 12)
929 assert (rowQuantity ~= nil)
930 assert ('number' == type(rowQuantity))
931 assert (rowQuantity >= 1)
932 assert (rowQuantity <= 12)
933
934 local padding = math.ceil(math.min(buttonWidth, buttonHeight) / 10)
935
936 local subsetFrame = CreateFrame('FRAME', frameDesignation, parentFrame, 'SecureHandlerBaseTemplate')
937 subsetFrame:SetSize(buttonWidth * columnQuantity + padding * (columnQuantity + 1),
938 buttonHeight * rowQuantity + padding * (rowQuantity + 1))
939
940 local nameFormat = subsetFrame:GetName() .. 'Button%03d'
941 local k = 0
942 local i = 0
943 while (i < columnQuantity) do
944 i = i + 1
945 local j = 0
946 while (j < rowQuantity) do
947 j = j + 1
948 k = k + 1
949 local n = string.format(nameFormat, k)
950 local emptySpellName = nil
951 local b = createSubsetButton(subsetFrame, n, unitDesignation, emptySpellName, filterDescriptor,
952 buttonWidth, buttonHeight)
953 b:SetPoint('BOTTOMLEFT', buttonWidth * (i - 1) + padding * i, buttonHeight * (j - 1) + padding * j)
954 end
955 end
956
957 subsetFrame.unit = unitDesignation
958 subsetFrame.filter = filterDescriptor
959 subsetFrame:RegisterEvent('PARTY_CONVERTED_TO_RAID')
960 subsetFrame:RegisterEvent('PARTY_MEMBERS_CHANGED')
961 subsetFrame:RegisterEvent('PLAYER_FOCUS_CHANGED')
962 subsetFrame:RegisterEvent('PLAYER_LOGIN')
963 subsetFrame:RegisterEvent('PLAYER_TARGET_CHANGED')
964 subsetFrame:RegisterEvent('RAID_ROSTER_UPDATE')
965 subsetFrame:RegisterEvent('UNIT_AURA')
966 subsetFrame:RegisterEvent('UNIT_HEALTH')
967 subsetFrame:RegisterEvent('UPDATE_BATTLEFIELD_SCORE')
968 subsetFrame:SetScript('OnEvent', subsetEventProcessor)
969
970 return subsetFrame
971 end
972
557 973 local function initSpellActivationOverlayAny(rootFrame) local function initSpellActivationOverlayAny(rootFrame)
558 974 local margin = rootFrame:GetWidth() / 2 - 28 * 5 / 2 local margin = rootFrame:GetWidth() / 2 - 28 * 5 / 2
559 975 local d0 = createIndicator(rootFrame, 'Magic', 'player', 'HARMFUL') local d0 = createIndicator(rootFrame, 'Magic', 'player', 'HARMFUL')
 
... ... local function initSpellActivationOverlayAny(rootFrame)
566 982 d3:SetPoint('BOTTOMLEFT', margin + 28 * 3, 64) d3:SetPoint('BOTTOMLEFT', margin + 28 * 3, 64)
567 983 local d4 = createIndicator(rootFrame, 'HARMFUL', 'player', 'HARMFUL') local d4 = createIndicator(rootFrame, 'HARMFUL', 'player', 'HARMFUL')
568 984 d4:SetPoint('BOTTOMLEFT', margin + 28 * 4, 64) d4:SetPoint('BOTTOMLEFT', margin + 28 * 4, 64)
985
986 local subset0 = createSubset(rootFrame, 'ClearcastingPlayerSubset', 'player', 'PLAYER HELPFUL',
987 6, 1)
988 subset0:SetPoint('CENTER', 144 * 2, 144 * 2)
989
990 local subset1 = createSubset(rootFrame, 'ClearcastingPlayerSubset', 'target', 'PLAYER HELPFUL',
991 6, 1)
992 subset1:SetPoint('CENTER', -144 * 2, 144 * 2)
993
994 local subset2 = createSubset(rootFrame, 'ClearcastingPlayerSubset', 'target', 'HARMFUL',
995 6, 1)
996 subset2:SetPoint('CENTER', 0, 144 * 2)
569 997 end end
570 998
571 999 local function initSpellActivationOverlayDeathKnight(rootFrame) local function initSpellActivationOverlayDeathKnight(rootFrame)
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/vrtc/clearcasting

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/vrtc/clearcasting

Clone this repository using git:
git clone git://git.rocketgit.com/user/vrtc/clearcasting

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main