Add player badges.
This commit is contained in:
@@ -9,10 +9,79 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class PlayerDatabase : IGlobalModData
|
||||
{
|
||||
public readonly string Profile = "https://forum.openra.net/openra/info/";
|
||||
|
||||
[FieldLoader.Ignore]
|
||||
readonly object syncObject = new object();
|
||||
|
||||
[FieldLoader.Ignore]
|
||||
readonly Dictionary<string, Sprite> spriteCache = new Dictionary<string, Sprite>();
|
||||
|
||||
// 128x128 is large enough for 25 unique 24x24 sprites
|
||||
[FieldLoader.Ignore]
|
||||
SheetBuilder sheetBuilder;
|
||||
|
||||
public PlayerBadge LoadBadge(MiniYaml yaml)
|
||||
{
|
||||
if (sheetBuilder == null)
|
||||
{
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, 128);
|
||||
|
||||
// We must manually force the buffer creation to avoid a crash
|
||||
// that is indirectly triggered by rendering from a Sheet that
|
||||
// has not yet been written to.
|
||||
sheetBuilder.Current.CreateBuffer();
|
||||
}
|
||||
|
||||
var labelNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Label");
|
||||
var icon24Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon24");
|
||||
if (labelNode == null || icon24Node == null)
|
||||
return null;
|
||||
|
||||
Sprite sprite;
|
||||
lock (syncObject)
|
||||
{
|
||||
if (!spriteCache.TryGetValue(icon24Node.Value.Value, out sprite))
|
||||
{
|
||||
sprite = spriteCache[icon24Node.Value.Value] = sheetBuilder.Allocate(new Size(24, 24));
|
||||
|
||||
Action<DownloadDataCompletedEventArgs> onComplete = i =>
|
||||
{
|
||||
if (i.Error != null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var icon = new Bitmap(new MemoryStream(i.Result));
|
||||
if (icon.Width == 24 && icon.Height == 24)
|
||||
{
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
Util.FastCopyIntoSprite(sprite, icon);
|
||||
sprite.Sheet.CommitBufferedData();
|
||||
});
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
};
|
||||
|
||||
new Download(icon24Node.Value.Value, _ => { }, onComplete);
|
||||
}
|
||||
}
|
||||
|
||||
return new PlayerBadge(labelNode.Value.Value, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class PlayerProfile
|
||||
@@ -20,5 +24,46 @@ namespace OpenRA
|
||||
public readonly int ProfileID;
|
||||
public readonly string ProfileName;
|
||||
public readonly string ProfileRank = "Registered Player";
|
||||
|
||||
[FieldLoader.LoadUsing("LoadBadges")]
|
||||
public readonly List<PlayerBadge> Badges;
|
||||
|
||||
static object LoadBadges(MiniYaml yaml)
|
||||
{
|
||||
var badges = new List<PlayerBadge>();
|
||||
|
||||
var badgesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Badges");
|
||||
if (badgesNode != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var playerDatabase = Game.ModData.Manifest.Get<PlayerDatabase>();
|
||||
foreach (var badgeNode in badgesNode.Value.Nodes)
|
||||
{
|
||||
var badge = playerDatabase.LoadBadge(badgeNode.Value);
|
||||
if (badge != null)
|
||||
badges.Add(badge);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Discard badges on error
|
||||
}
|
||||
}
|
||||
|
||||
return badges;
|
||||
}
|
||||
}
|
||||
|
||||
public class PlayerBadge
|
||||
{
|
||||
public readonly string Label;
|
||||
public readonly Sprite Icon24;
|
||||
|
||||
public PlayerBadge(string label, Sprite icon24)
|
||||
{
|
||||
Label = label;
|
||||
Icon24 = icon24;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,18 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
{
|
||||
public class LocalProfileLogic : ChromeLogic
|
||||
{
|
||||
readonly WorldRenderer worldRenderer;
|
||||
readonly LocalPlayerProfile localProfile;
|
||||
readonly Widget badgeContainer;
|
||||
readonly Widget widget;
|
||||
bool notFound;
|
||||
bool badgesVisible;
|
||||
|
||||
[ObjectCreator.UseCtor]
|
||||
public LocalProfileLogic(Widget widget, WorldRenderer worldRenderer, Func<bool> minimalProfile)
|
||||
{
|
||||
this.worldRenderer = worldRenderer;
|
||||
this.widget = widget;
|
||||
localProfile = Game.LocalPlayerProfile;
|
||||
|
||||
// Key registration
|
||||
@@ -78,6 +84,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
destroyKey.OnClick = localProfile.DeleteKeypair;
|
||||
destroyKey.IsDisabled = minimalProfile;
|
||||
|
||||
badgeContainer = widget.Get("BADGES_CONTAINER");
|
||||
badgeContainer.IsVisible = () => badgesVisible && !minimalProfile()
|
||||
&& localProfile.State == LocalPlayerProfile.LinkState.Linked;
|
||||
|
||||
localProfile.RefreshPlayerData(() => RefreshComplete(false));
|
||||
}
|
||||
|
||||
@@ -86,7 +96,36 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
if (updateNotFound)
|
||||
notFound = localProfile.State == LocalPlayerProfile.LinkState.Unlinked;
|
||||
|
||||
Game.RunAfterTick(Ui.ResetTooltips);
|
||||
Game.RunAfterTick(() =>
|
||||
{
|
||||
badgesVisible = false;
|
||||
|
||||
if (localProfile.State == LocalPlayerProfile.LinkState.Linked)
|
||||
{
|
||||
if (localProfile.ProfileData.Badges.Any())
|
||||
{
|
||||
Func<int, int> negotiateWidth = _ => widget.Get("PROFILE_HEADER").Bounds.Width;
|
||||
|
||||
// Remove any stale badges that may be left over from a previous session
|
||||
badgeContainer.RemoveChildren();
|
||||
|
||||
var badges = Ui.LoadWidget("PLAYER_PROFILE_BADGES_INSERT", badgeContainer, new WidgetArgs()
|
||||
{
|
||||
{ "worldRenderer", worldRenderer },
|
||||
{ "profile", localProfile.ProfileData },
|
||||
{ "negotiateWidth", negotiateWidth }
|
||||
});
|
||||
|
||||
if (badges.Bounds.Height > 0)
|
||||
{
|
||||
badgeContainer.Bounds.Height = badges.Bounds.Height;
|
||||
badgesVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ui.ResetTooltips();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +141,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
playerDatabase = modData.Manifest.Get<PlayerDatabase>();
|
||||
|
||||
var header = widget.Get("HEADER");
|
||||
var badgeContainer = widget.Get("BADGES_CONTAINER");
|
||||
var badgeSeparator = badgeContainer.GetOrNull("SEPARATOR");
|
||||
|
||||
var profileHeader = header.Get("PROFILE_HEADER");
|
||||
var messageHeader = header.Get("MESSAGE_HEADER");
|
||||
@@ -145,6 +186,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
profileWidth = Math.Max(profileWidth, rankFont.Measure(profile.ProfileRank).X + 2 * rankLabel.Bounds.Left);
|
||||
|
||||
header.Bounds.Height += headerSizeOffset;
|
||||
badgeContainer.Bounds.Y += header.Bounds.Height;
|
||||
if (client.IsAdmin)
|
||||
{
|
||||
profileWidth = Math.Max(profileWidth, adminFont.Measure(adminLabel.Text).X + 2 * adminLabel.Bounds.Left);
|
||||
@@ -152,11 +194,37 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
adminContainer.IsVisible = () => true;
|
||||
profileHeader.Bounds.Height += adminLabel.Bounds.Height;
|
||||
header.Bounds.Height += adminLabel.Bounds.Height;
|
||||
badgeContainer.Bounds.Y += adminLabel.Bounds.Height;
|
||||
}
|
||||
|
||||
Func<int, int> negotiateWidth = badgeWidth =>
|
||||
{
|
||||
profileWidth = Math.Min(Math.Max(badgeWidth, profileWidth), widget.Bounds.Width);
|
||||
return profileWidth;
|
||||
};
|
||||
|
||||
if (profile.Badges.Any())
|
||||
{
|
||||
var badges = Ui.LoadWidget("PLAYER_PROFILE_BADGES_INSERT", badgeContainer, new WidgetArgs()
|
||||
{
|
||||
{ "worldRenderer", worldRenderer },
|
||||
{ "profile", profile },
|
||||
{ "negotiateWidth", negotiateWidth }
|
||||
});
|
||||
|
||||
if (badges.Bounds.Height > 0)
|
||||
{
|
||||
badgeContainer.Bounds.Height = badges.Bounds.Height;
|
||||
badgeContainer.IsVisible = () => true;
|
||||
}
|
||||
}
|
||||
|
||||
profileWidth = Math.Min(profileWidth, widget.Bounds.Width);
|
||||
header.Bounds.Width = widget.Bounds.Width = profileWidth;
|
||||
widget.Bounds.Height = header.Bounds.Height;
|
||||
header.Bounds.Width = widget.Bounds.Width = badgeContainer.Bounds.Width = profileWidth;
|
||||
widget.Bounds.Height = header.Bounds.Height + badgeContainer.Bounds.Height;
|
||||
|
||||
if (badgeSeparator != null)
|
||||
badgeSeparator.Bounds.Width = profileWidth - 2 * badgeSeparator.Bounds.X;
|
||||
|
||||
profileLoaded = true;
|
||||
});
|
||||
@@ -182,11 +250,53 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
header.Bounds.Height += messageHeader.Bounds.Height;
|
||||
header.Bounds.Width = widget.Bounds.Width = messageWidth;
|
||||
widget.Bounds.Height = header.Bounds.Height;
|
||||
badgeContainer.Visible = false;
|
||||
|
||||
new Download(playerDatabase.Profile + client.Fingerprint, _ => { }, onQueryComplete);
|
||||
}
|
||||
}
|
||||
|
||||
public class PlayerProfileBadgesLogic : ChromeLogic
|
||||
{
|
||||
[ObjectCreator.UseCtor]
|
||||
public PlayerProfileBadgesLogic(Widget widget, PlayerProfile profile, Func<int, int> negotiateWidth)
|
||||
{
|
||||
var showBadges = profile.Badges.Any();
|
||||
widget.IsVisible = () => showBadges;
|
||||
|
||||
var badgeTemplate = widget.Get("BADGE_TEMPLATE");
|
||||
widget.RemoveChild(badgeTemplate);
|
||||
|
||||
var width = 0;
|
||||
var badgeOffset = badgeTemplate.Bounds.Y;
|
||||
foreach (var badge in profile.Badges)
|
||||
{
|
||||
var b = badgeTemplate.Clone();
|
||||
var icon = b.Get<SpriteWidget>("ICON");
|
||||
icon.GetSprite = () => badge.Icon24;
|
||||
|
||||
var label = b.Get<LabelWidget>("LABEL");
|
||||
var labelFont = Game.Renderer.Fonts[label.Font];
|
||||
|
||||
var labelText = WidgetUtils.TruncateText(badge.Label, label.Bounds.Width, labelFont);
|
||||
label.GetText = () => labelText;
|
||||
|
||||
width = Math.Max(width, label.Bounds.Left + labelFont.Measure(labelText).X + icon.Bounds.X);
|
||||
|
||||
b.Bounds.Y = badgeOffset;
|
||||
widget.AddChild(b);
|
||||
|
||||
badgeOffset += badgeTemplate.Bounds.Height;
|
||||
}
|
||||
|
||||
if (badgeOffset > badgeTemplate.Bounds.Y)
|
||||
badgeOffset += 5;
|
||||
|
||||
widget.Bounds.Width = negotiateWidth(width);
|
||||
widget.Bounds.Height = badgeOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public class AnonymousProfileTooltipLogic : ChromeLogic
|
||||
{
|
||||
[ObjectCreator.UseCtor]
|
||||
|
||||
@@ -28,6 +28,11 @@ Container@LOCAL_PROFILE_PANEL:
|
||||
Font: TinyBold
|
||||
BaseLine: 1
|
||||
Text: Logout
|
||||
Background@BADGES_CONTAINER:
|
||||
Width: PARENT_RIGHT
|
||||
Y: 48
|
||||
Visible: false
|
||||
Background: panel-black
|
||||
Background@GENERATE_KEYS:
|
||||
Width: PARENT_RIGHT
|
||||
Height: PARENT_BOTTOM
|
||||
@@ -208,3 +213,22 @@ Container@LOCAL_PROFILE_PANEL:
|
||||
BaseLine: 1
|
||||
Font: TinyBold
|
||||
Text: Retry
|
||||
|
||||
Container@PLAYER_PROFILE_BADGES_INSERT:
|
||||
Logic: PlayerProfileBadgesLogic
|
||||
Width: PARENT_RIGHT
|
||||
Children:
|
||||
Container@BADGE_TEMPLATE:
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Children:
|
||||
Sprite@ICON:
|
||||
X: 6
|
||||
Y: 1
|
||||
Width: 24
|
||||
Height: 24
|
||||
Label@LABEL:
|
||||
X: 36
|
||||
Width: PARENT_RIGHT - 60
|
||||
Height: 24
|
||||
Font: Bold
|
||||
|
||||
@@ -293,3 +293,8 @@ Container@REGISTERED_PLAYER_TOOLTIP:
|
||||
Width: PARENT_RIGHT - 20
|
||||
Height: 23
|
||||
Font: Bold
|
||||
Background@BADGES_CONTAINER:
|
||||
Width: PARENT_RIGHT
|
||||
Y: 0-1
|
||||
Visible: false
|
||||
Background: panel-black
|
||||
|
||||
@@ -28,6 +28,11 @@ Container@LOCAL_PROFILE_PANEL:
|
||||
Font: TinyBold
|
||||
BaseLine: 1
|
||||
Text: Logout
|
||||
Background@BADGES_CONTAINER:
|
||||
Width: PARENT_RIGHT
|
||||
Y: 48
|
||||
Visible: false
|
||||
Background: dialog3
|
||||
Background@GENERATE_KEYS:
|
||||
Width: PARENT_RIGHT
|
||||
Height: PARENT_BOTTOM
|
||||
@@ -208,3 +213,23 @@ Container@LOCAL_PROFILE_PANEL:
|
||||
BaseLine: 1
|
||||
Font: TinyBold
|
||||
Text: Retry
|
||||
|
||||
Container@PLAYER_PROFILE_BADGES_INSERT:
|
||||
Logic: PlayerProfileBadgesLogic
|
||||
Width: PARENT_RIGHT
|
||||
Height: 110
|
||||
Children:
|
||||
Container@BADGE_TEMPLATE:
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Children:
|
||||
Sprite@ICON:
|
||||
X: 6
|
||||
Y: 1
|
||||
Width: 24
|
||||
Height: 24
|
||||
Label@LABEL:
|
||||
X: 36
|
||||
Width: PARENT_RIGHT - 60
|
||||
Height: 24
|
||||
Font: Bold
|
||||
|
||||
@@ -218,6 +218,14 @@ Background@REGISTERED_PLAYER_TOOLTIP:
|
||||
Width: PARENT_RIGHT - 14
|
||||
Height: 23
|
||||
Font: Bold
|
||||
Container@BADGES_CONTAINER:
|
||||
Width: PARENT_RIGHT
|
||||
Visible: false
|
||||
Children:
|
||||
Background@SEPARATOR:
|
||||
X: 10
|
||||
Height: 1
|
||||
Background: tooltip-separator
|
||||
|
||||
Background@PRODUCTION_TOOLTIP:
|
||||
Logic: ProductionTooltipLogic
|
||||
|
||||
@@ -192,6 +192,9 @@ dialog3: dialog.png
|
||||
corner-bl: 640,127,1,1
|
||||
corner-br: 767,127,1,1
|
||||
|
||||
tooltip-separator: dialog.png
|
||||
border-t: 641,0,126,1
|
||||
|
||||
# Same as the half transparent frame used in the Asset Browser
|
||||
dialog4: dialog.png
|
||||
background: 517,392,54,54
|
||||
|
||||
@@ -221,6 +221,14 @@ Background@REGISTERED_PLAYER_TOOLTIP:
|
||||
Width: PARENT_RIGHT - 14
|
||||
Height: 23
|
||||
Font: Bold
|
||||
Container@BADGES_CONTAINER:
|
||||
Width: PARENT_RIGHT
|
||||
Visible: false
|
||||
Children:
|
||||
Background@SEPARATOR:
|
||||
X: 10
|
||||
Height: 1
|
||||
Background: tooltip-separator
|
||||
|
||||
Background@PRODUCTION_TOOLTIP:
|
||||
Logic: ProductionTooltipLogic
|
||||
|
||||
@@ -510,6 +510,9 @@ dialog4: dialog.png
|
||||
corner-bl: 512,446,6,6
|
||||
corner-br: 571,446,6,6
|
||||
|
||||
tooltip-separator: dialog.png
|
||||
border-t: 517,387,54,1
|
||||
|
||||
# completely black tile
|
||||
dialog5: dialog.png
|
||||
background: 579,387,64,64
|
||||
|
||||
@@ -464,6 +464,9 @@ dialog4: dialog.png
|
||||
corner-bl: 512,446,6,6
|
||||
corner-br: 571,446,6,6
|
||||
|
||||
tooltip-separator: dialog.png
|
||||
border-t: 517,387,54,1
|
||||
|
||||
# A copy of dialog3 (pressed button)
|
||||
progressbar-bg: dialog.png
|
||||
background: 641,1,126,126
|
||||
|
||||
Reference in New Issue
Block a user