[Mod] Always Visible Health Mana XP Text

By on June 7, 2011 7:14:29 PM from Demigod Forums Demigod Forums

pacov

Join Date 02/2008
+183

Background

We’ve been using the original Always visible health and mana text mod by gunblob (http://forums.demigodthegame.com/369556) for quite some time as part of the advanced ui mod.  I recently noticed that the original version has a minor bug that causes some issues with the visual display during replays.  Thanks to the assistance of miriyaka, a fix has been found for this issue.  I also came up with a new enhancement today that enables you to see your current experience without mousing over the xp bar.  You only need to download the version I’m linking to here in this thread if you do not use the advanced ui mod.  I will be updating the advanced ui mod and the combined mod installer to include this updated code.  This page is only for reference.  Do not use this mod AND the enhanced UI mod.

What’s it look like?xp bar

What’s the point of the mod?

The purpose of this mod is to alter the UI so that you will always see a text value indicating the exact value of HP and mana of the Demigod you control.  Without the mod, you can still view the text values, but you have to move your mouse over the hp and mana bars to view this.  With this mod, it is always on.

Why make a mod?

It’s a logical addition that improves the Demigod UI.

How do I use the mod?

Download the mod and run the installer.  Then, use the Demigod Mod manager to enable the mod. As this is a UI mod, all players DO NOT need to have this mod installed for you to use it.

Where can I download the mod?

http://www.box.net/shared/rnr99evq1huc5ut86at6


Revision history

v1.1
- Current level and xp required for next level automatically viewable as text
v1.01
- Fixed an issue with the visual display when viewing replays as an observer
v1.0
- HP and mana automatically viewable as text

Locked Post 12 Replies
Search this post
Subscription Options


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

NT

Reason for Karma (Optional)
Successfully updated karma reason!
June 9, 2011 7:43:09 PM from Demigod Forums Demigod Forums

So, I've been working on an update to this that follows similar logic.  It's coded and tested and works great, but I'd like a code review from one of you folks so that I can remove additional lines of code that are unnecessary.

Here's what the addition to the mod will do:  it will now show your current level and experience to next level (0/200 for instance).  This functionality existed previously but was only visible if you moused over it.  After the change, you automatically always see your current level and experience to next level. 

I also noticed that there is quite a bit of real estate on this bar that additional data could be added to. 

To make the mod, I simply copied the entirety of HUD_xp.lua and edited from there.  I commented out the mouse over functionality, changed the positioning of the EXP to next level, and changed the functionality so that all would appear.  That said, I'm pretty sure I don't need all of the code and would like some help to know what I can remove without doing trial and error.  Thanks!

Code: c++
  1. #*****************************************************************************
  2. #* File: lua/ui/game/HUD_xp.lua
  3. #*
  4. #* Hero XP bar
  5. #*
  6. #* Copyright © 2008 Gas Powered Games, Inc.  All rights reserved.
  7. #*****************************************************************************
  8. local UIUtil = import('/lua/ui/uiutil.lua')
  9. local LayoutHelpers = import('/lua/maui/layouthelpers.lua')
  10. local Group = import('/lua/maui/group.lua').Group
  11. local Bitmap = import('/lua/maui/bitmap.lua').Bitmap
  12. local StatusBar = import('/lua/maui/statusbar.lua').StatusBar
  13. local Tooltip = import('/lua/ui/game/tooltip.lua')
  14. local InGameUI = import('/lua/ui/game/InGameUI.lua')
  15. local Common = import('/lua/common/CommonUtils.lua')
  16. local GameMain = import('/lua/ui/game/gamemain.lua')
  17. local EffectHelpers = import('/lua/maui/effecthelpers.lua')
  18. local hero = nil
  19. local tempPrevXP = nil
  20. local currentHeroLevel = 1
  21. local currentExperience = 0
  22. local currentScores = false
  23. local maxLevelHit = false
  24. local bar = false
  25. function OnHeroChange(unit)
  26.     hero = unit
  27.     tempPrevXP = nil
  28.     currentHeroLevel = 0
  29.     currentExperience = 0
  30.     currentScores = false
  31.     maxLevelHit = false
  32.    
  33.     bar:Show()
  34.     xptolevelTxt:Show()
  35.     xpTxt:Show()
  36. end
  37. function Create(parent)
  38.     InGameUI.OnFocusArmyHeroChange:Add( OnHeroChange )
  39.     local xpBarImg = Bitmap(parent, '/textures/ui/hud/bars/xp.dds')
  40.     xpBarImg.Width:Set(770)
  41.     xpBarImg.Height:Set(25)
  42.     xpBarImg.glow = Bitmap(xpBarImg, '/textures/ui/hud/bars/xp_glow.dds')
  43.     LayoutHelpers.FillParent(xpBarImg.glow, xpBarImg)
  44.     xpBarImg.glow.Depth:Set( function() return xpBarImg.Depth() + 2 end)
  45.     xpBarImg.glow:SetAlpha(0)
  46.     bar = xpBarImg.xpBarGroup
  47.     bar = Group(parent)
  48.     bar.Width:Set(736)
  49.     bar.Height:Set(24)
  50.     LayoutHelpers.AtCenterIn(bar, xpBarImg, 1, 0)
  51.     bar.Depth:Set(function() return xpBarImg.glow.Depth() + 2 end)
  52.     # So we can mouseover for level/experience text
  53.     bar.dummy = Bitmap(bar)
  54.     LayoutHelpers.FillParent(bar.dummy, bar)
  55.     LayoutHelpers.DepthOverParent(bar.dummy, bar, 10000)
  56.     bar.level = StatusBar(bar, 0, 100, false, true, false, '/textures/ui/hud/bars/xp_black_mid.dds', "LevelBar")
  57.     LayoutHelpers.FillParent(bar.level, bar)
  58.     LayoutHelpers.ResetWidth(bar.level)
  59.     LayoutHelpers.ResetHeight(bar.level)
  60.     bar.level:SetValue(0)
  61.     bar.left = Bitmap(bar, '/textures/ui/hud/bars/xp_black_endcap_left.dds')
  62.     bar.left.Width:Set(19)
  63.     bar.left.Height:Set(26)
  64.     LayoutHelpers.AnchorToLeft(bar.left, bar.level._bar, -3)
  65.     bar.left.Bottom:Set(function() return bar.level._bar:Bottom() + 1 end)
  66.     bar.right = Bitmap(bar, '/textures/ui/hud/bars/xp_black_endcap_right.dds')
  67.     bar.right.Width:Set(19)
  68.     bar.right.Height:Set(26)
  69.     LayoutHelpers.CenteredRightOf(bar.right, bar.level, -1)
  70.     bar.levelText = UIUtil.CreateText(bar.level, '', 14, UIUtil.bodyFont )
  71.     bar.levelText:SetColor('ffefda9c')
  72.     LayoutHelpers.AtHorizontalCenterIn(bar.levelText, bar.level, - 80)
  73.     LayoutHelpers.AtVerticalCenterIn(bar.levelText, bar.level)
  74.     bar.levelText:SetDropShadow(true)
  75.     bar.levelText:Hide()
  76.     # XP text
  77.     xpTxt = UIUtil.CreateText(bar, '<LOC XPHUD_0000>Level 1', 16, UIUtil.bodyFont)
  78.     #LayoutHelpers.AtCenterIn(xpTxt, bar, 2, 0)
  79.     LayoutHelpers.AtLeftIn(xpTxt, bar, 5)
  80.     LayoutHelpers.AtVerticalCenterIn(xpTxt, bar)
  81.     xpTxt:SetDropShadow(true)
  82.     xpTxt:SetColor('ffefda9c')
  83.     LayoutHelpers.DepthOverParent(xpTxt, bar, 300)
  84.     xptolevelTxt = UIUtil.CreateText(bar, '<LOC XPHUD_0001>Experience to Level 2: 0/200', 14, UIUtil.bodyFont)
  85.     #LayoutHelpers.AtCenterIn(xptolevelTxt, bar, 70, 0)
  86.     LayoutHelpers.AtLeftIn(xptolevelTxt, bar, 76)
  87.     LayoutHelpers.AtVerticalCenterIn(xptolevelTxt, bar)
  88.     xptolevelTxt:SetDropShadow(true)
  89.     xptolevelTxt:SetColor('ffefda9c')
  90.     LayoutHelpers.DepthOverParent(xptolevelTxt, bar, 300)
  91.     xptolevelTxt:Hide()
  92.     # Gold display
  93.     local gold = import('/lua/ui/game/HUD_gold.lua').Create(bar)
  94.     LayoutHelpers.AtRightIn(gold, bar)
  95.     LayoutHelpers.AtVerticalCenterIn(gold, bar, 1)
  96.     LayoutHelpers.DepthOverParent(gold, bar, 300)
  97.     gold.dummy = Bitmap(gold)
  98.     LayoutHelpers.FillParent(gold.dummy, gold)
  99.     LayoutHelpers.DepthOverParent(gold.dummy, gold, 10000)
  100.     local xpbar_gold_title = '<LOC XPBAR_0000>Gold'
  101.     local xpbar_gold_text = '<LOC XPBAR_0001>Control Gold Mines and kill enemies to earn Gold. Spend Gold on items and Citadel Upgrades.'
  102.     function gold.dummy.HandleEvent(self, event)
  103.        if event.Type == 'MouseEnter' then
  104.             gold.dummy.tooltip = Tooltip.CreateExtendedTooltip(self, xpbar_gold_title, xpbar_gold_text, true)
  105.        elseif event.Type == 'MouseExit' then
  106.             if gold.dummy.tooltip then
  107.                 gold.dummy.tooltip:Destroy()
  108.                 gold.dummy.tooltip = nil
  109.             end
  110.        end
  111.     end
  112.     --[[
  113.     # REmoving war score per SD - dstaltman
  114.     # War Points display
  115.     warPts = UIUtil.CreateText(bar, '', 14, UIUtil.bodyFont)
  116.     LayoutHelpers.AtCenterIn(warPts, bar, 2, 0)
  117.     LayoutHelpers.DepthOverParent(warPts, bar, 300)
  118.     warPts:SetColor('ffefda9c')
  119.     warPts:SetDropShadow(true)
  120.     warPts.dummy = Bitmap(warPts)
  121.     LayoutHelpers.FillParent(warPts.dummy, warPts)
  122.     LayoutHelpers.DepthOverParent(warPts.dummy, warPts, 10000)
  123.     local xpbar_warscore_title = '<LOC XPBAR_0002>War Score (War Rank)'
  124.     local xpbar_warscore_text = '<LOC XPBAR_0003>Control flags to earn War Score. War Score determines your team\'s War Rank and available Ciadel Upgrades.'
  125.     function warPts.dummy.HandleEvent(self, event)
  126.        if event.Type == 'MouseEnter' then
  127.             warPts.dummy.tooltip = Tooltip.CreateExtendedTooltip(self, xpbar_warscore_title, xpbar_warscore_text, true)
  128.        elseif event.Type == 'MouseExit' then
  129.             if warPts.dummy.tooltip then
  130.                 warPts.dummy.tooltip:Destroy()
  131.                 warPts.dummy.tooltip = nil
  132.             end
  133.        end
  134.     end
  135.     ]]--
  136.    
  137.     #local warPtsIcon = Bitmap(bar, UIUtil.UIFile('/icons/warpoints.dds'))
  138.     #warPtsIcon.Height:Set(20)
  139.     #warPtsIcon.Width:Set(20)
  140.     #LayoutHelpers.AnchorToLeft(warPtsIcon, warPts, 2)
  141.     #LayoutHelpers.AtVerticalCenterIn(warPtsIcon, bar)
  142.     #LayoutHelpers.DepthOverParent(warPtsIcon, bar, 300)
  143. --[[
  144.     function bar.dummy.HandleEvent(self, event)
  145.        if event.Type == 'MouseEnter' then
  146.             if not maxLevelHit then
  147.                 xpTxt:Show()
  148.                 xptolevelTxt:Hide()
  149.             end
  150.        elseif event.Type == 'MouseExit' then
  151.             if not maxLevelHit then
  152.                 xpTxt:Hide()
  153.                 xptolevelTxt:Show()
  154.             end
  155.        end
  156.     end
  157. ]]--
  158.     function bar.Update()
  159.         if GetFocusArmy() != -1 then
  160.             if hero and not hero:IsDead() and not IsDestroyed(bar) then
  161.                 local bp = hero:GetBlueprint()
  162.                 local data = EntityData[hero:GetEntityId()]
  163.                 if not data then
  164.                     local id = BlipMap[hero:GetBlipId()]
  165.                     if id then
  166.                         data = EntityData[id]
  167.                     end
  168.                 end
  169.                 if currentHeroLevel != data.HeroLevel then
  170.                     xpTxt:SetText(LOCF('<LOC XPHUD_0002>Level %d', data.HeroLevel))
  171.                     currentHeroLevel = data.HeroLevel
  172.                 end
  173.                 if currentExperience != data.XP then
  174.                     if data.HeroLevel != 20 then
  175.                         xptolevelTxt:SetText(LOCF('<LOC XPHUD_0003>Experience to Level %d: %d/%d', data.HeroLevel+1, data.XP, data.NextLevelXP))
  176.                     else
  177.                         xptolevelTxt:SetText('')
  178.                     end
  179.                     currentExperience = data.XP
  180.                 end
  181.                 local levelPct
  182.                 if data.XP > 0 then
  183.                     levelPct = math.min(1.0,(data.XP - data.ThisLevelXP) / (data.NextLevelXP - data.ThisLevelXP))
  184.                 else
  185.                     levelPct = 0
  186.                 end
  187.                 #xpBar.level:SetValue(levelPct * 100)
  188.                 bar.level:SetValue(100 - (levelPct * 100))
  189.                 if levelPct > 0 then
  190.                     bar.left:SetTexture('/textures/ui/hud/bars/xp_black_endcap_left2.dds')
  191.                 else
  192.                     bar.left:SetTexture('/textures/ui/hud/bars/xp_black_endcap_left.dds')
  193.                 end
  194.                 if levelPct >= 0.999 then
  195.                     bar.right:Hide()
  196.                     bar.left:Hide()
  197.                 else
  198.                     bar.right:Show()
  199.                     bar.left:Show()
  200.                 end
  201.                 #if levelPct <= 0.55 then
  202.                     #xpTxt:SetColor('ffefda9c')
  203.                     #xpTxt:SetDropShadow(true)
  204.                 #else
  205.                     #xpTxt:SetColor('ff183349')
  206.                     #xpTxt:SetDropShadow(false)
  207.                 #end
  208.                 #if levelPct <= 0.65 then
  209.                     #xptolevelTxt:SetColor('ffefda9c')
  210.                     #xptolevelTxt:SetDropShadow(true)
  211.                 #else
  212.                     #xptolevelTxt:SetColor('ff183349')
  213.                     #xptolevelTxt:SetDropShadow(false)
  214.                # end
  215.                 if tempPrevXP != data.XP and data.HeroLevel != 20 then
  216.                     EffectHelpers.FadeOut(xpBarImg.glow, 1.0, 1.0, 0)
  217.                 end
  218.                 tempPrevXP = data.XP
  219.                 if data.HeroLevel == 20 then
  220.                     xptolevelTxt:SetText(LOC('<LOC xp_0000>Experience: 32400 / 32400'))
  221.                     maxLevelHit = true
  222.                 end
  223.             end
  224.         else
  225.             bar:Hide()
  226.         end
  227.         --[[
  228.         # removing war score from the XP bar per SD - dstaltman
  229.         if Sync.Score then
  230.             if not currentScores then
  231.                 currentScores = Sync.Score
  232.             else
  233.                 currentScores = table.merged(currentScores, Sync.Score)
  234.             end
  235.         end
  236.         local armiesInfo = GetArmiesTable()
  237.         local focusArmy = armiesInfo.focusArmy
  238.         local team = armiesInfo.armiesTable[focusArmy].team
  239.         for index, army in armiesInfo.armiesTable do
  240.             if army.name == team then
  241.                 local warScore = currentScores[index].WarScore or 0
  242.                 local warRank = currentScores[index].WarRank or 0
  243.                 if focusArmy and not IsDestroyed(warPts) then
  244.                     warPts:SetText(math.floor(warScore) .. ' (' .. warRank .. ')')
  245.                 end
  246.             end
  247.         end
  248.         ]]--
  249.         # Update War Score
  250.         ### This is weird, why is this different than what's in Sync.Score?
  251.         --[[
  252.         local focusArmy = GetFocusArmy()
  253.         local warScore = ArmyWarScore[focusArmy] or 0
  254.         local warRank = currentScores[focusArmy].WarRank or 0
  255.         if focusArmy and not IsDestroyed(warPts) then
  256.             warPts:SetText(warScore .. ' (' .. warRank .. ')')
  257.         end
  258.         #for k, v in ArmyWarScore do
  259.             #LOG("*UITest: index: ", k, "  warscore: ", v)
  260.         #end
  261.         ]]--
  262.     end
  263.     GameMain.BeatCallback:Add(bar.Update)
  264.     return xpBarImg
  265. end
Reason for Karma (Optional)
Successfully updated karma reason!
June 9, 2011 8:12:19 PM from Demigod Forums Demigod Forums

Oh - and on another note, I think I've magically figured out enough about the ui to be able to make a ui mod that would show all of the new stats enabled via uberfix.  Not that I'm exactly in a rush, but I did some toying around today with some of the other built in ui menus and I think I could pull this off.

Reason for Karma (Optional)
Successfully updated karma reason!
June 9, 2011 11:36:22 PM from Demigod Forums Demigod Forums

If you want help learning to write hooked / non-destructive code, or help hooking any individual files or functions, I'll be glad to assist with that - but I don't have any interest in digging through entire files to find changes.  At the very least, always leave tagged comments next to all of your changes, so that it's easy to find them (e.g. --pacov: etc etc).

Since I know what to look for in this particular instance, I'll write a hook for it for demonstration purposes, but in the future I'd highly recommend starting with a blank file and learning to do proper hooks - it's actually pretty simple, and it makes troubleshooting and debugging a lot easier (especially for people trying to help you):

Code: c++
  1. --save a local copy of the original OnHeroChange
  2. local prevOnHeroChange = OnHeroChange
  3. --re-define the function
  4. function OnHeroChange(unit)
  5.     --call the saved instance
  6.     prevOnHeroChange(unit)
  7.     --make sure the saved instance set the 'hero' variable
  8.     --that is declared at the top of the original file
  9.     --if it's nil, that means we're in observer mode
  10.     --we could also check 'unit', as that's what the original
  11.     --sets 'hero' to, I'm just mimicking the check that
  12.     --the original does in case I missed something
  13.     if hero then
  14.         --these will have all been hidden by the original
  15.         --instance of the function that we just ran, but
  16.         --we can just Show() them afterward without any
  17.         --visual disruption
  18.         xpTxt:Show()
  19.         xptolevelTxt:Show()
  20.     else
  21.         --if no hero, then insure that the bar is hidden
  22.         bar:Hide()
  23.     end
  24. end
  25. local prevCreate = Create
  26. function Create(parent)
  27.     --call saved, storing what it returns in a local
  28.     local xpBarImg = prevCreate(parent)
  29.     --change the position of the controls the original function created
  30.     --in this particular case, their reference variables are defined
  31.     --at the top of the file, so we don't need to reference them
  32.     --within the table/control that the saved function returns
  33.     LayoutHelpers.AtLeftIn(xptolevelTxt, bar, 76)
  34.    
  35.     --un-do the Hide() that the bar's HandleEvent does on mouseover
  36.     --first we need to save a copy of the original HandleEvent so that
  37.     --other mods' hooks of this function are preserved
  38.     --we could just nil this function out - the whole dummy object,
  39.     --in fact - but then if another mod attempted to hook it, it would
  40.     --generate an error and/or not work at all, so we need to play nice
  41.     local prevHandleEvent = bar.dummy.HandleEvent
  42.     --redefine it
  43.     bar.dummy.HandleEvent = function(self, event)
  44.         --call the saved copy
  45.         prevHandleEvent(self, event)
  46.         --counteract the appropriate Hide() on mouse enter/exit
  47.         if event == 'MouseEnter' then
  48.             xpTxt:Show()
  49.         elseif event == 'MouseExit' then
  50.             xptolevelTxt:Show()
  51.         end
  52.     end
  53.    
  54.     --return stored local, i.e. what the original function returned
  55.     --this must be done, because the UI function that calls Create
  56.     --uses the control object it returns
  57.     return xpBarImg
  58. end
Hopefully you can figure out what is being done and why from the comments.  I'd highly recommend downloading an editor like Notepad++ that does lua syntax highlighting, if you don't already have one.  It makes the comments more obvious and readable, if nothing else.

Take a stab at doing your next hook from a blank file, and post if you need help with anything.  Most of the UI isn't too difficult to hook, and changing the layout is usually just a matter of hooking a file's Create and adjusting positions etc after running the original function.

Reason for Karma (Optional)
Successfully updated karma reason!
June 9, 2011 11:39:57 PM from Demigod Forums Demigod Forums

Good tips - I'll be sure to leave comments going forward.  I'll dig into this sometime tomorrow.  Thanks for taking the time, miri!

oh - and I only use notepad ++, so I've got that covered at least.

Reason for Karma (Optional)
Successfully updated karma reason!
June 10, 2011 12:05:56 AM from Demigod Forums Demigod Forums

I'll try to sort through what you coded there.  I just ran it on the quick and it works great until you mouse over it.  Then it reverts to the original behavior. 

Reason for Karma (Optional)
Successfully updated karma reason!
June 10, 2011 12:14:27 AM from Demigod Forums Demigod Forums

ah - got it sorted now.  I changed...

Code: c++
  1. if event == 'MouseEnter' then
  2.                 xpTxt:Show()
  3.             elseif event == 'MouseExit' then
  4.                 xptolevelTxt:Show()

to

Code: c++
  1. if event.Type == 'MouseEnter' then
  2.                 xpTxt:Show()
  3.             elseif event.Type == 'MouseExit' then
  4.                 xptolevelTxt:Show()
Reason for Karma (Optional)
Successfully updated karma reason!
June 10, 2011 12:49:40 AM from Demigod Forums Demigod Forums

Compiled and uploaded new version.

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

Whoops, I missed that.  Obviously I typed it out by hand and didn't test it.  Sometimes it pays to copy bits and pieces of the original, and reduce it down to what you need, as it eliminates these kinds of errors.

Reason for Karma (Optional)
Successfully updated karma reason!
June 10, 2011 11:03:53 AM from Demigod Forums Demigod Forums

Quoting miriyaka,
Whoops, I missed that.  Obviously I typed it out by hand and didn't test it.  Sometimes it pays to copy bits and pieces of the original, and reduce it down to what you need, as it eliminates these kinds of errors.

yah - i know.  I'm just glad I was able to figure out what it was   Thanks again for all the comments in the code, though.  Very helpful.

Reason for Karma (Optional)
Successfully updated karma reason!
June 14, 2011 11:53:29 PM from Demigod Forums Demigod Forums

Miri - if you magically find yourself having some free time (and are feeling super helpful), I thought of one more bit of info I'd like to see added after the XP info.  I would love to see the total amount of warscore required for the next level.  It could be setup exactly like xp (100/1000 to level 2).  At least when I think of it, I would probably like it best if it was changed to just show something like this:  1000 WS to level 10 (and just have 1000 drop).  That might even be a better way to display the XP info. 

I'm finding that I glance down from time to time to check out that XP info, but perhaps if we just showed a decreasing counter, that might be even better.  Anyway, if you have  chance to check out the WS display, I'd appreciate it.  Thanks!

Reason for Karma (Optional)
Successfully updated karma reason!
June 15, 2011 12:23:20 AM from Demigod Forums Demigod Forums

Adding that to the actual scoreboard would probably be easier.  Take a look at HUD_scoreboard (and the in-game scoreboard itself), and see if you can find a way to fit in another 5 digits plus parentheses or brackets next to the war score.

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

Stardock Magazine | Register | Online Privacy Policy | Terms of Use

Copyright © 2016 Stardock Entertainment and Gas Powered Games. Demigod is a trademark of Gas Powered Games. All rights reserved. All other trademarks and copyrights are the properties of their respective owners. Windows, the Windows Vista Start button and Xbox 360 are trademarks of the Microsoft group of companies, and 'Games for Windows' and the Windows Vista Start button logo are used under license from Microsoft. © 2012 Advanced Micro Devices, Inc. All rights reserved. AMD, the AMD Arrow logo and combinations thereof are trademarks of Advanced Micro Devices, Inc.