Skip to content

Commit 6ec93bd

Browse files
pchoteabcdefg30
authored andcommitted
Add player badges.
1 parent 7ec19b8 commit 6ec93bd

File tree

11 files changed

+306
-3
lines changed

11 files changed

+306
-3
lines changed

OpenRA.Game/PlayerDatabase.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,79 @@
99
*/
1010
#endregion
1111

12+
using System;
13+
using System.Collections.Generic;
14+
using System.Drawing;
15+
using System.IO;
16+
using System.Linq;
17+
using System.Net;
18+
using OpenRA.Graphics;
19+
1220
namespace OpenRA
1321
{
1422
public class PlayerDatabase : IGlobalModData
1523
{
1624
public readonly string Profile = "https://forum.openra.net/openra/info/";
25+
26+
[FieldLoader.Ignore]
27+
readonly object syncObject = new object();
28+
29+
[FieldLoader.Ignore]
30+
readonly Dictionary<string, Sprite> spriteCache = new Dictionary<string, Sprite>();
31+
32+
// 128x128 is large enough for 25 unique 24x24 sprites
33+
[FieldLoader.Ignore]
34+
SheetBuilder sheetBuilder;
35+
36+
public PlayerBadge LoadBadge(MiniYaml yaml)
37+
{
38+
if (sheetBuilder == null)
39+
{
40+
sheetBuilder = new SheetBuilder(SheetType.BGRA, 128);
41+
42+
// We must manually force the buffer creation to avoid a crash
43+
// that is indirectly triggered by rendering from a Sheet that
44+
// has not yet been written to.
45+
sheetBuilder.Current.CreateBuffer();
46+
}
47+
48+
var labelNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Label");
49+
var icon24Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon24");
50+
if (labelNode == null || icon24Node == null)
51+
return null;
52+
53+
Sprite sprite;
54+
lock (syncObject)
55+
{
56+
if (!spriteCache.TryGetValue(icon24Node.Value.Value, out sprite))
57+
{
58+
sprite = spriteCache[icon24Node.Value.Value] = sheetBuilder.Allocate(new Size(24, 24));
59+
60+
Action<DownloadDataCompletedEventArgs> onComplete = i =>
61+
{
62+
if (i.Error != null)
63+
return;
64+
65+
try
66+
{
67+
var icon = new Bitmap(new MemoryStream(i.Result));
68+
if (icon.Width == 24 && icon.Height == 24)
69+
{
70+
Game.RunAfterTick(() =>
71+
{
72+
Util.FastCopyIntoSprite(sprite, icon);
73+
sprite.Sheet.CommitBufferedData();
74+
});
75+
}
76+
}
77+
catch { }
78+
};
79+
80+
new Download(icon24Node.Value.Value, _ => { }, onComplete);
81+
}
82+
}
83+
84+
return new PlayerBadge(labelNode.Value.Value, sprite);
85+
}
1786
}
1887
}

OpenRA.Game/PlayerProfile.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
*/
1010
#endregion
1111

12+
using System.Collections.Generic;
13+
using System.Linq;
14+
using OpenRA.Graphics;
15+
1216
namespace OpenRA
1317
{
1418
public class PlayerProfile
@@ -20,5 +24,46 @@ public class PlayerProfile
2024
public readonly int ProfileID;
2125
public readonly string ProfileName;
2226
public readonly string ProfileRank = "Registered Player";
27+
28+
[FieldLoader.LoadUsing("LoadBadges")]
29+
public readonly List<PlayerBadge> Badges;
30+
31+
static object LoadBadges(MiniYaml yaml)
32+
{
33+
var badges = new List<PlayerBadge>();
34+
35+
var badgesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Badges");
36+
if (badgesNode != null)
37+
{
38+
try
39+
{
40+
var playerDatabase = Game.ModData.Manifest.Get<PlayerDatabase>();
41+
foreach (var badgeNode in badgesNode.Value.Nodes)
42+
{
43+
var badge = playerDatabase.LoadBadge(badgeNode.Value);
44+
if (badge != null)
45+
badges.Add(badge);
46+
}
47+
}
48+
catch
49+
{
50+
// Discard badges on error
51+
}
52+
}
53+
54+
return badges;
55+
}
56+
}
57+
58+
public class PlayerBadge
59+
{
60+
public readonly string Label;
61+
public readonly Sprite Icon24;
62+
63+
public PlayerBadge(string label, Sprite icon24)
64+
{
65+
Label = label;
66+
Icon24 = icon24;
67+
}
2368
}
2469
}

OpenRA.Mods.Common/Widgets/Logic/PlayerProfileLogic.cs

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@ namespace OpenRA.Mods.Common.Widgets.Logic
2121
{
2222
public class LocalProfileLogic : ChromeLogic
2323
{
24+
readonly WorldRenderer worldRenderer;
2425
readonly LocalPlayerProfile localProfile;
26+
readonly Widget badgeContainer;
27+
readonly Widget widget;
2528
bool notFound;
29+
bool badgesVisible;
2630

2731
[ObjectCreator.UseCtor]
2832
public LocalProfileLogic(Widget widget, WorldRenderer worldRenderer, Func<bool> minimalProfile)
2933
{
34+
this.worldRenderer = worldRenderer;
35+
this.widget = widget;
3036
localProfile = Game.LocalPlayerProfile;
3137

3238
// Key registration
@@ -78,6 +84,10 @@ public LocalProfileLogic(Widget widget, WorldRenderer worldRenderer, Func<bool>
7884
destroyKey.OnClick = localProfile.DeleteKeypair;
7985
destroyKey.IsDisabled = minimalProfile;
8086

87+
badgeContainer = widget.Get("BADGES_CONTAINER");
88+
badgeContainer.IsVisible = () => badgesVisible && !minimalProfile()
89+
&& localProfile.State == LocalPlayerProfile.LinkState.Linked;
90+
8191
localProfile.RefreshPlayerData(() => RefreshComplete(false));
8292
}
8393

@@ -86,7 +96,36 @@ public void RefreshComplete(bool updateNotFound)
8696
if (updateNotFound)
8797
notFound = localProfile.State == LocalPlayerProfile.LinkState.Unlinked;
8898

89-
Game.RunAfterTick(Ui.ResetTooltips);
99+
Game.RunAfterTick(() =>
100+
{
101+
badgesVisible = false;
102+
103+
if (localProfile.State == LocalPlayerProfile.LinkState.Linked)
104+
{
105+
if (localProfile.ProfileData.Badges.Any())
106+
{
107+
Func<int, int> negotiateWidth = _ => widget.Get("PROFILE_HEADER").Bounds.Width;
108+
109+
// Remove any stale badges that may be left over from a previous session
110+
badgeContainer.RemoveChildren();
111+
112+
var badges = Ui.LoadWidget("PLAYER_PROFILE_BADGES_INSERT", badgeContainer, new WidgetArgs()
113+
{
114+
{ "worldRenderer", worldRenderer },
115+
{ "profile", localProfile.ProfileData },
116+
{ "negotiateWidth", negotiateWidth }
117+
});
118+
119+
if (badges.Bounds.Height > 0)
120+
{
121+
badgeContainer.Bounds.Height = badges.Bounds.Height;
122+
badgesVisible = true;
123+
}
124+
}
125+
}
126+
127+
Ui.ResetTooltips();
128+
});
90129
}
91130
}
92131

@@ -102,6 +141,8 @@ public RegisteredProfileTooltipLogic(Widget widget, WorldRenderer worldRenderer,
102141
playerDatabase = modData.Manifest.Get<PlayerDatabase>();
103142

104143
var header = widget.Get("HEADER");
144+
var badgeContainer = widget.Get("BADGES_CONTAINER");
145+
var badgeSeparator = badgeContainer.GetOrNull("SEPARATOR");
105146

106147
var profileHeader = header.Get("PROFILE_HEADER");
107148
var messageHeader = header.Get("MESSAGE_HEADER");
@@ -145,18 +186,45 @@ public RegisteredProfileTooltipLogic(Widget widget, WorldRenderer worldRenderer,
145186
profileWidth = Math.Max(profileWidth, rankFont.Measure(profile.ProfileRank).X + 2 * rankLabel.Bounds.Left);
146187

147188
header.Bounds.Height += headerSizeOffset;
189+
badgeContainer.Bounds.Y += header.Bounds.Height;
148190
if (client.IsAdmin)
149191
{
150192
profileWidth = Math.Max(profileWidth, adminFont.Measure(adminLabel.Text).X + 2 * adminLabel.Bounds.Left);
151193

152194
adminContainer.IsVisible = () => true;
153195
profileHeader.Bounds.Height += adminLabel.Bounds.Height;
154196
header.Bounds.Height += adminLabel.Bounds.Height;
197+
badgeContainer.Bounds.Y += adminLabel.Bounds.Height;
198+
}
199+
200+
Func<int, int> negotiateWidth = badgeWidth =>
201+
{
202+
profileWidth = Math.Min(Math.Max(badgeWidth, profileWidth), widget.Bounds.Width);
203+
return profileWidth;
204+
};
205+
206+
if (profile.Badges.Any())
207+
{
208+
var badges = Ui.LoadWidget("PLAYER_PROFILE_BADGES_INSERT", badgeContainer, new WidgetArgs()
209+
{
210+
{ "worldRenderer", worldRenderer },
211+
{ "profile", profile },
212+
{ "negotiateWidth", negotiateWidth }
213+
});
214+
215+
if (badges.Bounds.Height > 0)
216+
{
217+
badgeContainer.Bounds.Height = badges.Bounds.Height;
218+
badgeContainer.IsVisible = () => true;
219+
}
155220
}
156221

157222
profileWidth = Math.Min(profileWidth, widget.Bounds.Width);
158-
header.Bounds.Width = widget.Bounds.Width = profileWidth;
159-
widget.Bounds.Height = header.Bounds.Height;
223+
header.Bounds.Width = widget.Bounds.Width = badgeContainer.Bounds.Width = profileWidth;
224+
widget.Bounds.Height = header.Bounds.Height + badgeContainer.Bounds.Height;
225+
226+
if (badgeSeparator != null)
227+
badgeSeparator.Bounds.Width = profileWidth - 2 * badgeSeparator.Bounds.X;
160228

161229
profileLoaded = true;
162230
});
@@ -182,11 +250,53 @@ public RegisteredProfileTooltipLogic(Widget widget, WorldRenderer worldRenderer,
182250
header.Bounds.Height += messageHeader.Bounds.Height;
183251
header.Bounds.Width = widget.Bounds.Width = messageWidth;
184252
widget.Bounds.Height = header.Bounds.Height;
253+
badgeContainer.Visible = false;
185254

186255
new Download(playerDatabase.Profile + client.Fingerprint, _ => { }, onQueryComplete);
187256
}
188257
}
189258

259+
public class PlayerProfileBadgesLogic : ChromeLogic
260+
{
261+
[ObjectCreator.UseCtor]
262+
public PlayerProfileBadgesLogic(Widget widget, PlayerProfile profile, Func<int, int> negotiateWidth)
263+
{
264+
var showBadges = profile.Badges.Any();
265+
widget.IsVisible = () => showBadges;
266+
267+
var badgeTemplate = widget.Get("BADGE_TEMPLATE");
268+
widget.RemoveChild(badgeTemplate);
269+
270+
var width = 0;
271+
var badgeOffset = badgeTemplate.Bounds.Y;
272+
foreach (var badge in profile.Badges)
273+
{
274+
var b = badgeTemplate.Clone();
275+
var icon = b.Get<SpriteWidget>("ICON");
276+
icon.GetSprite = () => badge.Icon24;
277+
278+
var label = b.Get<LabelWidget>("LABEL");
279+
var labelFont = Game.Renderer.Fonts[label.Font];
280+
281+
var labelText = WidgetUtils.TruncateText(badge.Label, label.Bounds.Width, labelFont);
282+
label.GetText = () => labelText;
283+
284+
width = Math.Max(width, label.Bounds.Left + labelFont.Measure(labelText).X + icon.Bounds.X);
285+
286+
b.Bounds.Y = badgeOffset;
287+
widget.AddChild(b);
288+
289+
badgeOffset += badgeTemplate.Bounds.Height;
290+
}
291+
292+
if (badgeOffset > badgeTemplate.Bounds.Y)
293+
badgeOffset += 5;
294+
295+
widget.Bounds.Width = negotiateWidth(width);
296+
widget.Bounds.Height = badgeOffset;
297+
}
298+
}
299+
190300
public class AnonymousProfileTooltipLogic : ChromeLogic
191301
{
192302
[ObjectCreator.UseCtor]

mods/cnc/chrome/playerprofile.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ Container@LOCAL_PROFILE_PANEL:
2828
Font: TinyBold
2929
BaseLine: 1
3030
Text: Logout
31+
Background@BADGES_CONTAINER:
32+
Width: PARENT_RIGHT
33+
Y: 48
34+
Visible: false
35+
Background: panel-black
3136
Background@GENERATE_KEYS:
3237
Width: PARENT_RIGHT
3338
Height: PARENT_BOTTOM
@@ -208,3 +213,22 @@ Container@LOCAL_PROFILE_PANEL:
208213
BaseLine: 1
209214
Font: TinyBold
210215
Text: Retry
216+
217+
Container@PLAYER_PROFILE_BADGES_INSERT:
218+
Logic: PlayerProfileBadgesLogic
219+
Width: PARENT_RIGHT
220+
Children:
221+
Container@BADGE_TEMPLATE:
222+
Width: PARENT_RIGHT
223+
Height: 25
224+
Children:
225+
Sprite@ICON:
226+
X: 6
227+
Y: 1
228+
Width: 24
229+
Height: 24
230+
Label@LABEL:
231+
X: 36
232+
Width: PARENT_RIGHT - 60
233+
Height: 24
234+
Font: Bold

mods/cnc/chrome/tooltips.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,8 @@ Container@REGISTERED_PLAYER_TOOLTIP:
293293
Width: PARENT_RIGHT - 20
294294
Height: 23
295295
Font: Bold
296+
Background@BADGES_CONTAINER:
297+
Width: PARENT_RIGHT
298+
Y: 0-1
299+
Visible: false
300+
Background: panel-black

mods/common/chrome/playerprofile.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ Container@LOCAL_PROFILE_PANEL:
2828
Font: TinyBold
2929
BaseLine: 1
3030
Text: Logout
31+
Background@BADGES_CONTAINER:
32+
Width: PARENT_RIGHT
33+
Y: 48
34+
Visible: false
35+
Background: dialog3
3136
Background@GENERATE_KEYS:
3237
Width: PARENT_RIGHT
3338
Height: PARENT_BOTTOM
@@ -208,3 +213,23 @@ Container@LOCAL_PROFILE_PANEL:
208213
BaseLine: 1
209214
Font: TinyBold
210215
Text: Retry
216+
217+
Container@PLAYER_PROFILE_BADGES_INSERT:
218+
Logic: PlayerProfileBadgesLogic
219+
Width: PARENT_RIGHT
220+
Height: 110
221+
Children:
222+
Container@BADGE_TEMPLATE:
223+
Width: PARENT_RIGHT
224+
Height: 25
225+
Children:
226+
Sprite@ICON:
227+
X: 6
228+
Y: 1
229+
Width: 24
230+
Height: 24
231+
Label@LABEL:
232+
X: 36
233+
Width: PARENT_RIGHT - 60
234+
Height: 24
235+
Font: Bold

0 commit comments

Comments
 (0)