Skip to content

Commit 514b784

Browse files
authored
Merge pull request #151 from TeamWheelWizard/MiiEditor
feat/Mii editor
2 parents 29653f5 + e8c0f80 commit 514b784

129 files changed

Lines changed: 11989 additions & 1426 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

WheelWizard.Test/Features/MiiDbServiceTests.cs

Lines changed: 63 additions & 58 deletions
Large diffs are not rendered by default.

WheelWizard/Features/MiiImages/Domain/IMiiIMagesApi.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ Task<Stream> GetImageAsync(
1818
int instanceCount = 1,
1919
int cameraXRotate = 0,
2020
int cameraYRotate = 0,
21-
int cameraZRotate = 0,
22-
string lightDirectionMode = "none",
23-
string instanceRotationMode = "model"
21+
int cameraZRotate = 0
2422
);
2523
}

WheelWizard/Features/MiiImages/Domain/MiiImageSpecifications.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace WheelWizard.MiiImages.Domain;
66

77
public class MiiImageSpecifications
88
{
9+
// IMPORTANT: if you change this, make sure you also edit the Clone method in the extensions of this feature
910
public string Name { get; set; } = string.Empty;
1011
public ImageSize Size { get; set; } = ImageSize.small;
1112
public FaceExpression Expression { get; set; } = FaceExpression.normal;
@@ -23,7 +24,11 @@ public class MiiImageSpecifications
2324
public override string ToString()
2425
{
2526
// If we put all the things in this string, then the Key at least is unique
26-
return $"{Name}_{Size}{Expression}{Type}_{BackgroundColor}{InstanceCount}_{CharacterRotate}{CameraRotate}_{CachePriority}";
27+
var parts = $"{Name}_{Size}{Expression}{Type}";
28+
parts += $"{BackgroundColor}{InstanceCount}";
29+
parts += $"{CharacterRotate}{CameraRotate}";
30+
parts += $"{CachePriority}";
31+
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(parts));
2732
}
2833

2934
#region Enums
@@ -32,12 +37,24 @@ public override string ToString()
3237
public enum FaceExpression
3338
{
3439
normal,
40+
normal_open_mouth,
3541
smile,
42+
smile_open_mouth,
3643
frustrated,
3744
anger,
45+
anger_open_mouth,
3846
blink,
47+
blink_open_mouth,
3948
sorrow,
49+
sorrow_open_mouth,
4050
surprise,
51+
surprise_open_mouth,
52+
wink_right,
53+
wink_left,
54+
like_wink_left,
55+
like_wink_right,
56+
wink_left_open_mouth,
57+
wink_right_open_mouth,
4158
}
4259

4360
public enum ImageSize
@@ -49,6 +66,7 @@ public enum ImageSize
4966
public enum BodyType
5067
{
5168
face,
69+
face_only,
5270
all_body,
5371
}
5472

WheelWizard/Features/MiiImages/Domain/MiiImageVariants.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ public static class MiiImageVariants
1414
CachePriority = CacheItemPriority.High,
1515
};
1616

17+
public static readonly MiiImageSpecifications MiiBlockProfile = new()
18+
{
19+
Name = "MiiBlockProfile",
20+
Expression = MiiImageSpecifications.FaceExpression.normal,
21+
Type = MiiImageSpecifications.BodyType.face,
22+
Size = MiiImageSpecifications.ImageSize.medium,
23+
};
24+
1725
public static readonly MiiImageSpecifications OnlinePlayerSmall = new()
1826
{
1927
Name = "OnlinePlayerSmall",
@@ -23,8 +31,29 @@ public static class MiiImageVariants
2331
CachePriority = CacheItemPriority.Low,
2432
};
2533

34+
public static readonly MiiImageSpecifications MiiEditorSmall = new()
35+
{
36+
Name = "MiiEditorPreviewSmall",
37+
Expression = MiiImageSpecifications.FaceExpression.normal,
38+
Type = MiiImageSpecifications.BodyType.face,
39+
Size = MiiImageSpecifications.ImageSize.medium,
40+
ExpirationSeconds = TimeSpan.FromSeconds(30),
41+
CachePriority = CacheItemPriority.Low,
42+
};
43+
public static readonly MiiImageSpecifications MiiEditorPreviewCarousel = new()
44+
{
45+
Name = "MiiEditorPreviewCarousel",
46+
Expression = MiiImageSpecifications.FaceExpression.normal,
47+
Type = MiiImageSpecifications.BodyType.all_body,
48+
Size = MiiImageSpecifications.ImageSize.medium,
49+
CachePriority = CacheItemPriority.Low,
50+
ExpirationSeconds = TimeSpan.FromSeconds(30),
51+
InstanceCount = 8,
52+
};
53+
2654
public static readonly MiiImageSpecifications CurrentUserSideProfile = new()
2755
{
56+
Name = "CurrentUserSideProfile",
2857
Expression = MiiImageSpecifications.FaceExpression.normal,
2958
Type = MiiImageSpecifications.BodyType.face,
3059
Size = MiiImageSpecifications.ImageSize.medium,
@@ -33,6 +62,7 @@ public static class MiiImageVariants
3362
};
3463
public static readonly MiiImageSpecifications FriendsSideProfile = new()
3564
{
65+
Name = "FriendsSideProfile",
3666
Expression = MiiImageSpecifications.FaceExpression.normal,
3767
Type = MiiImageSpecifications.BodyType.face,
3868
Size = MiiImageSpecifications.ImageSize.medium,
@@ -43,9 +73,11 @@ public static class MiiImageVariants
4373

4474
public static readonly MiiImageSpecifications FullBodyCarousel = new()
4575
{
76+
Name = "FullBodyCarousel",
4677
Expression = MiiImageSpecifications.FaceExpression.normal,
4778
Type = MiiImageSpecifications.BodyType.all_body,
4879
Size = MiiImageSpecifications.ImageSize.medium,
80+
ExpirationSeconds = TimeSpan.FromMinutes(10),
4981
InstanceCount = 8,
5082
};
5183
}

WheelWizard/Features/MiiImages/MiiImagesExtensions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Numerics;
12
using WheelWizard.MiiImages.Domain;
23
using WheelWizard.Services;
34

@@ -13,4 +14,24 @@ public static IServiceCollection AddMiiImages(this IServiceCollection services)
1314

1415
return services;
1516
}
17+
18+
public static MiiImageSpecifications Clone(this MiiImageSpecifications specifications)
19+
{
20+
return new MiiImageSpecifications
21+
{
22+
Name = specifications.Name,
23+
Size = specifications.Size,
24+
Expression = specifications.Expression,
25+
Type = specifications.Type,
26+
BackgroundColor = specifications.BackgroundColor,
27+
InstanceCount = specifications.InstanceCount,
28+
CharacterRotate = new(specifications.CharacterRotate.X, specifications.CharacterRotate.Y, specifications.CharacterRotate.Z),
29+
CameraRotate = new(specifications.CameraRotate.X, specifications.CameraRotate.Y, specifications.CameraRotate.Z),
30+
ExpirationSeconds =
31+
specifications.ExpirationSeconds?.TotalSeconds == null
32+
? null
33+
: TimeSpan.FromSeconds(specifications.ExpirationSeconds.Value.TotalSeconds),
34+
CachePriority = specifications.CachePriority,
35+
};
36+
}
1637
}

WheelWizard/Features/MiiImages/MiiImagesSingletonService.cs

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Concurrent;
12
using Avalonia.Media.Imaging;
23
using Microsoft.Extensions.Caching.Memory;
34
using WheelWizard.MiiImages.Domain;
@@ -13,30 +14,56 @@ public interface IMiiImagesSingletonService
1314

1415
public class MiiImagesSingletonService(IApiCaller<IMiiIMagesApi> apiCaller, IMemoryCache cache) : IMiiImagesSingletonService
1516
{
17+
// Track in-flight requests to prevent duplicate API calls
18+
private readonly ConcurrentDictionary<string, SemaphoreSlim> _inFlightRequests = new();
19+
1620
public async Task<OperationResult<Bitmap>> GetImageAsync(Mii? mii, MiiImageSpecifications specifications)
1721
{
1822
var data = MiiStudioDataSerializer.Serialize(mii);
1923
if (data.IsFailure)
2024
return data.Error;
2125

2226
var miiConfigKey = data.Value + specifications;
23-
var isCached = cache.TryGetValue(miiConfigKey, out Bitmap? cachedValue);
24-
if (isCached)
27+
28+
// Even tho we also check it in the semaphore section, we also check here if it's in the cache, just to be tad faster.
29+
if (cache.TryGetValue(miiConfigKey, out Bitmap? cachedValue))
2530
return cachedValue ?? Fail<Bitmap>("Cached image is null.");
2631

27-
var newImageResult = await apiCaller.CallApiAsync(api => GetBitmapAsync(api, data.Value, specifications));
28-
Bitmap? newImage = null;
29-
if (newImageResult.IsSuccess)
30-
newImage = newImageResult.Value;
32+
var requestSemaphore = _inFlightRequests.GetOrAdd(miiConfigKey, _ => new(1, 1));
3133

32-
using (var entry = cache.CreateEntry(miiConfigKey))
34+
try
3335
{
34-
entry.Value = newImage;
35-
entry.SlidingExpiration = specifications.ExpirationSeconds;
36-
entry.Priority = specifications.CachePriority;
37-
}
36+
// Wait to acquire the semaphore - only the first request will proceed immediately
37+
await requestSemaphore.WaitAsync();
38+
39+
// Double-check the cache after acquiring the semaphore
40+
// Another thread might have completed the request while we were waiting
41+
if (cache.TryGetValue(miiConfigKey, out Bitmap? doubleCheckCached))
42+
return doubleCheckCached ?? Fail<Bitmap>("Cached image is null.");
43+
44+
// If we get here, we're the first request and need to call the API
45+
var newImageResult = await apiCaller.CallApiAsync(api => GetBitmapAsync(api, data.Value, specifications));
3846

39-
return newImage ?? Fail<Bitmap>("Failed to get new image.");
47+
Bitmap? newImage = null;
48+
if (newImageResult.IsSuccess)
49+
newImage = newImageResult.Value;
50+
51+
using (var entry = cache.CreateEntry(miiConfigKey))
52+
{
53+
entry.Value = newImage;
54+
entry.SlidingExpiration = specifications.ExpirationSeconds;
55+
entry.Priority = specifications.CachePriority;
56+
}
57+
58+
return newImage ?? Fail<Bitmap>("Failed to get new image.");
59+
}
60+
finally
61+
{
62+
// We can also do it all without try catch. But we need to make sure that whatever happens, we release the semaphore
63+
// So just to be safe, if anything happens, we release the semaphore anyway.
64+
requestSemaphore.Release();
65+
_inFlightRequests.TryRemove(miiConfigKey, out _);
66+
}
4067
}
4168

4269
private static async Task<Bitmap> GetBitmapAsync(IMiiIMagesApi api, string data, MiiImageSpecifications specifications)

WheelWizard/Features/MiiImages/MiiStudioDataSerializer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public static OperationResult<string> Serialize(Mii? mii)
3535
visualMiiClone.MiiMole = mii.MiiMole;
3636
visualMiiClone.MiiFavoriteColor = mii.MiiFavoriteColor;
3737
visualMiiClone.MiiFacial = mii.MiiFacial;
38-
visualMiiClone.MiiId = 1; // Mii ID cant be 0 if you want to serialize it so...
38+
// If id is 0, we keep it 0, any other ID will be set to 1.
39+
visualMiiClone.MiiId = (uint)(mii.MiiId == 0 ? 0 : 1);
3940

4041
var serialized = MiiSerializer.Serialize(visualMiiClone);
4142
if (serialized.IsFailure)

WheelWizard/Features/WiiManagement/Domain/Mii/Mii.cs

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,53 @@ public class Mii
1212
public MiiScale Height { get; set; } = new(1);
1313
public MiiScale Weight { get; set; } = new(1);
1414

15-
//Mii ID is also referred as Avatar ID
16-
public uint MiiId { get; set; }
15+
public bool IsForeign => (MiiId1 >> 5) == 0b110; // checks if the top 3 bits are set to 110, if so it got blue pants
16+
17+
//Mii ID is also refered as Avatar ID
18+
public byte MiiId1 { get; set; }
19+
public byte MiiId2 { get; set; }
20+
public byte MiiId3 { get; set; }
21+
public byte MiiId4 { get; set; }
22+
23+
public uint MiiId
24+
{
25+
get => (uint)(MiiId1 << 24 | MiiId2 << 16 | MiiId3 << 8 | MiiId4);
26+
set
27+
{
28+
MiiId1 = (byte)(value >> 24);
29+
MiiId2 = (byte)(value >> 16);
30+
MiiId3 = (byte)(value >> 8);
31+
MiiId4 = (byte)(value);
32+
}
33+
}
1734

1835
//This is also referred as Client ID
1936
public byte SystemId0 { get; set; }
2037
public byte SystemId1 { get; set; }
2138
public byte SystemId2 { get; set; }
2239
public byte SystemId3 { get; set; }
2340

41+
public uint SystemId
42+
{
43+
get => (uint)(SystemId0 << 24 | SystemId1 << 16 | SystemId2 << 8 | SystemId3);
44+
set
45+
{
46+
SystemId0 = (byte)(value >> 24);
47+
SystemId1 = (byte)(value >> 16);
48+
SystemId2 = (byte)(value >> 8);
49+
SystemId3 = (byte)(value);
50+
}
51+
}
52+
2453
public MiiFacialFeatures MiiFacial { get; set; } = new(MiiFaceShape.Bread, MiiSkinColor.Light, MiiFacialFeature.None, false, false);
2554

26-
public MiiHair MiiHair { get; set; } = new(0, HairColor.Black, false);
27-
public MiiEyebrow MiiEyebrows { get; set; } = new(0, 0, EyebrowColor.Black, 1, 1, 1);
28-
public MiiEye MiiEyes { get; set; } = new(0, 0, 0, EyeColor.Black, 0, 0);
29-
public MiiNose MiiNose { get; set; } = new(NoseType.Default, 0, 0);
30-
public MiiLip MiiLips { get; set; } = new(0, LipColor.Skin, 0, 0);
31-
public MiiGlasses MiiGlasses { get; set; } = new(GlassesType.None, GlassesColor.Dark, 0, 0);
32-
public MiiFacialHair MiiFacialHair { get; set; } = new(MustacheType.None, BeardType.None, MustacheColor.Black, 0, 0);
55+
public MiiHair MiiHair { get; set; } = new(1, HairColor.Black, false);
56+
public MiiEyebrow MiiEyebrows { get; set; } = new(1, 0, EyebrowColor.Black, 4, 10, 1);
57+
public MiiEye MiiEyes { get; set; } = new(1, 6, 7, EyeColor.Black, 3, 6);
58+
public MiiNose MiiNose { get; set; } = new(NoseType.Default, 6, 4);
59+
public MiiLip MiiLips { get; set; } = new(1, LipColor.Skin, 4, 9);
60+
public MiiGlasses MiiGlasses { get; set; } = new(GlassesType.None, GlassesColor.Dark, 4, 1);
61+
public MiiFacialHair MiiFacialHair { get; set; } = new(MustacheType.None, BeardType.None, MustacheColor.Black, 1, 1);
3362
public MiiMole MiiMole { get; set; } = new(false, 0, 0, 0);
34-
3563
public MiiName CreatorName { get; set; } = new("no name");
3664
}

WheelWizard/Features/WiiManagement/Domain/Mii/MiiEnums.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ public enum MiiFavoriteColor : uint
55
Red,
66
Orange,
77
Yellow,
8+
LightGreen,
89
Green,
910
Blue,
1011
LightBlue,
@@ -13,16 +14,15 @@ public enum MiiFavoriteColor : uint
1314
Brown,
1415
White,
1516
Black,
16-
Gray,
1717
}
1818

1919
public enum MiiFaceShape
2020
{
21-
RoundPointChin,
21+
Teardrop,
2222
Circle,
2323
Oval,
24-
BlobFatChin,
25-
RightAnglePointChin,
24+
Glob,
25+
Pointy,
2626
Bread,
2727
Octagon,
2828
Square,
@@ -31,8 +31,8 @@ public enum MiiFaceShape
3131
public enum MiiSkinColor
3232
{
3333
Light,
34-
LightTan,
35-
Tan,
34+
Yellow,
35+
Red,
3636
Pink,
3737
DarkBrown,
3838
Brown,
@@ -51,7 +51,7 @@ public enum MiiFacialFeature
5151
EyeShadow,
5252
Beard,
5353
MouthCorners,
54-
Old,
54+
Wrinkles,
5555
}
5656

5757
public enum HairColor
@@ -82,7 +82,7 @@ public enum EyeColor : uint
8282
{
8383
Black,
8484
Grey,
85-
Red,
85+
Brown,
8686
Gold,
8787
Blue,
8888
Green,
@@ -95,7 +95,7 @@ public enum NoseType
9595
Dots,
9696
VShape,
9797
FullNose,
98-
Triangle,
98+
UShape,
9999
FlatC,
100100
UpsideDownC,
101101
Squidward,
@@ -142,16 +142,16 @@ public enum MustacheColor
142142
LightRed,
143143
Grey,
144144
LightBrown,
145-
Blonde,
145+
Tan,
146146
White,
147147
}
148148

149149
public enum MustacheType
150150
{
151151
None,
152-
Fat,
153-
Thin,
154-
Goatee,
152+
Normal,
153+
Lines,
154+
Droopy,
155155
}
156156

157157
public enum BeardType

0 commit comments

Comments
 (0)