Avatar and 3d thumbnail changes.
- Fixed 3d view causing a rerender on resize. - Avatar editor page. - Made avatar editor page mobile-friendly. - Fixed bug on shop where thumbnails would persist across pages. - Avatar editor CSS changes. Added a class for the shop item card. - Avatar editor api. - Updated 2d/3d thumbnail JS to support the 2d asset/user thumbnail api. - Fixed some rate limiters to not be global. - Tightened up the rate limit on the maintenance bypass api to 20 requests with a decay of 30 minutes. - APIs for avatar editor. This is for wearing/removing/listing items. - Ability to delete files from the CDN. - BodyColors/CharacterAppearance client endpoints. - Wearable assets. - BodyColors XML in grid storage folder for use by the BodyColors endpoint. - Owned/worn asset on sign up configurable. - Models for outfits. They are not finished at this moment in time.
This commit is contained in:
parent
d9ca6b4785
commit
131ff27343
|
|
@ -41,6 +41,19 @@ class AssetHelper
|
|||
if(!$marketplaceResult->ok() || !$assetResult->ok())
|
||||
return false;
|
||||
|
||||
$assetTypeId = $marketplaceResult['AssetTypeId'];
|
||||
if(
|
||||
$assetTypeId == 41 || // Hair Accessory
|
||||
$assetTypeId == 42 || // Face Accessory
|
||||
$assetTypeId == 43 || // Neck Accessory
|
||||
$assetTypeId == 44 || // Shoulder Accessory
|
||||
$assetTypeId == 45 || // Front Accessory
|
||||
$assetTypeId == 46 || // Back Accessory
|
||||
$assetTypeId == 47 // Waist Accessory
|
||||
) {
|
||||
$assetTypeId = 8;
|
||||
}
|
||||
|
||||
$assetContent = Http::get($assetResult['locations'][0]['location']);
|
||||
$hash = CdnHelper::SaveContent($assetContent->body(), $assetContent->header('Content-Type'));
|
||||
$asset = Asset::create([
|
||||
|
|
@ -50,7 +63,7 @@ class AssetHelper
|
|||
'approved' => true,
|
||||
'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0,
|
||||
'onSale' => $marketplaceResult['IsForSale'],
|
||||
'assetTypeId' => $marketplaceResult['AssetTypeId'],
|
||||
'assetTypeId' => $assetTypeId,
|
||||
'assetVersionId' => 0
|
||||
]);
|
||||
$assetVersion = AssetVersion::create([
|
||||
|
|
@ -82,6 +95,19 @@ class AssetHelper
|
|||
if(!$marketplaceResult->ok())
|
||||
return false;
|
||||
|
||||
$assetTypeId = $marketplaceResult['AssetTypeId'];
|
||||
if(
|
||||
$assetTypeId == 41 || // Hair Accessory
|
||||
$assetTypeId == 42 || // Face Accessory
|
||||
$assetTypeId == 43 || // Neck Accessory
|
||||
$assetTypeId == 44 || // Shoulder Accessory
|
||||
$assetTypeId == 45 || // Front Accessory
|
||||
$assetTypeId == 46 || // Back Accessory
|
||||
$assetTypeId == 47 // Waist Accessory
|
||||
) {
|
||||
$assetTypeId = 8;
|
||||
}
|
||||
|
||||
$hash = CdnHelper::SaveContentB64($b64Content, 'application/octet-stream');
|
||||
$asset = Asset::create([
|
||||
'creatorId' => ($uploadToHolder ? 1 : Auth::user()->id),
|
||||
|
|
@ -90,7 +116,7 @@ class AssetHelper
|
|||
'approved' => true,
|
||||
'priceInTokens' => $marketplaceResult['PriceInRobux'] ?: 0,
|
||||
'onSale' => $marketplaceResult['IsForSale'],
|
||||
'assetTypeId' => $marketplaceResult['AssetTypeId'],
|
||||
'assetTypeId' => $assetTypeId,
|
||||
'assetVersionId' => 0
|
||||
]);
|
||||
$assetVersion = AssetVersion::create([
|
||||
|
|
|
|||
|
|
@ -0,0 +1,851 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
XlXi 2023
|
||||
Brick color helper.
|
||||
*/
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class BrickColorHelper
|
||||
{
|
||||
private static $brickColors = [
|
||||
1 => [
|
||||
'Color' => [242, 243, 243],
|
||||
'Name' => 'White'
|
||||
],
|
||||
2 => [
|
||||
'Color' => [161, 165, 162],
|
||||
'Name' => 'Grey'
|
||||
],
|
||||
3 => [
|
||||
'Color' => [249, 233, 153],
|
||||
'Name' => 'Light yellow'
|
||||
],
|
||||
5 => [
|
||||
'Color' => [215, 197, 154],
|
||||
'Name' => 'Brick yellow'
|
||||
],
|
||||
6 => [
|
||||
'Color' => [194, 218, 184],
|
||||
'Name' => 'Light green (Mint)'
|
||||
],
|
||||
9 => [
|
||||
'Color' => [232, 186, 200],
|
||||
'Name' => 'Light reddish violet'
|
||||
],
|
||||
11 => [
|
||||
'Color' => [0x80, 0xbb, 0xdb],
|
||||
'Name' => 'Pastel Blue'
|
||||
],
|
||||
12 => [
|
||||
'Color' => [203, 132, 66],
|
||||
'Name' => 'Light orange brown'
|
||||
],
|
||||
18 => [
|
||||
'Color' => [204, 142, 105],
|
||||
'Name' => 'Nougat'
|
||||
],
|
||||
21 => [
|
||||
'Color' => [196, 40, 28],
|
||||
'Name' => 'Bright red'
|
||||
],
|
||||
22 => [
|
||||
'Color' => [196, 112, 160],
|
||||
'Name' => 'Med. reddish violet'
|
||||
],
|
||||
23 => [
|
||||
'Color' => [13, 105, 172],
|
||||
'Name' => 'Bright blue'
|
||||
],
|
||||
24 => [
|
||||
'Color' => [245, 205, 48],
|
||||
'Name' => 'Bright yellow'
|
||||
],
|
||||
25 => [
|
||||
'Color' => [98, 71, 50],
|
||||
'Name' => 'Earth orange'
|
||||
],
|
||||
26 => [
|
||||
'Color' => [27, 42, 53],
|
||||
'Name' => 'Black'
|
||||
],
|
||||
27 => [
|
||||
'Color' => [109, 110, 108],
|
||||
'Name' => 'Dark grey'
|
||||
],
|
||||
28 => [
|
||||
'Color' => [40, 127, 71],
|
||||
'Name' => 'Dark green'
|
||||
],
|
||||
29 => [
|
||||
'Color' => [161, 196, 140],
|
||||
'Name' => 'Medium green'
|
||||
],
|
||||
36 => [
|
||||
'Color' => [243, 207, 155],
|
||||
'Name' => 'Lig. Yellowich orange'
|
||||
],
|
||||
37 => [
|
||||
'Color' => [75, 151, 75],
|
||||
'Name' => 'Bright green'
|
||||
],
|
||||
38 => [
|
||||
'Color' => [160, 95, 53],
|
||||
'Name' => 'Dark orange'
|
||||
],
|
||||
39 => [
|
||||
'Color' => [193, 202, 222],
|
||||
'Name' => 'Light bluish violet'
|
||||
],
|
||||
40 => [
|
||||
'Color' => [236, 236, 236],
|
||||
'Name' => 'Transparent'
|
||||
],
|
||||
41 => [
|
||||
'Color' => [205, 84, 75],
|
||||
'Name' => 'Tr. Red'
|
||||
],
|
||||
42 => [
|
||||
'Color' => [193, 223, 240],
|
||||
'Name' => 'Tr. Lg blue'
|
||||
],
|
||||
43 => [
|
||||
'Color' => [123, 182, 232],
|
||||
'Name' => 'Tr. Blue'
|
||||
],
|
||||
44 => [
|
||||
'Color' => [247, 241, 141],
|
||||
'Name' => 'Tr. Yellow'
|
||||
],
|
||||
45 => [
|
||||
'Color' => [180, 210, 228],
|
||||
'Name' => 'Light blue'
|
||||
],
|
||||
47 => [
|
||||
'Color' => [217, 133, 108],
|
||||
'Name' => 'Tr. Flu. Reddish orange'
|
||||
],
|
||||
48 => [
|
||||
'Color' => [132, 182, 141],
|
||||
'Name' => 'Tr. Green'
|
||||
],
|
||||
49 => [
|
||||
'Color' => [248, 241, 132],
|
||||
'Name' => 'Tr. Flu. Green'
|
||||
],
|
||||
50 => [
|
||||
'Color' => [236, 232, 222],
|
||||
'Name' => 'Phosph. White'
|
||||
],
|
||||
100 => [
|
||||
'Color' => [238, 196, 182],
|
||||
'Name' => 'Light red'
|
||||
],
|
||||
101 => [
|
||||
'Color' => [218, 134, 122],
|
||||
'Name' => 'Medium red'
|
||||
],
|
||||
102 => [
|
||||
'Color' => [110, 153, 202],
|
||||
'Name' => 'Medium blue'
|
||||
],
|
||||
103 => [
|
||||
'Color' => [199, 193, 183],
|
||||
'Name' => 'Light grey'
|
||||
],
|
||||
104 => [
|
||||
'Color' => [107, 50, 124],
|
||||
'Name' => 'Bright violet'
|
||||
],
|
||||
105 => [
|
||||
'Color' => [226, 155, 64],
|
||||
'Name' => 'Br. yellowish orange'
|
||||
],
|
||||
106 => [
|
||||
'Color' => [218, 133, 65],
|
||||
'Name' => 'Bright orange'
|
||||
],
|
||||
107 => [
|
||||
'Color' => [0, 143, 156],
|
||||
'Name' => 'Bright bluish green'
|
||||
],
|
||||
108 => [
|
||||
'Color' => [104, 92, 67],
|
||||
'Name' => 'Earth yellow'
|
||||
],
|
||||
110 => [
|
||||
'Color' => [67, 84, 147],
|
||||
'Name' => 'Bright bluish violet'
|
||||
],
|
||||
111 => [
|
||||
'Color' => [191, 183, 177],
|
||||
'Name' => 'Tr. Brown'
|
||||
],
|
||||
112 => [
|
||||
'Color' => [104, 116, 172],
|
||||
'Name' => 'Medium bluish violet'
|
||||
],
|
||||
113 => [
|
||||
'Color' => [228, 173, 200],
|
||||
'Name' => 'Tr. Medi. reddish violet'
|
||||
],
|
||||
115 => [
|
||||
'Color' => [199, 210, 60],
|
||||
'Name' => 'Med. yellowish green'
|
||||
],
|
||||
116 => [
|
||||
'Color' => [85, 165, 175],
|
||||
'Name' => 'Med. bluish green'
|
||||
],
|
||||
118 => [
|
||||
'Color' => [183, 215, 213],
|
||||
'Name' => 'Light bluish green'
|
||||
],
|
||||
119 => [
|
||||
'Color' => [164, 189, 71],
|
||||
'Name' => 'Br. yellowish green'
|
||||
],
|
||||
120 => [
|
||||
'Color' => [217, 228, 167],
|
||||
'Name' => 'Lig. yellowish green'
|
||||
],
|
||||
121 => [
|
||||
'Color' => [231, 172, 88],
|
||||
'Name' => 'Med. yellowish orange'
|
||||
],
|
||||
123 => [
|
||||
'Color' => [211, 111, 76],
|
||||
'Name' => 'Br. reddish orange'
|
||||
],
|
||||
124 => [
|
||||
'Color' => [146, 57, 120],
|
||||
'Name' => 'Bright reddish violet'
|
||||
],
|
||||
125 => [
|
||||
'Color' => [234, 184, 146],
|
||||
'Name' => 'Light orange'
|
||||
],
|
||||
126 => [
|
||||
'Color' => [165, 165, 203],
|
||||
'Name' => 'Tr. Bright bluish violet'
|
||||
],
|
||||
127 => [
|
||||
'Color' => [220, 188, 129],
|
||||
'Name' => 'Gold'
|
||||
],
|
||||
128 => [
|
||||
'Color' => [174, 122, 89],
|
||||
'Name' => 'Dark nougat'
|
||||
],
|
||||
131 => [
|
||||
'Color' => [156, 163, 168],
|
||||
'Name' => 'Silver'
|
||||
],
|
||||
133 => [
|
||||
'Color' => [213, 115, 61],
|
||||
'Name' => 'Neon orange'
|
||||
],
|
||||
134 => [
|
||||
'Color' => [216, 221, 86],
|
||||
'Name' => 'Neon green'
|
||||
],
|
||||
135 => [
|
||||
'Color' => [116, 134, 157],
|
||||
'Name' => 'Sand blue'
|
||||
],
|
||||
136 => [
|
||||
'Color' => [135, 124, 144],
|
||||
'Name' => 'Sand violet'
|
||||
],
|
||||
137 => [
|
||||
'Color' => [224, 152, 100],
|
||||
'Name' => 'Medium orange'
|
||||
],
|
||||
138 => [
|
||||
'Color' => [149, 138, 115],
|
||||
'Name' => 'Sand yellow'
|
||||
],
|
||||
140 => [
|
||||
'Color' => [32, 58, 86],
|
||||
'Name' => 'Earth blue'
|
||||
],
|
||||
141 => [
|
||||
'Color' => [39, 70, 45],
|
||||
'Name' => 'Earth green'
|
||||
],
|
||||
143 => [
|
||||
'Color' => [207, 226, 247],
|
||||
'Name' => 'Tr. Flu. Blue'
|
||||
],
|
||||
145 => [
|
||||
'Color' => [121, 136, 161],
|
||||
'Name' => 'Sand blue metallic'
|
||||
],
|
||||
146 => [
|
||||
'Color' => [149, 142, 163],
|
||||
'Name' => 'Sand violet metallic'
|
||||
],
|
||||
147 => [
|
||||
'Color' => [147, 135, 103],
|
||||
'Name' => 'Sand yellow metallic'
|
||||
],
|
||||
148 => [
|
||||
'Color' => [87, 88, 87],
|
||||
'Name' => 'Dark grey metallic'
|
||||
],
|
||||
149 => [
|
||||
'Color' => [22, 29, 50],
|
||||
'Name' => 'Black metallic'
|
||||
],
|
||||
150 => [
|
||||
'Color' => [171, 173, 172],
|
||||
'Name' => 'Light grey metallic'
|
||||
],
|
||||
151 => [
|
||||
'Color' => [120, 144, 130],
|
||||
'Name' => 'Sand green'
|
||||
],
|
||||
153 => [
|
||||
'Color' => [149, 121, 119],
|
||||
'Name' => 'Sand red'
|
||||
],
|
||||
154 => [
|
||||
'Color' => [123, 46, 47],
|
||||
'Name' => 'Dark red'
|
||||
],
|
||||
157 => [
|
||||
'Color' => [255, 246, 123],
|
||||
'Name' => 'Tr. Flu. Yellow'
|
||||
],
|
||||
158 => [
|
||||
'Color' => [225, 164, 194],
|
||||
'Name' => 'Tr. Flu. Red'
|
||||
],
|
||||
168 => [
|
||||
'Color' => [117, 108, 98],
|
||||
'Name' => 'Gun metallic'
|
||||
],
|
||||
176 => [
|
||||
'Color' => [151, 105, 91],
|
||||
'Name' => 'Red flip/flop'
|
||||
],
|
||||
178 => [
|
||||
'Color' => [180, 132, 85],
|
||||
'Name' => 'Yellow flip/flop'
|
||||
],
|
||||
179 => [
|
||||
'Color' => [137, 135, 136],
|
||||
'Name' => 'Silver flip/flop'
|
||||
],
|
||||
180 => [
|
||||
'Color' => [215, 169, 75],
|
||||
'Name' => 'Curry'
|
||||
],
|
||||
190 => [
|
||||
'Color' => [249, 214, 46],
|
||||
'Name' => 'Fire Yellow'
|
||||
],
|
||||
191 => [
|
||||
'Color' => [232, 171, 45],
|
||||
'Name' => 'Flame yellowish orange'
|
||||
],
|
||||
192 => [
|
||||
'Color' => [105, 64, 40],
|
||||
'Name' => 'Reddish brown'
|
||||
],
|
||||
193 => [
|
||||
'Color' => [207, 96, 36],
|
||||
'Name' => 'Flame reddish orange'
|
||||
],
|
||||
194 => [
|
||||
'Color' => [163, 162, 165],
|
||||
'Name' => 'Medium stone grey'
|
||||
],
|
||||
195 => [
|
||||
'Color' => [70, 103, 164],
|
||||
'Name' => 'Royal blue'
|
||||
],
|
||||
196 => [
|
||||
'Color' => [35, 71, 139],
|
||||
'Name' => 'Dark Royal blue'
|
||||
],
|
||||
198 => [
|
||||
'Color' => [142, 66, 133],
|
||||
'Name' => 'Bright reddish lilac'
|
||||
],
|
||||
199 => [
|
||||
'Color' => [99, 95, 98],
|
||||
'Name' => 'Dark stone grey'
|
||||
],
|
||||
200 => [
|
||||
'Color' => [130, 138, 93],
|
||||
'Name' => 'Lemon metalic'
|
||||
],
|
||||
208 => [
|
||||
'Color' => [229, 228, 223],
|
||||
'Name' => 'Light stone grey'
|
||||
],
|
||||
209 => [
|
||||
'Color' => [176, 142, 68],
|
||||
'Name' => 'Dark Curry'
|
||||
],
|
||||
210 => [
|
||||
'Color' => [112, 149, 120],
|
||||
'Name' => 'Faded green'
|
||||
],
|
||||
211 => [
|
||||
'Color' => [121, 181, 181],
|
||||
'Name' => 'Turquoise'
|
||||
],
|
||||
212 => [
|
||||
'Color' => [159, 195, 233],
|
||||
'Name' => 'Light Royal blue'
|
||||
],
|
||||
213 => [
|
||||
'Color' => [108, 129, 183],
|
||||
'Name' => 'Medium Royal blue'
|
||||
],
|
||||
216 => [
|
||||
'Color' => [143, 76, 42],
|
||||
'Name' => 'Rust'
|
||||
],
|
||||
217 => [
|
||||
'Color' => [124, 92, 70],
|
||||
'Name' => 'Brown'
|
||||
],
|
||||
218 => [
|
||||
'Color' => [150, 112, 159],
|
||||
'Name' => 'Reddish lilac'
|
||||
],
|
||||
219 => [
|
||||
'Color' => [107, 98, 155],
|
||||
'Name' => 'Lilac'
|
||||
],
|
||||
220 => [
|
||||
'Color' => [167, 169, 206],
|
||||
'Name' => 'Light lilac'
|
||||
],
|
||||
221 => [
|
||||
'Color' => [205, 98, 152],
|
||||
'Name' => 'Bright purple'
|
||||
],
|
||||
222 => [
|
||||
'Color' => [228, 173, 200],
|
||||
'Name' => 'Light purple'
|
||||
],
|
||||
223 => [
|
||||
'Color' => [220, 144, 149],
|
||||
'Name' => 'Light pink'
|
||||
],
|
||||
224 => [
|
||||
'Color' => [240, 213, 160],
|
||||
'Name' => 'Light brick yellow'
|
||||
],
|
||||
225 => [
|
||||
'Color' => [235, 184, 127],
|
||||
'Name' => 'Warm yellowish orange'
|
||||
],
|
||||
226 => [
|
||||
'Color' => [253, 234, 141],
|
||||
'Name' => 'Cool yellow'
|
||||
],
|
||||
232 => [
|
||||
'Color' => [125, 187, 221],
|
||||
'Name' => 'Dove blue'
|
||||
],
|
||||
268 => [
|
||||
'Color' => [52, 43, 117],
|
||||
'Name' => 'Medium lilac'
|
||||
],
|
||||
301 => [
|
||||
'Color' => [80, 109, 84],
|
||||
'Name' => 'Slime green'
|
||||
],
|
||||
302 => [
|
||||
'Color' => [91, 93, 105],
|
||||
'Name' => 'Smoky grey'
|
||||
],
|
||||
303 => [
|
||||
'Color' => [0, 16, 176],
|
||||
'Name' => 'Dark blue'
|
||||
],
|
||||
304 => [
|
||||
'Color' => [44, 101, 29],
|
||||
'Name' => 'Parsley green'
|
||||
],
|
||||
305 => [
|
||||
'Color' => [82, 124, 174],
|
||||
'Name' => 'Steel blue'
|
||||
],
|
||||
306 => [
|
||||
'Color' => [51, 88, 130],
|
||||
'Name' => 'Storm blue'
|
||||
],
|
||||
307 => [
|
||||
'Color' => [16, 42, 220],
|
||||
'Name' => 'Lapis'
|
||||
],
|
||||
308 => [
|
||||
'Color' => [61, 21, 133],
|
||||
'Name' => 'Dark indigo'
|
||||
],
|
||||
309 => [
|
||||
'Color' => [52, 142, 64],
|
||||
'Name' => 'Sea green'
|
||||
],
|
||||
310 => [
|
||||
'Color' => [91, 154, 76],
|
||||
'Name' => 'Shamrock'
|
||||
],
|
||||
311 => [
|
||||
'Color' => [159, 161, 172],
|
||||
'Name' => 'Fossil'
|
||||
],
|
||||
312 => [
|
||||
'Color' => [89, 34, 89],
|
||||
'Name' => 'Mulberry'
|
||||
],
|
||||
313 => [
|
||||
'Color' => [31, 128, 29],
|
||||
'Name' => 'Forest green'
|
||||
],
|
||||
314 => [
|
||||
'Color' => [159, 173, 192],
|
||||
'Name' => 'Cadet blue'
|
||||
],
|
||||
315 => [
|
||||
'Color' => [9, 137, 207],
|
||||
'Name' => 'Electric blue'
|
||||
],
|
||||
316 => [
|
||||
'Color' => [123, 0, 123],
|
||||
'Name' => 'Eggplant'
|
||||
],
|
||||
317 => [
|
||||
'Color' => [124, 156, 107],
|
||||
'Name' => 'Moss'
|
||||
],
|
||||
318 => [
|
||||
'Color' => [138, 171, 133],
|
||||
'Name' => 'Artichoke'
|
||||
],
|
||||
319 => [
|
||||
'Color' => [185, 196, 177],
|
||||
'Name' => 'Sage green'
|
||||
],
|
||||
320 => [
|
||||
'Color' => [202, 203, 209],
|
||||
'Name' => 'Ghost grey'
|
||||
],
|
||||
321 => [
|
||||
'Color' => [167, 94, 155],
|
||||
'Name' => 'Lilac'
|
||||
],
|
||||
322 => [
|
||||
'Color' => [123, 47, 123],
|
||||
'Name' => 'Plum'
|
||||
],
|
||||
323 => [
|
||||
'Color' => [148, 190, 129],
|
||||
'Name' => 'Olivine'
|
||||
],
|
||||
324 => [
|
||||
'Color' => [168, 189, 153],
|
||||
'Name' => 'Laurel green'
|
||||
],
|
||||
325 => [
|
||||
'Color' => [223, 223, 222],
|
||||
'Name' => 'Quill grey'
|
||||
],
|
||||
327 => [
|
||||
'Color' => [151, 0, 0],
|
||||
'Name' => 'Crimson'
|
||||
],
|
||||
328 => [
|
||||
'Color' => [177, 229, 166],
|
||||
'Name' => 'Mint'
|
||||
],
|
||||
329 => [
|
||||
'Color' => [152, 194, 219],
|
||||
'Name' => 'Baby blue'
|
||||
],
|
||||
330 => [
|
||||
'Color' => [255, 152, 220],
|
||||
'Name' => 'Carnation pink'
|
||||
],
|
||||
331 => [
|
||||
'Color' => [255, 89, 89],
|
||||
'Name' => 'Persimmon'
|
||||
],
|
||||
332 => [
|
||||
'Color' => [117, 0, 0],
|
||||
'Name' => 'Maroon'
|
||||
],
|
||||
333 => [
|
||||
'Color' => [239, 184, 56],
|
||||
'Name' => 'Gold'
|
||||
],
|
||||
334 => [
|
||||
'Color' => [248, 217, 109],
|
||||
'Name' => 'Daisy orange'
|
||||
],
|
||||
335 => [
|
||||
'Color' => [231, 231, 236],
|
||||
'Name' => 'Pearl'
|
||||
],
|
||||
336 => [
|
||||
'Color' => [199, 212, 228],
|
||||
'Name' => 'Fog'
|
||||
],
|
||||
337 => [
|
||||
'Color' => [255, 148, 148],
|
||||
'Name' => 'Salmon'
|
||||
],
|
||||
338 => [
|
||||
'Color' => [190, 104, 98],
|
||||
'Name' => 'Terra Cotta'
|
||||
],
|
||||
339 => [
|
||||
'Color' => [86, 36, 36],
|
||||
'Name' => 'Cocoa'
|
||||
],
|
||||
340 => [
|
||||
'Color' => [241, 231, 199],
|
||||
'Name' => 'Wheat'
|
||||
],
|
||||
341 => [
|
||||
'Color' => [254, 243, 187],
|
||||
'Name' => 'Buttermilk'
|
||||
],
|
||||
342 => [
|
||||
'Color' => [224, 178, 208],
|
||||
'Name' => 'Mauve'
|
||||
],
|
||||
343 => [
|
||||
'Color' => [212, 144, 189],
|
||||
'Name' => 'Sunrise'
|
||||
],
|
||||
344 => [
|
||||
'Color' => [150, 85, 85],
|
||||
'Name' => 'Tawny'
|
||||
],
|
||||
345 => [
|
||||
'Color' => [143, 76, 42],
|
||||
'Name' => 'Rust'
|
||||
],
|
||||
346 => [
|
||||
'Color' => [211, 190, 150],
|
||||
'Name' => 'Cashmere'
|
||||
],
|
||||
347 => [
|
||||
'Color' => [226, 220, 188],
|
||||
'Name' => 'Khaki'
|
||||
],
|
||||
348 => [
|
||||
'Color' => [237, 234, 234],
|
||||
'Name' => 'Lily white'
|
||||
],
|
||||
349 => [
|
||||
'Color' => [233, 218, 218],
|
||||
'Name' => 'Seashell'
|
||||
],
|
||||
350 => [
|
||||
'Color' => [136, 62, 62],
|
||||
'Name' => 'Burgundy'
|
||||
],
|
||||
351 => [
|
||||
'Color' => [188, 155, 93],
|
||||
'Name' => 'Cork'
|
||||
],
|
||||
352 => [
|
||||
'Color' => [199, 172, 120],
|
||||
'Name' => 'Burlap'
|
||||
],
|
||||
353 => [
|
||||
'Color' => [202, 191, 163],
|
||||
'Name' => 'Beige'
|
||||
],
|
||||
354 => [
|
||||
'Color' => [187, 179, 178],
|
||||
'Name' => 'Oyster'
|
||||
],
|
||||
355 => [
|
||||
'Color' => [108, 88, 75],
|
||||
'Name' => 'Pine Cone'
|
||||
],
|
||||
356 => [
|
||||
'Color' => [160, 132, 79],
|
||||
'Name' => 'Fawn brown'
|
||||
],
|
||||
357 => [
|
||||
'Color' => [149, 137, 136],
|
||||
'Name' => 'Hurricane grey'
|
||||
],
|
||||
358 => [
|
||||
'Color' => [171, 168, 158],
|
||||
'Name' => 'Cloudy grey'
|
||||
],
|
||||
359 => [
|
||||
'Color' => [175, 148, 131],
|
||||
'Name' => 'Linen'
|
||||
],
|
||||
360 => [
|
||||
'Color' => [150, 103, 102],
|
||||
'Name' => 'Copper'
|
||||
],
|
||||
361 => [
|
||||
'Color' => [86, 66, 54],
|
||||
'Name' => 'Dirt brown'
|
||||
],
|
||||
362 => [
|
||||
'Color' => [126, 104, 63],
|
||||
'Name' => 'Bronze'
|
||||
],
|
||||
363 => [
|
||||
'Color' => [105, 102, 92],
|
||||
'Name' => 'Flint'
|
||||
],
|
||||
364 => [
|
||||
'Color' => [90, 76, 66],
|
||||
'Name' => 'Dark taupe'
|
||||
],
|
||||
365 => [
|
||||
'Color' => [106, 57, 9],
|
||||
'Name' => 'Burnt Sienna'
|
||||
],
|
||||
1001 => [
|
||||
'Color' => [248, 248, 248],
|
||||
'Name' => 'Institutional white'
|
||||
],
|
||||
1002 => [
|
||||
'Color' => [205, 205, 205],
|
||||
'Name' => 'Mid gray'
|
||||
],
|
||||
1003 => [
|
||||
'Color' => [17, 17, 17],
|
||||
'Name' => 'Really black'
|
||||
],
|
||||
1004 => [
|
||||
'Color' => [255, 0, 0],
|
||||
'Name' => 'Really red'
|
||||
],
|
||||
1005 => [
|
||||
'Color' => [255, 175, 0],
|
||||
'Name' => 'Deep orange'
|
||||
],
|
||||
1006 => [
|
||||
'Color' => [180, 128, 255],
|
||||
'Name' => 'Alder'
|
||||
],
|
||||
1007 => [
|
||||
'Color' => [163, 75, 75],
|
||||
'Name' => 'Dusty Rose'
|
||||
],
|
||||
1008 => [
|
||||
'Color' => [193, 190, 66],
|
||||
'Name' => 'Olive'
|
||||
],
|
||||
1009 => [
|
||||
'Color' => [255, 255, 0],
|
||||
'Name' => 'New Yeller'
|
||||
],
|
||||
1010 => [
|
||||
'Color' => [0, 0, 255],
|
||||
'Name' => 'Really blue'
|
||||
],
|
||||
1011 => [
|
||||
'Color' => [0, 32, 96],
|
||||
'Name' => 'Navy blue'
|
||||
],
|
||||
1012 => [
|
||||
'Color' => [33, 84, 185],
|
||||
'Name' => 'Deep blue'
|
||||
],
|
||||
1013 => [
|
||||
'Color' => [4, 175, 236],
|
||||
'Name' => 'Cyan'
|
||||
],
|
||||
1014 => [
|
||||
'Color' => [170, 85, 0],
|
||||
'Name' => 'CGA brown'
|
||||
],
|
||||
1015 => [
|
||||
'Color' => [170, 0, 170],
|
||||
'Name' => 'Magenta'
|
||||
],
|
||||
1016 => [
|
||||
'Color' => [255, 102, 204],
|
||||
'Name' => 'Pink'
|
||||
],
|
||||
1017 => [
|
||||
'Color' => [255, 175, 0],
|
||||
'Name' => 'Deep orange'
|
||||
],
|
||||
1018 => [
|
||||
'Color' => [18, 238, 212],
|
||||
'Name' => 'Teal'
|
||||
],
|
||||
1019 => [
|
||||
'Color' => [0, 255, 255],
|
||||
'Name' => 'Toothpaste'
|
||||
],
|
||||
1020 => [
|
||||
'Color' => [0, 255, 0],
|
||||
'Name' => 'Lime green'
|
||||
],
|
||||
1021 => [
|
||||
'Color' => [58, 125, 21],
|
||||
'Name' => 'Camo'
|
||||
],
|
||||
1022 => [
|
||||
'Color' => [127, 142, 100],
|
||||
'Name' => 'Grime'
|
||||
],
|
||||
1023 => [
|
||||
'Color' => [140, 91, 159],
|
||||
'Name' => 'Lavender'
|
||||
],
|
||||
1024 => [
|
||||
'Color' => [175, 221, 255],
|
||||
'Name' => 'Pastel light blue'
|
||||
],
|
||||
1025 => [
|
||||
'Color' => [255, 201, 201],
|
||||
'Name' => 'Pastel orange'
|
||||
],
|
||||
1026 => [
|
||||
'Color' => [177, 167, 255],
|
||||
'Name' => 'Pastel violet'
|
||||
],
|
||||
1027 => [
|
||||
'Color' => [159, 243, 233],
|
||||
'Name' => 'Pastel blue-green'
|
||||
],
|
||||
1028 => [
|
||||
'Color' => [204, 255, 204],
|
||||
'Name' => 'Pastel green'
|
||||
],
|
||||
1029 => [
|
||||
'Color' => [255, 255, 204],
|
||||
'Name' => 'Pastel yellow'
|
||||
],
|
||||
1030 => [
|
||||
'Color' => [255, 204, 153],
|
||||
'Name' => 'Pastel brown'
|
||||
],
|
||||
1031 => [
|
||||
'Color' => [98, 37, 209],
|
||||
'Name' => 'Royal purple'
|
||||
],
|
||||
1032 => [
|
||||
'Color' => [255, 0, 191],
|
||||
'Name' => 'Hot pink'
|
||||
]
|
||||
];
|
||||
|
||||
public static function IsValidColor($colorId)
|
||||
{
|
||||
return array_key_exists($colorId, self::$brickColors);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,21 @@ class CdnHelper
|
|||
return $hash;
|
||||
}
|
||||
|
||||
public static function Delete($hash)
|
||||
{
|
||||
$disk = self::GetDisk();
|
||||
|
||||
$cdnHash = CdnHash::where('hash', $hash);
|
||||
if($disk->exists($hash) && $cdnHash->exists()) {
|
||||
$cdnHash->delete();
|
||||
$disk->delete($hash);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function SaveContentB64($contentB64, $mime)
|
||||
{
|
||||
return self::SaveContent(base64_decode($contentB64), $mime);
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ class GridHelper
|
|||
|
||||
public static function getDefaultThumbnail($fileName)
|
||||
{
|
||||
$disk = $self::getThumbDisk();
|
||||
$disk = self::getThumbDisk();
|
||||
|
||||
if(!$disk->exists($fileName))
|
||||
throw new Exception('Unable to locate template file.');
|
||||
|
|
@ -133,7 +133,26 @@ class GridHelper
|
|||
|
||||
public static function getUnknownThumbnail()
|
||||
{
|
||||
return $self::getDefaultThumbnail('UnknownThumbnail.png');
|
||||
return self::getDefaultThumbnail('UnknownThumbnail.png');
|
||||
}
|
||||
|
||||
private static function getGameDisk()
|
||||
{
|
||||
return Storage::build([
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/grid/game'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getBodyColorsXML()
|
||||
{
|
||||
$disk = self::getGameDisk();
|
||||
$fileName = 'BodyColors.xml';
|
||||
|
||||
if(!$disk->exists($fileName))
|
||||
throw new Exception('Unable to locate template file.');
|
||||
|
||||
return $disk->get($fileName);
|
||||
}
|
||||
|
||||
public static function getArbiter($name)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,263 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use App\Helpers\BrickColorHelper;
|
||||
use App\Helpers\ValidationHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AvatarAsset;
|
||||
use App\Models\AvatarColor;
|
||||
use App\Models\UserAsset;
|
||||
|
||||
class AvatarController extends Controller
|
||||
{
|
||||
protected $validAssetTypeIds = [
|
||||
'17', // Heads
|
||||
'18', // Faces
|
||||
'8', // Hats
|
||||
'2', // T-Shirts
|
||||
'11', // Shirts
|
||||
'12', // Pants
|
||||
'19', // Gear
|
||||
'27', // Torsos
|
||||
'29', // Left Arms
|
||||
'28', // Right Arms
|
||||
'30', // Left Legs
|
||||
'31', // Right Legs
|
||||
'32' // Packages
|
||||
];
|
||||
|
||||
public function redrawUser()
|
||||
{
|
||||
Auth::user()->redraw();
|
||||
|
||||
return response(['success' => true]);
|
||||
}
|
||||
|
||||
public static function GetUserAssets($userId)
|
||||
{
|
||||
return UserAsset::where('owner_id', $userId)
|
||||
->whereRelation('asset', 'moderated', false)
|
||||
->orderByDesc('id');
|
||||
}
|
||||
|
||||
public function listAssets(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'assetTypeId' => ['required', 'int']
|
||||
]);
|
||||
|
||||
if($validator->fails()) {
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$valid = $validator->valid();
|
||||
|
||||
if(!in_array($valid['assetTypeId'], $this->validAssetTypeIds)) {
|
||||
$validator->errors()->add('assetTypeId', 'Invalid assetTypeId supplied.');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$userAssets = self::GetUserAssets(Auth::user()->id)
|
||||
->whereRelation('asset', 'assetTypeId', $valid['assetTypeId'])
|
||||
->groupBy('asset_id')
|
||||
->paginate(12);
|
||||
$data = [];
|
||||
|
||||
foreach($userAssets as $userAsset)
|
||||
{
|
||||
$asset = $userAsset->asset;
|
||||
|
||||
array_push($data, [
|
||||
'id' => $asset->id,
|
||||
'Url' => route('shop.asset', ['asset' => $asset, 'assetName' => Str::slug($asset->name, '-')]),
|
||||
'Thumbnail' => $asset->getThumbnail(),
|
||||
'Name' => $asset->name,
|
||||
'Wearing' => Auth::user()->isWearing($asset->id)
|
||||
]);
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => $data,
|
||||
'pages' => ($userAssets->hasPages() ? $userAssets->lastPage() : 1)
|
||||
]);
|
||||
}
|
||||
|
||||
public function listWearing(Request $request)
|
||||
{
|
||||
$avatarAssets = AvatarAsset::where('owner_id', Auth::user()->id)->get();
|
||||
$data = [];
|
||||
|
||||
foreach($avatarAssets as $avatarAsset)
|
||||
{
|
||||
$asset = $avatarAsset->asset;
|
||||
|
||||
array_push($data, [
|
||||
'id' => $asset->id,
|
||||
'Url' => route('shop.asset', ['asset' => $asset, 'assetName' => Str::slug($asset->name, '-')]),
|
||||
'Thumbnail' => $asset->getThumbnail(),
|
||||
'Name' => $asset->name,
|
||||
'Wearing' => true
|
||||
]);
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
public function wearAsset(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'id' => [
|
||||
'required',
|
||||
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
|
||||
return $query->where('moderated', false);
|
||||
})
|
||||
]
|
||||
]);
|
||||
|
||||
if($validator->fails()) {
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$valid = $validator->valid();
|
||||
|
||||
$userAsset = self::GetUserAssets(Auth::user()->id)
|
||||
->where('asset_id', $valid['id'])
|
||||
->first();
|
||||
|
||||
if(!$userAsset) {
|
||||
$validator->errors()->add('id', 'User does not own asset.');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
if(Auth::user()->isWearing($valid['id']) && $userAsset->asset->assetTypeId == 8) { // 8 = hat
|
||||
$validator->errors()->add('id', 'User is already wearing asset.');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
if(!in_array($userAsset->asset->assetTypeId, $this->validAssetTypeIds)) {
|
||||
$validator->errors()->add('id', 'This asset cannot be worn.');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$wornItems = AvatarAsset::where('owner_id', Auth::user()->id)
|
||||
->whereRelation('asset', 'assetTypeId', $userAsset->asset->assetTypeId);
|
||||
if($userAsset->asset->assetTypeId != 8 && $wornItems->exists()) // 8 = hat
|
||||
{
|
||||
$wornItems->delete();
|
||||
}
|
||||
elseif($userAsset->asset->assetTypeId == 8 && $wornItems->count() >= 10)
|
||||
{
|
||||
$validator->errors()->add('id', 'User has hit the wearing limit on this asset type.');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
AvatarAsset::Create([
|
||||
'owner_id' => Auth::user()->id,
|
||||
'asset_id' => $valid['id']
|
||||
]);
|
||||
|
||||
Auth::user()->redraw();
|
||||
|
||||
return response(['success' => true]);
|
||||
}
|
||||
|
||||
public function removeAsset(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'id' => [
|
||||
'required',
|
||||
Rule::exists('App\Models\Asset', 'id')->where(function($query) {
|
||||
return $query->where('moderated', false);
|
||||
})
|
||||
]
|
||||
]);
|
||||
|
||||
if($validator->fails()) {
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$valid = $validator->valid();
|
||||
|
||||
if(!Auth::user()->isWearing($valid['id'])) {
|
||||
$validator->errors()->add('id', 'User is not wearing asset.');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
AvatarAsset::where('owner_id', Auth::user()->id)
|
||||
->where('asset_id', $valid['id'])
|
||||
->delete();
|
||||
|
||||
Auth::user()->redraw();
|
||||
|
||||
return response(['success' => true]);
|
||||
}
|
||||
|
||||
public function setBodyColor(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'part' => ['required', 'regex:/(Head|Torso|LeftArm|RightArm|LeftLeg|RightLeg)/i'],
|
||||
'color' => ['required', 'int']
|
||||
]);
|
||||
|
||||
if($validator->fails()) {
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$valid = $validator->valid();
|
||||
|
||||
if(!BrickColorHelper::isValidColor($valid['color'])) {
|
||||
$validator->errors()->add('color', 'Invalid color id.');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$part = strtolower($valid['part']);
|
||||
switch($part)
|
||||
{
|
||||
case 'leftarm':
|
||||
$part = 'leftArm';
|
||||
break;
|
||||
case 'rightarm':
|
||||
$part = 'rightArm';
|
||||
break;
|
||||
case 'leftleg':
|
||||
$part = 'leftLeg';
|
||||
break;
|
||||
case 'rightleg':
|
||||
$part = 'rightLeg';
|
||||
break;
|
||||
}
|
||||
|
||||
$bodyColors = Auth::user()->getBodyColors();
|
||||
$bodyColors->{$part} = $valid['color'];
|
||||
$bodyColors->save();
|
||||
|
||||
Auth::user()->redraw();
|
||||
|
||||
return response(['success' => true]);
|
||||
}
|
||||
|
||||
public function getBodyColors(Request $request)
|
||||
{
|
||||
$bodyColors = Auth::user()->getBodyColors();
|
||||
|
||||
return response([
|
||||
'data' => [
|
||||
'Head' => $bodyColors->head,
|
||||
'Torso' => $bodyColors->torso,
|
||||
'RightArm' => $bodyColors->rightArm,
|
||||
'LeftArm' => $bodyColors->leftArm,
|
||||
'RightLeg' => $bodyColors->rightLeg,
|
||||
'LeftLeg' => $bodyColors->leftLeg
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,19 @@
|
|||
|
||||
namespace App\Http\Controllers\Web\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rules;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AvatarAsset;
|
||||
use App\Models\DefaultUserAsset;
|
||||
use App\Models\UserAsset;
|
||||
use App\Models\User;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
|
|
@ -53,7 +57,22 @@ class RegisteredUserController extends Controller
|
|||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
]);
|
||||
|
||||
|
||||
foreach(DefaultUserAsset::all() as $defaultAsset)
|
||||
{
|
||||
UserAsset::createSerialed($user->id, $defaultAsset->asset_id);
|
||||
|
||||
if($defaultAsset->wearing)
|
||||
{
|
||||
AvatarAsset::create([
|
||||
'owner_id' => $user->id,
|
||||
'asset_id' => $defaultAsset->asset_id
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$user->redraw();
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Web;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
use App\Helpers\GridHelper;
|
||||
use App\Helpers\ValidationHelper;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AvatarAsset;
|
||||
use App\Models\User;
|
||||
|
||||
class ClientAvatarController extends Controller
|
||||
{
|
||||
public function bodyColors(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'userId' => [
|
||||
'required',
|
||||
Rule::exists('App\Models\User', 'id')
|
||||
]
|
||||
]);
|
||||
|
||||
if($validator->fails()) {
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$valid = $validator->valid();
|
||||
$user = User::where('id', $valid['userId'])->first();
|
||||
|
||||
if($user->hasActivePunishment() && $user->getPunishment()->isDeletion()) {
|
||||
$validator->errors()->add('id', 'User is moderated.');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$document = simplexml_load_string(GridHelper::getBodyColorsXML());
|
||||
$bodyColors = $user->getBodyColors();
|
||||
|
||||
$document->xpath('//int[@name="HeadColor"]')[0][0] = $bodyColors->head;
|
||||
$document->xpath('//int[@name="TorsoColor"]')[0][0] = $bodyColors->torso;
|
||||
$document->xpath('//int[@name="LeftArmColor"]')[0][0] = $bodyColors->leftArm;
|
||||
$document->xpath('//int[@name="LeftLegColor"]')[0][0] = $bodyColors->leftLeg;
|
||||
$document->xpath('//int[@name="RightArmColor"]')[0][0] = $bodyColors->rightArm;
|
||||
$document->xpath('//int[@name="RightLegColor"]')[0][0] = $bodyColors->rightLeg;
|
||||
|
||||
return response($document->asXML())
|
||||
->header('Content-Type', 'application/xml');
|
||||
}
|
||||
|
||||
public function characterFetch(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'userId' => [
|
||||
'required',
|
||||
Rule::exists('App\Models\User', 'id')
|
||||
]
|
||||
]);
|
||||
|
||||
if($validator->fails()) {
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$valid = $validator->valid();
|
||||
$user = User::where('id', $valid['userId'])->first();
|
||||
|
||||
if($user->hasActivePunishment() && $user->getPunishment()->isDeletion()) {
|
||||
$validator->errors()->add('id', 'User is moderated.');
|
||||
return ValidationHelper::generateValidatorError($validator);
|
||||
}
|
||||
|
||||
$charApp = '';
|
||||
$charApp .= route('client.bodyColors', ['userId' => $user->id]);
|
||||
|
||||
foreach($user->getWearing()->get() as $avatarAsset)
|
||||
{
|
||||
$charApp .= ';' . route('client.asset', ['id' => $avatarAsset->asset->id]);
|
||||
|
||||
if($avatarAsset->asset->assetTypeId == 19) // Gear
|
||||
$charApp .= '&equipped=1';
|
||||
}
|
||||
|
||||
return response($charApp)
|
||||
->header('Content-Type', 'text/plain');
|
||||
}
|
||||
}
|
||||
|
|
@ -100,12 +100,17 @@ class ArbiterRender implements ShouldQueue
|
|||
array_push($arguments, 0); // Camera Offset X
|
||||
array_push($arguments, 0); // Camera Offset Y
|
||||
case 'Avatar':
|
||||
$arguments[0] = url('test', ['id' => $this->assetId]); // TODO: this
|
||||
$arguments[0] = route('client.characterFetch', ['userId' => $this->assetId]);
|
||||
break;
|
||||
case 'Torso':
|
||||
case 'Right Arm':
|
||||
case 'Left Arm':
|
||||
case 'Left Leg':
|
||||
case 'Right Leg':
|
||||
$this->type = 'BodyPart';
|
||||
case 'Head':
|
||||
case 'Shirt':
|
||||
case 'Pants':
|
||||
case 'BodyPart':
|
||||
// TODO: XlXi: Move this to config, as it could be different from prod in a testing environment. Also move this to it's own asset (not loading from roblox).
|
||||
array_push($arguments, 'https://www.roblox.com/asset/?id=1785197'); // Rig
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AvatarAsset extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'owner_id',
|
||||
'asset_id'
|
||||
];
|
||||
|
||||
public function asset()
|
||||
{
|
||||
return $this->belongsTo(Asset::class, 'asset_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AvatarColor extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'owner_id',
|
||||
'head',
|
||||
'torso',
|
||||
'leftArm',
|
||||
'rightArm',
|
||||
'leftLeg',
|
||||
'rightLeg'
|
||||
];
|
||||
|
||||
public static function GetRandomPrimaryColor()
|
||||
{
|
||||
$colors = [1, 208, 194, 199, 26, 21, 24, 23, 102, 141, 37, 29];
|
||||
return $colors[array_rand($colors)];
|
||||
}
|
||||
|
||||
public static function GetRandomHeadColor()
|
||||
{
|
||||
$colors = [1, 208, 194, 226];
|
||||
return $colors[array_rand($colors)];
|
||||
}
|
||||
|
||||
public static function user($userId)
|
||||
{
|
||||
return self::where('owner_id', $userId);
|
||||
}
|
||||
|
||||
public static function newForUser($userId)
|
||||
{
|
||||
$headColor = self::GetRandomHeadColor();
|
||||
$torsoColor = self::GetRandomPrimaryColor();
|
||||
|
||||
return self::create([
|
||||
'owner_id' => $userId,
|
||||
'head' => $headColor,
|
||||
'torso' => $torsoColor,
|
||||
'leftArm' => $headColor,
|
||||
'rightArm' => $headColor,
|
||||
'leftLeg' => 102,
|
||||
'rightLeg' => 102
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AvatarOutfit extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AvatarOutfitAsset extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DefaultUserAsset extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
|
@ -11,12 +10,10 @@ use Illuminate\Support\Facades\Auth;
|
|||
use Illuminate\Support\Facades\Http;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
use App\Helpers\CdnHelper;
|
||||
use App\Notifications\ResetPasswordNotification;
|
||||
use App\Models\UserRoleset;
|
||||
use App\Models\Roleset;
|
||||
use App\Models\Friend;
|
||||
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
|
|
@ -210,6 +207,49 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
$this->save();
|
||||
}
|
||||
|
||||
public function redraw()
|
||||
{
|
||||
$oldHashes = [
|
||||
$this->thumbnailBustHash,
|
||||
$this->thumbnail2DHash,
|
||||
$this->thumbnail3DHash
|
||||
];
|
||||
|
||||
$this->thumbnailBustHash = null;
|
||||
$this->thumbnail2DHash = null;
|
||||
$this->thumbnail3DHash = null;
|
||||
$this->timestamps = false;
|
||||
$this->save();
|
||||
|
||||
foreach($oldHashes as $hash)
|
||||
{
|
||||
if(!User::where('thumbnailBustHash', $hash)->orWhere('thumbnail2DHash', $hash)->orWhere('thumbnail3DHash', $hash)->exists())
|
||||
CdnHelper::Delete($hash);
|
||||
}
|
||||
}
|
||||
|
||||
public function isWearing($assetId)
|
||||
{
|
||||
return AvatarAsset::where('owner_id', $this->id)
|
||||
->where('asset_id', $assetId)
|
||||
->exists();
|
||||
}
|
||||
|
||||
public function getWearing()
|
||||
{
|
||||
return AvatarAsset::where('owner_id', $this->id)
|
||||
->whereRelation('asset', 'moderated', 0);
|
||||
}
|
||||
|
||||
public function getBodyColors()
|
||||
{
|
||||
$colors = AvatarColor::user($this->id);
|
||||
if($colors->exists())
|
||||
return $colors->first();
|
||||
|
||||
return AvatarColor::newForUser($this->id);
|
||||
}
|
||||
|
||||
public function userToJson()
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -87,6 +87,15 @@ return [
|
|||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => true,
|
||||
'modes' => [
|
||||
//'ONLY_FULL_GROUP_BY',
|
||||
'STRICT_TRANS_TABLES',
|
||||
'NO_ZERO_IN_DATE',
|
||||
'NO_ZERO_DATE',
|
||||
'ERROR_FOR_DIVISION_BY_ZERO',
|
||||
'NO_AUTO_CREATE_USER',
|
||||
'NO_ENGINE_SUBSTITUTION'
|
||||
],
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('avatar_assets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedBigInteger('owner_id');
|
||||
$table->unsignedBigInteger('asset_id');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('avatar_assets');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('avatar_colors', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedBigInteger('owner_id')->unique();
|
||||
$table->unsignedBigInteger('head');
|
||||
$table->unsignedBigInteger('torso');
|
||||
$table->unsignedBigInteger('leftArm');
|
||||
$table->unsignedBigInteger('rightArm');
|
||||
$table->unsignedBigInteger('leftLeg');
|
||||
$table->unsignedBigInteger('rightLeg');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('avatar_colors');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('default_user_assets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedBigInteger('asset_id');
|
||||
$table->boolean('wearing');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('default_user_assets');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('avatar_outfits', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedBigInteger('owner_id');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('avatar_outfits');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('avatar_outfit_assets', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->unsignedBigInteger('outfit_id');
|
||||
$table->unsignedBigInteger('asset_id');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('avatar_outfit_assets');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,882 @@
|
|||
/*
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import { Component, createRef, createElement } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
import classNames from 'classnames/bind';
|
||||
|
||||
import { buildGenericApiUrl } from '../util/HTTP.js';
|
||||
import ProgressiveImage from './ProgressiveImage';
|
||||
import Loader from './Loader';
|
||||
|
||||
import ThumbnailTool from './ThumbnailTool';
|
||||
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
const brickColors = [
|
||||
{id: 1, name: 'White'},
|
||||
{id: 2, name: 'Grey'},
|
||||
{id: 3, name: 'Light yellow'},
|
||||
{id: 5, name: 'Brick yellow'},
|
||||
{id: 6, name: 'Light green (Mint)'},
|
||||
{id: 9, name: 'Light reddish violet'},
|
||||
{id: 11, name: 'Pastel Blue'},
|
||||
{id: 12, name: 'Light orange brown'},
|
||||
{id: 18, name: 'Nougat'},
|
||||
{id: 21, name: 'Bright red'},
|
||||
{id: 22, name: 'Med. reddish violet'},
|
||||
{id: 23, name: 'Bright blue'},
|
||||
{id: 24, name: 'Bright yellow'},
|
||||
{id: 25, name: 'Earth orange'},
|
||||
{id: 26, name: 'Black'},
|
||||
{id: 27, name: 'Dark grey'},
|
||||
{id: 28, name: 'Dark green'},
|
||||
{id: 29, name: 'Medium green'},
|
||||
{id: 36, name: 'Lig. Yellowich orange'},
|
||||
{id: 37, name: 'Bright green'},
|
||||
{id: 38, name: 'Dark orange'},
|
||||
{id: 39, name: 'Light bluish violet'},
|
||||
{id: 40, name: 'Transparent'},
|
||||
{id: 41, name: 'Tr. Red'},
|
||||
{id: 42, name: 'Tr. Lg blue'},
|
||||
{id: 43, name: 'Tr. Blue'},
|
||||
{id: 44, name: 'Tr. Yellow'},
|
||||
{id: 45, name: 'Light blue'},
|
||||
{id: 47, name: 'Tr. Flu. Reddish orange'},
|
||||
{id: 48, name: 'Tr. Green'},
|
||||
{id: 49, name: 'Tr. Flu. Green'},
|
||||
{id: 50, name: 'Phosph. White'},
|
||||
{id: 100, name: 'Light red'},
|
||||
{id: 101, name: 'Medium red'},
|
||||
{id: 102, name: 'Medium blue'},
|
||||
{id: 103, name: 'Light grey'},
|
||||
{id: 104, name: 'Bright violet'},
|
||||
{id: 105, name: 'Br. yellowish orange'},
|
||||
{id: 106, name: 'Bright orange'},
|
||||
{id: 107, name: 'Bright bluish green'},
|
||||
{id: 108, name: 'Earth yellow'},
|
||||
{id: 110, name: 'Bright bluish violet'},
|
||||
{id: 111, name: 'Tr. Brown'},
|
||||
{id: 112, name: 'Medium bluish violet'},
|
||||
{id: 113, name: 'Tr. Medi. reddish violet'},
|
||||
{id: 115, name: 'Med. yellowish green'},
|
||||
{id: 116, name: 'Med. bluish green'},
|
||||
{id: 118, name: 'Light bluish green'},
|
||||
{id: 119, name: 'Br. yellowish green'},
|
||||
{id: 120, name: 'Lig. yellowish green'},
|
||||
{id: 121, name: 'Med. yellowish orange'},
|
||||
{id: 123, name: 'Br. reddish orange'},
|
||||
{id: 124, name: 'Bright reddish violet'},
|
||||
{id: 125, name: 'Light orange'},
|
||||
{id: 126, name: 'Tr. Bright bluish violet'},
|
||||
{id: 127, name: 'Gold'},
|
||||
{id: 128, name: 'Dark nougat'},
|
||||
{id: 131, name: 'Silver'},
|
||||
{id: 133, name: 'Neon orange'},
|
||||
{id: 134, name: 'Neon green'},
|
||||
{id: 135, name: 'Sand blue'},
|
||||
{id: 136, name: 'Sand violet'},
|
||||
{id: 137, name: 'Medium orange'},
|
||||
{id: 138, name: 'Sand yellow'},
|
||||
{id: 140, name: 'Earth blue'},
|
||||
{id: 141, name: 'Earth green'},
|
||||
{id: 143, name: 'Tr. Flu. Blue'},
|
||||
{id: 145, name: 'Sand blue metallic'},
|
||||
{id: 146, name: 'Sand violet metallic'},
|
||||
{id: 147, name: 'Sand yellow metallic'},
|
||||
{id: 148, name: 'Dark grey metallic'},
|
||||
{id: 149, name: 'Black metallic'},
|
||||
{id: 150, name: 'Light grey metallic'},
|
||||
{id: 151, name: 'Sand green'},
|
||||
{id: 153, name: 'Sand red'},
|
||||
{id: 154, name: 'Dark red'},
|
||||
{id: 157, name: 'Tr. Flu. Yellow'},
|
||||
{id: 158, name: 'Tr. Flu. Red'},
|
||||
{id: 168, name: 'Gun metallic'},
|
||||
{id: 176, name: 'Red flip/flop'},
|
||||
{id: 178, name: 'Yellow flip/flop'},
|
||||
{id: 179, name: 'Silver flip/flop'},
|
||||
{id: 180, name: 'Curry'},
|
||||
{id: 190, name: 'Fire Yellow'},
|
||||
{id: 191, name: 'Flame yellowish orange'},
|
||||
{id: 192, name: 'Reddish brown'},
|
||||
{id: 193, name: 'Flame reddish orange'},
|
||||
{id: 194, name: 'Medium stone grey'},
|
||||
{id: 195, name: 'Royal blue'},
|
||||
{id: 196, name: 'Dark Royal blue'},
|
||||
{id: 198, name: 'Bright reddish lilac'},
|
||||
{id: 199, name: 'Dark stone grey'},
|
||||
{id: 200, name: 'Lemon metalic'},
|
||||
{id: 208, name: 'Light stone grey'},
|
||||
{id: 209, name: 'Dark Curry'},
|
||||
{id: 210, name: 'Faded green'},
|
||||
{id: 211, name: 'Turquoise'},
|
||||
{id: 212, name: 'Light Royal blue'},
|
||||
{id: 213, name: 'Medium Royal blue'},
|
||||
{id: 216, name: 'Rust'},
|
||||
{id: 217, name: 'Brown'},
|
||||
{id: 218, name: 'Reddish lilac'},
|
||||
{id: 219, name: 'Lilac'},
|
||||
{id: 220, name: 'Light lilac'},
|
||||
{id: 221, name: 'Bright purple'},
|
||||
{id: 222, name: 'Light purple'},
|
||||
{id: 223, name: 'Light pink'},
|
||||
{id: 224, name: 'Light brick yellow'},
|
||||
{id: 225, name: 'Warm yellowish orange'},
|
||||
{id: 226, name: 'Cool yellow'},
|
||||
{id: 232, name: 'Dove blue'},
|
||||
{id: 268, name: 'Medium lilac'},
|
||||
{id: 301, name: 'Slime green'},
|
||||
{id: 302, name: 'Smoky grey'},
|
||||
{id: 303, name: 'Dark blue'},
|
||||
{id: 304, name: 'Parsley green'},
|
||||
{id: 305, name: 'Steel blue'},
|
||||
{id: 306, name: 'Storm blue'},
|
||||
{id: 307, name: 'Lapis'},
|
||||
{id: 308, name: 'Dark indigo'},
|
||||
{id: 309, name: 'Sea green'},
|
||||
{id: 310, name: 'Shamrock'},
|
||||
{id: 311, name: 'Fossil'},
|
||||
{id: 312, name: 'Mulberry'},
|
||||
{id: 313, name: 'Forest green'},
|
||||
{id: 314, name: 'Cadet blue'},
|
||||
{id: 315, name: 'Electric blue'},
|
||||
{id: 316, name: 'Eggplant'},
|
||||
{id: 317, name: 'Moss'},
|
||||
{id: 318, name: 'Artichoke'},
|
||||
{id: 319, name: 'Sage green'},
|
||||
{id: 320, name: 'Ghost grey'},
|
||||
{id: 321, name: 'Lilac'},
|
||||
{id: 322, name: 'Plum'},
|
||||
{id: 323, name: 'Olivine'},
|
||||
{id: 324, name: 'Laurel green'},
|
||||
{id: 325, name: 'Quill grey'},
|
||||
{id: 327, name: 'Crimson'},
|
||||
{id: 328, name: 'Mint'},
|
||||
{id: 329, name: 'Baby blue'},
|
||||
{id: 330, name: 'Carnation pink'},
|
||||
{id: 331, name: 'Persimmon'},
|
||||
{id: 332, name: 'Maroon'},
|
||||
{id: 333, name: 'Gold'},
|
||||
{id: 334, name: 'Daisy orange'},
|
||||
{id: 335, name: 'Pearl'},
|
||||
{id: 336, name: 'Fog'},
|
||||
{id: 337, name: 'Salmon'},
|
||||
{id: 338, name: 'Terra Cotta'},
|
||||
{id: 339, name: 'Cocoa'},
|
||||
{id: 340, name: 'Wheat'},
|
||||
{id: 341, name: 'Buttermilk'},
|
||||
{id: 342, name: 'Mauve'},
|
||||
{id: 343, name: 'Sunrise'},
|
||||
{id: 344, name: 'Tawny'},
|
||||
{id: 345, name: 'Rust'},
|
||||
{id: 346, name: 'Cashmere'},
|
||||
{id: 347, name: 'Khaki'},
|
||||
{id: 348, name: 'Lily white'},
|
||||
{id: 349, name: 'Seashell'},
|
||||
{id: 350, name: 'Burgundy'},
|
||||
{id: 351, name: 'Cork'},
|
||||
{id: 352, name: 'Burlap'},
|
||||
{id: 353, name: 'Beige'},
|
||||
{id: 354, name: 'Oyster'},
|
||||
{id: 355, name: 'Pine Cone'},
|
||||
{id: 356, name: 'Fawn brown'},
|
||||
{id: 357, name: 'Hurricane grey'},
|
||||
{id: 358, name: 'Cloudy grey'},
|
||||
{id: 359, name: 'Linen'},
|
||||
{id: 360, name: 'Copper'},
|
||||
{id: 361, name: 'Dirt brown'},
|
||||
{id: 362, name: 'Bronze'},
|
||||
{id: 363, name: 'Flint'},
|
||||
{id: 364, name: 'Dark taupe'},
|
||||
{id: 365, name: 'Burnt Sienna'},
|
||||
{id: 1001, name: 'Institutional white'},
|
||||
{id: 1002, name: 'Mid gray'},
|
||||
{id: 1003, name: 'Really black'},
|
||||
{id: 1004, name: 'Really red'},
|
||||
{id: 1005, name: 'Deep orange'},
|
||||
{id: 1006, name: 'Alder'},
|
||||
{id: 1007, name: 'Dusty Rose'},
|
||||
{id: 1008, name: 'Olive'},
|
||||
{id: 1009, name: 'New Yeller'},
|
||||
{id: 1010, name: 'Really blue'},
|
||||
{id: 1011, name: 'Navy blue'},
|
||||
{id: 1012, name: 'Deep blue'},
|
||||
{id: 1013, name: 'Cyan'},
|
||||
{id: 1014, name: 'CGA brown'},
|
||||
{id: 1015, name: 'Magenta'},
|
||||
{id: 1016, name: 'Pink'},
|
||||
{id: 1017, name: 'Deep orange'},
|
||||
{id: 1018, name: 'Teal'},
|
||||
{id: 1019, name: 'Toothpaste'},
|
||||
{id: 1020, name: 'Lime green'},
|
||||
{id: 1021, name: 'Camo'},
|
||||
{id: 1022, name: 'Grime'},
|
||||
{id: 1023, name: 'Lavender'},
|
||||
{id: 1024, name: 'Pastel light blue'},
|
||||
{id: 1025, name: 'Pastel orange'},
|
||||
{id: 1026, name: 'Pastel violet'},
|
||||
{id: 1027, name: 'Pastel blue-green'},
|
||||
{id: 1028, name: 'Pastel green'},
|
||||
{id: 1029, name: 'Pastel yellow'},
|
||||
{id: 1030, name: 'Pastel brown'},
|
||||
{id: 1031, name: 'Royal purple'},
|
||||
{id: 1032, name: 'Hot pink'}
|
||||
];
|
||||
|
||||
class EditorItemCard extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
worn: false
|
||||
};
|
||||
|
||||
this.wearAssetId = this.wearAssetId.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if(this.props.item.Wearing)
|
||||
this.setState({ worn: true });
|
||||
}
|
||||
|
||||
wearAssetId(assetId) {
|
||||
this.setState({ worn: !this.state.worn });
|
||||
if(this.state.worn)
|
||||
this.props.unwearAssetId(assetId);
|
||||
else
|
||||
this.props.wearAssetId(assetId);
|
||||
}
|
||||
|
||||
render() {
|
||||
var item = this.props.item;
|
||||
|
||||
return (
|
||||
<div className="virtubrick-item-card virtubrick-avatar-card">
|
||||
<span className="card m-2">
|
||||
<a className="text-decoration-none text-reset" href={ item.Url }>
|
||||
<ProgressiveImage
|
||||
src={ item.Thumbnail }
|
||||
placeholderImg={ buildGenericApiUrl('www', 'images/busy/asset.png') }
|
||||
alt={ item.Name }
|
||||
className='img-fluid'
|
||||
/>
|
||||
<div className="p-2 pb-0">
|
||||
<p className="text-truncate">{ item.Name }</p>
|
||||
</div>
|
||||
</a>
|
||||
<button className={classNames({'btn': true, 'btn-sm': true, 'btn-primary': !this.state.worn, 'btn-danger': this.state.worn, 'm-2': true,})} onClick={ () => this.wearAssetId(item.id) }>{ this.state.worn ? 'Take off' : 'Wear' }</button>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WardrobeTab extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loaded: false,
|
||||
assetTabs: [
|
||||
{label: 'Heads', typeId: 17},
|
||||
{label: 'Faces', typeId: 18},
|
||||
{label: 'Hats', typeId: 8},
|
||||
{label: 'T-Shirts', typeId: 2},
|
||||
{label: 'Shirts', typeId: 11},
|
||||
{label: 'Pants', typeId: 12},
|
||||
{label: 'Gear', typeId: 19},
|
||||
{label: 'Torsos', typeId: 27},
|
||||
{label: 'Left Arms', typeId: 29},
|
||||
{label: 'Right Arms', typeId: 28},
|
||||
{label: 'Left Legs', typeId: 30},
|
||||
{label: 'Right Legs', typeId: 31},
|
||||
{label: 'Packages', typeId: 32}
|
||||
],
|
||||
pageKey: 0,
|
||||
pageNumber: 1,
|
||||
pages: 0,
|
||||
items: []
|
||||
};
|
||||
|
||||
this.setTypeId = this.setTypeId.bind(this);
|
||||
this.incrementPage = this.incrementPage.bind(this);
|
||||
this.loadPage = this.loadPage.bind(this);
|
||||
this.refresh = this.refresh.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setTypeId(8, true); // XlXi: Bypass needed, else the initial page load isn't going to happen.
|
||||
}
|
||||
|
||||
setTypeId(assetTypeId, forceLoad = false) {
|
||||
this.setState({ selectedTypeId: assetTypeId}, function() {
|
||||
this.loadPage(1, !forceLoad);
|
||||
});
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.loadPage(this.state.pageNumber);
|
||||
}
|
||||
|
||||
incrementPage(amount) {
|
||||
this.loadPage(this.state.pageNumber + amount);
|
||||
}
|
||||
|
||||
loadPage(pageNum, noBypass = true) {
|
||||
if(!this.state.loaded && noBypass)
|
||||
return;
|
||||
|
||||
this.setState({ loaded: false });
|
||||
|
||||
let oldPageNum = this.state.pageNumber;
|
||||
this.setState({ pageNumber: pageNum });
|
||||
|
||||
axios.get(buildGenericApiUrl('api', `avatar/v1/list?assetTypeId=${this.state.selectedTypeId}${ pageNum > 1 ? '&page=' + pageNum : '' }`))
|
||||
.then(res => {
|
||||
const items = res.data;
|
||||
let newKey = this.state.pageKey + 1;
|
||||
|
||||
this.setState({ pageKey: newKey, items: items.data, pages: items.pages, loaded: true });
|
||||
})
|
||||
.catch(() => {
|
||||
this.props.setError('Error loading wardrobe page.');
|
||||
this.setState({ pageNumber: oldPageNum, loaded: true });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<ul className="nav nav-pills my-2 mx-auto justify-content-center vb-wardrobe-nav">
|
||||
{
|
||||
this.state.assetTabs.map(({ label, typeId, ref }) =>
|
||||
<li className="nav-item">
|
||||
<button className={classNames({'nav-link': true, 'active': this.state.selectedTypeId == typeId, 'disabled': !this.state.loaded})} disabled={ !this.state.loaded } onClick={ () => this.setTypeId(typeId) }>{ label }</button>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
{
|
||||
!this.state.loaded
|
||||
?
|
||||
<div className="virtubrick-shop-overlay">
|
||||
<Loader />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
this.state.items.length == 0
|
||||
?
|
||||
<p className="text-muted text-center">Nothing found.</p>
|
||||
:
|
||||
<div key={ this.state.pageKey }>
|
||||
{
|
||||
this.state.items.map((item, index) =>
|
||||
<EditorItemCard unwearAssetId={ this.props.unwearAssetId } wearAssetId={ this.props.wearAssetId } item={ item } key={ index } />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
this.state.pages > 1 ?
|
||||
<ul className="list-inline mx-auto mt-3">
|
||||
<li className="list-inline-item">
|
||||
<button className="btn btn-secondary" disabled={(this.state.pageNumber <= 1) ? true : null} onClick={ () => this.incrementPage(-1) }><i className="fa-solid fa-angle-left"></i></button>
|
||||
</li>
|
||||
<li className="list-inline-item virtubrick-paginator">
|
||||
<span>Page </span>
|
||||
<input type="text" value={ this.state.pageNumber || '' } className="form-control" disabled={this.state.loaded ? null : true} />
|
||||
<span> of { this.state.pages || '???' }</span>
|
||||
</li>
|
||||
<li className="list-inline-item">
|
||||
<button className="btn btn-secondary" disabled={(this.state.pageNumber >= this.state.pages) ? true : null} onClick={ () => this.incrementPage(1) }><i className="fa-solid fa-angle-right"></i></button>
|
||||
</li>
|
||||
</ul>
|
||||
:
|
||||
null
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OutfitsTab extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<><p>outfits</p></>);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: XlXi: Move this out of this component. I was too lazy to do it initially but it can be done.
|
||||
class BodyColorPaneBodyPart extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
color: 'vb-bc-194'
|
||||
}
|
||||
|
||||
this.setColor = this.setColor.bind(this);
|
||||
}
|
||||
|
||||
setColor(color) {
|
||||
this.setState({ color: color });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<button
|
||||
className={ `vb-character vb-character-${ this.props.className } ${ this.state.color }` }
|
||||
onClick={ this.props.onClick }
|
||||
></button>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class BodyColorSelectionModal extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
};
|
||||
|
||||
this.ModalRef = createRef();
|
||||
this.Modal = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.Modal = new Bootstrap.Modal(this.ModalRef.current);
|
||||
this.Modal.show();
|
||||
|
||||
this.ModalRef.current.addEventListener('hidden.bs.modal', (event) => {
|
||||
this.props.setModal(null);
|
||||
});
|
||||
|
||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="bc-tooltip"]'));
|
||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new Bootstrap.Tooltip(tooltipTriggerEl)
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.Modal.dispose();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div ref={this.ModalRef} className="modal fade">
|
||||
<div className="modal-dialog modal-dialog-centered">
|
||||
<div className="modal-content text-center">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Set Color</h5>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="vb-hex pb-5">
|
||||
<div className="vb-hex-container">
|
||||
{
|
||||
brickColors.map(({ id, name }) =>
|
||||
<div className={ `vb-bc-${ id }` } onClick={ () => this.props.setBodyColor(id) } data-bs-dismiss="modal" data-bs-toggle="bc-tooltip" data-bs-placement="top" title="" data-bs-original-title={ name }></div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BodyColorPane extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
showModal: false
|
||||
};
|
||||
|
||||
this.bodyParts = [
|
||||
{name: 'Head', className: 'head', ref: createRef()},
|
||||
{name: 'Torso', className: 'torso', ref: createRef()},
|
||||
{name: 'RightArm', className: 'limb', ref: createRef()},
|
||||
{name: 'LeftArm', className: 'limb', ref: createRef()},
|
||||
{name: 'RightLeg', className: 'limb', ref: createRef()},
|
||||
{name: 'LeftLeg', className: 'limb', ref: createRef()}
|
||||
];
|
||||
|
||||
this.findPart = this.findPart.bind(this);
|
||||
this.setModal = this.setModal.bind(this);
|
||||
this.load = this.load.bind(this);
|
||||
this.openColorModal = this.openColorModal.bind(this);
|
||||
this.setBodyColor = this.setBodyColor.bind(this);
|
||||
this.getBodyPartElem = this.getBodyPartElem.bind(this);
|
||||
}
|
||||
|
||||
findPart(name) {
|
||||
return this.bodyParts.find(obj => {
|
||||
return obj.name === name
|
||||
});
|
||||
}
|
||||
|
||||
setModal(modal = null) {
|
||||
this.visibleModal = modal;
|
||||
|
||||
if(modal) {
|
||||
this.setState({'showModal': true});
|
||||
} else {
|
||||
this.setState({'showModal': false});
|
||||
}
|
||||
}
|
||||
|
||||
load() {
|
||||
this.setState({ loading: true });
|
||||
|
||||
axios.get(buildGenericApiUrl('api', 'avatar/v1/body-color'))
|
||||
.then(res => {
|
||||
Object.keys(res.data.data).map(key => {
|
||||
let part = this.findPart(key);
|
||||
part.ref.current.setColor(`vb-bc-${ res.data.data[key] }`);
|
||||
});
|
||||
|
||||
this.setState({ loading: false });
|
||||
})
|
||||
.catch(() => {
|
||||
this.props.setError('Error loading body colors pane.');
|
||||
this.setState({ loading: false });
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.load();
|
||||
}
|
||||
|
||||
openColorModal(name) {
|
||||
this.setModal(<BodyColorSelectionModal setModal={ this.setModal } setBodyColor={ (color) => this.setBodyColor(name, color) } />);
|
||||
}
|
||||
|
||||
setBodyColor(name, color) {
|
||||
let part = this.findPart(name);
|
||||
part.ref.current.setColor(`vb-bc-${ color }`);
|
||||
|
||||
this.props.setBodyColor(name, color);
|
||||
}
|
||||
|
||||
getBodyPartElem(name) {
|
||||
let part = this.findPart(name);
|
||||
|
||||
if(!part.elem)
|
||||
{
|
||||
let elem = createElement(
|
||||
BodyColorPaneBodyPart,
|
||||
{
|
||||
className: part.className,
|
||||
onClick: (() => this.openColorModal(part.name)),
|
||||
ref: part.ref,
|
||||
key: part.name
|
||||
}
|
||||
);
|
||||
part.elem = elem;
|
||||
return elem
|
||||
}
|
||||
|
||||
return part.elem;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
this.state.loading
|
||||
?
|
||||
<div className="virtubrick-shop-overlay">
|
||||
<Loader />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
||||
{ this.getBodyPartElem('Head') }
|
||||
<div className="d-flex mx-auto">
|
||||
{ this.getBodyPartElem('RightArm') }
|
||||
{ this.getBodyPartElem('Torso') }
|
||||
{ this.getBodyPartElem('LeftArm') }
|
||||
</div>
|
||||
<div className="d-flex mx-auto">
|
||||
{ this.getBodyPartElem('RightLeg') }
|
||||
{ this.getBodyPartElem('LeftLeg') }
|
||||
</div>
|
||||
|
||||
{ this.state.showModal ? this.visibleModal : null }
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WearingPane extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
items: [],
|
||||
pageKey: 0
|
||||
};
|
||||
|
||||
this.load = this.load.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.load();
|
||||
}
|
||||
|
||||
load() {
|
||||
this.setState({ loading: true });
|
||||
|
||||
axios.get(buildGenericApiUrl('api', 'avatar/v1/wearing'))
|
||||
.then(res => {
|
||||
const items = res.data;
|
||||
let newKey = this.state.pageKey + 1;
|
||||
|
||||
this.setState({ pageKey: newKey, items: items.data, loading: false });
|
||||
})
|
||||
.catch(() => {
|
||||
this.props.setError('Error loading wearing pane.');
|
||||
this.setState({ pageNumber: oldPageNum, loading: false });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
(this.state.loading)
|
||||
?
|
||||
<div className="virtubrick-shop-overlay">
|
||||
<Loader />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
this.state.items.length == 0
|
||||
?
|
||||
<p className="text-muted text-center">Nothing found.</p>
|
||||
:
|
||||
<div key={ this.state.pageKey }>
|
||||
{
|
||||
this.state.items.map((item, index) =>
|
||||
<EditorItemCard unwearAssetId={ this.props.unwearAssetId } item={ item } key={ index } />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AvatarEditor extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: false,
|
||||
thumbnailLoading: false,
|
||||
tabLoaded: false,
|
||||
tabs: [
|
||||
{label: 'Wardrobe', name: 'wardrobe', ref: createRef()},
|
||||
{label: 'Outfits', name: 'outfits', ref: createRef()}
|
||||
],
|
||||
error: null,
|
||||
thumbKey: 0
|
||||
};
|
||||
|
||||
this.tabIndex = 0;
|
||||
|
||||
this.wearingPane = createRef();
|
||||
this.bodyColorPane = createRef();
|
||||
this.tabPane = createRef();
|
||||
|
||||
this.setCurrentTab = this.setCurrentTab.bind(this);
|
||||
this.setTab = this.setTab.bind(this);
|
||||
this.redrawCharacter = this.redrawCharacter.bind(this);
|
||||
this.reloadCharacter = this.reloadCharacter.bind(this);
|
||||
this.setError = this.setError.bind(this);
|
||||
this.thumbCallback = this.thumbCallback.bind(this);
|
||||
this.wearAssetId = this.wearAssetId.bind(this);
|
||||
this.unwearAssetId = this.unwearAssetId.bind(this);
|
||||
this.setBodyColor = this.setBodyColor.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setTab('wardrobe');
|
||||
}
|
||||
|
||||
setCurrentTab(instance)
|
||||
{
|
||||
this.currentTab = instance;
|
||||
this.setState({tabLoaded: true});
|
||||
}
|
||||
|
||||
setTab(tabType) {
|
||||
this.setState({tabLoaded: false});
|
||||
this.tabIndex += 1;
|
||||
|
||||
let component = {
|
||||
wardrobe: WardrobeTab,
|
||||
outfits: OutfitsTab
|
||||
}[tabType];
|
||||
|
||||
this.setCurrentTab(createElement(
|
||||
component,
|
||||
{
|
||||
setError: this.setError,
|
||||
reloadCharacter: this.reloadCharacter,
|
||||
wearAssetId: this.wearAssetId,
|
||||
unwearAssetId: this.unwearAssetId,
|
||||
ref: this.tabPane,
|
||||
key: this.tabIndex
|
||||
}
|
||||
));
|
||||
|
||||
this.state.tabs.map(({ name, ref }) => {
|
||||
if(name == tabType)
|
||||
ref.current.classList.add('active');
|
||||
else
|
||||
ref.current.classList.remove('active');
|
||||
});
|
||||
}
|
||||
|
||||
redrawCharacter() {
|
||||
if(this.state.loading) return;
|
||||
|
||||
this.setState({ loading: true });
|
||||
|
||||
axios.post(buildGenericApiUrl('api', 'avatar/v1/redraw'))
|
||||
.then(res => {
|
||||
this.reloadCharacter();
|
||||
this.setState({ loading: false });
|
||||
})
|
||||
.catch(() => {
|
||||
this.setError('An error occurred while redrawing.');
|
||||
this.setState({ loading: false });
|
||||
});
|
||||
}
|
||||
|
||||
setError(state) {
|
||||
this.setState({ error: state });
|
||||
setTimeout(function(){
|
||||
this.setState({ error: null });
|
||||
}.bind(this), 2000);
|
||||
}
|
||||
|
||||
reloadCharacter() {
|
||||
let thumbKey = this.state.thumbKey + 1;
|
||||
|
||||
this.wearingPane.current.load();
|
||||
this.bodyColorPane.current.load();
|
||||
this.tabPane.current.refresh();
|
||||
|
||||
this.setState({ thumbKey: thumbKey });
|
||||
}
|
||||
|
||||
thumbCallback(loading) {
|
||||
this.setState({ thumbnailLoading: loading });
|
||||
}
|
||||
|
||||
wearAssetId(assetId, callback) {
|
||||
if(this.state.thumbnailLoading) return;
|
||||
|
||||
axios.post(buildGenericApiUrl('api', `avatar/v1/wear?id=${ assetId }`))
|
||||
.then(res => {
|
||||
this.reloadCharacter();
|
||||
if(callback) callback();
|
||||
})
|
||||
.catch(() => {
|
||||
this.setError('An error occurred while trying to wear this item.');
|
||||
});
|
||||
}
|
||||
|
||||
unwearAssetId(assetId, callback) {
|
||||
if(this.state.thumbnailLoading) return;
|
||||
|
||||
axios.post(buildGenericApiUrl('api', `avatar/v1/unwear?id=${ assetId }`))
|
||||
.then(res => {
|
||||
this.reloadCharacter();
|
||||
if(callback) callback();
|
||||
})
|
||||
.catch(() => {
|
||||
this.setError('An error occurred while trying to take off this item.');
|
||||
});
|
||||
}
|
||||
|
||||
setBodyColor(name, color) {
|
||||
if(this.state.thumbnailLoading) return;
|
||||
|
||||
axios.post(buildGenericApiUrl('api', `avatar/v1/set-body-color?part=${ name }&color=${ color }`))
|
||||
.then(res => {
|
||||
this.reloadCharacter();
|
||||
})
|
||||
.catch(() => {
|
||||
this.setError('An error occurred while trying to change body color.');
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
this.state.error
|
||||
?
|
||||
<div className="alert alert-danger virtubrick-alert virtubrick-error-popup">{ this.state.error }</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className="col-lg-3">
|
||||
<div className="card text-center vb-avatar-editor-card">
|
||||
{
|
||||
this.state.loading
|
||||
?
|
||||
<div className='position-absolute top-50 start-50 translate-middle'>
|
||||
<Loader />
|
||||
</div>
|
||||
:
|
||||
<ThumbnailTool element={ this.props.element } key={ this.state.thumbKey } placeholder="images/busy/user.png" thumbLoadCallback={ this.thumbCallback } />
|
||||
}
|
||||
</div>
|
||||
<p className="fst-italic">Is something wrong with your avatar?</p>
|
||||
<a className="text-decoration-none" href="#" onClick={ this.redrawCharacter }>Click here to re-draw it!</a>
|
||||
|
||||
<h4 className="mt-3">Colors</h4>
|
||||
<div className="card p-4">
|
||||
<BodyColorPane setError={ this.setError } setBodyColor={ this.setBodyColor } ref={ this.bodyColorPane } />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-lg-0 mt-4 col-lg-9">
|
||||
<ul className="nav nav-tabs">
|
||||
{
|
||||
this.state.tabs.map(({ label, name, ref }) =>
|
||||
<li className="nav-item">
|
||||
<button className="nav-link" onClick={ () => this.setTab(name) } ref={ ref }>{ label }</button>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
<div className="card p-2">
|
||||
{ this.state.tabLoaded ? this.currentTab : <Loader /> }
|
||||
</div>
|
||||
|
||||
<h4 className="mt-3">Currently Wearing</h4>
|
||||
<div className="card p-2">
|
||||
<WearingPane setError={ this.setError } reloadCharacter={ this.reloadCharacter } unwearAssetId={ this.unwearAssetId } ref={ this.wearingPane } />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AvatarEditor;
|
||||
|
|
@ -270,6 +270,7 @@ class Shop extends Component {
|
|||
super(props);
|
||||
this.state = {
|
||||
selectedCategoryId: -1,
|
||||
pageKey: 0,
|
||||
pageItems: [],
|
||||
pageLoaded: true,
|
||||
pageNumber: null,
|
||||
|
|
@ -332,11 +333,12 @@ class Shop extends Component {
|
|||
url += ((paramIterator++ == 0 ? '?' : '&') + `${key}=${data[key]}`);
|
||||
});
|
||||
|
||||
let newKey = this.state.pageKey + 1;
|
||||
axios.get(url)
|
||||
.then(res => {
|
||||
const items = res.data;
|
||||
|
||||
this.setState({ pageItems: items.data, pageCount: items.pages, pageLoaded: true, error: false });
|
||||
this.setState({ pageKey: newKey, pageItems: items.data, pageCount: items.pages, pageLoaded: true, error: false });
|
||||
}).catch(err => {
|
||||
const data = err.response.data;
|
||||
|
||||
|
|
@ -344,7 +346,7 @@ class Shop extends Component {
|
|||
if(data.errors)
|
||||
errorMessage = data.errors[0].message;
|
||||
|
||||
this.setState({ pageItems: [], pageCount: 1, pageNumber: 1, pageLoaded: true, error: errorMessage });
|
||||
this.setState({ pageKey: newKey, pageItems: [], pageCount: 1, pageNumber: 1, pageLoaded: true, error: errorMessage });
|
||||
this.inputBox.current.focus();
|
||||
});
|
||||
}
|
||||
|
|
@ -404,7 +406,7 @@ class Shop extends Component {
|
|||
(this.state.pageItems.length == 0 && !this.state.error) ?
|
||||
<p className="text-muted text-center">Nothing found.</p>
|
||||
:
|
||||
<div>
|
||||
<div key={ this.state.pageKey }>
|
||||
{
|
||||
this.state.pageItems.map((item, index) =>
|
||||
<ShopItemCard item={ item } key={ index } />
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import Loader from './Loader';
|
|||
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
const Scene = ({json}) => {
|
||||
function Scene({json}) {
|
||||
const mtl = useLoader(MTLLoader, json.mtl);
|
||||
const obj = useLoader(OBJLoader, json.obj, (loader) => {
|
||||
mtl.preload();
|
||||
|
|
@ -29,7 +29,8 @@ const Scene = ({json}) => {
|
|||
let controls = useRef();
|
||||
let midPoint;
|
||||
|
||||
useThree(({camera, scene}) => {
|
||||
const {camera, scene} = useThree();
|
||||
useEffect(() => {
|
||||
let aabbMax = json.AABB.max;
|
||||
let aabbMin = json.AABB.min;
|
||||
aabbMax = new THREE.Vector3(aabbMax.x, aabbMax.y, aabbMax.z);
|
||||
|
|
@ -53,27 +54,24 @@ const Scene = ({json}) => {
|
|||
camera.translateZ(0.5);
|
||||
|
||||
// lighting
|
||||
// FIXME: XlXi: if you toggle 3d on and off it'll create these twice
|
||||
let ambient = new THREE.AmbientLight(0x878780);
|
||||
const ambient = new THREE.AmbientLight(0x7F7F7F);
|
||||
scene.add(ambient);
|
||||
|
||||
let sunLight = new THREE.DirectionalLight(0xacacac);
|
||||
sunLight.position.set(0.671597898, 0.671597898, -0.312909544).normalize();
|
||||
const sunLight = new THREE.DirectionalLight(0xFFFFFF);
|
||||
sunLight.position.set(-0.17786, 0.2563, -0.2787).normalize();
|
||||
scene.add(sunLight);
|
||||
|
||||
let backLight = new THREE.DirectionalLight(0x444444);
|
||||
let backLightPos = new THREE.Vector3()
|
||||
const backLight = new THREE.DirectionalLight(0xB3B3B8);
|
||||
const backLightPos = new THREE.Vector3()
|
||||
.copy(sunLight.position)
|
||||
.negate()
|
||||
.normalize(); // inverse of sun direction
|
||||
backLight.position.set(backLightPos);
|
||||
scene.add(backLight);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
controls.current.target = midPoint;
|
||||
controls.current.update();
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -98,8 +96,9 @@ class ThumbnailTool extends Component {
|
|||
super(props);
|
||||
this.state = {
|
||||
initialLoading: true,
|
||||
loading: false,
|
||||
loading: true,
|
||||
is3d: false,
|
||||
image2d: null,
|
||||
seed3d: 0
|
||||
};
|
||||
|
||||
|
|
@ -112,6 +111,7 @@ class ThumbnailTool extends Component {
|
|||
let thumbnailElement = this.props.element;
|
||||
if (thumbnailElement) {
|
||||
this.thumbnail2d = thumbnailElement.getAttribute('data-asset-thumbnail-2d');
|
||||
this.thumbnail3d = thumbnailElement.getAttribute('data-asset-thumbnail-3d');
|
||||
this.assetId = thumbnailElement.getAttribute('data-asset-id');
|
||||
this.assetName = thumbnailElement.getAttribute('data-asset-name');
|
||||
this.wearable = Boolean(thumbnailElement.getAttribute('data-wearable'));
|
||||
|
|
@ -119,13 +119,29 @@ class ThumbnailTool extends Component {
|
|||
|
||||
this.setState({ initialLoading: false });
|
||||
|
||||
if(this.props.thumbLoadCallback)
|
||||
this.props.thumbLoadCallback(true);
|
||||
|
||||
if(this.renderable3d && localStorage.getItem('vb-use-3d-thumbnails') === 'true')
|
||||
this.toggle3D();
|
||||
else
|
||||
{
|
||||
if(this.thumbnail2d && this.thumbnail2d.match(/^https?:\/\/api\./gi))
|
||||
this.loadThumbnail(this.thumbnail2d, false);
|
||||
else
|
||||
this.setState({ loading: false, image2d: this.thumbnail2d });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(oldProps, oldState)
|
||||
{
|
||||
if(oldState.loading != this.state.loading && this.props.thumbLoadCallback)
|
||||
this.props.thumbLoadCallback(this.state.loading);
|
||||
}
|
||||
|
||||
loadThumbnail(url, is3d) {
|
||||
axios.get(buildGenericApiUrl('api', url))
|
||||
axios.get(url)
|
||||
.then(res => {
|
||||
let data = res.data;
|
||||
|
||||
|
|
@ -149,7 +165,7 @@ class ThumbnailTool extends Component {
|
|||
this.setState({ loading: false, json3d: res.data });
|
||||
});
|
||||
} else {
|
||||
this.setState({ loading: false });
|
||||
this.setState({ loading: false, image2d: data.data });
|
||||
}
|
||||
} else {
|
||||
let lt = this.loadThumbnail;
|
||||
|
|
@ -162,7 +178,7 @@ class ThumbnailTool extends Component {
|
|||
let is3d = !this.state.is3d;
|
||||
|
||||
this.setState({ loading: true, is3d: is3d });
|
||||
this.loadThumbnail(`thumbnails/v1/try-asset?id=${this.assetId}&type=${this.state.is3d ? '3D' : '2D'}`, is3d);
|
||||
this.loadThumbnail(buildGenericApiUrl('api', `thumbnails/v1/try-asset?id=${this.assetId}&type=${this.state.is3d ? '3D' : '2D'}`), is3d);
|
||||
}
|
||||
|
||||
toggle3D() {
|
||||
|
|
@ -171,70 +187,74 @@ class ThumbnailTool extends Component {
|
|||
this.setState({ loading: true, is3d: is3d, seed3d: Math.random() });
|
||||
localStorage.setItem('vb-use-3d-thumbnails', is3d);
|
||||
|
||||
if(is3d) {
|
||||
this.loadThumbnail(`thumbnails/v1/asset?id=${this.assetId}&type=3D`, true);
|
||||
} else {
|
||||
this.setState({ loading: false });
|
||||
if(is3d)
|
||||
this.loadThumbnail(this.thumbnail3d, true);
|
||||
else
|
||||
{
|
||||
if(this.thumbnail2d && this.thumbnail2d.match(/^https?:\/\/api\./gi))
|
||||
this.loadThumbnail(this.thumbnail2d, false);
|
||||
else
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
this.state.initialLoading
|
||||
?
|
||||
<div className='position-absolute top-50 start-50 translate-middle'>
|
||||
<Loader />
|
||||
</div>
|
||||
:
|
||||
<>
|
||||
{
|
||||
this.state.loading
|
||||
?
|
||||
<div className='position-absolute top-50 start-50 translate-middle'>
|
||||
<Loader />
|
||||
</div>
|
||||
:
|
||||
(
|
||||
this.state.is3d
|
||||
?
|
||||
<Canvas key={ this.state.seed3d }>
|
||||
<Suspense fallback={null}>
|
||||
<Scene json={ this.state.json3d } />
|
||||
</Suspense>
|
||||
</Canvas>
|
||||
:
|
||||
<ProgressiveImage
|
||||
src={ this.thumbnail2d }
|
||||
placeholderImg={ buildGenericApiUrl('www', (this.props.placeholder == null ? 'images/busy/asset.png' : this.props.placeholder)) }
|
||||
alt={ this.assetName }
|
||||
className='img-fluid'
|
||||
width={ this.props.width }
|
||||
height={ this.props.height }
|
||||
/>
|
||||
)
|
||||
}
|
||||
{ this.wearable || this.renderable3d ?
|
||||
<div className='d-flex position-absolute bottom-0 end-0 pb-2 pe-2'>
|
||||
{
|
||||
this.wearable ?
|
||||
<button className='btn btn-secondary me-2' onClick={ this.tryAsset } disabled={ this.state.loading }>Try On</button>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
this.renderable3d ?
|
||||
<button className='btn btn-secondary' onClick={ this.toggle3D } disabled={ this.state.loading }>{ this.state.is3d ? '2D' : '3D' }</button>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
this.state.initialLoading
|
||||
?
|
||||
<div className='position-absolute top-50 start-50 translate-middle'>
|
||||
<Loader />
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</>
|
||||
}
|
||||
<>
|
||||
{
|
||||
this.state.loading
|
||||
?
|
||||
<div className='position-absolute top-50 start-50 translate-middle'>
|
||||
<Loader />
|
||||
</div>
|
||||
:
|
||||
(
|
||||
this.state.is3d
|
||||
?
|
||||
<Canvas key={ this.state.seed3d }>
|
||||
<Suspense fallback={null}>
|
||||
<Scene json={ this.state.json3d } />
|
||||
</Suspense>
|
||||
</Canvas>
|
||||
:
|
||||
<ProgressiveImage
|
||||
src={ this.state.image2d }
|
||||
placeholderImg={ buildGenericApiUrl('www', (this.props.placeholder == null ? 'images/busy/asset.png' : this.props.placeholder)) }
|
||||
alt={ this.assetName }
|
||||
className='img-fluid'
|
||||
width={ this.props.width }
|
||||
height={ this.props.height }
|
||||
/>
|
||||
)
|
||||
}
|
||||
{ this.wearable || this.renderable3d ?
|
||||
<div className='d-flex position-absolute bottom-0 end-0 pb-2 pe-2'>
|
||||
{
|
||||
this.wearable ?
|
||||
<button className='btn btn-secondary me-2' onClick={ this.tryAsset } disabled={ this.state.loading }>Try On</button>
|
||||
:
|
||||
null
|
||||
}
|
||||
{
|
||||
this.renderable3d ?
|
||||
<button className='btn btn-secondary' onClick={ this.toggle3D } disabled={ this.state.loading }>{ this.state.is3d ? '2D' : '3D' }</button>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
Copyright © XlXi 2022
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
|
||||
import AvatarEditor from '../components/AvatarEditor';
|
||||
|
||||
const editorId = 'vb-avatar-editor';
|
||||
|
||||
$(document).ready(function() {
|
||||
if (document.getElementById(editorId)) {
|
||||
let eElem = document.getElementById(editorId);
|
||||
render(<AvatarEditor element={ eElem }/>, eElem);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
// © XlXi 2023
|
||||
|
||||
$brickColors: (
|
||||
1: #f2f3f3, // White
|
||||
2: #a1a5a2, // Grey
|
||||
3: #f9e999, // Light yellow
|
||||
5: #d7c59a, // Brick yellow
|
||||
6: #c2dab8, // Light green (Mint)
|
||||
9: #e8bac8, // Light reddish violet
|
||||
11: #80bbdb, // Pastel Blue
|
||||
12: #cb8442, // Light orange brown
|
||||
18: #cc8e69, // Nougat
|
||||
21: #c4281c, // Bright red
|
||||
22: #c470a0, // Med. reddish violet
|
||||
23: #0d69ac, // Bright blue
|
||||
24: #f5cd30, // Bright yellow
|
||||
25: #624732, // Earth orange
|
||||
26: #1b2a35, // Black
|
||||
27: #6d6e6c, // Dark grey
|
||||
28: #287f47, // Dark green
|
||||
29: #a1c48c, // Medium green
|
||||
36: #f3cf9b, // Lig. Yellowich orange
|
||||
37: #4b974b, // Bright green
|
||||
38: #a05f35, // Dark orange
|
||||
39: #c1cade, // Light bluish violet
|
||||
40: #ececec, // Transparent
|
||||
41: #cd544b, // Tr. Red
|
||||
42: #c1dff0, // Tr. Lg blue
|
||||
43: #7bb6e8, // Tr. Blue
|
||||
44: #f7f18d, // Tr. Yellow
|
||||
45: #b4d2e4, // Light blue
|
||||
47: #d9856c, // Tr. Flu. Reddish orange
|
||||
48: #84b68d, // Tr. Green
|
||||
49: #f8f184, // Tr. Flu. Green
|
||||
50: #ece8de, // Phosph. White
|
||||
100: #eec4b6, // Light red
|
||||
101: #da867a, // Medium red
|
||||
102: #6e99ca, // Medium blue
|
||||
103: #c7c1b7, // Light grey
|
||||
104: #6b327c, // Bright violet
|
||||
105: #e29b40, // Br. yellowish orange
|
||||
106: #da8541, // Bright orange
|
||||
107: #008f9c, // Bright bluish green
|
||||
108: #685c43, // Earth yellow
|
||||
110: #435493, // Bright bluish violet
|
||||
111: #bfb7b1, // Tr. Brown
|
||||
112: #6874ac, // Medium bluish violet
|
||||
113: #e4adc8, // Tr. Medi. reddish violet
|
||||
115: #c7d23c, // Med. yellowish green
|
||||
116: #55a5af, // Med. bluish green
|
||||
118: #b7d7d5, // Light bluish green
|
||||
119: #a4bd47, // Br. yellowish green
|
||||
120: #d9e4a7, // Lig. yellowish green
|
||||
121: #e7ac58, // Med. yellowish orange
|
||||
123: #d36f4c, // Br. reddish orange
|
||||
124: #923978, // Bright reddish violet
|
||||
125: #eab892, // Light orange
|
||||
126: #a5a5cb, // Tr. Bright bluish violet
|
||||
127: #dcbc81, // Gold
|
||||
128: #ae7a59, // Dark nougat
|
||||
131: #9ca3a8, // Silver
|
||||
133: #d5733d, // Neon orange
|
||||
134: #d8dd56, // Neon green
|
||||
135: #74869d, // Sand blue
|
||||
136: #877c90, // Sand violet
|
||||
137: #e09864, // Medium orange
|
||||
138: #958a73, // Sand yellow
|
||||
140: #203a56, // Earth blue
|
||||
141: #27462d, // Earth green
|
||||
143: #cfe2f7, // Tr. Flu. Blue
|
||||
145: #7988a1, // Sand blue metallic
|
||||
146: #958ea3, // Sand violet metallic
|
||||
147: #938767, // Sand yellow metallic
|
||||
148: #575857, // Dark grey metallic
|
||||
149: #161d32, // Black metallic
|
||||
150: #abadac, // Light grey metallic
|
||||
151: #789082, // Sand green
|
||||
153: #957977, // Sand red
|
||||
154: #7b2e2f, // Dark red
|
||||
157: #fff67b, // Tr. Flu. Yellow
|
||||
158: #e1a4c2, // Tr. Flu. Red
|
||||
168: #756c62, // Gun metallic
|
||||
176: #97695b, // Red flip/flop
|
||||
178: #b48455, // Yellow flip/flop
|
||||
179: #898788, // Silver flip/flop
|
||||
180: #d7a94b, // Curry
|
||||
190: #f9d62e, // Fire Yellow
|
||||
191: #e8ab2d, // Flame yellowish orange
|
||||
192: #694028, // Reddish brown
|
||||
193: #cf6024, // Flame reddish orange
|
||||
194: #a3a2a5, // Medium stone grey
|
||||
195: #4667a4, // Royal blue
|
||||
196: #23478b, // Dark Royal blue
|
||||
198: #8e4285, // Bright reddish lilac
|
||||
199: #635f62, // Dark stone grey
|
||||
200: #828a5d, // Lemon metalic
|
||||
208: #e5e4df, // Light stone grey
|
||||
209: #b08e44, // Dark Curry
|
||||
210: #709578, // Faded green
|
||||
211: #79b5b5, // Turquoise
|
||||
212: #9fc3e9, // Light Royal blue
|
||||
213: #6c81b7, // Medium Royal blue
|
||||
216: #8f4c2a, // Rust
|
||||
217: #7c5c46, // Brown
|
||||
218: #96709f, // Reddish lilac
|
||||
219: #6b629b, // Lilac
|
||||
220: #a7a9ce, // Light lilac
|
||||
221: #cd6298, // Bright purple
|
||||
222: #e4adc8, // Light purple
|
||||
223: #dc9095, // Light pink
|
||||
224: #f0d5a0, // Light brick yellow
|
||||
225: #ebb87f, // Warm yellowish orange
|
||||
226: #fdea8d, // Cool yellow
|
||||
232: #7dbbdd, // Dove blue
|
||||
268: #342b75, // Medium lilac
|
||||
301: #506d54, // Slime green
|
||||
302: #5b5d69, // Smoky grey
|
||||
303: #0010b0, // Dark blue
|
||||
304: #2c651d, // Parsley green
|
||||
305: #527cae, // Steel blue
|
||||
306: #335882, // Storm blue
|
||||
307: #102adc, // Lapis
|
||||
308: #3d1585, // Dark indigo
|
||||
309: #348e40, // Sea green
|
||||
310: #5b9a4c, // Shamrock
|
||||
311: #9fa1ac, // Fossil
|
||||
312: #592259, // Mulberry
|
||||
313: #1f801d, // Forest green
|
||||
314: #9fadc0, // Cadet blue
|
||||
315: #0989cf, // Electric blue
|
||||
316: #7b007b, // Eggplant
|
||||
317: #7c9c6b, // Moss
|
||||
318: #8aab85, // Artichoke
|
||||
319: #b9c4b1, // Sage green
|
||||
320: #cacbd1, // Ghost grey
|
||||
321: #a75e9b, // Lilac
|
||||
322: #7b2f7b, // Plum
|
||||
323: #94be81, // Olivine
|
||||
324: #a8bd99, // Laurel green
|
||||
325: #dfdfde, // Quill grey
|
||||
327: #970000, // Crimson
|
||||
328: #b1e5a6, // Mint
|
||||
329: #98c2db, // Baby blue
|
||||
330: #ff98dc, // Carnation pink
|
||||
331: #ff5959, // Persimmon
|
||||
332: #750000, // Maroon
|
||||
333: #efb838, // Gold
|
||||
334: #f8d96d, // Daisy orange
|
||||
335: #e7e7ec, // Pearl
|
||||
336: #c7d4e4, // Fog
|
||||
337: #ff9494, // Salmon
|
||||
338: #be6862, // Terra Cotta
|
||||
339: #562424, // Cocoa
|
||||
340: #f1e7c7, // Wheat
|
||||
341: #fef3bb, // Buttermilk
|
||||
342: #e0b2d0, // Mauve
|
||||
343: #d490bd, // Sunrise
|
||||
344: #965555, // Tawny
|
||||
345: #8f4c2a, // Rust
|
||||
346: #d3be96, // Cashmere
|
||||
347: #e2dcbc, // Khaki
|
||||
348: #edeaea, // Lily white
|
||||
349: #e9dada, // Seashell
|
||||
350: #883e3e, // Burgundy
|
||||
351: #bc9b5d, // Cork
|
||||
352: #c7ac78, // Burlap
|
||||
353: #cabfa3, // Beige
|
||||
354: #bbb3b2, // Oyster
|
||||
355: #6c584b, // Pine Cone
|
||||
356: #a0844f, // Fawn brown
|
||||
357: #958988, // Hurricane grey
|
||||
358: #aba89e, // Cloudy grey
|
||||
359: #af9483, // Linen
|
||||
360: #966766, // Copper
|
||||
361: #564236, // Dirt brown
|
||||
362: #7e683f, // Bronze
|
||||
363: #69665c, // Flint
|
||||
364: #5a4c42, // Dark taupe
|
||||
365: #6a3909, // Burnt Sienna
|
||||
1001: #f8f8f8, // Institutional white
|
||||
1002: #cdcdcd, // Mid gray
|
||||
1003: #111111, // Really black
|
||||
1004: #ff0000, // Really red
|
||||
1005: #ffaf00, // Deep orange
|
||||
1006: #b480ff, // Alder
|
||||
1007: #a34b4b, // Dusty Rose
|
||||
1008: #c1be42, // Olive
|
||||
1009: #ffff00, // New Yeller
|
||||
1010: #0000ff, // Really blue
|
||||
1011: #002060, // Navy blue
|
||||
1012: #2154b9, // Deep blue
|
||||
1013: #04afec, // Cyan
|
||||
1014: #aa5500, // CGA brown
|
||||
1015: #aa00aa, // Magenta
|
||||
1016: #ff66cc, // Pink
|
||||
1017: #ffaf00, // Deep orange
|
||||
1018: #12eed4, // Teal
|
||||
1019: #00ffff, // Toothpaste
|
||||
1020: #00ff00, // Lime green
|
||||
1021: #3a7d15, // Camo
|
||||
1022: #7f8e64, // Grime
|
||||
1023: #8c5b9f, // Lavender
|
||||
1024: #afddff, // Pastel light blue
|
||||
1025: #ffc9c9, // Pastel orange
|
||||
1026: #b1a7ff, // Pastel violet
|
||||
1027: #9ff3e9, // Pastel blue-green
|
||||
1028: #ccffcc, // Pastel green
|
||||
1029: #ffffcc, // Pastel yellow
|
||||
1030: #ffcc99, // Pastel brown
|
||||
1031: #6225d1, // Royal purple
|
||||
1032: #ff00bf // Hot pink
|
||||
);
|
||||
|
||||
@each $color-id, $color in $brickColors {
|
||||
.vb-bc-#{$color-id} {
|
||||
background-color: $color;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// © XlXi 2021
|
||||
// © XlXi 2023
|
||||
|
||||
@function tint-color($color, $percent){
|
||||
@return mix(white, $color, $percent);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// © XlXi 2021
|
||||
// © XlXi 2023
|
||||
|
||||
@use "sass:math";
|
||||
|
||||
// Lumen 5.0.1
|
||||
// Bootswatch
|
||||
|
||||
@import "BrickColors";
|
||||
@import "./scss/fontawesome.scss";
|
||||
@import "./scss/brands.scss";
|
||||
@import "./scss/duotone.scss";
|
||||
|
|
@ -45,6 +46,22 @@ img.twemoji {
|
|||
|
||||
// Shop
|
||||
|
||||
|
||||
.vb-avatar-editor-card {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.vb-wardrobe-nav > .nav-item > .nav-link {
|
||||
padding-right: 0.5rem!important;
|
||||
padding-left: 0.5rem!important;
|
||||
padding-bottom: 0!important;
|
||||
padding-top: 0!important;
|
||||
}
|
||||
|
||||
.vb-wardrobe-nav > .nav-item > .nav-link:not(:last-child) {
|
||||
margin-left: 0.25rem!important;
|
||||
}
|
||||
|
||||
.virtubrick-tokens {
|
||||
background: 0 1px/contain no-repeat url("/images/symbols/token.svg");
|
||||
color: #e59800!important;
|
||||
|
|
@ -154,6 +171,11 @@ img.twemoji {
|
|||
background-size: cover;
|
||||
}
|
||||
|
||||
.virtubrick-item-card > span > a > img {
|
||||
background: url("/Images/Item-Image-Vignette.png");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.virtubrick-game-card {
|
||||
@media (max-width: 576px) {
|
||||
width: math.div(100%, 2)
|
||||
|
|
@ -176,6 +198,28 @@ img.twemoji {
|
|||
}
|
||||
}
|
||||
|
||||
.virtubrick-avatar-card {
|
||||
@media (max-width: 576px) {
|
||||
width: math.div(100%, 2)
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
width: math.div(100%, 3)
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
width: math.div(100%, 4)
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
width: math.div(100%, 5)
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
width: 157px;
|
||||
}
|
||||
}
|
||||
|
||||
.virtubrick-shop-overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
|
@ -538,6 +582,65 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
// Avatar
|
||||
|
||||
.vb-character {
|
||||
border: 1px solid $gray-500;
|
||||
transition: background-color .1s;
|
||||
margin: 0.125rem;
|
||||
|
||||
&-head {
|
||||
margin: 0.125rem auto;
|
||||
width: 50px!important;
|
||||
height: 50px!important;
|
||||
}
|
||||
|
||||
&-limb {
|
||||
width: 50px!important;
|
||||
height: 100px!important;
|
||||
}
|
||||
|
||||
&-torso {
|
||||
width: 104px!important;
|
||||
height: 100px!important;
|
||||
}
|
||||
}
|
||||
|
||||
$hex-size: 32px;
|
||||
$hex-margin: .04rem;
|
||||
$hex-calc: calc(1.732 * $hex-size + 4 * $hex-margin - 1px);
|
||||
|
||||
.vb-hex {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
|
||||
&-container {
|
||||
font-size: 0;
|
||||
|
||||
& div {
|
||||
width: $hex-size;
|
||||
height: calc($hex-size * 1.1547);
|
||||
margin: $hex-margin;
|
||||
margin-bottom: calc($hex-margin - $hex-size * 0.2885);
|
||||
display: inline-block;
|
||||
font-size: initial;
|
||||
clip-path: polygon(0% 25%, 0% 75%, 50% 100%, 100% 75%, 100% 25%, 50% 0%);
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
width: calc($hex-size/2 + $hex-margin);
|
||||
float: left;
|
||||
height: 120%;
|
||||
shape-outside: repeating-linear-gradient(
|
||||
#0000 0 calc($hex-calc - 3px),
|
||||
#000 0 $hex-calc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navbar
|
||||
|
||||
.navbar {
|
||||
|
|
|
|||
|
|
@ -2,22 +2,39 @@
|
|||
|
||||
@section('title', 'Avatar Editor')
|
||||
|
||||
@section('page-specific')
|
||||
<script src="{{ mix('js/AvatarEditor.js') }}"></script>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="alert alert-yellow virtubrick-alert text-black">
|
||||
<p>Todo:</p>
|
||||
<ul>
|
||||
<li>Section for wearing outfits</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="container my-2">
|
||||
<h4>Avatar Editor</h4>
|
||||
<div class="row mt-2">
|
||||
<div class="col-3">
|
||||
<div class="card text-center" id="vb-character-thumbnail">
|
||||
<div
|
||||
class="row mt-2"
|
||||
id="vb-avatar-editor"
|
||||
data-asset-thumbnail-2d="{{ route('thumbnails.v1.user', ['id' => Auth::user()->id, 'position' => 'full', 'type' => '2d']) }}"
|
||||
data-asset-thumbnail-3d="{{ route('thumbnails.v1.user', ['id' => Auth::user()->id, 'position' => 'full', 'type' => '3d']) }}"
|
||||
data-asset-name="{{ Auth::user()->username }}"
|
||||
data-renderable3d="1"
|
||||
>
|
||||
<div class="col-lg-3">
|
||||
<div class="card text-center vb-avatar-editor-card">
|
||||
<img src="{{ Auth::user()->getImageUrl() }}" class="img-fluid vb-charimg" />
|
||||
</div>
|
||||
|
||||
<h4 class="mt-3">Colors</h4>
|
||||
<div class="card p-2" id="vb-character-colors">
|
||||
<div class="card p-4">
|
||||
<x-loader />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<ul class="nav nav-tabs" id="vb-character-tabs">
|
||||
<div class="mt-lg-0 mt-4 col-lg-9">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<button class="nav-link active disabled" disabled>Wardrobe</button>
|
||||
</li>
|
||||
|
|
@ -25,24 +42,15 @@
|
|||
<button class="nav-link disabled" disabled>Outfits</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="card p-2" id="vb-character-editor">
|
||||
<div class="card p-2">
|
||||
<x-loader />
|
||||
</div>
|
||||
|
||||
<h4 class="mt-3">Currently Wearing</h4>
|
||||
<div class="card p-2" id="vb-character-wearing">
|
||||
<div class="card p-2">
|
||||
<x-loader />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Todo:</p>
|
||||
<ul>
|
||||
<li>Character Preview</li>
|
||||
<li>3d Character Preview</li>
|
||||
<li>Redraw button</li>
|
||||
<li>Section for changing body colors</li>
|
||||
<li>Section for wearing items</li>
|
||||
<li>Section for taking off worn items</li>
|
||||
</ul>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -8,21 +8,35 @@ Route::get('/ping', function() {
|
|||
|
||||
Route::group(['as' => 'maintenance.', 'prefix' => 'maintenance'], function() {
|
||||
Route::group(['as' => 'v1.', 'prefix' => 'v1'], function() {
|
||||
Route::post('/bypass', 'MaintenanceController@bypass')->name('bypass')->middleware('throttle:100,30');
|
||||
Route::post('/bypass', 'MaintenanceController@bypass')->name('bypass')->middleware('throttle:20,30,maintenance');
|
||||
});
|
||||
});
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
Route::group(['as' => 'client.', 'prefix' => 'auth'], function() {
|
||||
Route::group(['as' => 'v1.', 'prefix' => 'v1'], function() {
|
||||
Route::get('/generate-token', 'ClientController@generateAuthTicket')->name('generatetoken')->middleware('throttle:40,10');
|
||||
Route::get('/generate-token', 'ClientController@generateAuthTicket')->name('generatetoken')->middleware('throttle:40,10,clientticket');
|
||||
});
|
||||
});
|
||||
|
||||
Route::group(['as' => 'feed.', 'prefix' => 'feed'], function() {
|
||||
Route::group(['as' => 'v1.', 'prefix' => 'v1'], function() {
|
||||
Route::get('/list-json', 'FeedController@listJson')->name('list');
|
||||
Route::post('/share', 'FeedController@share')->name('share')->middleware('throttle:3,2');
|
||||
Route::post('/share', 'FeedController@share')->name('share')->middleware('throttle:3,2,comments');
|
||||
});
|
||||
});
|
||||
|
||||
Route::group(['as' => 'avatar.', 'prefix' => 'avatar'], function() {
|
||||
Route::middleware('throttle:100,10,renders')->group(function () {
|
||||
Route::group(['as' => 'v1.', 'prefix' => 'v1'], function() {
|
||||
Route::get('/list', 'AvatarController@listAssets')->name('list');
|
||||
Route::get('/wearing', 'AvatarController@listWearing')->name('listWearing');
|
||||
Route::get('/body-color', 'AvatarController@getBodyColors')->name('getBodyColors');
|
||||
Route::post('/wear', 'AvatarController@wearAsset')->name('wear');
|
||||
Route::post('/unwear', 'AvatarController@removeAsset')->name('remove');
|
||||
Route::post('/redraw', 'AvatarController@redrawUser')->name('redraw');
|
||||
Route::post('/set-body-color', 'AvatarController@setBodyColor')->name('setBodyColor');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -43,7 +57,7 @@ Route::middleware('auth')->group(function () {
|
|||
Route::group(['as' => 'comments.', 'prefix' => 'comments'], function() {
|
||||
Route::group(['as' => 'v1.', 'prefix' => 'v1'], function() {
|
||||
Route::get('/list-json', 'CommentsController@listJson')->name('list');
|
||||
Route::post('/share', 'CommentsController@share')->name('share')->middleware(['auth', 'throttle:3,2']);
|
||||
Route::post('/share', 'CommentsController@share')->name('share')->middleware(['auth', 'throttle:3,2,comments']);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,10 @@ Route::group(['as' => 'auth.', 'namespace' => 'Auth'], function() {
|
|||
Route::withoutMiddleware(['csrf'])->group(function () {
|
||||
Route::group(['as' => 'client.'], function() {
|
||||
Route::get('/asset', 'ClientController@asset')->name('asset');
|
||||
Route::group(['prefix' => 'asset'], function() {
|
||||
Route::get('/CharacterFetch.ashx', 'ClientAvatarController@characterFetch')->name('characterFetch');
|
||||
Route::get('/BodyColors.ashx', 'ClientAvatarController@bodyColors')->name('bodyColors');
|
||||
});
|
||||
|
||||
Route::group(['as' => 'game.', 'prefix' => 'game'], function() {
|
||||
Route::post('/PlaceLauncher', 'ClientGameController@placeLauncher')->name('placelauncher');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
|
||||
<External>null</External>
|
||||
<External>nil</External>
|
||||
<Item class="BodyColors">
|
||||
<Properties>
|
||||
<int name="HeadColor"></int>
|
||||
<int name="LeftArmColor"></int>
|
||||
<int name="LeftLegColor"></int>
|
||||
<string name="Name">Body Colors</string>
|
||||
<int name="RightArmColor"></int>
|
||||
<int name="RightLegColor"></int>
|
||||
<int name="TorsoColor"></int>
|
||||
<bool name="archivable">true</bool>
|
||||
</Properties>
|
||||
</Item>
|
||||
</roblox>
|
||||
|
|
@ -12,6 +12,7 @@ mix.js('resources/js/app.js', 'public/js')
|
|||
.js('resources/js/pages/Shop.js', 'public/js')
|
||||
.js('resources/js/pages/Games.js', 'public/js')
|
||||
.js('resources/js/pages/Transactions.js', 'public/js')
|
||||
.js('resources/js/pages/AvatarEditor.js', 'public/js')
|
||||
.js('resources/js/pages/Item.js', 'public/js')
|
||||
.js('resources/js/pages/Place.js', 'public/js')
|
||||
.js('resources/js/pages/AppDeployer.js', 'public/js/adm')
|
||||
|
|
|
|||
Loading…
Reference in New Issue