MichaelCrow

[WotLK][TC][Eluna][AIO] Custom class powerType / resource

May 7th, 2021
945
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --[[INSTRUCTIONS:
  2.     1   Requires core modification to fix stance/form changing:
  3.         in Unit.cpp
  4.         void Unit::setPowerType(Powers new_powertype)
  5.         change:
  6.             case POWER_HAPPINESS:
  7.                 SetMaxPower(POWER_HAPPINESS, uint32(std::ceil(GetCreatePowers(POWER_HAPPINESS) * powerMultiplier)));
  8.                 SetPower(POWER_HAPPINESS, uint32(std::ceil(GetCreatePowers(POWER_HAPPINESS) * powerMultiplier)));
  9.                 break;
  10.         to:
  11.             case POWER_HAPPINESS:
  12.                 if (GetTypeId() == TYPEID_PLAYER && GetMaxPower(POWER_HAPPINESS) >= 0)
  13.                     break;
  14.                 SetMaxPower(POWER_HAPPINESS, uint32(std::ceil(GetCreatePowers(POWER_HAPPINESS) * powerMultiplier)));
  15.                 SetPower(POWER_HAPPINESS, uint32(std::ceil(GetCreatePowers(POWER_HAPPINESS) * powerMultiplier)));
  16.                 break;
  17.        
  18.         You may want to apply other core modifications to give POWER_HAPPINESS as neutral of a behavior as possible and script
  19.         functionality specific to each custom powerType when a specific player event occurs, or add some class checks in the
  20.         core when a player's POWER_HAPPINESS is manipulated.
  21.  
  22.     2   If you want a class to use a custom resource, set the SERVER SIDE powertype for that class to 4 (HAPPINESS)
  23.         in ChrClasses.dbc (the field that has a default value of 6 for Death Knights). Client side changes to ChrClasses.dbc
  24.         are not required. Happiness is simply the powertype we use behind the scenes for all custom powertypes.
  25.  
  26.     3   Next, add an entry for your custom powertype in the customPowerTypes table. Each entry should have a table of classes,
  27.         an id, a powerString, a bar color table, a defaultMax value and a consumingSpells table (for correct GetSpellInfo powerType).
  28.         defaultMax should be the value you want, multiplied by 1000. All Happiness values have to be multiplied by 1000.
  29.  
  30.     4   To create ANY spell (in Spell.dbc) that uses or generates your custom powertype, give it a power cost or energize effect
  31.         for powerType 4 (HAPPINESS). Make sure Happiness values (costs, energize effects etc) are multiplied by 1000.
  32.  
  33.  
  34. TROUBLESHOOTING:
  35.         Make sure that the class has the powerType set to 4 (HAPPINESS) in SERVER SIDE ChrClasses.dbc.
  36.         Make sure that the class is listed in only one customPowerTypes[POWERTOKEN].classes table.
  37.         Make sure table entries are separated by commas.
  38.         MAKE SURE ALL RESOURCE VALUES ARE MULTIPLIED BY 1000. Happiness for some reason needs everything
  39.         multiplied by 1000. That includes spell costs and generation or if you're setting current power value
  40.         or maxvalue. All of them need to be multiplied by 1000.
  41.         Custom spells should always use 4 (HAPPINESS) in Spell.dbc, but everywhere in-game the powerType and
  42.         powerToken will match your custom values. Don't get confused and give a spell in Spell.dbc a custom
  43.         cost powerType (eg. a Spell.dbc entry that costs powerType 201). It should instead have cost powerType 4 (HAPPINESS).
  44.         Check the worldserver console for errors (scripts get loaded at the end).
  45.         If you get UI errors in-game, keep in mind the line number might not be accurate if you have server code and
  46.         client code in the same script file. Try subtracting the server-only line count, or split the script
  47.         into a server file and client file.
  48.  
  49.  
  50. Currently this system has some (minor?) flaws:
  51.         - No error speech or text if you fail client-side resource check (not even a "Not enough happiness" message).
  52.         - Spells linked in chat simply show the cost as "X Happiness", but it should be possible to alter the tooltip if the
  53.           spell is found in a custom powertype's consumingSpells table.
  54.         - The official Blizzard UI function UnitFrameManaBar_UpdateType(manaBar) from UnitFrame.lua is overwritten, which
  55.           I guess is bad form because if the function is overwritten by two different addons, one of them will misbehave.
  56.         - A class can't swap between two custom primary resources on the fly (like a druid swapping between mana, energy, rage)
  57.           because the custom resource is using Happiness and MaxHappiness to store values. You would need to store extra values
  58.           somewhere and change how unitframes set their manabar and change how spells' usable status is determined.
  59. ]]--
  60.  
  61.  
  62. local AIO = AIO or require("AIO")
  63.  
  64. local customPowerTypes = {}                     -- Table for custom class resource data, used by server and client. Not all of it is used by both the server and client, but it's convenient to have everything in one place.
  65.  
  66.  
  67. customPowerTypes["BRAINCELLS"] =                -- Our first custom class resource, used by Shamans and Rogues (remember to set ChrClasses.dbc powerTypes to use this example)
  68. {
  69.     classes = { "Shaman", "Rogue" },            -- List of classes that use this as primary resource. Remember to set powertype to 4 (HAPPINESS) in server's ChrClasses.dbc
  70.     defaultMax = 300000,                        -- Happiness values need to be multiplied by 1000, so for the power bar to read "powertype 0 / 123", set defaultMax to 123000
  71.     id = 200,                                   -- The id for our custom resource (first value returned by UnitPowerType). MUST BE UNIQUE. Some values below 200 are reserved for existing powerTypes.
  72.     powerString = "Braincells",                 -- The string to be displayed for our powertype
  73.     color = { r = 0.83, g = 0.76, b = 0.60 },   -- RGB colors for our power bar. Values should be between 0 and 1
  74.     consumingSpells = { 90031 }                 -- Spell IDs that consume this resource. Required for correct GetSpellInfo(spellID) API functionality
  75. }
  76.  
  77. customPowerTypes["TEMPER"] =                    -- Our second custom class resource, used by Warriors
  78. {
  79.     classes = { "Warrior" },
  80.     defaultMax = 100000,
  81.     id = 201,
  82.     powerString = "Temper",
  83.     color = { r = 1.0, g = 1.0, b = 1.0 },
  84.     consumingSpells = { 90032 },
  85.     passiveUpdate = {                                                                   -- Optional passive update data, containing update interval and an update function. Event gets automatically registered, so long as you use these names ("interval" and "UpdateFunc")
  86.                         interval = 100,                                                 -- Update interval in milliseconds
  87.                         UpdateFunc = function(event, delay, repeats, player)            -- power update function that gets called every [interval] milliseconds
  88.                             -- points (re/de)generation per second
  89.                             local regen = -10000
  90.                             if (player:IsInCombat()) then
  91.                                 regen = 10000
  92.                             end
  93.                             local prevPower = player:GetPower(4)
  94.                             local newPower = prevPower + (regen * (delay / 1000))
  95.                             local maxPower = player:GetMaxPower(4)
  96.                            
  97.                             if (newPower < 0) then
  98.                                 newPower = 0
  99.                             elseif ( newPower > maxPower ) then
  100.                                 newPower = maxPower
  101.                             end
  102.                            
  103.                             player:SetPower(4, newPower)
  104.                         end
  105.                     }
  106. }
  107.  
  108.  
  109. if AIO.AddAddon() then
  110.     local function OnLogin(event, player)
  111.         local playerclass = player:GetClassAsString()
  112.        
  113.         for powerToken, powerTable in pairs(customPowerTypes) do
  114.             for _, class in pairs(powerTable.classes) do
  115.                 if (playerclass == class) then
  116.                     player:SetMaxPower(4, powerTable.defaultMax)
  117.                    
  118.                     -- Prevent tiny mana bar from re-appearing below the main resource bar by setting max mana to 0 (normally appears for druids while they're shapeshifted)
  119.                     -- Ideally you would set this class to never get any mana or maxmana increases in class levelup data
  120.                     player:SetMaxPower(0, 0)
  121.                    
  122.                     if (powerTable.passiveUpdate ~= nil) then
  123.                         player:RegisterEvent(powerTable.passiveUpdate.UpdateFunc, powerTable.passiveUpdate.interval, 0)
  124.                     end
  125.                 end
  126.             end
  127.         end
  128.        
  129.     end
  130.    
  131.     -- Prevent player's maximum Happiness from being reset to core default 1050 (aka 1050000). Preferably you would remove the reset in the core, because
  132.     -- this way you can't change a player's maximum value without resetting it to the defaultMax, unless you store and restore desired maxpower per character
  133.     local function OnLevelChange(event, player, oldLevel)
  134.         local playerclass = player:GetClassAsString()
  135.         for _, powerTable in pairs(customPowerTypes) do
  136.             for _, class in pairs(powerTable.classes) do
  137.                 if (playerclass == class) then
  138.                     if (powerTable.defaultMax ~= nil) then
  139.                         player:SetMaxPower(4, powerTable.defaultMax)
  140.                     end
  141.                 end
  142.             end
  143.         end
  144.     end
  145.    
  146.     RegisterPlayerEvent( 3, OnLogin )
  147.     RegisterPlayerEvent( 13, OnLevelChange )
  148.     return
  149. end
  150.  
  151.  
  152. HAPPINESS_COST = "%d Happiness";
  153. local myclass = UnitClass("player")
  154.  
  155. for customToken, powerTable in pairs(customPowerTypes) do
  156.     _G[customToken] = powerTable.powerString
  157.     PowerBarColor[customToken] = powerTable.color
  158.     PowerBarColor[powerTable.id] = PowerBarColor[customToken]
  159. end
  160.  
  161.  
  162. -- Hook official Blizzard UI function GetSpellInfo(spellId or spellName or spellLink), to fix custom powerType return, if applicable
  163. -- SpellLink as argument is broken in the official API, apparently.
  164. local origGetSpellInfo = GetSpellInfo;
  165. GetSpellInfo = function(...)
  166.     local spellID = ...;
  167.     local name, rank, icon, cost, isFunnel, powerType, castTime, minRange, maxRange = origGetSpellInfo(...)
  168.    
  169.     -- if a spellName was passed as argument, jump through some hoops to get the spellID
  170.     if (type(spellID) == "string") then
  171.         spellLink = GetSpellLink(spellID)
  172.         if (spellLink ~= nil) then
  173.             spellID = tonumber(strmatch(spellLink, "^\124c%x+\124Hspell:(%d+)\124h%[.*%]"))
  174.         end
  175.     end
  176.    
  177.     if (spellID ~= nil) then
  178.         for _, powerTable in pairs(customPowerTypes) do
  179.             local earlyReturn = false
  180.             for _, id in pairs(powerTable.consumingSpells) do
  181.                 if (id == spellID) then
  182.                     powerType = powerTable.id
  183.                     break
  184.                 end
  185.             end
  186.             if earlyReturn then break end
  187.         end
  188.     end
  189.    
  190.     return name, rank, icon, cost, isFunnel, powerType, castTime, minRange, maxRange;
  191. end
  192.  
  193. -- Hook official Blizzard UI function UnitPower(unit, powerType), to add custom value return, if applicable
  194. local origUnitPower = UnitPower;
  195. UnitPower = function(...)
  196.     local unit, powerType = ...;
  197.    
  198.     if (UnitIsPlayer(unit)) then
  199.         --local unitClass = UnitClass(unit)
  200.         for customToken, powerTable in pairs(customPowerTypes) do
  201.             if (powerTable.id == powerType) then
  202.                 -- ask for Happiness value instead
  203.                 return origUnitPower(unit, 4)
  204.             end
  205.         end
  206.     end
  207.    
  208.     return origUnitPower(...);
  209. end
  210.  
  211. -- Hook official Blizzard UI function UnitPowerMax(unit, powerType), to add custom value return, if applicable
  212. local origUnitPowerMax = UnitPowerMax;
  213. UnitPowerMax = function(...)
  214.     local unit, powerType = ...;
  215.    
  216.     if (UnitIsPlayer(unit)) then
  217.         for customToken, powerTable in pairs(customPowerTypes) do
  218.             if (powerTable.id == powerType) then
  219.                 -- ask for Happiness value instead
  220.                 return origUnitPowerMax(unit, 4)
  221.             end
  222.         end
  223.     end
  224.    
  225.     return origUnitPowerMax(...);
  226. end
  227.  
  228. -- Hook official Blizzard UI function UnitPowerType(unit), to add custom powerType and powerToken return, if applicable
  229. local origUnitPowerType = UnitPowerType;
  230. UnitPowerType = function(...)
  231.     local unit = ...;
  232.    
  233.     local unitClass = UnitClass(unit)
  234.     for customToken, powerTable in pairs(customPowerTypes) do
  235.         for _, class in pairs(powerTable.classes) do
  236.             if (unitClass == class) then
  237.                 local powerType, _, altR, altG, altB = origUnitPowerType(unit)
  238.                 powerType = powerTable.id
  239.                 return powerType, customToken, altR, altG, altB
  240.             end
  241.         end
  242.     end
  243.    
  244.     return origUnitPowerType(...);
  245. end
  246.  
  247. -- Overwrite official Blizzard UI function UnitFrameManaBar_UpdateType(manaBar), hacky fix to display Happiness value and maxvalue in mana bar
  248. function UnitFrameManaBar_UpdateType(manaBar)
  249.     if ( not manaBar ) then
  250.         return;
  251.     end
  252.     local unitFrame = manaBar:GetParent();
  253.     local powerType, powerToken, altR, altG, altB = UnitPowerType(manaBar.unit);
  254.     local prefix = _G[powerToken];
  255.     local info = PowerBarColor[powerToken];
  256.     if ( info ) then
  257.         if ( not manaBar.lockColor ) then
  258.             manaBar:SetStatusBarColor(info.r, info.g, info.b);
  259.         end
  260.     else
  261.         if ( not altR) then
  262.             -- couldn't find a power token entry...default to indexing by power type or just mana if we don't have that either
  263.             info = PowerBarColor[powerType] or PowerBarColor["MANA"];
  264.         else
  265.             if ( not manaBar.lockColor ) then
  266.                 manaBar:SetStatusBarColor(altR, altG, altB);
  267.             end
  268.         end
  269.     end
  270.    
  271.     -- We still want to use Happiness behind the scenes, so if we're using a custom resource, restore the powerType ID to 4: HAPPINESS
  272.     if (customPowerTypes[powerToken] ~= nil) then
  273.         powerType = 4;
  274.     end
  275.     manaBar.powerType = powerType;
  276.    
  277.     -- Update the manabar text
  278.     if ( not unitFrame.noTextPrefix ) then
  279.         SetTextStatusBarTextPrefix(manaBar, prefix);
  280.     end
  281.     TextStatusBar_UpdateTextString(manaBar);
  282.  
  283.     -- Setup newbie tooltip
  284.     -- FIXME: Fix this to use powerToken instead of powerType
  285.     if ( manaBar.unit ~= "pet" or powerToken == "HAPPINESS" ) then
  286.         if ( unitFrame:GetName() == "PlayerFrame" ) then
  287.             manaBar.tooltipTitle = prefix;
  288.             manaBar.tooltipText = _G["NEWBIE_TOOLTIP_MANABAR_"..powerType];
  289.         else
  290.             manaBar.tooltipTitle = nil;
  291.             manaBar.tooltipText = nil;
  292.         end
  293.     end
  294. end
  295.  
  296. local function CustomPrimaryResourceTooltip_OnShow(self, ...)
  297.     local _, _, spellID = GameTooltip:GetSpell()
  298.     if (spellID == nil) then
  299.         return
  300.     end
  301.    
  302.     local numLines = GameTooltip:NumLines();
  303.     local i = 1;
  304.    
  305.     for currentLine=1, numLines do
  306.         local line = {};
  307.         local left = _G["GameTooltipTextLeft"..currentLine];
  308.         if ( left ) then
  309.             line.w = true;
  310.             line.leftR, line.leftG, line.leftB = left:GetTextColor();
  311.             local t = left:GetText();
  312.            
  313.             local happinessCost = strmatch(t, "(%d+) Happiness")
  314.             if (happinessCost ~= nil) then
  315.                 local _, powerToken = UnitPowerType("player")
  316.                 left:SetText(happinessCost.." ".._G[powerToken])
  317.             end
  318.            
  319.             line.left = t;
  320.         end
  321.         i = i + 1;
  322.     end
  323.    
  324.     GameTooltip:Show();
  325. end
  326.  
  327.  
  328. for powerToken, powerTable in pairs(customPowerTypes) do
  329.     for _, class in pairs(powerTable.classes) do
  330.         if (myclass == class) then
  331.             UnitFrameManaBar_UpdateType(PlayerFrameManaBar)
  332.             -- Hide mini mana bar
  333.             AlternatePowerBar_UpdatePowerType(PlayerFrameAlternateManaBar)
  334.         end
  335.     end
  336. end
  337.  
  338. GameTooltip:HookScript( "OnShow", CustomPrimaryResourceTooltip_OnShow )
RAW Paste Data