mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-13 11:17:42 +01:00
Compare commits
520 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
360c8bb92a | ||
|
|
e96134a134 | ||
|
|
74517d8ec5 | ||
|
|
4d466fb7eb | ||
|
|
f8ca572d38 | ||
|
|
59c9601a9b | ||
|
|
0d9a0d49ab | ||
|
|
96f825e298 | ||
|
|
62c9152d8c | ||
|
|
98b702d6e6 | ||
|
|
3c68124b29 | ||
|
|
77f3912bf2 | ||
|
|
643c83a6f3 | ||
|
|
5656d88c94 | ||
|
|
cf87184c92 | ||
|
|
598f598e82 | ||
|
|
06c593bbcb | ||
|
|
507e1268ac | ||
|
|
da14548c43 | ||
|
|
5b6517aae8 | ||
|
|
aadcf771e7 | ||
|
|
04fb37d661 | ||
|
|
bf4673a1d9 | ||
|
|
76b214c643 | ||
|
|
434a5a809e | ||
|
|
88fe25f69e | ||
|
|
bef1e39ac3 | ||
|
|
c604d5dbe5 | ||
|
|
a0d912a395 | ||
|
|
a56852f918 | ||
|
|
4228fc1b89 | ||
|
|
e644b8da28 | ||
|
|
ace3a8f755 | ||
|
|
76ed347cbf | ||
|
|
48bef12555 | ||
|
|
c3469a1687 | ||
|
|
d6c36ca4f7 | ||
|
|
44345b9429 | ||
|
|
20914bc064 | ||
|
|
0a9693daea | ||
|
|
8f362c5121 | ||
|
|
0442fb7b60 | ||
|
|
c62c3c4eea | ||
|
|
6a34d41f6a | ||
|
|
c31f6c19a6 | ||
|
|
da1db70635 | ||
|
|
c420b1f180 | ||
|
|
414bd8bee7 | ||
|
|
8e1745d67a | ||
|
|
889f01a724 | ||
|
|
6e62905fa7 | ||
|
|
654787fa0d | ||
|
|
835ba23935 | ||
|
|
389a8781d6 | ||
|
|
3eabe591df | ||
|
|
487d3b9399 | ||
|
|
4d4e4669dd | ||
|
|
fb065549e9 | ||
|
|
2c34154915 | ||
|
|
3704051b0f | ||
|
|
b2b8f2b6eb | ||
|
|
22e6c0655b | ||
|
|
bb2ba0cf11 | ||
|
|
e854386b23 | ||
|
|
49d24df2e7 | ||
|
|
c9b291c2f3 | ||
|
|
65f789880d | ||
|
|
abf998a727 | ||
|
|
4cc191cb25 | ||
|
|
dc431c10a5 | ||
|
|
26862ba78f | ||
|
|
e4374337f2 | ||
|
|
240c889fff | ||
|
|
612cd31c3e | ||
|
|
304b362002 | ||
|
|
8f34f197d0 | ||
|
|
1c97266a93 | ||
|
|
a9caddafd5 | ||
|
|
34bf95dddb | ||
|
|
4761b8f584 | ||
|
|
0d94aae732 | ||
|
|
e83f328cdc | ||
|
|
52fd29c478 | ||
|
|
a8b79993df | ||
|
|
98574558e5 | ||
|
|
557cbf23ce | ||
|
|
56753ae7ba | ||
|
|
b66df624f7 | ||
|
|
be78f1447b | ||
|
|
97e32a3cb7 | ||
|
|
4472920536 | ||
|
|
ac6a726f57 | ||
|
|
00d550f4fe | ||
|
|
0f98fac157 | ||
|
|
c25f0f72db | ||
|
|
72e05e23bc | ||
|
|
2c3bed6ba5 | ||
|
|
d6df9885dc | ||
|
|
e7936500e0 | ||
|
|
4ef4e65d46 | ||
|
|
40b4a8fd7a | ||
|
|
8a1f03c272 | ||
|
|
8ed479eddf | ||
|
|
c0a278ca2c | ||
|
|
2e9a7004c6 | ||
|
|
75c76a92b9 | ||
|
|
282935c6d6 | ||
|
|
d7b189b714 | ||
|
|
e3da3f356c | ||
|
|
66bed4217f | ||
|
|
56bbf6593a | ||
|
|
b8e1e7c384 | ||
|
|
a0d2c39f45 | ||
|
|
07df3186c2 | ||
|
|
5b59e74417 | ||
|
|
b4485f028d | ||
|
|
74674cfa0c | ||
|
|
f192c17c9b | ||
|
|
aa1ac29182 | ||
|
|
081ac6bf8b | ||
|
|
e4b32343ae | ||
|
|
c93370ec92 | ||
|
|
9abd7f2767 | ||
|
|
8a9877bb01 | ||
|
|
c1e1476fa6 | ||
|
|
b1abbb8e77 | ||
|
|
fcb0660def | ||
|
|
a6073e2a42 | ||
|
|
2c87077918 | ||
|
|
4e0a9f62b9 | ||
|
|
39636f5293 | ||
|
|
b53124e708 | ||
|
|
9a684c9ff5 | ||
|
|
c7d1620c1e | ||
|
|
325b54031c | ||
|
|
155a9d6266 | ||
|
|
4f6fb44f79 | ||
|
|
bfce99859f | ||
|
|
6c556d6a61 | ||
|
|
7ed42005dd | ||
|
|
aad978f5f6 | ||
|
|
c0ad4aab51 | ||
|
|
8fe0ac8195 | ||
|
|
46f8818cee | ||
|
|
118f51cc64 | ||
|
|
096d82741d | ||
|
|
b98cb31fd2 | ||
|
|
90813ce030 | ||
|
|
95bc52b2bc | ||
|
|
d79e4b5853 | ||
|
|
a40a6905be | ||
|
|
6b3a64ce14 | ||
|
|
4fca1600ca | ||
|
|
b1e65e6f9d | ||
|
|
381b23fe0c | ||
|
|
782c4446b2 | ||
|
|
76eaa75d04 | ||
|
|
361ed536a3 | ||
|
|
b1d00e9812 | ||
|
|
b0abf865cb | ||
|
|
d398381b52 | ||
|
|
d75d70bee5 | ||
|
|
296f1e90b5 | ||
|
|
9d3dfbbece | ||
|
|
d6d592f099 | ||
|
|
00cb1b6643 | ||
|
|
22babad789 | ||
|
|
18ff905746 | ||
|
|
fd0d761b92 | ||
|
|
750d4f9eca | ||
|
|
25517525c9 | ||
|
|
99a8e1e8c5 | ||
|
|
381b2a1b8b | ||
|
|
3dee511b9a | ||
|
|
e93c3b7bb8 | ||
|
|
773682838e | ||
|
|
c9f00c6369 | ||
|
|
2026069ed3 | ||
|
|
ae093506c1 | ||
|
|
87f1b613f9 | ||
|
|
71e15474b2 | ||
|
|
3fd6108fa1 | ||
|
|
b9e4c144c2 | ||
|
|
6e685b96d1 | ||
|
|
c96f009fb4 | ||
|
|
3112079776 | ||
|
|
7015737d88 | ||
|
|
94f6e870e6 | ||
|
|
528aae7eee | ||
|
|
425c9471fb | ||
|
|
0c8110e15e | ||
|
|
8a7ec45bbf | ||
|
|
c1f84b4303 | ||
|
|
5a9e9513f4 | ||
|
|
9e7679e70f | ||
|
|
e5b2114ac2 | ||
|
|
1168460942 | ||
|
|
f7d6e75a9b | ||
|
|
d56c2db547 | ||
|
|
ab2a3f5bd9 | ||
|
|
4748d1e211 | ||
|
|
5ca151b675 | ||
|
|
67fd65d366 | ||
|
|
45981f2fee | ||
|
|
1df2a46884 | ||
|
|
2c7b7d59be | ||
|
|
d849506ecd | ||
|
|
47f2cfc210 | ||
|
|
5d1c65cce7 | ||
|
|
cf308fc118 | ||
|
|
87016419c5 | ||
|
|
da46705b52 | ||
|
|
9a1cf3d9e6 | ||
|
|
8e0908dbf7 | ||
|
|
1f255a98e6 | ||
|
|
2a067ef60b | ||
|
|
b3afa2067c | ||
|
|
630c4dd894 | ||
|
|
f1fa5c2fa9 | ||
|
|
f6c9ecad60 | ||
|
|
5eb545f62d | ||
|
|
0f127a557d | ||
|
|
611200d311 | ||
|
|
0ed4603ba5 | ||
|
|
439849edfa | ||
|
|
091aadd4a6 | ||
|
|
356b814102 | ||
|
|
cd6a6a462d | ||
|
|
0123fe1fbd | ||
|
|
8b518d3c6f | ||
|
|
d9f9937d41 | ||
|
|
7be283ca30 | ||
|
|
748c324acf | ||
|
|
2916669319 | ||
|
|
30468e0b09 | ||
|
|
70918e5393 | ||
|
|
c43ce9d978 | ||
|
|
2e11481276 | ||
|
|
f1b335e794 | ||
|
|
96dca5dbe7 | ||
|
|
4db0822443 | ||
|
|
cdc67a035c | ||
|
|
8add6e5519 | ||
|
|
ebdfd3f8bc | ||
|
|
1cd8e5fb7e | ||
|
|
1d185e9bfe | ||
|
|
9c57935a87 | ||
|
|
8b609e5f05 | ||
|
|
e1a41b5f3c | ||
|
|
c605d19510 | ||
|
|
8160f420db | ||
|
|
60a1ee728a | ||
|
|
c83ddf054a | ||
|
|
02bfd17794 | ||
|
|
7d490f9cfb | ||
|
|
c541fd62c4 | ||
|
|
2e038350ef | ||
|
|
157a5b150b | ||
|
|
8a87ff16b4 | ||
|
|
3d6d04dde1 | ||
|
|
d675cdc804 | ||
|
|
6475c3c65b | ||
|
|
d57743763b | ||
|
|
9b9e356ad1 | ||
|
|
24452f3c79 | ||
|
|
e41755ed7e | ||
|
|
71e80740f6 | ||
|
|
70cf21cf57 | ||
|
|
ebd3fb3fbf | ||
|
|
56d014e14f | ||
|
|
664b42eee8 | ||
|
|
a116243900 | ||
|
|
33bf5c5392 | ||
|
|
f3eb542940 | ||
|
|
b90e68fbaf | ||
|
|
c951854d5a | ||
|
|
f424857bd3 | ||
|
|
27e9223c81 | ||
|
|
31cd812519 | ||
|
|
c9febe2c74 | ||
|
|
467dc2c22f | ||
|
|
4215e0ad44 | ||
|
|
533c53fd8d | ||
|
|
66ed721105 | ||
|
|
9e09d64c66 | ||
|
|
7f95726cc3 | ||
|
|
22c7e32760 | ||
|
|
40a684ff46 | ||
|
|
9ed8c9517b | ||
|
|
3722df199b | ||
|
|
5464fa3a0f | ||
|
|
525f65e70a | ||
|
|
b44a147fdd | ||
|
|
86d370226a | ||
|
|
f9c7e567b6 | ||
|
|
7605c7cb29 | ||
|
|
0ba9f538ba | ||
|
|
de9d605fb0 | ||
|
|
db34255127 | ||
|
|
60c7debb5e | ||
|
|
c7b9791a6a | ||
|
|
fe028e5ec7 | ||
|
|
2ce8076e9a | ||
|
|
a5998b84ba | ||
|
|
dd217a2475 | ||
|
|
4738830b8a | ||
|
|
c49102959f | ||
|
|
1d974a0c6c | ||
|
|
dee79c121b | ||
|
|
667ff2490d | ||
|
|
87d8876972 | ||
|
|
530166b81a | ||
|
|
6db4a9623b | ||
|
|
11111817d7 | ||
|
|
da944b50cc | ||
|
|
cca2bf645f | ||
|
|
ef96dadba5 | ||
|
|
210bca4c7c | ||
|
|
9d99d936aa | ||
|
|
415ac63767 | ||
|
|
8f53253bae | ||
|
|
d104b794ae | ||
|
|
ea8b23d3fd | ||
|
|
65e33d91ac | ||
|
|
ce02c64655 | ||
|
|
83e1476e7f | ||
|
|
885063d389 | ||
|
|
816e88e0bd | ||
|
|
726eb52e4f | ||
|
|
5da0738147 | ||
|
|
9b5271bca4 | ||
|
|
c8cc375d4b | ||
|
|
be9c3eab40 | ||
|
|
1f1b04bdfe | ||
|
|
f473abb99c | ||
|
|
9a02ba2987 | ||
|
|
03043ba2c9 | ||
|
|
82536c75c9 | ||
|
|
773f526fba | ||
|
|
fc6604fd5a | ||
|
|
a7c5d513d4 | ||
|
|
38a489d7f0 | ||
|
|
76cdacf80f | ||
|
|
3880c1870b | ||
|
|
6a46a410f7 | ||
|
|
5971592217 | ||
|
|
9e06125092 | ||
|
|
3a07acbd05 | ||
|
|
edc54203b5 | ||
|
|
af58a52a59 | ||
|
|
2282f3f87a | ||
|
|
5cd224b164 | ||
|
|
d7074c5791 | ||
|
|
d594082165 | ||
|
|
1c8d01bdd3 | ||
|
|
a1b455d9a5 | ||
|
|
61cb46a298 | ||
|
|
2e52030c31 | ||
|
|
5bca4b9118 | ||
|
|
1cc7c2f0cd | ||
|
|
6115d24775 | ||
|
|
68bffba3cd | ||
|
|
e2ffcea0b2 | ||
|
|
9f04ee7695 | ||
|
|
f69915dcb3 | ||
|
|
34caf29b32 | ||
|
|
3a63a1b22d | ||
|
|
5460ffd0a0 | ||
|
|
e516fd9318 | ||
|
|
ad79d9cc07 | ||
|
|
ab771fd010 | ||
|
|
d8085dc022 | ||
|
|
f6434cbc61 | ||
|
|
9d569266b5 | ||
|
|
6446309bd7 | ||
|
|
d0d518ddc2 | ||
|
|
e87b216541 | ||
|
|
1e0b7fdfce | ||
|
|
1bee5c680b | ||
|
|
029cf12bed | ||
|
|
ff96e51963 | ||
|
|
60302c37cd | ||
|
|
fb2b676ff0 | ||
|
|
d256702005 | ||
|
|
b2bb50b3d9 | ||
|
|
b1c1cf0f99 | ||
|
|
a7f36da3f5 | ||
|
|
94b7ea2d9d | ||
|
|
55f2053fe6 | ||
|
|
a885411a8c | ||
|
|
3ad67f661a | ||
|
|
ed329ec989 | ||
|
|
f669616616 | ||
|
|
2f95a4ea34 | ||
|
|
de9fb1fd9f | ||
|
|
8b10b3fdfb | ||
|
|
dae3fbc901 | ||
|
|
608ab7beb9 | ||
|
|
0320da0fc5 | ||
|
|
124656c22e | ||
|
|
1725dbb583 | ||
|
|
b3818a90df | ||
|
|
e7d5cf02e6 | ||
|
|
3484a29f7a | ||
|
|
a807c90885 | ||
|
|
951c167058 | ||
|
|
25491adbe3 | ||
|
|
51c7fc83dd | ||
|
|
a5f35dbd17 | ||
|
|
c75315f0f7 | ||
|
|
d3824d6a23 | ||
|
|
303001fed0 | ||
|
|
717f9eb393 | ||
|
|
9529963aa2 | ||
|
|
81059411e5 | ||
|
|
7a602d6ec5 | ||
|
|
7caf6cc08a | ||
|
|
c1d9af2dd0 | ||
|
|
3f99d11179 | ||
|
|
52b89a4177 | ||
|
|
5cf6b0eb75 | ||
|
|
205a95b254 | ||
|
|
cd498b539b | ||
|
|
089c7d392d | ||
|
|
6207f3a67f | ||
|
|
dec3598eff | ||
|
|
eb7cc6ffa0 | ||
|
|
960548f7b2 | ||
|
|
0f40906451 | ||
|
|
9a14f725b8 | ||
|
|
0450c4e3f7 | ||
|
|
86fc472144 | ||
|
|
4c32ca6e63 | ||
|
|
67acdd2d13 | ||
|
|
b7d482a24e | ||
|
|
8ce667e7e4 | ||
|
|
9851533143 | ||
|
|
87d51c04ad | ||
|
|
edd55087db | ||
|
|
9c49b66d71 | ||
|
|
448090e8f5 | ||
|
|
794cea72cc | ||
|
|
20983a5605 | ||
|
|
284920b8fe | ||
|
|
13181311ae | ||
|
|
1341c4316c | ||
|
|
93dcd317c1 | ||
|
|
dbd11f6a95 | ||
|
|
e4883b15cc | ||
|
|
91138176e0 | ||
|
|
6efd89e0ab | ||
|
|
2713e6f1f6 | ||
|
|
86c871fa81 | ||
|
|
8fff09f92e | ||
|
|
9d18707cb7 | ||
|
|
e8096f6e00 | ||
|
|
e0447b1ed4 | ||
|
|
78d7aef13f | ||
|
|
552338e5b5 | ||
|
|
e50474f12d | ||
|
|
dfb3ef3d79 | ||
|
|
4900ccb75a | ||
|
|
f949153292 | ||
|
|
efd51b0b5e | ||
|
|
fb64315d51 | ||
|
|
ee78a2a983 | ||
|
|
a722abb141 | ||
|
|
21aa3e8efc | ||
|
|
0268546f63 | ||
|
|
9f276c7674 | ||
|
|
a1b40068e3 | ||
|
|
43d683ac66 | ||
|
|
9a52dddba3 | ||
|
|
e35f2816e2 | ||
|
|
d81197a40f | ||
|
|
10e508b4e7 | ||
|
|
7091fdd808 | ||
|
|
f6e74c06cc | ||
|
|
12fa14e1c6 | ||
|
|
c573feefec | ||
|
|
0d427dcaba | ||
|
|
8375abd6cb | ||
|
|
3d421881f6 | ||
|
|
71d6a658d6 | ||
|
|
73122ad9bf | ||
|
|
9f3f78cf4c | ||
|
|
43c26f8499 | ||
|
|
02fc8452d2 | ||
|
|
eab1352340 | ||
|
|
633a01f176 | ||
|
|
411ab84d80 | ||
|
|
43d5700d72 | ||
|
|
7c8bd514de | ||
|
|
6269777760 | ||
|
|
2d75c24371 | ||
|
|
9750736d57 | ||
|
|
05d261d4a3 | ||
|
|
8dde1689f7 | ||
|
|
6362c79aa2 | ||
|
|
5ddf882077 | ||
|
|
7af0a1d562 | ||
|
|
fdd74c0514 | ||
|
|
78b15c085f | ||
|
|
2c0423d2b5 | ||
|
|
e8907871af | ||
|
|
85d9dea2dd | ||
|
|
c9160b8167 | ||
|
|
c99aa51f8a | ||
|
|
ee426eb29f | ||
|
|
b5bdf52d16 | ||
|
|
fdb9479f2d | ||
|
|
6696d539d3 | ||
|
|
f35c20ed4d | ||
|
|
ce3197cb25 | ||
|
|
9f9a58af2a | ||
|
|
e6bd91319b | ||
|
|
62d89633bb | ||
|
|
93ba44d230 | ||
|
|
bade38f82e | ||
|
|
96fcf9e727 |
241 changed files with 14029 additions and 6338 deletions
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
|
|
@ -9,13 +9,15 @@ jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v5
|
||||||
with:
|
with:
|
||||||
dotnet-version: '7.x.x'
|
dotnet-version: |
|
||||||
|
10.x.x
|
||||||
|
9.x.x
|
||||||
- name: Restore dependencies
|
- name: Restore dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
- name: Download Dalamud
|
- name: Download Dalamud
|
||||||
|
|
@ -37,7 +39,7 @@ jobs:
|
||||||
- name: Archive
|
- name: Archive
|
||||||
run: Compress-Archive -Path Glamourer/bin/Release/* -DestinationPath Glamourer.zip
|
run: Compress-Archive -Path Glamourer/bin/Release/* -DestinationPath Glamourer.zip
|
||||||
- name: Upload a Build Artifact
|
- name: Upload a Build Artifact
|
||||||
uses: actions/upload-artifact@v2.2.1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
./Glamourer/bin/Release/*
|
./Glamourer/bin/Release/*
|
||||||
|
|
|
||||||
10
.github/workflows/test_release.yml
vendored
10
.github/workflows/test_release.yml
vendored
|
|
@ -9,13 +9,15 @@ jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v5
|
||||||
with:
|
with:
|
||||||
dotnet-version: '7.x.x'
|
dotnet-version: |
|
||||||
|
10.x.x
|
||||||
|
9.x.x
|
||||||
- name: Restore dependencies
|
- name: Restore dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
- name: Download Dalamud
|
- name: Download Dalamud
|
||||||
|
|
@ -37,7 +39,7 @@ jobs:
|
||||||
- name: Archive
|
- name: Archive
|
||||||
run: Compress-Archive -Path Glamourer/bin/Debug/* -DestinationPath Glamourer.zip
|
run: Compress-Archive -Path Glamourer/bin/Debug/* -DestinationPath Glamourer.zip
|
||||||
- name: Upload a Build Artifact
|
- name: Upload a Build Artifact
|
||||||
uses: actions/upload-artifact@v2.2.1
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
./Glamourer/bin/Debug/*
|
./Glamourer/bin/Debug/*
|
||||||
|
|
|
||||||
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -14,3 +14,7 @@
|
||||||
path = Penumbra.Api
|
path = Penumbra.Api
|
||||||
url = https://github.com/Ottermandias/Penumbra.Api.git
|
url = https://github.com/Ottermandias/Penumbra.Api.git
|
||||||
branch = main
|
branch = main
|
||||||
|
[submodule "Glamourer.Api"]
|
||||||
|
path = Glamourer.Api
|
||||||
|
url = https://github.com/Ottermandias/Glamourer.Api.git
|
||||||
|
branch = main
|
||||||
|
|
|
||||||
1
Glamourer.Api
Submodule
1
Glamourer.Api
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5b6730d46f17bdd02a441e23e2141576cf7acf53
|
||||||
|
|
@ -6,7 +6,10 @@ MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{383AEE76-D423-431C-893A-7AB3DEA13630}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{383AEE76-D423-431C-893A-7AB3DEA13630}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
|
.github\workflows\release.yml = .github\workflows\release.yml
|
||||||
|
Glamourer\Glamourer.json = Glamourer\Glamourer.json
|
||||||
repo.json = repo.json
|
repo.json = repo.json
|
||||||
|
.github\workflows\test_release.yml = .github\workflows\test_release.yml
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer", "Glamourer\Glamourer.csproj", "{01EB903D-871F-4285-A8CF-6486561D5B5B}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer", "Glamourer\Glamourer.csproj", "{01EB903D-871F-4285-A8CF-6486561D5B5B}"
|
||||||
|
|
@ -19,32 +22,38 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "OtterGui\OtterGui.csproj", "{EF233CE2-F243-449E-BE05-72B9D110E419}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "OtterGui\OtterGui.csproj", "{EF233CE2-F243-449E-BE05-72B9D110E419}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.Api", "Glamourer.Api\Glamourer.Api.csproj", "{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.Build.0 = Release|Any CPU
|
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.Build.0 = Release|Any CPU
|
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.Build.0 = Release|x64
|
||||||
{EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
{EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
{EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
{EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.Build.0 = Release|Any CPU
|
{EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.Build.0 = Release|x64
|
||||||
|
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||||
|
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.Build.0 = Debug|x64
|
||||||
|
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.ActiveCfg = Release|x64
|
||||||
|
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.Build.0 = Release|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
133
Glamourer/Api/ApiHelpers.cs
Normal file
133
Glamourer/Api/ApiHelpers.cs
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.State;
|
||||||
|
using OtterGui.Extensions;
|
||||||
|
using OtterGui.Log;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using Penumbra.String;
|
||||||
|
|
||||||
|
namespace Glamourer.Api;
|
||||||
|
|
||||||
|
public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
internal IEnumerable<ActorState> FindExistingStates(string actorName, ushort worldId = ushort.MaxValue)
|
||||||
|
{
|
||||||
|
if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString))
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
if (worldId == WorldId.AnyWorld.Id)
|
||||||
|
{
|
||||||
|
foreach (var state in stateManager.Values.Where(state
|
||||||
|
=> state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString))
|
||||||
|
yield return state;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var identifier = actors.CreatePlayer(byteString, worldId);
|
||||||
|
if (stateManager.TryGetValue(identifier, out var state))
|
||||||
|
yield return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
internal GlamourerApiEc FindExistingState(int objectIndex, out ActorState? state)
|
||||||
|
{
|
||||||
|
var actor = objects.Objects[objectIndex];
|
||||||
|
var identifier = actor.GetIdentifier(actors);
|
||||||
|
if (!identifier.IsValid)
|
||||||
|
{
|
||||||
|
state = null;
|
||||||
|
return GlamourerApiEc.ActorNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
stateManager.TryGetValue(identifier, out state);
|
||||||
|
return GlamourerApiEc.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
internal ActorState? FindState(int objectIndex)
|
||||||
|
{
|
||||||
|
var actor = objects.Objects[objectIndex];
|
||||||
|
var identifier = actor.GetIdentifier(actors);
|
||||||
|
if (identifier.IsValid && stateManager.GetOrCreate(identifier, actor, out var state))
|
||||||
|
return state;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
internal static DesignBase.FlagRestrictionResetter Restrict(DesignBase design, ApplyFlag flags)
|
||||||
|
=> (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) switch
|
||||||
|
{
|
||||||
|
ApplyFlag.Equipment => design.TemporarilyRestrictApplication(ApplicationCollection.Equipment),
|
||||||
|
ApplyFlag.Customization => design.TemporarilyRestrictApplication(ApplicationCollection.Customizations),
|
||||||
|
ApplyFlag.Equipment | ApplyFlag.Customization => design.TemporarilyRestrictApplication(ApplicationCollection.All),
|
||||||
|
_ => design.TemporarilyRestrictApplication(ApplicationCollection.None),
|
||||||
|
};
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
internal static void Lock(ActorState state, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
if ((flags & ApplyFlag.Lock) != 0 && key != 0)
|
||||||
|
state.Lock(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
internal IEnumerable<ActorState> FindStates(string objectName)
|
||||||
|
{
|
||||||
|
if (objectName.Length == 0 || !ByteString.FromString(objectName, out var byteString))
|
||||||
|
return [];
|
||||||
|
|
||||||
|
return stateManager.Values.Where(state => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString)
|
||||||
|
.Concat(objects
|
||||||
|
.Where(kvp => kvp.Key is { IsValid: true, Type: IdentifierType.Player } && kvp.Key.PlayerName == byteString)
|
||||||
|
.SelectWhere(kvp =>
|
||||||
|
{
|
||||||
|
if (stateManager.ContainsKey(kvp.Key))
|
||||||
|
return (false, null);
|
||||||
|
|
||||||
|
var ret = stateManager.GetOrCreate(kvp.Key, kvp.Value.Objects[0], out var state);
|
||||||
|
return (ret, state);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
internal static GlamourerApiEc Return(GlamourerApiEc ec, LazyString args, [CallerMemberName] string name = "Unknown")
|
||||||
|
{
|
||||||
|
if (ec is GlamourerApiEc.Success or GlamourerApiEc.NothingDone)
|
||||||
|
Glamourer.Log.Verbose($"[{name}] Called with {args}, returned {ec}.");
|
||||||
|
else
|
||||||
|
Glamourer.Log.Debug($"[{name}] Called with {args}, returned {ec}.");
|
||||||
|
return ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
internal static LazyString Args(params object[] arguments)
|
||||||
|
{
|
||||||
|
if (arguments.Length == 0)
|
||||||
|
return new LazyString(() => "no arguments");
|
||||||
|
|
||||||
|
return new LazyString(() =>
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (var i = 0; i < arguments.Length / 2; ++i)
|
||||||
|
{
|
||||||
|
sb.Append(arguments[2 * i]);
|
||||||
|
sb.Append(" = ");
|
||||||
|
if (arguments[2 * i + 1] is IEnumerable e)
|
||||||
|
sb.Append($"[{string.Join(',', e)}]");
|
||||||
|
else
|
||||||
|
sb.Append(arguments[2 * i + 1]);
|
||||||
|
sb.Append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString(0, sb.Length - 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
138
Glamourer/Api/DesignsApi.cs
Normal file
138
Glamourer/Api/DesignsApi.cs
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
using Glamourer.Api.Api;
|
||||||
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.State;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using OtterGui.Services;
|
||||||
|
|
||||||
|
namespace Glamourer.Api;
|
||||||
|
|
||||||
|
public class DesignsApi(
|
||||||
|
ApiHelpers helpers,
|
||||||
|
DesignManager designs,
|
||||||
|
StateManager stateManager,
|
||||||
|
DesignFileSystem fileSystem,
|
||||||
|
DesignColors color,
|
||||||
|
DesignConverter converter)
|
||||||
|
: IGlamourerApiDesigns, IApiService
|
||||||
|
{
|
||||||
|
public Dictionary<Guid, string> GetDesignList()
|
||||||
|
=> designs.Designs.ToDictionary(d => d.Identifier, d => d.Name.Text);
|
||||||
|
|
||||||
|
public Dictionary<Guid, (string DisplayName, string FullPath, uint DisplayColor, bool ShownInQdb)> GetDesignListExtended()
|
||||||
|
=> fileSystem.ToDictionary(kvp => kvp.Key.Identifier,
|
||||||
|
kvp => (kvp.Key.Name.Text, kvp.Value.FullName(), color.GetColor(kvp.Key), kvp.Key.QuickDesign));
|
||||||
|
|
||||||
|
public (string DisplayName, string FullPath, uint DisplayColor, bool ShowInQdb) GetExtendedDesignData(Guid designId)
|
||||||
|
=> designs.Designs.ByIdentifier(designId) is { } d
|
||||||
|
? (d.Name.Text, fileSystem.TryGetValue(d, out var leaf) ? leaf.FullName() : d.Name.Text, color.GetColor(d), d.QuickDesign)
|
||||||
|
: (string.Empty, string.Empty, 0, false);
|
||||||
|
|
||||||
|
public GlamourerApiEc ApplyDesign(Guid designId, int objectIndex, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Design", designId, "Index", objectIndex, "Key", key, "Flags", flags);
|
||||||
|
var design = designs.Designs.ByIdentifier(designId);
|
||||||
|
if (design == null)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.DesignNotFound, args);
|
||||||
|
|
||||||
|
if (helpers.FindState(objectIndex) is not { } state)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
ApplyDesign(state, design, key, flags);
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyDesign(ActorState state, Design design, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var once = (flags & ApplyFlag.Once) != 0;
|
||||||
|
var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true,
|
||||||
|
ResetMaterials: !once && key != 0, IsFinal: true);
|
||||||
|
|
||||||
|
using var restrict = ApiHelpers.Restrict(design, flags);
|
||||||
|
stateManager.ApplyDesign(state, design, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc ApplyDesignName(Guid designId, string playerName, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Design", designId, "Name", playerName, "Key", key, "Flags", flags);
|
||||||
|
var design = designs.Designs.ByIdentifier(designId);
|
||||||
|
if (design == null)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.DesignNotFound, args);
|
||||||
|
|
||||||
|
var any = false;
|
||||||
|
var anyUnlocked = false;
|
||||||
|
foreach (var state in helpers.FindStates(playerName))
|
||||||
|
{
|
||||||
|
any = true;
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyUnlocked = true;
|
||||||
|
ApplyDesign(state, design, key, flags);
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!any)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
if (!anyUnlocked)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (GlamourerApiEc, Guid) AddDesign(string designInput, string name)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("DesignData", designInput, "Name", name);
|
||||||
|
|
||||||
|
if (converter.FromBase64(designInput, true, true, out _) is not { } designBase)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jObj = JObject.Parse(designInput);
|
||||||
|
designBase = converter.FromJObject(jObj, true, true);
|
||||||
|
if (designBase is null)
|
||||||
|
return (ApiHelpers.Return(GlamourerApiEc.CouldNotParse, args), Guid.Empty);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Glamourer.Log.Error($"Failure parsing data for AddDesign due to\n{ex}");
|
||||||
|
return (ApiHelpers.Return(GlamourerApiEc.CouldNotParse, args), Guid.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var design = designBase is Design d
|
||||||
|
? designs.CreateClone(d, name, true)
|
||||||
|
: designs.CreateClone(designBase, name, true);
|
||||||
|
return (ApiHelpers.Return(GlamourerApiEc.Success, args), design.Identifier);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Glamourer.Log.Error($"Unknown error creating design via IPC:\n{ex}");
|
||||||
|
return (ApiHelpers.Return(GlamourerApiEc.UnknownError, args), Guid.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc DeleteDesign(Guid designId)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("DesignId", designId);
|
||||||
|
if (designs.Designs.ByIdentifier(designId) is not { } design)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||||
|
|
||||||
|
designs.Delete(design);
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? GetDesignBase64(Guid designId)
|
||||||
|
=> designs.Designs.ByIdentifier(designId) is { } design
|
||||||
|
? converter.ShareBase64(design)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
public JObject? GetDesignJObject(Guid designId)
|
||||||
|
=> designs.Designs.ByIdentifier(designId) is { } design
|
||||||
|
? converter.ShareJObject(design)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
25
Glamourer/Api/GlamourerApi.cs
Normal file
25
Glamourer/Api/GlamourerApi.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
using Glamourer.Api.Api;
|
||||||
|
using OtterGui.Services;
|
||||||
|
|
||||||
|
namespace Glamourer.Api;
|
||||||
|
|
||||||
|
public class GlamourerApi(Configuration config, DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService
|
||||||
|
{
|
||||||
|
public const int CurrentApiVersionMajor = 1;
|
||||||
|
public const int CurrentApiVersionMinor = 7;
|
||||||
|
|
||||||
|
public (int Major, int Minor) ApiVersion
|
||||||
|
=> (CurrentApiVersionMajor, CurrentApiVersionMinor);
|
||||||
|
|
||||||
|
public bool AutoReloadGearEnabled
|
||||||
|
=> config.AutoRedrawEquipOnChanges;
|
||||||
|
|
||||||
|
public IGlamourerApiDesigns Designs
|
||||||
|
=> designs;
|
||||||
|
|
||||||
|
public IGlamourerApiItems Items
|
||||||
|
=> items;
|
||||||
|
|
||||||
|
public IGlamourerApiState State
|
||||||
|
=> state;
|
||||||
|
}
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Penumbra.Api.Helpers;
|
|
||||||
|
|
||||||
namespace Glamourer.Api;
|
|
||||||
|
|
||||||
public partial class GlamourerIpc
|
|
||||||
{
|
|
||||||
public const string LabelApiVersion = "Glamourer.ApiVersion";
|
|
||||||
public const string LabelApiVersions = "Glamourer.ApiVersions";
|
|
||||||
|
|
||||||
private readonly FuncProvider<int> _apiVersionProvider;
|
|
||||||
private readonly FuncProvider<(int Major, int Minor)> _apiVersionsProvider;
|
|
||||||
|
|
||||||
public static FuncSubscriber<int> ApiVersionSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApiVersion);
|
|
||||||
|
|
||||||
public static FuncSubscriber<(int Major, int Minor)> ApiVersionsSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApiVersions);
|
|
||||||
|
|
||||||
public int ApiVersion()
|
|
||||||
=> CurrentApiVersionMajor;
|
|
||||||
|
|
||||||
public (int Major, int Minor) ApiVersions()
|
|
||||||
=> (CurrentApiVersionMajor, CurrentApiVersionMinor);
|
|
||||||
}
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Glamourer.Designs;
|
|
||||||
using Glamourer.Interop.Structs;
|
|
||||||
using Glamourer.State;
|
|
||||||
using Penumbra.Api.Helpers;
|
|
||||||
using Penumbra.GameData.Actors;
|
|
||||||
|
|
||||||
namespace Glamourer.Api;
|
|
||||||
|
|
||||||
public partial class GlamourerIpc
|
|
||||||
{
|
|
||||||
public const string LabelApplyAll = "Glamourer.ApplyAll";
|
|
||||||
public const string LabelApplyAllOnce = "Glamourer.ApplyAllOnce";
|
|
||||||
public const string LabelApplyAllToCharacter = "Glamourer.ApplyAllToCharacter";
|
|
||||||
public const string LabelApplyAllOnceToCharacter = "Glamourer.ApplyAllOnceToCharacter";
|
|
||||||
public const string LabelApplyOnlyEquipment = "Glamourer.ApplyOnlyEquipment";
|
|
||||||
public const string LabelApplyOnlyEquipmentToCharacter = "Glamourer.ApplyOnlyEquipmentToCharacter";
|
|
||||||
public const string LabelApplyOnlyCustomization = "Glamourer.ApplyOnlyCustomization";
|
|
||||||
public const string LabelApplyOnlyCustomizationToCharacter = "Glamourer.ApplyOnlyCustomizationToCharacter";
|
|
||||||
|
|
||||||
public const string LabelApplyAllLock = "Glamourer.ApplyAllLock";
|
|
||||||
public const string LabelApplyAllToCharacterLock = "Glamourer.ApplyAllToCharacterLock";
|
|
||||||
public const string LabelApplyOnlyEquipmentLock = "Glamourer.ApplyOnlyEquipmentLock";
|
|
||||||
public const string LabelApplyOnlyEquipmentToCharacterLock = "Glamourer.ApplyOnlyEquipmentToCharacterLock";
|
|
||||||
public const string LabelApplyOnlyCustomizationLock = "Glamourer.ApplyOnlyCustomizationLock";
|
|
||||||
public const string LabelApplyOnlyCustomizationToCharacterLock = "Glamourer.ApplyOnlyCustomizationToCharacterLock";
|
|
||||||
|
|
||||||
public const string LabelApplyByGuid = "Glamourer.ApplyByGuid";
|
|
||||||
public const string LabelApplyByGuidOnce = "Glamourer.ApplyByGuidOnce";
|
|
||||||
public const string LabelApplyByGuidToCharacter = "Glamourer.ApplyByGuidToCharacter";
|
|
||||||
public const string LabelApplyByGuidOnceToCharacter = "Glamourer.ApplyByGuidOnceToCharacter";
|
|
||||||
|
|
||||||
private readonly ActionProvider<string, string> _applyAllProvider;
|
|
||||||
private readonly ActionProvider<string, string> _applyAllOnceProvider;
|
|
||||||
private readonly ActionProvider<string, Character?> _applyAllToCharacterProvider;
|
|
||||||
private readonly ActionProvider<string, Character?> _applyAllOnceToCharacterProvider;
|
|
||||||
private readonly ActionProvider<string, string> _applyOnlyEquipmentProvider;
|
|
||||||
private readonly ActionProvider<string, Character?> _applyOnlyEquipmentToCharacterProvider;
|
|
||||||
private readonly ActionProvider<string, string> _applyOnlyCustomizationProvider;
|
|
||||||
private readonly ActionProvider<string, Character?> _applyOnlyCustomizationToCharacterProvider;
|
|
||||||
|
|
||||||
private readonly ActionProvider<string, string, uint> _applyAllProviderLock;
|
|
||||||
private readonly ActionProvider<string, Character?, uint> _applyAllToCharacterProviderLock;
|
|
||||||
private readonly ActionProvider<string, string, uint> _applyOnlyEquipmentProviderLock;
|
|
||||||
private readonly ActionProvider<string, Character?, uint> _applyOnlyEquipmentToCharacterProviderLock;
|
|
||||||
private readonly ActionProvider<string, string, uint> _applyOnlyCustomizationProviderLock;
|
|
||||||
private readonly ActionProvider<string, Character?, uint> _applyOnlyCustomizationToCharacterProviderLock;
|
|
||||||
|
|
||||||
private readonly ActionProvider<Guid, string> _applyByGuidProvider;
|
|
||||||
private readonly ActionProvider<Guid, string> _applyByGuidOnceProvider;
|
|
||||||
private readonly ActionProvider<Guid, Character?> _applyByGuidToCharacterProvider;
|
|
||||||
private readonly ActionProvider<Guid, Character?> _applyByGuidOnceToCharacterProvider;
|
|
||||||
|
|
||||||
public static ActionSubscriber<string, string> ApplyAllSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyAll);
|
|
||||||
|
|
||||||
public static ActionSubscriber<string, string> ApplyAllOnceSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyAllOnce);
|
|
||||||
|
|
||||||
public static ActionSubscriber<string, Character?> ApplyAllToCharacterSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyAllToCharacter);
|
|
||||||
|
|
||||||
public static ActionSubscriber<string, Character?> ApplyAllOnceToCharacterSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyAllOnceToCharacter);
|
|
||||||
|
|
||||||
public static ActionSubscriber<string, string> ApplyOnlyEquipmentSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyOnlyEquipment);
|
|
||||||
|
|
||||||
public static ActionSubscriber<string, Character?> ApplyOnlyEquipmentToCharacterSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyOnlyEquipmentToCharacter);
|
|
||||||
|
|
||||||
public static ActionSubscriber<string, string> ApplyOnlyCustomizationSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyOnlyCustomization);
|
|
||||||
|
|
||||||
public static ActionSubscriber<string, Character?> ApplyOnlyCustomizationToCharacterSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyOnlyCustomizationToCharacter);
|
|
||||||
|
|
||||||
public static ActionSubscriber<Guid, string> ApplyByGuidSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyByGuid);
|
|
||||||
|
|
||||||
public static ActionSubscriber<Guid, string> ApplyByGuidOnceSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyByGuidOnce);
|
|
||||||
|
|
||||||
public static ActionSubscriber<Guid, Character?> ApplyByGuidToCharacterSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyByGuidToCharacter);
|
|
||||||
|
|
||||||
public static ActionSubscriber<Guid, Character?> ApplyByGuidOnceToCharacterSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelApplyByGuidOnceToCharacter);
|
|
||||||
|
|
||||||
public void ApplyAll(string base64, string characterName)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, 0);
|
|
||||||
|
|
||||||
public void ApplyAllOnce(string base64, string characterName)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, 0, true);
|
|
||||||
|
|
||||||
public void ApplyAllToCharacter(string base64, Character? character)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, 0);
|
|
||||||
|
|
||||||
public void ApplyAllOnceToCharacter(string base64, Character? character)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, 0, true);
|
|
||||||
|
|
||||||
public void ApplyOnlyEquipment(string base64, string characterName)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(characterName), version, 0);
|
|
||||||
|
|
||||||
public void ApplyOnlyEquipmentToCharacter(string base64, Character? character)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(character), version, 0);
|
|
||||||
|
|
||||||
public void ApplyOnlyCustomization(string base64, string characterName)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(characterName), version, 0);
|
|
||||||
|
|
||||||
public void ApplyOnlyCustomizationToCharacter(string base64, Character? character)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(character), version, 0);
|
|
||||||
|
|
||||||
|
|
||||||
public void ApplyAllLock(string base64, string characterName, uint lockCode)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(characterName), version, lockCode);
|
|
||||||
|
|
||||||
public void ApplyAllToCharacterLock(string base64, Character? character, uint lockCode)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, true, out var version), FindActors(character), version, lockCode);
|
|
||||||
|
|
||||||
public void ApplyOnlyEquipmentLock(string base64, string characterName, uint lockCode)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(characterName), version, lockCode);
|
|
||||||
|
|
||||||
public void ApplyOnlyEquipmentToCharacterLock(string base64, Character? character, uint lockCode)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, false, true, out var version), FindActors(character), version, lockCode);
|
|
||||||
|
|
||||||
public void ApplyOnlyCustomizationLock(string base64, string characterName, uint lockCode)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(characterName), version, lockCode);
|
|
||||||
|
|
||||||
public void ApplyOnlyCustomizationToCharacterLock(string base64, Character? character, uint lockCode)
|
|
||||||
=> ApplyDesign(_designConverter.FromBase64(base64, true, false, out var version), FindActors(character), version, lockCode);
|
|
||||||
|
|
||||||
|
|
||||||
public void ApplyByGuid(Guid identifier, string characterName)
|
|
||||||
=> ApplyDesignByGuid(identifier, FindActors(characterName), 0, false);
|
|
||||||
|
|
||||||
public void ApplyByGuidOnce(Guid identifier, string characterName)
|
|
||||||
=> ApplyDesignByGuid(identifier, FindActors(characterName), 0, true);
|
|
||||||
|
|
||||||
public void ApplyByGuidToCharacter(Guid identifier, Character? character)
|
|
||||||
=> ApplyDesignByGuid(identifier, FindActors(character), 0, false);
|
|
||||||
|
|
||||||
public void ApplyByGuidOnceToCharacter(Guid identifier, Character? character)
|
|
||||||
=> ApplyDesignByGuid(identifier, FindActors(character), 0, true);
|
|
||||||
|
|
||||||
private void ApplyDesign(DesignBase? design, IEnumerable<ActorIdentifier> actors, byte version, uint lockCode, bool once = false)
|
|
||||||
{
|
|
||||||
if (design == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var hasModelId = version >= 3;
|
|
||||||
_objects.Update();
|
|
||||||
foreach (var id in actors)
|
|
||||||
{
|
|
||||||
if (!_stateManager.TryGetValue(id, out var state))
|
|
||||||
{
|
|
||||||
var data = _objects.TryGetValue(id, out var d) ? d : ActorData.Invalid;
|
|
||||||
if (!data.Valid || !_stateManager.GetOrCreate(id, data.Objects[0], out state))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode))
|
|
||||||
{
|
|
||||||
_stateManager.ApplyDesign(state, design,
|
|
||||||
new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: lockCode, MergeLinks: true, ResetMaterials: !once && lockCode != 0));
|
|
||||||
state.Lock(lockCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyDesignByGuid(Guid identifier, IEnumerable<ActorIdentifier> actors, uint lockCode, bool once)
|
|
||||||
=> ApplyDesign(_designManager.Designs.ByIdentifier(identifier), actors, DesignConverter.Version, lockCode, once);
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Penumbra.Api.Helpers;
|
|
||||||
|
|
||||||
namespace Glamourer.Api;
|
|
||||||
|
|
||||||
public partial class GlamourerIpc
|
|
||||||
{
|
|
||||||
public const string LabelGetDesignList = "Glamourer.GetDesignList";
|
|
||||||
|
|
||||||
private readonly FuncProvider<(string Name, Guid Identifier)[]> _getDesignListProvider;
|
|
||||||
|
|
||||||
public static FuncSubscriber<(string Name, Guid Identifier)[]> GetDesignListSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelGetDesignList);
|
|
||||||
|
|
||||||
public (string Name, Guid Identifier)[] GetDesignList()
|
|
||||||
=> _designManager.Designs.Select(x => (x.Name.Text, x.Identifier)).ToArray();
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
using Glamourer.Designs;
|
|
||||||
using Glamourer.Events;
|
|
||||||
using Glamourer.Interop.Structs;
|
|
||||||
using Glamourer.State;
|
|
||||||
using Penumbra.Api.Helpers;
|
|
||||||
|
|
||||||
namespace Glamourer.Api;
|
|
||||||
|
|
||||||
public partial class GlamourerIpc
|
|
||||||
{
|
|
||||||
public const string LabelStateChanged = "Glamourer.StateChanged";
|
|
||||||
public const string LabelGPoseChanged = "Glamourer.GPoseChanged";
|
|
||||||
|
|
||||||
private readonly GPoseService _gPose;
|
|
||||||
private readonly StateChanged _stateChangedEvent;
|
|
||||||
private readonly EventProvider<StateChanged.Type, nint, Lazy<string>> _stateChangedProvider;
|
|
||||||
private readonly EventProvider<bool> _gPoseChangedProvider;
|
|
||||||
|
|
||||||
private void OnStateChanged(StateChanged.Type type, StateSource source, ActorState state, ActorData actors, object? data = null)
|
|
||||||
{
|
|
||||||
foreach (var actor in actors.Objects)
|
|
||||||
_stateChangedProvider.Invoke(type, actor.Address, new Lazy<string>(() => _designConverter.ShareBase64(state, ApplicationRules.AllButParameters(state))));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnGPoseChanged(bool value)
|
|
||||||
=> _gPoseChangedProvider.Invoke(value);
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Glamourer.Designs;
|
|
||||||
using Penumbra.Api.Helpers;
|
|
||||||
using Penumbra.GameData.Actors;
|
|
||||||
|
|
||||||
namespace Glamourer.Api;
|
|
||||||
|
|
||||||
public partial class GlamourerIpc
|
|
||||||
{
|
|
||||||
public const string LabelGetAllCustomization = "Glamourer.GetAllCustomization";
|
|
||||||
public const string LabelGetAllCustomizationFromCharacter = "Glamourer.GetAllCustomizationFromCharacter";
|
|
||||||
|
|
||||||
private readonly FuncProvider<string, string?> _getAllCustomizationProvider;
|
|
||||||
private readonly FuncProvider<Character?, string?> _getAllCustomizationFromCharacterProvider;
|
|
||||||
|
|
||||||
public static FuncSubscriber<string, string?> GetAllCustomizationSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelGetAllCustomization);
|
|
||||||
|
|
||||||
public static FuncSubscriber<Character?, string?> GetAllCustomizationFromCharacterSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelGetAllCustomizationFromCharacter);
|
|
||||||
|
|
||||||
public string? GetAllCustomization(string characterName)
|
|
||||||
=> GetCustomization(FindActors(characterName));
|
|
||||||
|
|
||||||
public string? GetAllCustomizationFromCharacter(Character? character)
|
|
||||||
=> GetCustomization(FindActors(character));
|
|
||||||
|
|
||||||
private string? GetCustomization(IEnumerable<ActorIdentifier> actors)
|
|
||||||
{
|
|
||||||
var actor = actors.FirstOrDefault(ActorIdentifier.Invalid);
|
|
||||||
if (!actor.IsValid)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (!_stateManager.TryGetValue(actor, out var state))
|
|
||||||
{
|
|
||||||
_objects.Update();
|
|
||||||
if (!_objects.TryGetValue(actor, out var data) || !data.Valid)
|
|
||||||
return null;
|
|
||||||
if (!_stateManager.GetOrCreate(actor, data.Objects[0], out state))
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _designConverter.ShareBase64(state, ApplicationRules.All);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Glamourer.State;
|
|
||||||
using Penumbra.Api.Helpers;
|
|
||||||
using Penumbra.GameData.Actors;
|
|
||||||
|
|
||||||
namespace Glamourer.Api;
|
|
||||||
|
|
||||||
public partial class GlamourerIpc
|
|
||||||
{
|
|
||||||
public const string LabelRevert = "Glamourer.Revert";
|
|
||||||
public const string LabelRevertCharacter = "Glamourer.RevertCharacter";
|
|
||||||
public const string LabelRevertLock = "Glamourer.RevertLock";
|
|
||||||
public const string LabelRevertCharacterLock = "Glamourer.RevertCharacterLock";
|
|
||||||
public const string LabelRevertToAutomation = "Glamourer.RevertToAutomation";
|
|
||||||
public const string LabelRevertToAutomationCharacter = "Glamourer.RevertToAutomationCharacter";
|
|
||||||
public const string LabelUnlock = "Glamourer.Unlock";
|
|
||||||
public const string LabelUnlockName = "Glamourer.UnlockName";
|
|
||||||
|
|
||||||
private readonly ActionProvider<string> _revertProvider;
|
|
||||||
private readonly ActionProvider<Character?> _revertCharacterProvider;
|
|
||||||
|
|
||||||
private readonly ActionProvider<string, uint> _revertProviderLock;
|
|
||||||
private readonly ActionProvider<Character?, uint> _revertCharacterProviderLock;
|
|
||||||
|
|
||||||
private readonly FuncProvider<string, uint, bool> _revertToAutomationProvider;
|
|
||||||
private readonly FuncProvider<Character?, uint, bool> _revertToAutomationCharacterProvider;
|
|
||||||
|
|
||||||
private readonly FuncProvider<string, uint, bool> _unlockNameProvider;
|
|
||||||
private readonly FuncProvider<Character?, uint, bool> _unlockProvider;
|
|
||||||
|
|
||||||
public static ActionSubscriber<string> RevertSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelRevert);
|
|
||||||
|
|
||||||
public static ActionSubscriber<Character?> RevertCharacterSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelRevertCharacter);
|
|
||||||
|
|
||||||
public static ActionSubscriber<string> RevertLockSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelRevertLock);
|
|
||||||
|
|
||||||
public static ActionSubscriber<Character?> RevertCharacterLockSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelRevertCharacterLock);
|
|
||||||
|
|
||||||
public static FuncSubscriber<string, uint, bool> UnlockNameSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelUnlockName);
|
|
||||||
|
|
||||||
public static FuncSubscriber<Character?, uint, bool> UnlockSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelUnlock);
|
|
||||||
|
|
||||||
public static FuncSubscriber<string, uint, bool> RevertToAutomationSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelRevertToAutomation);
|
|
||||||
|
|
||||||
public static FuncSubscriber<Character?, uint, bool> RevertToAutomationCharacterSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelRevertToAutomationCharacter);
|
|
||||||
|
|
||||||
public void Revert(string characterName)
|
|
||||||
=> Revert(FindActorsRevert(characterName), 0);
|
|
||||||
|
|
||||||
public void RevertCharacter(Character? character)
|
|
||||||
=> Revert(FindActors(character), 0);
|
|
||||||
|
|
||||||
public void RevertLock(string characterName, uint lockCode)
|
|
||||||
=> Revert(FindActorsRevert(characterName), lockCode);
|
|
||||||
|
|
||||||
public void RevertCharacterLock(Character? character, uint lockCode)
|
|
||||||
=> Revert(FindActors(character), lockCode);
|
|
||||||
|
|
||||||
public bool Unlock(string characterName, uint lockCode)
|
|
||||||
=> Unlock(FindActorsRevert(characterName), lockCode);
|
|
||||||
|
|
||||||
public bool Unlock(Character? character, uint lockCode)
|
|
||||||
=> Unlock(FindActors(character), lockCode);
|
|
||||||
|
|
||||||
public bool RevertToAutomation(string characterName, uint lockCode)
|
|
||||||
=> RevertToAutomation(FindActorsRevert(characterName), lockCode);
|
|
||||||
|
|
||||||
public bool RevertToAutomation(Character? character, uint lockCode)
|
|
||||||
=> RevertToAutomation(FindActors(character), lockCode);
|
|
||||||
|
|
||||||
private void Revert(IEnumerable<ActorIdentifier> actors, uint lockCode)
|
|
||||||
{
|
|
||||||
foreach (var id in actors)
|
|
||||||
{
|
|
||||||
if (_stateManager.TryGetValue(id, out var state))
|
|
||||||
_stateManager.ResetState(state, StateSource.IpcFixed, lockCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool Unlock(IEnumerable<ActorIdentifier> actors, uint lockCode)
|
|
||||||
{
|
|
||||||
var ret = false;
|
|
||||||
foreach (var id in actors)
|
|
||||||
{
|
|
||||||
if (_stateManager.TryGetValue(id, out var state))
|
|
||||||
ret |= state.Unlock(lockCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool RevertToAutomation(IEnumerable<ActorIdentifier> actors, uint lockCode)
|
|
||||||
{
|
|
||||||
var ret = false;
|
|
||||||
foreach (var id in actors)
|
|
||||||
{
|
|
||||||
if (_stateManager.TryGetValue(id, out var state))
|
|
||||||
{
|
|
||||||
ret |= state.Unlock(lockCode);
|
|
||||||
if (_objects.TryGetValue(id, out var data))
|
|
||||||
foreach (var obj in data.Objects)
|
|
||||||
{
|
|
||||||
_autoDesignApplier.ReapplyAutomation(obj, state.Identifier, state);
|
|
||||||
_stateManager.ReapplyState(obj, StateSource.IpcManual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Glamourer.Designs;
|
|
||||||
using Glamourer.Events;
|
|
||||||
using Glamourer.Services;
|
|
||||||
using Glamourer.State;
|
|
||||||
using Penumbra.Api.Helpers;
|
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
|
|
||||||
namespace Glamourer.Api;
|
|
||||||
|
|
||||||
public partial class GlamourerIpc
|
|
||||||
{
|
|
||||||
public enum GlamourerErrorCode
|
|
||||||
{
|
|
||||||
Success,
|
|
||||||
ActorNotFound,
|
|
||||||
ActorNotHuman,
|
|
||||||
ItemInvalid,
|
|
||||||
}
|
|
||||||
|
|
||||||
public const string LabelSetItem = "Glamourer.SetItem";
|
|
||||||
public const string LabelSetItemOnce = "Glamourer.SetItemOnce";
|
|
||||||
public const string LabelSetItemByActorName = "Glamourer.SetItemByActorName";
|
|
||||||
public const string LabelSetItemOnceByActorName = "Glamourer.SetItemOnceByActorName";
|
|
||||||
|
|
||||||
|
|
||||||
private readonly FuncProvider<Character?, byte, ulong, byte, uint, int> _setItemProvider;
|
|
||||||
private readonly FuncProvider<Character?, byte, ulong, byte, uint, int> _setItemOnceProvider;
|
|
||||||
private readonly FuncProvider<string, byte, ulong, byte, uint, int> _setItemByActorNameProvider;
|
|
||||||
private readonly FuncProvider<string, byte, ulong, byte, uint, int> _setItemOnceByActorNameProvider;
|
|
||||||
|
|
||||||
public static FuncSubscriber<Character?, byte, ulong, byte, uint, int> SetItemSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelSetItem);
|
|
||||||
|
|
||||||
public static FuncSubscriber<Character?, byte, ulong, byte, uint, int> SetItemOnceSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelSetItemOnce);
|
|
||||||
|
|
||||||
public static FuncSubscriber<string, byte, ulong, byte, uint, int> SetItemByActorNameSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelSetItemByActorName);
|
|
||||||
|
|
||||||
public static FuncSubscriber<string, byte, ulong, byte, uint, int> SetItemOnceByActorNameSubscriber(DalamudPluginInterface pi)
|
|
||||||
=> new(pi, LabelSetItemOnceByActorName);
|
|
||||||
|
|
||||||
private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key, bool once)
|
|
||||||
{
|
|
||||||
if (itemId.Id == 0)
|
|
||||||
itemId = ItemManager.NothingId(slot);
|
|
||||||
|
|
||||||
var item = _items.Resolve(slot, itemId);
|
|
||||||
if (!item.Valid)
|
|
||||||
return GlamourerErrorCode.ItemInvalid;
|
|
||||||
|
|
||||||
var identifier = _actors.FromObject(character, false, false, false);
|
|
||||||
if (!identifier.IsValid)
|
|
||||||
return GlamourerErrorCode.ActorNotFound;
|
|
||||||
|
|
||||||
if (!_stateManager.TryGetValue(identifier, out var state))
|
|
||||||
{
|
|
||||||
_objects.Update();
|
|
||||||
var data = _objects[identifier];
|
|
||||||
if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state))
|
|
||||||
return GlamourerErrorCode.ActorNotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.ModelData.IsHuman)
|
|
||||||
return GlamourerErrorCode.ActorNotHuman;
|
|
||||||
|
|
||||||
_stateManager.ChangeEquip(state, slot, item, stainId,
|
|
||||||
new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key));
|
|
||||||
return GlamourerErrorCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key, bool once)
|
|
||||||
{
|
|
||||||
if (itemId.Id == 0)
|
|
||||||
itemId = ItemManager.NothingId(slot);
|
|
||||||
|
|
||||||
var item = _items.Resolve(slot, itemId);
|
|
||||||
if (!item.Valid)
|
|
||||||
return GlamourerErrorCode.ItemInvalid;
|
|
||||||
|
|
||||||
var found = false;
|
|
||||||
_objects.Update();
|
|
||||||
foreach (var identifier in FindActorsRevert(name).Distinct())
|
|
||||||
{
|
|
||||||
if (!_stateManager.TryGetValue(identifier, out var state))
|
|
||||||
{
|
|
||||||
var data = _objects[identifier];
|
|
||||||
if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.ModelData.IsHuman)
|
|
||||||
return GlamourerErrorCode.ActorNotHuman;
|
|
||||||
|
|
||||||
_stateManager.ChangeEquip(state, slot, item, stainId,
|
|
||||||
new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key));
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return found ? GlamourerErrorCode.Success : GlamourerErrorCode.ActorNotFound;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Glamourer.Automation;
|
|
||||||
using Glamourer.Designs;
|
|
||||||
using Glamourer.Events;
|
|
||||||
using Glamourer.Interop;
|
|
||||||
using Glamourer.Services;
|
|
||||||
using Glamourer.State;
|
|
||||||
using Penumbra.Api.Helpers;
|
|
||||||
using Penumbra.GameData.Actors;
|
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.String;
|
|
||||||
|
|
||||||
namespace Glamourer.Api;
|
|
||||||
|
|
||||||
public sealed partial class GlamourerIpc : IDisposable
|
|
||||||
{
|
|
||||||
public const int CurrentApiVersionMajor = 0;
|
|
||||||
public const int CurrentApiVersionMinor = 4;
|
|
||||||
|
|
||||||
private readonly StateManager _stateManager;
|
|
||||||
private readonly ObjectManager _objects;
|
|
||||||
private readonly ActorManager _actors;
|
|
||||||
private readonly DesignConverter _designConverter;
|
|
||||||
private readonly AutoDesignApplier _autoDesignApplier;
|
|
||||||
private readonly DesignManager _designManager;
|
|
||||||
private readonly ItemManager _items;
|
|
||||||
|
|
||||||
public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorManager actors,
|
|
||||||
DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier,
|
|
||||||
DesignManager designManager, ItemManager items)
|
|
||||||
{
|
|
||||||
_stateManager = stateManager;
|
|
||||||
_objects = objects;
|
|
||||||
_actors = actors;
|
|
||||||
_designConverter = designConverter;
|
|
||||||
_autoDesignApplier = autoDesignApplier;
|
|
||||||
_items = items;
|
|
||||||
_gPose = gPose;
|
|
||||||
_stateChangedEvent = stateChangedEvent;
|
|
||||||
_designManager = designManager;
|
|
||||||
_apiVersionProvider = new FuncProvider<int>(pi, LabelApiVersion, ApiVersion);
|
|
||||||
_apiVersionsProvider = new FuncProvider<(int Major, int Minor)>(pi, LabelApiVersions, ApiVersions);
|
|
||||||
|
|
||||||
_getAllCustomizationProvider = new FuncProvider<string, string?>(pi, LabelGetAllCustomization, GetAllCustomization);
|
|
||||||
_getAllCustomizationFromCharacterProvider =
|
|
||||||
new FuncProvider<Character?, string?>(pi, LabelGetAllCustomizationFromCharacter, GetAllCustomizationFromCharacter);
|
|
||||||
|
|
||||||
_applyAllProvider = new ActionProvider<string, string>(pi, LabelApplyAll, ApplyAll);
|
|
||||||
_applyAllOnceProvider = new ActionProvider<string, string>(pi, LabelApplyAllOnce, ApplyAllOnce);
|
|
||||||
_applyAllToCharacterProvider = new ActionProvider<string, Character?>(pi, LabelApplyAllToCharacter, ApplyAllToCharacter);
|
|
||||||
_applyAllOnceToCharacterProvider = new ActionProvider<string, Character?>(pi, LabelApplyAllOnceToCharacter, ApplyAllOnceToCharacter);
|
|
||||||
_applyOnlyEquipmentProvider = new ActionProvider<string, string>(pi, LabelApplyOnlyEquipment, ApplyOnlyEquipment);
|
|
||||||
_applyOnlyEquipmentToCharacterProvider =
|
|
||||||
new ActionProvider<string, Character?>(pi, LabelApplyOnlyEquipmentToCharacter, ApplyOnlyEquipmentToCharacter);
|
|
||||||
_applyOnlyCustomizationProvider = new ActionProvider<string, string>(pi, LabelApplyOnlyCustomization, ApplyOnlyCustomization);
|
|
||||||
_applyOnlyCustomizationToCharacterProvider =
|
|
||||||
new ActionProvider<string, Character?>(pi, LabelApplyOnlyCustomizationToCharacter, ApplyOnlyCustomizationToCharacter);
|
|
||||||
|
|
||||||
_applyAllProviderLock = new ActionProvider<string, string, uint>(pi, LabelApplyAllLock, ApplyAllLock);
|
|
||||||
_applyAllToCharacterProviderLock =
|
|
||||||
new ActionProvider<string, Character?, uint>(pi, LabelApplyAllToCharacterLock, ApplyAllToCharacterLock);
|
|
||||||
_applyOnlyEquipmentProviderLock = new ActionProvider<string, string, uint>(pi, LabelApplyOnlyEquipmentLock, ApplyOnlyEquipmentLock);
|
|
||||||
_applyOnlyEquipmentToCharacterProviderLock =
|
|
||||||
new ActionProvider<string, Character?, uint>(pi, LabelApplyOnlyEquipmentToCharacterLock, ApplyOnlyEquipmentToCharacterLock);
|
|
||||||
_applyOnlyCustomizationProviderLock =
|
|
||||||
new ActionProvider<string, string, uint>(pi, LabelApplyOnlyCustomizationLock, ApplyOnlyCustomizationLock);
|
|
||||||
_applyOnlyCustomizationToCharacterProviderLock =
|
|
||||||
new ActionProvider<string, Character?, uint>(pi, LabelApplyOnlyCustomizationToCharacterLock, ApplyOnlyCustomizationToCharacterLock);
|
|
||||||
|
|
||||||
_applyByGuidProvider = new ActionProvider<Guid, string>(pi, LabelApplyByGuid, ApplyByGuid);
|
|
||||||
_applyByGuidOnceProvider = new ActionProvider<Guid, string>(pi, LabelApplyByGuidOnce, ApplyByGuidOnce);
|
|
||||||
_applyByGuidToCharacterProvider = new ActionProvider<Guid, Character?>(pi, LabelApplyByGuidToCharacter, ApplyByGuidToCharacter);
|
|
||||||
_applyByGuidOnceToCharacterProvider =
|
|
||||||
new ActionProvider<Guid, Character?>(pi, LabelApplyByGuidOnceToCharacter, ApplyByGuidOnceToCharacter);
|
|
||||||
|
|
||||||
_revertProvider = new ActionProvider<string>(pi, LabelRevert, Revert);
|
|
||||||
_revertCharacterProvider = new ActionProvider<Character?>(pi, LabelRevertCharacter, RevertCharacter);
|
|
||||||
_revertProviderLock = new ActionProvider<string, uint>(pi, LabelRevertLock, RevertLock);
|
|
||||||
_revertCharacterProviderLock = new ActionProvider<Character?, uint>(pi, LabelRevertCharacterLock, RevertCharacterLock);
|
|
||||||
_unlockNameProvider = new FuncProvider<string, uint, bool>(pi, LabelUnlockName, Unlock);
|
|
||||||
_unlockProvider = new FuncProvider<Character?, uint, bool>(pi, LabelUnlock, Unlock);
|
|
||||||
_revertToAutomationProvider = new FuncProvider<string, uint, bool>(pi, LabelRevertToAutomation, RevertToAutomation);
|
|
||||||
_revertToAutomationCharacterProvider =
|
|
||||||
new FuncProvider<Character?, uint, bool>(pi, LabelRevertToAutomationCharacter, RevertToAutomation);
|
|
||||||
|
|
||||||
_stateChangedProvider = new EventProvider<StateChanged.Type, nint, Lazy<string>>(pi, LabelStateChanged);
|
|
||||||
_gPoseChangedProvider = new EventProvider<bool>(pi, LabelGPoseChanged);
|
|
||||||
|
|
||||||
_setItemProvider = new FuncProvider<Character?, byte, ulong, byte, uint, int>(pi, LabelSetItem,
|
|
||||||
(idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, false));
|
|
||||||
_setItemOnceProvider = new FuncProvider<Character?, byte, ulong, byte, uint, int>(pi, LabelSetItemOnce,
|
|
||||||
(idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key, true));
|
|
||||||
|
|
||||||
_setItemByActorNameProvider = new FuncProvider<string, byte, ulong, byte, uint, int>(pi, LabelSetItemByActorName,
|
|
||||||
(name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key, false));
|
|
||||||
_setItemOnceByActorNameProvider = new FuncProvider<string, byte, ulong, byte, uint, int>(pi, LabelSetItemOnceByActorName,
|
|
||||||
(name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key, true));
|
|
||||||
|
|
||||||
_stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc);
|
|
||||||
_gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc);
|
|
||||||
|
|
||||||
_getDesignListProvider = new FuncProvider<(string Name, Guid Identifier)[]>(pi, LabelGetDesignList, GetDesignList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_apiVersionProvider.Dispose();
|
|
||||||
_apiVersionsProvider.Dispose();
|
|
||||||
|
|
||||||
_getAllCustomizationProvider.Dispose();
|
|
||||||
_getAllCustomizationFromCharacterProvider.Dispose();
|
|
||||||
|
|
||||||
_applyAllProvider.Dispose();
|
|
||||||
_applyAllOnceProvider.Dispose();
|
|
||||||
_applyAllToCharacterProvider.Dispose();
|
|
||||||
_applyAllOnceToCharacterProvider.Dispose();
|
|
||||||
_applyOnlyEquipmentProvider.Dispose();
|
|
||||||
_applyOnlyEquipmentToCharacterProvider.Dispose();
|
|
||||||
_applyOnlyCustomizationProvider.Dispose();
|
|
||||||
_applyOnlyCustomizationToCharacterProvider.Dispose();
|
|
||||||
_applyAllProviderLock.Dispose();
|
|
||||||
_applyAllToCharacterProviderLock.Dispose();
|
|
||||||
_applyOnlyEquipmentProviderLock.Dispose();
|
|
||||||
_applyOnlyEquipmentToCharacterProviderLock.Dispose();
|
|
||||||
_applyOnlyCustomizationProviderLock.Dispose();
|
|
||||||
_applyOnlyCustomizationToCharacterProviderLock.Dispose();
|
|
||||||
|
|
||||||
_applyByGuidProvider.Dispose();
|
|
||||||
_applyByGuidOnceProvider.Dispose();
|
|
||||||
_applyByGuidToCharacterProvider.Dispose();
|
|
||||||
_applyByGuidOnceToCharacterProvider.Dispose();
|
|
||||||
|
|
||||||
_revertProvider.Dispose();
|
|
||||||
_revertCharacterProvider.Dispose();
|
|
||||||
_revertProviderLock.Dispose();
|
|
||||||
_revertCharacterProviderLock.Dispose();
|
|
||||||
_unlockNameProvider.Dispose();
|
|
||||||
_unlockProvider.Dispose();
|
|
||||||
_revertToAutomationProvider.Dispose();
|
|
||||||
_revertToAutomationCharacterProvider.Dispose();
|
|
||||||
|
|
||||||
_stateChangedEvent.Unsubscribe(OnStateChanged);
|
|
||||||
_stateChangedProvider.Dispose();
|
|
||||||
_gPose.Unsubscribe(OnGPoseChanged);
|
|
||||||
_gPoseChangedProvider.Dispose();
|
|
||||||
|
|
||||||
_getDesignListProvider.Dispose();
|
|
||||||
|
|
||||||
_setItemProvider.Dispose();
|
|
||||||
_setItemOnceProvider.Dispose();
|
|
||||||
_setItemByActorNameProvider.Dispose();
|
|
||||||
_setItemOnceByActorNameProvider.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<ActorIdentifier> FindActors(string actorName)
|
|
||||||
{
|
|
||||||
if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString))
|
|
||||||
return Array.Empty<ActorIdentifier>();
|
|
||||||
|
|
||||||
_objects.Update();
|
|
||||||
return _objects.Where(i => i.Key is { IsValid: true, Type: IdentifierType.Player } && i.Key.PlayerName == byteString)
|
|
||||||
.Select(i => i.Key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<ActorIdentifier> FindActorsRevert(string actorName)
|
|
||||||
{
|
|
||||||
if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString))
|
|
||||||
yield break;
|
|
||||||
|
|
||||||
_objects.Update();
|
|
||||||
foreach (var id in _objects.Where(i => i.Key is { IsValid: true, Type: IdentifierType.Player } && i.Key.PlayerName == byteString)
|
|
||||||
.Select(i => i.Key))
|
|
||||||
yield return id;
|
|
||||||
|
|
||||||
foreach (var id in _stateManager.Keys.Where(s => s.Type is IdentifierType.Player && s.PlayerName == byteString))
|
|
||||||
yield return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<ActorIdentifier> FindActors(Character? character)
|
|
||||||
{
|
|
||||||
var id = _actors.FromObject(character, true, true, false);
|
|
||||||
if (!id.IsValid)
|
|
||||||
yield break;
|
|
||||||
|
|
||||||
yield return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
85
Glamourer/Api/IpcProviders.cs
Normal file
85
Glamourer/Api/IpcProviders.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Glamourer.Api.Api;
|
||||||
|
using Glamourer.Api.Helpers;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using Glamourer.Api.Enums;
|
||||||
|
|
||||||
|
namespace Glamourer.Api;
|
||||||
|
|
||||||
|
public sealed class IpcProviders : IDisposable, IApiService
|
||||||
|
{
|
||||||
|
private readonly List<IDisposable> _providers;
|
||||||
|
|
||||||
|
private readonly EventProvider _disposedProvider;
|
||||||
|
private readonly EventProvider _initializedProvider;
|
||||||
|
|
||||||
|
public IpcProviders(IDalamudPluginInterface pi, IGlamourerApi api)
|
||||||
|
{
|
||||||
|
_disposedProvider = IpcSubscribers.Disposed.Provider(pi);
|
||||||
|
_initializedProvider = IpcSubscribers.Initialized.Provider(pi);
|
||||||
|
_providers =
|
||||||
|
[
|
||||||
|
new FuncProvider<(int Major, int Minor)>(pi, "Glamourer.ApiVersions", () => api.ApiVersion), // backward compatibility
|
||||||
|
new FuncProvider<int>(pi, "Glamourer.ApiVersion", () => api.ApiVersion.Major), // backward compatibility
|
||||||
|
IpcSubscribers.ApiVersion.Provider(pi, api),
|
||||||
|
IpcSubscribers.AutoReloadGearEnabled.Provider(pi, api),
|
||||||
|
|
||||||
|
IpcSubscribers.GetDesignList.Provider(pi, api.Designs),
|
||||||
|
IpcSubscribers.GetDesignListExtended.Provider(pi, api.Designs),
|
||||||
|
IpcSubscribers.GetExtendedDesignData.Provider(pi, api.Designs),
|
||||||
|
IpcSubscribers.ApplyDesign.Provider(pi, api.Designs),
|
||||||
|
IpcSubscribers.ApplyDesignName.Provider(pi, api.Designs),
|
||||||
|
IpcSubscribers.AddDesign.Provider(pi, api.Designs),
|
||||||
|
IpcSubscribers.DeleteDesign.Provider(pi, api.Designs),
|
||||||
|
IpcSubscribers.GetDesignBase64.Provider(pi, api.Designs),
|
||||||
|
IpcSubscribers.GetDesignJObject.Provider(pi, api.Designs),
|
||||||
|
|
||||||
|
IpcSubscribers.SetItem.Provider(pi, api.Items),
|
||||||
|
IpcSubscribers.SetItemName.Provider(pi, api.Items),
|
||||||
|
// backward compatibility
|
||||||
|
new FuncProvider<int, byte, ulong, byte, uint, ulong, int>(pi, IpcSubscribers.Legacy.SetItemV2.Label,
|
||||||
|
(a, b, c, d, e, f) => (int)api.Items.SetItem(a, (ApiEquipSlot)b, c, [d], e, (ApplyFlag)f)),
|
||||||
|
new FuncProvider<string, byte, ulong, byte, uint, ulong, int>(pi, IpcSubscribers.Legacy.SetItemName.Label,
|
||||||
|
(a, b, c, d, e, f) => (int)api.Items.SetItemName(a, (ApiEquipSlot)b, c, [d], e, (ApplyFlag)f)),
|
||||||
|
IpcSubscribers.SetBonusItem.Provider(pi, api.Items),
|
||||||
|
IpcSubscribers.SetBonusItemName.Provider(pi, api.Items),
|
||||||
|
IpcSubscribers.SetMetaState.Provider(pi, api.Items),
|
||||||
|
IpcSubscribers.SetMetaStateName.Provider(pi, api.Items),
|
||||||
|
IpcSubscribers.GetState.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.GetStateName.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.GetStateBase64.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.GetStateBase64Name.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.ApplyState.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.ApplyStateName.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.ReapplyState.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.ReapplyStateName.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.RevertState.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.RevertStateName.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.UnlockState.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.CanUnlock.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.UnlockStateName.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.DeletePlayerState.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.UnlockAll.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.RevertToAutomation.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.RevertToAutomationName.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.AutoReloadGearChanged.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.StateChanged.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.StateChangedWithType.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.StateFinalized.Provider(pi, api.State),
|
||||||
|
IpcSubscribers.GPoseChanged.Provider(pi, api.State),
|
||||||
|
];
|
||||||
|
_initializedProvider.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var provider in _providers)
|
||||||
|
provider.Dispose();
|
||||||
|
_providers.Clear();
|
||||||
|
_initializedProvider.Dispose();
|
||||||
|
_disposedProvider.Invoke();
|
||||||
|
_disposedProvider.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
217
Glamourer/Api/ItemsApi.cs
Normal file
217
Glamourer/Api/ItemsApi.cs
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
using Glamourer.Api.Api;
|
||||||
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using Glamourer.State;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Api;
|
||||||
|
|
||||||
|
public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager stateManager) : IGlamourerApiItems, IApiService
|
||||||
|
{
|
||||||
|
public GlamourerApiEc SetItem(int objectIndex, ApiEquipSlot slot, ulong itemId, IReadOnlyList<byte> stains, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Index", objectIndex, "Slot", slot, "ID", itemId, "Stains", stains, "Key", key, "Flags", flags);
|
||||||
|
if (!ResolveItem(slot, itemId, out var item))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args);
|
||||||
|
|
||||||
|
if (helpers.FindState(objectIndex) is not { } state)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (!state.ModelData.IsHuman)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
|
||||||
|
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key);
|
||||||
|
stateManager.ChangeEquip(state, (EquipSlot)slot, item, new StainIds(stains), settings);
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
return GlamourerApiEc.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc SetItemName(string playerName, ApiEquipSlot slot, ulong itemId, IReadOnlyList<byte> stains, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Name", playerName, "Slot", slot, "ID", itemId, "Stains", stains, "Key", key, "Flags", flags);
|
||||||
|
if (!ResolveItem(slot, itemId, out var item))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args);
|
||||||
|
|
||||||
|
var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key);
|
||||||
|
var anyHuman = false;
|
||||||
|
var anyFound = false;
|
||||||
|
var anyUnlocked = false;
|
||||||
|
foreach (var state in helpers.FindStates(playerName))
|
||||||
|
{
|
||||||
|
anyFound = true;
|
||||||
|
if (!state.ModelData.IsHuman)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyHuman = true;
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyUnlocked = true;
|
||||||
|
stateManager.ChangeEquip(state, (EquipSlot)slot, item, new StainIds(stains), settings);
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyFound)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (!anyHuman)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
|
||||||
|
|
||||||
|
if (!anyUnlocked)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc SetBonusItem(int objectIndex, ApiBonusSlot slot, ulong bonusItemId, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Index", objectIndex, "Slot", slot, "ID", bonusItemId, "Key", key, "Flags", flags);
|
||||||
|
if (!ResolveBonusItem(slot, bonusItemId, out var item))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args);
|
||||||
|
|
||||||
|
if (helpers.FindState(objectIndex) is not { } state)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (!state.ModelData.IsHuman)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
|
||||||
|
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key);
|
||||||
|
stateManager.ChangeBonusItem(state, item.Type.ToBonus(), item, settings);
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
return GlamourerApiEc.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc SetBonusItemName(string playerName, ApiBonusSlot slot, ulong bonusItemId, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Name", playerName, "Slot", slot, "ID", bonusItemId, "Key", key, "Flags", flags);
|
||||||
|
if (!ResolveBonusItem(slot, bonusItemId, out var item))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args);
|
||||||
|
|
||||||
|
var settings = new ApplySettings(Source: flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcManual : StateSource.IpcFixed, Key: key);
|
||||||
|
var anyHuman = false;
|
||||||
|
var anyFound = false;
|
||||||
|
var anyUnlocked = false;
|
||||||
|
foreach (var state in helpers.FindStates(playerName))
|
||||||
|
{
|
||||||
|
anyFound = true;
|
||||||
|
if (!state.ModelData.IsHuman)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyHuman = true;
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyUnlocked = true;
|
||||||
|
stateManager.ChangeBonusItem(state, item.Type.ToBonus(), item, settings);
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyFound)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (!anyHuman)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
|
||||||
|
|
||||||
|
if (!anyUnlocked)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc SetMetaState(int objectIndex, MetaFlag types, bool newValue, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Index", objectIndex, "MetaTypes", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags);
|
||||||
|
if (types == 0)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidState, args);
|
||||||
|
|
||||||
|
if (helpers.FindState(objectIndex) is not { } state)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (!state.ModelData.IsHuman)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
|
||||||
|
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
// Grab MetaIndices from attached flags, and update the states.
|
||||||
|
var indices = types.ToIndices();
|
||||||
|
foreach (var index in indices)
|
||||||
|
{
|
||||||
|
stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual);
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GlamourerApiEc.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc SetMetaStateName(string playerName, MetaFlag types, bool newValue, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Name", playerName, "MetaTypes", types, "NewValue", newValue, "Key", key, "ApplyFlags", flags);
|
||||||
|
if (types == 0)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ItemInvalid, args);
|
||||||
|
|
||||||
|
var anyHuman = false;
|
||||||
|
var anyFound = false;
|
||||||
|
var anyUnlocked = false;
|
||||||
|
foreach (var state in helpers.FindStates(playerName))
|
||||||
|
{
|
||||||
|
anyFound = true;
|
||||||
|
if (!state.ModelData.IsHuman)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyHuman = true;
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyUnlocked = true;
|
||||||
|
// update all MetaStates for this ActorState
|
||||||
|
foreach (var index in types.ToIndices())
|
||||||
|
{
|
||||||
|
stateManager.ChangeMetaState(state, index, newValue, ApplySettings.Manual);
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyFound)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (!anyHuman)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
|
||||||
|
|
||||||
|
if (!anyUnlocked)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ResolveItem(ApiEquipSlot apiSlot, ulong itemId, out EquipItem item)
|
||||||
|
{
|
||||||
|
var id = (CustomItemId)itemId;
|
||||||
|
var slot = (EquipSlot)apiSlot;
|
||||||
|
if (id.Id == 0)
|
||||||
|
id = ItemManager.NothingId(slot);
|
||||||
|
|
||||||
|
item = itemManager.Resolve(slot, id);
|
||||||
|
return item.Valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ResolveBonusItem(ApiBonusSlot apiSlot, ulong itemId, out EquipItem item)
|
||||||
|
{
|
||||||
|
var slot = apiSlot switch
|
||||||
|
{
|
||||||
|
ApiBonusSlot.Glasses => BonusItemFlag.Glasses,
|
||||||
|
_ => BonusItemFlag.Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
return itemManager.IsBonusItemValid(slot, (BonusItemId)itemId, out item);
|
||||||
|
}
|
||||||
|
}
|
||||||
452
Glamourer/Api/StateApi.cs
Normal file
452
Glamourer/Api/StateApi.cs
Normal file
|
|
@ -0,0 +1,452 @@
|
||||||
|
using Glamourer.Api.Api;
|
||||||
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.Automation;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Designs.History;
|
||||||
|
using Glamourer.Events;
|
||||||
|
using Glamourer.State;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using StateChanged = Glamourer.Events.StateChanged;
|
||||||
|
|
||||||
|
namespace Glamourer.Api;
|
||||||
|
|
||||||
|
public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
|
||||||
|
{
|
||||||
|
private readonly ApiHelpers _helpers;
|
||||||
|
private readonly StateManager _stateManager;
|
||||||
|
private readonly DesignConverter _converter;
|
||||||
|
private readonly AutoDesignApplier _autoDesigns;
|
||||||
|
private readonly ActorObjectManager _objects;
|
||||||
|
private readonly AutoRedrawChanged _autoRedraw;
|
||||||
|
private readonly StateChanged _stateChanged;
|
||||||
|
private readonly StateFinalized _stateFinalized;
|
||||||
|
private readonly GPoseService _gPose;
|
||||||
|
|
||||||
|
public StateApi(ApiHelpers helpers,
|
||||||
|
StateManager stateManager,
|
||||||
|
DesignConverter converter,
|
||||||
|
AutoDesignApplier autoDesigns,
|
||||||
|
ActorObjectManager objects,
|
||||||
|
AutoRedrawChanged autoRedraw,
|
||||||
|
StateChanged stateChanged,
|
||||||
|
StateFinalized stateFinalized,
|
||||||
|
GPoseService gPose)
|
||||||
|
{
|
||||||
|
_helpers = helpers;
|
||||||
|
_stateManager = stateManager;
|
||||||
|
_converter = converter;
|
||||||
|
_autoDesigns = autoDesigns;
|
||||||
|
_objects = objects;
|
||||||
|
_autoRedraw = autoRedraw;
|
||||||
|
_stateChanged = stateChanged;
|
||||||
|
_stateFinalized = stateFinalized;
|
||||||
|
_gPose = gPose;
|
||||||
|
_autoRedraw.Subscribe(OnAutoRedrawChange, AutoRedrawChanged.Priority.StateApi);
|
||||||
|
_stateChanged.Subscribe(OnStateChanged, Events.StateChanged.Priority.GlamourerIpc);
|
||||||
|
_stateFinalized.Subscribe(OnStateFinalized, Events.StateFinalized.Priority.StateApi);
|
||||||
|
_gPose.Subscribe(OnGPoseChange, GPoseService.Priority.StateApi);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_autoRedraw.Unsubscribe(OnAutoRedrawChange);
|
||||||
|
_stateChanged.Unsubscribe(OnStateChanged);
|
||||||
|
_stateFinalized.Unsubscribe(OnStateFinalized);
|
||||||
|
_gPose.Unsubscribe(OnGPoseChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (GlamourerApiEc, JObject?) GetState(int objectIndex, uint key)
|
||||||
|
=> Convert(_helpers.FindState(objectIndex), key);
|
||||||
|
|
||||||
|
public (GlamourerApiEc, JObject?) GetStateName(string playerName, uint key)
|
||||||
|
=> Convert(_helpers.FindStates(playerName).FirstOrDefault(), key);
|
||||||
|
|
||||||
|
public (GlamourerApiEc, string?) GetStateBase64(int objectIndex, uint key)
|
||||||
|
=> ConvertBase64(_helpers.FindState(objectIndex), key);
|
||||||
|
|
||||||
|
public (GlamourerApiEc, string?) GetStateBase64Name(string objectName, uint key)
|
||||||
|
=> ConvertBase64(_helpers.FindStates(objectName).FirstOrDefault(), key);
|
||||||
|
|
||||||
|
public GlamourerApiEc ApplyState(object applyState, int objectIndex, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags);
|
||||||
|
if (Convert(applyState, flags, out var version) is not { } design)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidState, args);
|
||||||
|
|
||||||
|
if (_helpers.FindState(objectIndex) is not { } state)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
if (version < 3 && state.ModelData.ModelId != 0)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
|
||||||
|
|
||||||
|
ApplyDesign(state, design, key, flags);
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc ApplyStateName(object applyState, string playerName, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags);
|
||||||
|
if (Convert(applyState, flags, out var version) is not { } design)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidState, args);
|
||||||
|
|
||||||
|
var states = _helpers.FindExistingStates(playerName);
|
||||||
|
|
||||||
|
var any = false;
|
||||||
|
var anyUnlocked = false;
|
||||||
|
var anyHuman = false;
|
||||||
|
foreach (var state in states)
|
||||||
|
{
|
||||||
|
any = true;
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyUnlocked = true;
|
||||||
|
if (version < 3 && state.ModelData.ModelId != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyHuman = true;
|
||||||
|
ApplyDesign(state, design, key, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (any)
|
||||||
|
ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||||
|
|
||||||
|
if (!anyUnlocked)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
if (!anyHuman)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotHuman, args);
|
||||||
|
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc ReapplyState(int objectIndex, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags);
|
||||||
|
if (_helpers.FindExistingState(objectIndex, out var state) is not GlamourerApiEc.Success)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (state is null)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||||
|
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
Reapply(_objects.Objects[objectIndex], state, key, flags);
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc ReapplyStateName(string playerName, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags);
|
||||||
|
var states = _helpers.FindExistingStates(playerName);
|
||||||
|
|
||||||
|
var any = false;
|
||||||
|
var anyReapplied = false;
|
||||||
|
foreach (var state in states)
|
||||||
|
{
|
||||||
|
any = true;
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyReapplied = true;
|
||||||
|
anyReapplied |= Reapply(state, key, flags) is GlamourerApiEc.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (any)
|
||||||
|
ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||||
|
|
||||||
|
if (!anyReapplied)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc RevertState(int objectIndex, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags);
|
||||||
|
if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (state == null)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||||
|
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
Revert(state, key, flags);
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc RevertStateName(string playerName, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags);
|
||||||
|
var states = _helpers.FindExistingStates(playerName);
|
||||||
|
|
||||||
|
var any = false;
|
||||||
|
var anyUnlocked = false;
|
||||||
|
foreach (var state in states)
|
||||||
|
{
|
||||||
|
any = true;
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyUnlocked = true;
|
||||||
|
Revert(state, key, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (any)
|
||||||
|
ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||||
|
|
||||||
|
if (!anyUnlocked)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc UnlockState(int objectIndex, uint key)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Index", objectIndex, "Key", key);
|
||||||
|
if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (state == null)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||||
|
|
||||||
|
if (!state.Unlock(key))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc CanUnlock(int objectIndex, uint key, out bool isLocked, out bool canUnlock)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Index", objectIndex, "Key", key);
|
||||||
|
isLocked = false;
|
||||||
|
canUnlock = true;
|
||||||
|
if (_helpers.FindExistingState(objectIndex, out var state) is not GlamourerApiEc.Success)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
if (state is null)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
isLocked = state.IsLocked;
|
||||||
|
canUnlock = state.CanUnlock(key);
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc UnlockStateName(string playerName, uint key)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Name", playerName, "Key", key);
|
||||||
|
var states = _helpers.FindExistingStates(playerName);
|
||||||
|
|
||||||
|
var any = false;
|
||||||
|
var anyUnlocked = false;
|
||||||
|
foreach (var state in states)
|
||||||
|
{
|
||||||
|
any = true;
|
||||||
|
anyUnlocked |= state.Unlock(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (any)
|
||||||
|
ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||||
|
|
||||||
|
if (!anyUnlocked)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc DeletePlayerState(string playerName, ushort worldId, uint key)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Name", playerName, "World", worldId, "Key", key);
|
||||||
|
var states = _helpers.FindExistingStates(playerName).ToList();
|
||||||
|
if (states.Count is 0)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||||
|
|
||||||
|
var anyLocked = false;
|
||||||
|
foreach (var state in states)
|
||||||
|
{
|
||||||
|
if (state.CanUnlock(key))
|
||||||
|
_stateManager.DeleteState(state.Identifier);
|
||||||
|
else
|
||||||
|
anyLocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiHelpers.Return(anyLocked
|
||||||
|
? GlamourerApiEc.InvalidKey
|
||||||
|
: GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int UnlockAll(uint key)
|
||||||
|
=> _stateManager.Values.Count(state => state.Unlock(key));
|
||||||
|
|
||||||
|
public GlamourerApiEc RevertToAutomation(int objectIndex, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Index", objectIndex, "Key", key, "Flags", flags);
|
||||||
|
if (_helpers.FindExistingState(objectIndex, out var state) != GlamourerApiEc.Success)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (state == null)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||||
|
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
RevertToAutomation(_objects.Objects[objectIndex], state, key, flags);
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlamourerApiEc RevertToAutomationName(string playerName, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var args = ApiHelpers.Args("Name", playerName, "Key", key, "Flags", flags);
|
||||||
|
var states = _helpers.FindExistingStates(playerName);
|
||||||
|
|
||||||
|
var any = false;
|
||||||
|
var anyUnlocked = false;
|
||||||
|
var anyReverted = false;
|
||||||
|
foreach (var state in states)
|
||||||
|
{
|
||||||
|
any = true;
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
anyUnlocked = true;
|
||||||
|
anyReverted |= RevertToAutomation(state, key, flags) is GlamourerApiEc.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (any)
|
||||||
|
ApiHelpers.Return(GlamourerApiEc.NothingDone, args);
|
||||||
|
|
||||||
|
if (!anyReverted)
|
||||||
|
ApiHelpers.Return(GlamourerApiEc.ActorNotFound, args);
|
||||||
|
|
||||||
|
if (!anyUnlocked)
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
|
||||||
|
|
||||||
|
return ApiHelpers.Return(GlamourerApiEc.Success, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<bool>? AutoReloadGearChanged;
|
||||||
|
public event Action<nint>? StateChanged;
|
||||||
|
public event Action<IntPtr, StateChangeType>? StateChangedWithType;
|
||||||
|
public event Action<IntPtr, StateFinalizationType>? StateFinalized;
|
||||||
|
public event Action<bool>? GPoseChanged;
|
||||||
|
|
||||||
|
private void ApplyDesign(ActorState state, DesignBase design, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var once = (flags & ApplyFlag.Once) != 0;
|
||||||
|
var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true,
|
||||||
|
ResetMaterials: !once && key != 0, IsFinal: true);
|
||||||
|
_stateManager.ApplyDesign(state, design, settings);
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GlamourerApiEc Reapply(ActorState state, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
if (!_objects.TryGetValue(state.Identifier, out var actors) || !actors.Valid)
|
||||||
|
return GlamourerApiEc.ActorNotFound;
|
||||||
|
|
||||||
|
foreach (var actor in actors.Objects)
|
||||||
|
Reapply(actor, state, key, flags);
|
||||||
|
|
||||||
|
return GlamourerApiEc.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reapply(Actor actor, ActorState state, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var source = flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcFixed : StateSource.IpcManual;
|
||||||
|
_stateManager.ReapplyState(actor, state, false, source, true);
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Revert(ActorState state, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var source = flags.HasFlag(ApplyFlag.Once) ? StateSource.IpcFixed : StateSource.IpcManual;
|
||||||
|
switch (flags & (ApplyFlag.Equipment | ApplyFlag.Customization))
|
||||||
|
{
|
||||||
|
case ApplyFlag.Equipment: _stateManager.ResetEquip(state, source, key); break;
|
||||||
|
case ApplyFlag.Customization: _stateManager.ResetCustomize(state, source, key); break;
|
||||||
|
case ApplyFlag.Equipment | ApplyFlag.Customization: _stateManager.ResetState(state, source, key, true); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GlamourerApiEc RevertToAutomation(ActorState state, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
if (!_objects.TryGetValue(state.Identifier, out var actors) || !actors.Valid)
|
||||||
|
return GlamourerApiEc.ActorNotFound;
|
||||||
|
|
||||||
|
foreach (var actor in actors.Objects)
|
||||||
|
RevertToAutomation(actor, state, key, flags);
|
||||||
|
|
||||||
|
return GlamourerApiEc.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RevertToAutomation(Actor actor, ActorState state, uint key, ApplyFlag flags)
|
||||||
|
{
|
||||||
|
var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed;
|
||||||
|
_autoDesigns.ReapplyAutomation(actor, state.Identifier, state, true, false, out var forcedRedraw);
|
||||||
|
_stateManager.ReapplyAutomationState(actor, state, forcedRedraw, true, source);
|
||||||
|
ApiHelpers.Lock(state, key, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (GlamourerApiEc, JObject?) Convert(ActorState? state, uint key)
|
||||||
|
{
|
||||||
|
if (state == null)
|
||||||
|
return (GlamourerApiEc.ActorNotFound, null);
|
||||||
|
|
||||||
|
if (!state.CanUnlock(key))
|
||||||
|
return (GlamourerApiEc.InvalidKey, null);
|
||||||
|
|
||||||
|
return (GlamourerApiEc.Success, _converter.ShareJObject(state, ApplicationRules.All));
|
||||||
|
}
|
||||||
|
|
||||||
|
private (GlamourerApiEc, string?) ConvertBase64(ActorState? state, uint key)
|
||||||
|
{
|
||||||
|
var (ec, jObj) = Convert(state, key);
|
||||||
|
return (ec, jObj != null ? DesignConverter.ToBase64(jObj) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DesignBase? Convert(object? state, ApplyFlag flags, out byte version)
|
||||||
|
{
|
||||||
|
version = DesignConverter.Version;
|
||||||
|
return state switch
|
||||||
|
{
|
||||||
|
string s => _converter.FromBase64(s, (flags & ApplyFlag.Customization) != 0, (flags & ApplyFlag.Equipment) != 0, out version),
|
||||||
|
JObject j => _converter.FromJObject(j, (flags & ApplyFlag.Customization) != 0, (flags & ApplyFlag.Equipment) != 0),
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAutoRedrawChange(bool autoReload)
|
||||||
|
=> AutoReloadGearChanged?.Invoke(autoReload);
|
||||||
|
|
||||||
|
private void OnStateChanged(StateChangeType type, StateSource _2, ActorState _3, ActorData actors, ITransaction? _5)
|
||||||
|
{
|
||||||
|
Glamourer.Log.Excessive($"[OnStateChanged] State Changed with Type {type} [Affecting {actors.ToLazyString("nothing")}.]");
|
||||||
|
if (StateChanged != null)
|
||||||
|
foreach (var actor in actors.Objects)
|
||||||
|
StateChanged.Invoke(actor.Address);
|
||||||
|
|
||||||
|
if (StateChangedWithType != null)
|
||||||
|
foreach (var actor in actors.Objects)
|
||||||
|
StateChangedWithType.Invoke(actor.Address, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStateFinalized(StateFinalizationType type, ActorData actors)
|
||||||
|
{
|
||||||
|
Glamourer.Log.Verbose($"[OnStateUpdated] State Updated with Type {type}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||||
|
if (StateFinalized != null)
|
||||||
|
foreach (var actor in actors.Objects)
|
||||||
|
StateFinalized.Invoke(actor.Address, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGPoseChange(bool gPose)
|
||||||
|
=> GPoseChanged?.Invoke(gPose);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Glamourer.Designs;
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.Designs;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
|
@ -28,8 +29,7 @@ public static class ApplicationTypeExtensions
|
||||||
(ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."),
|
(ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."),
|
||||||
];
|
];
|
||||||
|
|
||||||
public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat(
|
public static ApplicationCollection Collection(this ApplicationType type)
|
||||||
this ApplicationType type, IDesignStandIn designStandIn)
|
|
||||||
{
|
{
|
||||||
var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0)
|
var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0)
|
||||||
| (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0)
|
| (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0)
|
||||||
|
|
@ -37,16 +37,22 @@ public static class ApplicationTypeExtensions
|
||||||
| (type.HasFlag(ApplicationType.GearCustomization) ? StainFlags : 0);
|
| (type.HasFlag(ApplicationType.GearCustomization) ? StainFlags : 0);
|
||||||
var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0;
|
var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0;
|
||||||
var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0;
|
var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0;
|
||||||
var crestFlag = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0;
|
var crestFlags = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0;
|
||||||
var metaFlag = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0)
|
var metaFlags = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.EarState : 0)
|
||||||
| (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0)
|
| (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0)
|
||||||
| (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0);
|
| (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0);
|
||||||
|
var bonusFlags = type.HasFlag(ApplicationType.Armor) ? BonusExtensions.All : 0;
|
||||||
|
|
||||||
if (designStandIn is not DesignBase design)
|
return new ApplicationCollection(equipFlags, bonusFlags, customizeFlags, crestFlags, parameterFlags, metaFlags);
|
||||||
return (equipFlags, customizeFlags, crestFlag, parameterFlags, metaFlag);
|
}
|
||||||
|
|
||||||
return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest,
|
public static ApplicationCollection ApplyWhat(this ApplicationType type, IDesignStandIn designStandIn)
|
||||||
parameterFlags & design.ApplyParameters, metaFlag & design.ApplyMeta);
|
{
|
||||||
|
if(designStandIn is not DesignBase design)
|
||||||
|
return type.Collection();
|
||||||
|
var ret = type.Collection().Restrict(design.Application);
|
||||||
|
ret.CustomizeRaw = ret.CustomizeRaw.FixApplication(design.CustomizeSet);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand;
|
public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Designs.Special;
|
using Glamourer.Designs.Special;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
using Glamourer.Interop.Structs;
|
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Automation;
|
namespace Glamourer.Automation;
|
||||||
|
|
@ -61,6 +61,6 @@ public class AutoDesign
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat()
|
public ApplicationCollection ApplyWhat()
|
||||||
=> Type.ApplyWhat(Design);
|
=> Type.ApplyWhat(Design);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,34 +4,35 @@ using Glamourer.Designs;
|
||||||
using Glamourer.Designs.Links;
|
using Glamourer.Designs.Links;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.GameData.DataContainers;
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Automation;
|
namespace Glamourer.Automation;
|
||||||
|
|
||||||
public sealed class AutoDesignApplier : IDisposable
|
public sealed class AutoDesignApplier : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly AutoDesignManager _manager;
|
private readonly AutoDesignManager _manager;
|
||||||
private readonly StateManager _state;
|
private readonly StateManager _state;
|
||||||
private readonly JobService _jobs;
|
private readonly JobService _jobs;
|
||||||
private readonly EquippedGearset _equippedGearset;
|
private readonly EquippedGearset _equippedGearset;
|
||||||
private readonly ActorManager _actors;
|
private readonly ActorManager _actors;
|
||||||
private readonly AutomationChanged _event;
|
private readonly AutomationChanged _event;
|
||||||
private readonly ObjectManager _objects;
|
private readonly ActorObjectManager _objects;
|
||||||
private readonly WeaponLoading _weapons;
|
private readonly WeaponLoading _weapons;
|
||||||
private readonly HumanModelList _humans;
|
private readonly HumanModelList _humans;
|
||||||
private readonly DesignMerger _designMerger;
|
private readonly DesignMerger _designMerger;
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
|
||||||
private readonly JobChangeState _jobChangeState;
|
private readonly JobChangeState _jobChangeState;
|
||||||
|
|
||||||
public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors,
|
public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors,
|
||||||
AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState,
|
AutomationChanged @event, ActorObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState,
|
||||||
EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState)
|
EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
@ -53,6 +54,15 @@ public sealed class AutoDesignApplier : IDisposable
|
||||||
_equippedGearset.Subscribe(OnEquippedGearset, EquippedGearset.Priority.AutoDesignApplier);
|
_equippedGearset.Subscribe(OnEquippedGearset, EquippedGearset.Priority.AutoDesignApplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnEnableAutoDesignsChanged(bool value)
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var state in _state.Values)
|
||||||
|
state.Sources.RemoveFixedDesignSources();
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_weapons.Unsubscribe(OnWeaponLoading);
|
_weapons.Unsubscribe(OnWeaponLoading);
|
||||||
|
|
@ -75,7 +85,7 @@ public sealed class AutoDesignApplier : IDisposable
|
||||||
{
|
{
|
||||||
case EquipSlot.MainHand:
|
case EquipSlot.MainHand:
|
||||||
{
|
{
|
||||||
if (_jobChangeState.TryGetValue(current.Type, out var data))
|
if (_jobChangeState.TryGetValue(current.Type, actor.Job, false, out var data))
|
||||||
{
|
{
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}.");
|
$"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}.");
|
||||||
|
|
@ -87,7 +97,7 @@ public sealed class AutoDesignApplier : IDisposable
|
||||||
}
|
}
|
||||||
case EquipSlot.OffHand when current.Type == state.BaseData.MainhandType.Offhand():
|
case EquipSlot.OffHand when current.Type == state.BaseData.MainhandType.Offhand():
|
||||||
{
|
{
|
||||||
if (_jobChangeState.TryGetValue(current.Type, out var data))
|
if (_jobChangeState.TryGetValue(current.Type, actor.Job, false, out var data))
|
||||||
{
|
{
|
||||||
Glamourer.Log.Verbose(
|
Glamourer.Log.Verbose(
|
||||||
$"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}.");
|
$"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}.");
|
||||||
|
|
@ -143,16 +153,15 @@ public sealed class AutoDesignApplier : IDisposable
|
||||||
if (newSet is not { Enabled: true })
|
if (newSet is not { Enabled: true })
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_objects.Update();
|
|
||||||
foreach (var id in newSet.Identifiers)
|
foreach (var id in newSet.Identifiers)
|
||||||
{
|
{
|
||||||
if (_objects.TryGetValue(id, out var data))
|
if (_objects.TryGetValue(id, out var data))
|
||||||
{
|
{
|
||||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||||
{
|
{
|
||||||
Reduce(data.Objects[0], state, newSet, false, false);
|
Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw);
|
||||||
foreach (var actor in data.Objects)
|
foreach (var actor in data.Objects)
|
||||||
_state.ReapplyState(actor, StateSource.Fixed);
|
_state.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Fixed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (_objects.TryGetValueAllWorld(id, out data) || _objects.TryGetValueNonOwned(id, out data))
|
else if (_objects.TryGetValueAllWorld(id, out data) || _objects.TryGetValueNonOwned(id, out data))
|
||||||
|
|
@ -162,8 +171,8 @@ public sealed class AutoDesignApplier : IDisposable
|
||||||
var specificId = actor.GetIdentifier(_actors);
|
var specificId = actor.GetIdentifier(_actors);
|
||||||
if (_state.GetOrCreate(specificId, actor, out var state))
|
if (_state.GetOrCreate(specificId, actor, out var state))
|
||||||
{
|
{
|
||||||
Reduce(actor, state, newSet, false, false);
|
Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw);
|
||||||
_state.ReapplyState(actor, StateSource.Fixed);
|
_state.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Fixed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -202,7 +211,7 @@ public sealed class AutoDesignApplier : IDisposable
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_state.TryGetValue(id, out var state))
|
if (!_state.GetOrCreate(actor, out var state))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (oldJob.Id == newJob.Id && state.LastJob == newJob.Id)
|
if (oldJob.Id == newJob.Id && state.LastJob == newJob.Id)
|
||||||
|
|
@ -210,19 +219,21 @@ public sealed class AutoDesignApplier : IDisposable
|
||||||
|
|
||||||
var respectManual = state.LastJob == newJob.Id;
|
var respectManual = state.LastJob == newJob.Id;
|
||||||
state.LastJob = actor.Job;
|
state.LastJob = actor.Job;
|
||||||
Reduce(actor, state, set, respectManual, true);
|
Reduce(actor, state, set, respectManual, true, true, out var forcedRedraw);
|
||||||
_state.ReapplyState(actor, StateSource.Fixed);
|
_state.ReapplyState(actor, forcedRedraw, StateSource.Fixed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state)
|
public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset, bool forcedNew, out bool forcedRedraw)
|
||||||
{
|
{
|
||||||
|
forcedRedraw = false;
|
||||||
if (!_config.EnableAutoDesigns)
|
if (!_config.EnableAutoDesigns)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!GetPlayerSet(identifier, out var set))
|
if (reset)
|
||||||
return;
|
_state.ResetState(state, StateSource.Game);
|
||||||
|
|
||||||
Reduce(actor, state, set, false, false);
|
if (GetPlayerSet(identifier, out var set))
|
||||||
|
Reduce(actor, state, set, false, false, forcedNew, out forcedRedraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Reduce(Actor actor, ActorIdentifier identifier, [NotNullWhen(true)] out ActorState? state)
|
public bool Reduce(Actor actor, ActorIdentifier identifier, [NotNullWhen(true)] out ActorState? state)
|
||||||
|
|
@ -230,9 +241,6 @@ public sealed class AutoDesignApplier : IDisposable
|
||||||
AutoDesignSet set;
|
AutoDesignSet set;
|
||||||
if (!_state.TryGetValue(identifier, out state))
|
if (!_state.TryGetValue(identifier, out state))
|
||||||
{
|
{
|
||||||
if (!_config.EnableAutoDesigns)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!GetPlayerSet(identifier, out set!))
|
if (!GetPlayerSet(identifier, out set!))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
@ -249,41 +257,83 @@ public sealed class AutoDesignApplier : IDisposable
|
||||||
var respectManual = !state.UpdateTerritory(_clientState.TerritoryType) || !_config.RevertManualChangesOnZoneChange;
|
var respectManual = !state.UpdateTerritory(_clientState.TerritoryType) || !_config.RevertManualChangesOnZoneChange;
|
||||||
if (!respectManual)
|
if (!respectManual)
|
||||||
_state.ResetState(state, StateSource.Game);
|
_state.ResetState(state, StateSource.Game);
|
||||||
Reduce(actor, state, set, respectManual, false);
|
Reduce(actor, state, set, respectManual, false, false, out _);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange)
|
private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange, bool newApplication,
|
||||||
|
out bool forcedRedraw)
|
||||||
{
|
{
|
||||||
if (set.BaseState is AutoDesignSet.Base.Game)
|
if (set.BaseState is AutoDesignSet.Base.Game)
|
||||||
|
{
|
||||||
_state.ResetStateFixed(state, respectManual);
|
_state.ResetStateFixed(state, respectManual);
|
||||||
|
}
|
||||||
else if (!respectManual)
|
else if (!respectManual)
|
||||||
|
{
|
||||||
state.Sources.RemoveFixedDesignSources();
|
state.Sources.RemoveFixedDesignSources();
|
||||||
|
for (var i = 0; i < state.Materials.Values.Count; ++i)
|
||||||
|
{
|
||||||
|
var (key, value) = state.Materials.Values[i];
|
||||||
|
if (value.Source is StateSource.Fixed)
|
||||||
|
state.Materials.UpdateValue(key, new MaterialValueState(value.Game, value.Model, value.DrawData, StateSource.Manual),
|
||||||
|
out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
|
forcedRedraw = false;
|
||||||
|
if (!_humans.IsHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (actor.IsTransformed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var mergedDesign = _designMerger.Merge(
|
var mergedDesign = _designMerger.Merge(
|
||||||
set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design.AllLinks.Select(l => (l.Design, l.Flags & d.Type))),
|
set.Designs.Where(d => d.IsActive(actor))
|
||||||
|
.SelectMany(d => d.Design.AllLinks(newApplication).Select(l => (l.Design, l.Flags & d.Type, d.Jobs.Flags))),
|
||||||
state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods);
|
state.ModelData.Customize, state.BaseData, true, _config.AlwaysApplyAssociatedMods);
|
||||||
_state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, set.BaseState is AutoDesignSet.Base.Game));
|
|
||||||
|
if (_objects.IsInGPose && actor.IsGPoseOrCutscene)
|
||||||
|
{
|
||||||
|
mergedDesign.ResetTemporarySettings = false;
|
||||||
|
mergedDesign.AssociatedMods.Clear();
|
||||||
|
}
|
||||||
|
else if (set.ResetTemporarySettings)
|
||||||
|
{
|
||||||
|
mergedDesign.ResetTemporarySettings = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false, false, false));
|
||||||
|
forcedRedraw = mergedDesign.ForcedRedraw;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Get world-specific first and all-world afterward. </summary>
|
/// <summary> Get world-specific first and all-world afterward. </summary>
|
||||||
private bool GetPlayerSet(ActorIdentifier identifier, [NotNullWhen(true)] out AutoDesignSet? set)
|
private bool GetPlayerSet(ActorIdentifier identifier, [NotNullWhen(true)] out AutoDesignSet? set)
|
||||||
{
|
{
|
||||||
|
if (!_config.EnableAutoDesigns)
|
||||||
|
{
|
||||||
|
set = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
switch (identifier.Type)
|
switch (identifier.Type)
|
||||||
{
|
{
|
||||||
case IdentifierType.Player:
|
case IdentifierType.Player:
|
||||||
if (_manager.EnabledSets.TryGetValue(identifier, out set))
|
if (_manager.EnabledSets.TryGetValue(identifier, out set))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
identifier = _actors.CreatePlayer(identifier.PlayerName, ushort.MaxValue);
|
identifier = _actors.CreatePlayer(identifier.PlayerName, WorldId.AnyWorld);
|
||||||
return _manager.EnabledSets.TryGetValue(identifier, out set);
|
return _manager.EnabledSets.TryGetValue(identifier, out set);
|
||||||
case IdentifierType.Retainer:
|
case IdentifierType.Retainer:
|
||||||
case IdentifierType.Npc:
|
case IdentifierType.Npc:
|
||||||
return _manager.EnabledSets.TryGetValue(identifier, out set);
|
return _manager.EnabledSets.TryGetValue(identifier, out set);
|
||||||
case IdentifierType.Owned:
|
case IdentifierType.Owned:
|
||||||
|
if (_manager.EnabledSets.TryGetValue(identifier, out set))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
identifier = _actors.CreateOwned(identifier.PlayerName, WorldId.AnyWorld, identifier.Kind, identifier.DataId);
|
||||||
|
if (_manager.EnabledSets.TryGetValue(identifier, out set))
|
||||||
|
return true;
|
||||||
|
|
||||||
identifier = _actors.CreateNpc(identifier.Kind, identifier.DataId);
|
identifier = _actors.CreateNpc(identifier.Kind, identifier.DataId);
|
||||||
return _manager.EnabledSets.TryGetValue(identifier, out set);
|
return _manager.EnabledSets.TryGetValue(identifier, out set);
|
||||||
default:
|
default:
|
||||||
|
|
@ -308,10 +358,10 @@ public sealed class AutoDesignApplier : IDisposable
|
||||||
|
|
||||||
var respectManual = prior == id;
|
var respectManual = prior == id;
|
||||||
NewGearsetId = id;
|
NewGearsetId = id;
|
||||||
Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob);
|
Reduce(data.Objects[0], state, set, respectManual, job != state.LastJob, prior == id, out var forcedRedraw);
|
||||||
NewGearsetId = -1;
|
NewGearsetId = -1;
|
||||||
foreach (var actor in data.Objects)
|
foreach (var actor in data.Objects)
|
||||||
_state.ReapplyState(actor, StateSource.Fixed);
|
_state.ReapplyState(actor, forcedRedraw, StateSource.Fixed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static unsafe bool CheckGearset(short check)
|
public static unsafe bool CheckGearset(short check)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Designs.History;
|
||||||
using Glamourer.Designs.Special;
|
using Glamourer.Designs.Special;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
|
|
@ -9,6 +10,7 @@ using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Extensions;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -28,6 +30,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
||||||
private readonly AutomationChanged _event;
|
private readonly AutomationChanged _event;
|
||||||
private readonly DesignChanged _designEvent;
|
private readonly DesignChanged _designEvent;
|
||||||
private readonly RandomDesignGenerator _randomDesigns;
|
private readonly RandomDesignGenerator _randomDesigns;
|
||||||
|
private readonly QuickSelectedDesign _quickSelectedDesign;
|
||||||
|
|
||||||
private readonly List<AutoDesignSet> _data = [];
|
private readonly List<AutoDesignSet> _data = [];
|
||||||
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = [];
|
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = [];
|
||||||
|
|
@ -36,15 +39,17 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
||||||
=> _enabled;
|
=> _enabled;
|
||||||
|
|
||||||
public AutoDesignManager(JobService jobs, ActorManager actors, SaveService saveService, DesignManager designs, AutomationChanged @event,
|
public AutoDesignManager(JobService jobs, ActorManager actors, SaveService saveService, DesignManager designs, AutomationChanged @event,
|
||||||
FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent, RandomDesignGenerator randomDesigns)
|
FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent, RandomDesignGenerator randomDesigns,
|
||||||
|
QuickSelectedDesign quickSelectedDesign)
|
||||||
{
|
{
|
||||||
_jobs = jobs;
|
_jobs = jobs;
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
_saveService = saveService;
|
_saveService = saveService;
|
||||||
_designs = designs;
|
_designs = designs;
|
||||||
_event = @event;
|
_event = @event;
|
||||||
_designEvent = designEvent;
|
_designEvent = designEvent;
|
||||||
_randomDesigns = randomDesigns;
|
_randomDesigns = randomDesigns;
|
||||||
|
_quickSelectedDesign = quickSelectedDesign;
|
||||||
_designEvent.Subscribe(OnDesignChange, DesignChanged.Priority.AutoDesignManager);
|
_designEvent.Subscribe(OnDesignChange, DesignChanged.Priority.AutoDesignManager);
|
||||||
Load();
|
Load();
|
||||||
migrator.ConsumeMigratedData(_actors, fileSystem, this);
|
migrator.ConsumeMigratedData(_actors, fileSystem, this);
|
||||||
|
|
@ -230,6 +235,22 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
||||||
_event.Invoke(AutomationChanged.Type.ChangedBase, set, (old, newBase));
|
_event.Invoke(AutomationChanged.Type.ChangedBase, set, (old, newBase));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ChangeResetSettings(int whichSet, bool newValue)
|
||||||
|
{
|
||||||
|
if (whichSet >= _data.Count || whichSet < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var set = _data[whichSet];
|
||||||
|
if (newValue == set.ResetTemporarySettings)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var old = set.ResetTemporarySettings;
|
||||||
|
set.ResetTemporarySettings = newValue;
|
||||||
|
Save();
|
||||||
|
Glamourer.Log.Debug($"Changed resetting of temporary settings of set {whichSet + 1} from {old} to {newValue}.");
|
||||||
|
_event.Invoke(AutomationChanged.Type.ChangedTemporarySettingsReset, set, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
public void AddDesign(AutoDesignSet set, IDesignStandIn design)
|
public void AddDesign(AutoDesignSet set, IDesignStandIn design)
|
||||||
{
|
{
|
||||||
var newDesign = new AutoDesign()
|
var newDesign = new AutoDesign()
|
||||||
|
|
@ -440,8 +461,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
||||||
|
|
||||||
var set = new AutoDesignSet(name, group)
|
var set = new AutoDesignSet(name, group)
|
||||||
{
|
{
|
||||||
Enabled = obj["Enabled"]?.ToObject<bool>() ?? false,
|
Enabled = obj["Enabled"]?.ToObject<bool>() ?? false,
|
||||||
BaseState = obj["BaseState"]?.ToObject<AutoDesignSet.Base>() ?? AutoDesignSet.Base.Current,
|
ResetTemporarySettings = obj["ResetTemporarySettings"]?.ToObject<bool>() ?? false,
|
||||||
|
BaseState = obj["BaseState"]?.ToObject<AutoDesignSet.Base>() ?? AutoDesignSet.Base.Current,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (set.Enabled)
|
if (set.Enabled)
|
||||||
|
|
@ -486,6 +508,10 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
||||||
{
|
{
|
||||||
design = new RandomDesign(_randomDesigns);
|
design = new RandomDesign(_randomDesigns);
|
||||||
}
|
}
|
||||||
|
else if (designIdentifier is QuickSelectedDesign.SerializedName)
|
||||||
|
{
|
||||||
|
design = _quickSelectedDesign;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (designIdentifier.Length == 0)
|
if (designIdentifier.Length == 0)
|
||||||
|
|
@ -562,12 +588,13 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
||||||
IdentifierType.Player => true,
|
IdentifierType.Player => true,
|
||||||
IdentifierType.Retainer => true,
|
IdentifierType.Retainer => true,
|
||||||
IdentifierType.Npc => true,
|
IdentifierType.Npc => true,
|
||||||
|
IdentifierType.Owned => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!validType)
|
if (!validType)
|
||||||
{
|
{
|
||||||
group = Array.Empty<ActorIdentifier>();
|
group = [];
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -593,8 +620,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
||||||
? ActorIdentifier.RetainerType.Mannequin
|
? ActorIdentifier.RetainerType.Mannequin
|
||||||
: ActorIdentifier.RetainerType.Bell).CreatePermanent(),
|
: ActorIdentifier.RetainerType.Bell).CreatePermanent(),
|
||||||
],
|
],
|
||||||
IdentifierType.Npc => CreateNpcs(_actors, identifier),
|
IdentifierType.Npc => CreateNpcs(_actors, identifier),
|
||||||
_ => [],
|
IdentifierType.Owned => CreateNpcs(_actors, identifier),
|
||||||
|
_ => [],
|
||||||
};
|
};
|
||||||
|
|
||||||
static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier)
|
static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier)
|
||||||
|
|
@ -608,12 +636,11 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
||||||
};
|
};
|
||||||
return table.Where(kvp => kvp.Value == name)
|
return table.Where(kvp => kvp.Value == name)
|
||||||
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id,
|
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id,
|
||||||
identifier.Kind,
|
identifier.Kind, kvp.Key)).ToArray();
|
||||||
kvp.Key)).ToArray();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDesignChange(DesignChanged.Type type, Design design, object? data)
|
private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? _)
|
||||||
{
|
{
|
||||||
if (type is not DesignChanged.Type.Deleted)
|
if (type is not DesignChanged.Type.Deleted)
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ public class AutoDesignSet(string name, ActorIdentifier[] identifiers, List<Auto
|
||||||
public string Name = name;
|
public string Name = name;
|
||||||
public ActorIdentifier[] Identifiers = identifiers;
|
public ActorIdentifier[] Identifiers = identifiers;
|
||||||
public bool Enabled;
|
public bool Enabled;
|
||||||
public Base BaseState = Base.Current;
|
public Base BaseState = Base.Current;
|
||||||
|
public bool ResetTemporarySettings = false;
|
||||||
|
|
||||||
public JObject Serialize()
|
public JObject Serialize()
|
||||||
{
|
{
|
||||||
|
|
@ -20,11 +21,12 @@ public class AutoDesignSet(string name, ActorIdentifier[] identifiers, List<Auto
|
||||||
|
|
||||||
return new JObject()
|
return new JObject()
|
||||||
{
|
{
|
||||||
["Name"] = Name,
|
["Name"] = Name,
|
||||||
["Identifier"] = Identifiers[0].ToJson(),
|
["Identifier"] = Identifiers[0].ToJson(),
|
||||||
["Enabled"] = Enabled,
|
["Enabled"] = Enabled,
|
||||||
["BaseState"] = BaseState.ToString(),
|
["BaseState"] = BaseState.ToString(),
|
||||||
["Designs"] = list,
|
["ResetTemporarySettings"] = ResetTemporarySettings.ToString(),
|
||||||
|
["Designs"] = list,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,91 @@
|
||||||
using Dalamud.Configuration;
|
using Dalamud.Configuration;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
|
using Glamourer.Gui.Tabs.DesignTab;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Extensions;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
|
||||||
|
|
||||||
namespace Glamourer;
|
namespace Glamourer;
|
||||||
|
|
||||||
|
public enum HeightDisplayType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Centimetre,
|
||||||
|
Metre,
|
||||||
|
Wrong,
|
||||||
|
WrongFoot,
|
||||||
|
Corgi,
|
||||||
|
OlympicPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DefaultDesignSettings
|
||||||
|
{
|
||||||
|
public bool AlwaysForceRedrawing = false;
|
||||||
|
public bool ResetAdvancedDyes = false;
|
||||||
|
public bool ShowQuickDesignBar = true;
|
||||||
|
public bool ResetTemporarySettings = false;
|
||||||
|
public bool Locked = false;
|
||||||
|
}
|
||||||
|
|
||||||
public class Configuration : IPluginConfiguration, ISavable
|
public class Configuration : IPluginConfiguration, ISavable
|
||||||
{
|
{
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public readonly EphemeralConfig Ephemeral;
|
public readonly EphemeralConfig Ephemeral;
|
||||||
|
|
||||||
public bool UseRestrictedGearProtection { get; set; } = false;
|
public bool AttachToPcp { get; set; } = true;
|
||||||
public bool OpenFoldersByDefault { get; set; } = false;
|
public bool UseRestrictedGearProtection { get; set; } = false;
|
||||||
public bool AutoRedrawEquipOnChanges { get; set; } = false;
|
public bool OpenFoldersByDefault { get; set; } = false;
|
||||||
public bool EnableAutoDesigns { get; set; } = true;
|
public bool AutoRedrawEquipOnChanges { get; set; } = false;
|
||||||
public bool HideApplyCheckmarks { get; set; } = false;
|
public bool EnableAutoDesigns { get; set; } = true;
|
||||||
public bool SmallEquip { get; set; } = false;
|
public bool HideApplyCheckmarks { get; set; } = false;
|
||||||
public bool UnlockedItemMode { get; set; } = false;
|
public bool SmallEquip { get; set; } = false;
|
||||||
public byte DisableFestivals { get; set; } = 1;
|
public bool UnlockedItemMode { get; set; } = false;
|
||||||
public bool EnableGameContextMenu { get; set; } = true;
|
public byte DisableFestivals { get; set; } = 1;
|
||||||
public bool HideWindowInCutscene { get; set; } = false;
|
public bool EnableGameContextMenu { get; set; } = true;
|
||||||
public bool ShowAutomationSetEditing { get; set; } = true;
|
public bool HideWindowInCutscene { get; set; } = false;
|
||||||
public bool ShowAllAutomatedApplicationRules { get; set; } = true;
|
public bool ShowAutomationSetEditing { get; set; } = true;
|
||||||
public bool ShowUnlockedItemWarnings { get; set; } = true;
|
public bool ShowAllAutomatedApplicationRules { get; set; } = true;
|
||||||
public bool RevertManualChangesOnZoneChange { get; set; } = false;
|
public bool ShowUnlockedItemWarnings { get; set; } = true;
|
||||||
public bool ShowQuickBarInTabs { get; set; } = true;
|
public bool RevertManualChangesOnZoneChange { get; set; } = false;
|
||||||
public bool OpenWindowAtStart { get; set; } = false;
|
public bool ShowQuickBarInTabs { get; set; } = true;
|
||||||
public bool ShowWindowWhenUiHidden { get; set; } = false;
|
public bool OpenWindowAtStart { get; set; } = false;
|
||||||
public bool UseAdvancedParameters { get; set; } = true;
|
public bool ShowWindowWhenUiHidden { get; set; } = false;
|
||||||
public bool UseAdvancedDyes { get; set; } = true;
|
public bool KeepAdvancedDyesAttached { get; set; } = true;
|
||||||
public bool KeepAdvancedDyesAttached { get; set; } = true;
|
public bool ShowPalettePlusImport { get; set; } = true;
|
||||||
public bool ShowPalettePlusImport { get; set; } = true;
|
public bool UseFloatForColors { get; set; } = true;
|
||||||
public bool UseFloatForColors { get; set; } = true;
|
public bool UseRgbForColors { get; set; } = true;
|
||||||
public bool UseRgbForColors { get; set; } = true;
|
public bool ShowColorConfig { get; set; } = true;
|
||||||
public bool ShowColorConfig { get; set; } = true;
|
public bool ChangeEntireItem { get; set; } = false;
|
||||||
public bool ChangeEntireItem { get; set; } = false;
|
public bool AlwaysApplyAssociatedMods { get; set; } = true;
|
||||||
public bool AlwaysApplyAssociatedMods { get; set; } = false;
|
public bool UseTemporarySettings { get; set; } = true;
|
||||||
public bool AllowDoubleClickToApply { get; set; } = false;
|
public bool AllowDoubleClickToApply { get; set; } = false;
|
||||||
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
|
public bool RespectManualOnAutomationUpdate { get; set; } = false;
|
||||||
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
public bool PreventRandomRepeats { get; set; } = false;
|
||||||
public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New;
|
public string PcpFolder { get; set; } = "PCP";
|
||||||
|
public string PcpColor { get; set; } = "";
|
||||||
|
|
||||||
|
public DesignPanelFlag HideDesignPanel { get; set; } = 0;
|
||||||
|
public DesignPanelFlag AutoExpandDesignPanel { get; set; } = 0;
|
||||||
|
|
||||||
|
public DefaultDesignSettings DefaultDesignSettings { get; set; } = new();
|
||||||
|
|
||||||
|
public HeightDisplayType HeightDisplayType { get; set; } = HeightDisplayType.Centimetre;
|
||||||
|
public RenameField ShowRename { get; set; } = RenameField.BothDataPrio;
|
||||||
|
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
|
||||||
|
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
||||||
|
public DoubleModifier IncognitoModifier { get; set; } = new(ModifierHotkey.Control);
|
||||||
|
public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New;
|
||||||
|
|
||||||
public QdbButtons QdbButtons { get; set; } =
|
public QdbButtons QdbButtons { get; set; } =
|
||||||
QdbButtons.ApplyDesign | QdbButtons.RevertAll | QdbButtons.RevertAutomation | QdbButtons.RevertAdvanced;
|
QdbButtons.ApplyDesign | QdbButtons.RevertAll | QdbButtons.RevertAutomation | QdbButtons.RevertAdvancedDyes;
|
||||||
|
|
||||||
[JsonConverter(typeof(SortModeConverter))]
|
[JsonConverter(typeof(SortModeConverter))]
|
||||||
[JsonProperty(Order = int.MaxValue)]
|
[JsonProperty(Order = int.MaxValue)]
|
||||||
|
|
@ -127,10 +162,10 @@ public class Configuration : IPluginConfiguration, ISavable
|
||||||
|
|
||||||
public static class Constants
|
public static class Constants
|
||||||
{
|
{
|
||||||
public const int CurrentVersion = 6;
|
public const int CurrentVersion = 8;
|
||||||
|
|
||||||
public static readonly ISortMode<Design>[] ValidSortModes =
|
public static readonly ISortMode<Design>[] ValidSortModes =
|
||||||
{
|
[
|
||||||
ISortMode<Design>.FoldersFirst,
|
ISortMode<Design>.FoldersFirst,
|
||||||
ISortMode<Design>.Lexicographical,
|
ISortMode<Design>.Lexicographical,
|
||||||
new DesignFileSystem.CreationDate(),
|
new DesignFileSystem.CreationDate(),
|
||||||
|
|
@ -143,7 +178,7 @@ public class Configuration : IPluginConfiguration, ISavable
|
||||||
ISortMode<Design>.InverseFoldersLast,
|
ISortMode<Design>.InverseFoldersLast,
|
||||||
ISortMode<Design>.InternalOrder,
|
ISortMode<Design>.InternalOrder,
|
||||||
ISortMode<Design>.InverseInternalOrder,
|
ISortMode<Design>.InverseInternalOrder,
|
||||||
};
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Convert SortMode Types to their name. </summary>
|
/// <summary> Convert SortMode Types to their name. </summary>
|
||||||
|
|
|
||||||
96
Glamourer/DesignPanelFlag.cs
Normal file
96
Glamourer/DesignPanelFlag.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using OtterGui.Text;
|
||||||
|
using OtterGui.Text.EndObjects;
|
||||||
|
|
||||||
|
namespace Glamourer;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum DesignPanelFlag : uint
|
||||||
|
{
|
||||||
|
Customization = 0x0001,
|
||||||
|
Equipment = 0x0002,
|
||||||
|
AdvancedCustomizations = 0x0004,
|
||||||
|
AdvancedDyes = 0x0008,
|
||||||
|
AppearanceDetails = 0x0010,
|
||||||
|
DesignDetails = 0x0020,
|
||||||
|
ModAssociations = 0x0040,
|
||||||
|
DesignLinks = 0x0080,
|
||||||
|
ApplicationRules = 0x0100,
|
||||||
|
DebugData = 0x0200,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DesignPanelFlagExtensions
|
||||||
|
{
|
||||||
|
public static ReadOnlySpan<byte> ToName(this DesignPanelFlag flag)
|
||||||
|
=> flag switch
|
||||||
|
{
|
||||||
|
DesignPanelFlag.Customization => "Customization"u8,
|
||||||
|
DesignPanelFlag.Equipment => "Equipment"u8,
|
||||||
|
DesignPanelFlag.AdvancedCustomizations => "Advanced Customization"u8,
|
||||||
|
DesignPanelFlag.AdvancedDyes => "Advanced Dyes"u8,
|
||||||
|
DesignPanelFlag.DesignDetails => "Design Details"u8,
|
||||||
|
DesignPanelFlag.ApplicationRules => "Application Rules"u8,
|
||||||
|
DesignPanelFlag.ModAssociations => "Mod Associations"u8,
|
||||||
|
DesignPanelFlag.DesignLinks => "Design Links"u8,
|
||||||
|
DesignPanelFlag.DebugData => "Debug Data"u8,
|
||||||
|
DesignPanelFlag.AppearanceDetails => "Appearance Details"u8,
|
||||||
|
_ => ""u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static CollapsingHeader Header(this DesignPanelFlag flag, Configuration config)
|
||||||
|
{
|
||||||
|
if (config.HideDesignPanel.HasFlag(flag))
|
||||||
|
return new CollapsingHeader()
|
||||||
|
{
|
||||||
|
Disposed = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var expand = config.AutoExpandDesignPanel.HasFlag(flag);
|
||||||
|
return ImUtf8.CollapsingHeaderId(flag.ToName(), expand ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawTable(ReadOnlySpan<byte> label, DesignPanelFlag hidden, DesignPanelFlag expanded, Action<DesignPanelFlag> setterHide,
|
||||||
|
Action<DesignPanelFlag> setterExpand)
|
||||||
|
{
|
||||||
|
var checkBoxWidth = Math.Max(ImGui.GetFrameHeight(), ImUtf8.CalcTextSize("Expand"u8).X);
|
||||||
|
var textWidth = ImUtf8.CalcTextSize(DesignPanelFlag.AdvancedCustomizations.ToName()).X;
|
||||||
|
var tableSize = 2 * (textWidth + 2 * checkBoxWidth) + 10 * ImGui.GetStyle().CellPadding.X + 2 * ImGui.GetStyle().WindowPadding.X + 2 * ImGui.GetStyle().FrameBorderSize;
|
||||||
|
using var table = ImUtf8.Table(label, 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Borders, new Vector2(tableSize, 6 * ImGui.GetFrameHeight()));
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var headerColor = ImGui.GetColorU32(ImGuiCol.TableHeaderBg);
|
||||||
|
var checkBoxOffset = (checkBoxWidth - ImGui.GetFrameHeight()) / 2;
|
||||||
|
ImUtf8.TableSetupColumn("Panel##1"u8, ImGuiTableColumnFlags.WidthFixed, textWidth);
|
||||||
|
ImUtf8.TableSetupColumn("Show##1"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth);
|
||||||
|
ImUtf8.TableSetupColumn("Expand##1"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth);
|
||||||
|
ImUtf8.TableSetupColumn("Panel##2"u8, ImGuiTableColumnFlags.WidthFixed, textWidth);
|
||||||
|
ImUtf8.TableSetupColumn("Show##2"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth);
|
||||||
|
ImUtf8.TableSetupColumn("Expand##2"u8, ImGuiTableColumnFlags.WidthFixed, checkBoxWidth);
|
||||||
|
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
foreach (var panel in Enum.GetValues<DesignPanelFlag>())
|
||||||
|
{
|
||||||
|
using var id = ImUtf8.PushId((int)panel);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, headerColor);
|
||||||
|
ImUtf8.TextFrameAligned(panel.ToName());
|
||||||
|
var isShown = !hidden.HasFlag(panel);
|
||||||
|
var isExpanded = expanded.HasFlag(panel);
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + checkBoxOffset);
|
||||||
|
if (ImUtf8.Checkbox("##show"u8, ref isShown))
|
||||||
|
setterHide.Invoke(isShown ? hidden & ~panel : hidden | panel);
|
||||||
|
ImUtf8.HoverTooltip(
|
||||||
|
"Show this panel and associated functionality in all relevant tabs.\n\nToggling this off does NOT disable any functionality, just the display of it, so hide panels at your own risk."u8);
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + checkBoxOffset);
|
||||||
|
if (ImUtf8.Checkbox("##expand"u8, ref isExpanded))
|
||||||
|
setterExpand.Invoke(isExpanded ? expanded | panel : expanded & ~panel);
|
||||||
|
ImUtf8.HoverTooltip("Expand this panel by default in all relevant tabs."u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
Glamourer/Designs/ApplicationCollection.cs
Normal file
68
Glamourer/Designs/ApplicationCollection.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.GameData;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
|
public record struct ApplicationCollection(
|
||||||
|
EquipFlag Equip,
|
||||||
|
BonusItemFlag BonusItem,
|
||||||
|
CustomizeFlag CustomizeRaw,
|
||||||
|
CrestFlag Crest,
|
||||||
|
CustomizeParameterFlag Parameters,
|
||||||
|
MetaFlag Meta)
|
||||||
|
{
|
||||||
|
public static readonly ApplicationCollection All = new(EquipFlagExtensions.All, BonusExtensions.All,
|
||||||
|
CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All);
|
||||||
|
|
||||||
|
public static readonly ApplicationCollection None = new(0, 0, CustomizeFlag.BodyType, 0, 0, 0);
|
||||||
|
|
||||||
|
public static readonly ApplicationCollection Equipment = new(EquipFlagExtensions.All, BonusExtensions.All,
|
||||||
|
CustomizeFlag.BodyType, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState | MetaFlag.EarState);
|
||||||
|
|
||||||
|
public static readonly ApplicationCollection Customizations = new(0, 0, CustomizeFlagExtensions.AllRelevant, 0,
|
||||||
|
CustomizeParameterExtensions.All, MetaFlag.Wetness);
|
||||||
|
|
||||||
|
public static readonly ApplicationCollection Default = new(EquipFlagExtensions.All, BonusExtensions.All,
|
||||||
|
CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState | MetaFlag.EarState);
|
||||||
|
|
||||||
|
public static ApplicationCollection FromKeys()
|
||||||
|
=> (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch
|
||||||
|
{
|
||||||
|
(false, false) => All,
|
||||||
|
(true, true) => All,
|
||||||
|
(true, false) => Equipment,
|
||||||
|
(false, true) => Customizations,
|
||||||
|
};
|
||||||
|
|
||||||
|
public CustomizeFlag Customize
|
||||||
|
{
|
||||||
|
get => CustomizeRaw;
|
||||||
|
set => CustomizeRaw = value | CustomizeFlag.BodyType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveEquip()
|
||||||
|
{
|
||||||
|
Equip = 0;
|
||||||
|
BonusItem = 0;
|
||||||
|
Crest = 0;
|
||||||
|
Meta &= ~(MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState | MetaFlag.EarState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveCustomize()
|
||||||
|
{
|
||||||
|
Customize = 0;
|
||||||
|
Parameters = 0;
|
||||||
|
Meta &= MetaFlag.Wetness;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApplicationCollection Restrict(ApplicationCollection old)
|
||||||
|
=> new(old.Equip & Equip, old.BonusItem & BonusItem, (old.Customize & Customize) | CustomizeFlag.BodyType, old.Crest & Crest,
|
||||||
|
old.Parameters & Parameters, old.Meta & Meta);
|
||||||
|
|
||||||
|
public ApplicationCollection CloneSecure()
|
||||||
|
=> new(Equip & EquipFlagExtensions.All, BonusItem & BonusExtensions.All,
|
||||||
|
(Customize & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType, Crest & CrestExtensions.AllRelevant,
|
||||||
|
Parameters & CustomizeParameterExtensions.All, Meta & MetaExtensions.All);
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,14 @@
|
||||||
using Glamourer.GameData;
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.GameData;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
public readonly struct ApplicationRules(
|
public readonly struct ApplicationRules(ApplicationCollection application, bool materials)
|
||||||
EquipFlag equip,
|
|
||||||
CustomizeFlag customize,
|
|
||||||
CrestFlag crest,
|
|
||||||
CustomizeParameterFlag parameters,
|
|
||||||
MetaFlag meta)
|
|
||||||
{
|
{
|
||||||
public static readonly ApplicationRules All = new(EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant,
|
public static readonly ApplicationRules All = new(ApplicationCollection.All, true);
|
||||||
CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All);
|
|
||||||
|
|
||||||
public static ApplicationRules FromModifiers(ActorState state)
|
public static ApplicationRules FromModifiers(ActorState state)
|
||||||
=> FromModifiers(state, ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift);
|
=> FromModifiers(state, ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift);
|
||||||
|
|
@ -22,53 +17,45 @@ public readonly struct ApplicationRules(
|
||||||
=> NpcFromModifiers(ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift);
|
=> NpcFromModifiers(ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift);
|
||||||
|
|
||||||
public static ApplicationRules AllButParameters(ActorState state)
|
public static ApplicationRules AllButParameters(ActorState state)
|
||||||
=> new(All.Equip, All.Customize, All.Crest, ComputeParameters(state.ModelData, state.BaseData, All.Parameters), All.Meta);
|
=> new(ApplicationCollection.All with { Parameters = ComputeParameters(state.ModelData, state.BaseData, All.Parameters) }, true);
|
||||||
|
|
||||||
public static ApplicationRules NpcFromModifiers(bool ctrl, bool shift)
|
public static ApplicationRules NpcFromModifiers(bool ctrl, bool shift)
|
||||||
=> new(ctrl || !shift ? EquipFlagExtensions.All : 0,
|
{
|
||||||
!ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0,
|
var equip = ctrl || !shift ? EquipFlagExtensions.All : 0;
|
||||||
0,
|
var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0;
|
||||||
0,
|
var visor = equip != 0 ? MetaFlag.VisorState : 0;
|
||||||
ctrl || !shift ? MetaFlag.VisorState : 0);
|
return new ApplicationRules(new ApplicationCollection(equip, 0, customize, 0, 0, visor), false);
|
||||||
|
}
|
||||||
|
|
||||||
public static ApplicationRules FromModifiers(ActorState state, bool ctrl, bool shift)
|
public static ApplicationRules FromModifiers(ActorState state, bool ctrl, bool shift)
|
||||||
{
|
{
|
||||||
var equip = ctrl || !shift ? EquipFlagExtensions.All : 0;
|
var equip = ctrl || !shift ? EquipFlagExtensions.All : 0;
|
||||||
var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0;
|
var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0;
|
||||||
|
var bonus = equip == 0 ? 0 : BonusExtensions.All;
|
||||||
var crest = equip == 0 ? 0 : CrestExtensions.AllRelevant;
|
var crest = equip == 0 ? 0 : CrestExtensions.AllRelevant;
|
||||||
var parameters = customize == 0 ? 0 : CustomizeParameterExtensions.All;
|
var parameters = customize == 0 ? 0 : CustomizeParameterExtensions.All;
|
||||||
var meta = state.ModelData.IsWet() ? MetaFlag.Wetness : 0;
|
var meta = state.ModelData.IsWet() ? MetaFlag.Wetness : 0;
|
||||||
if (equip != 0)
|
if (equip != 0)
|
||||||
meta |= MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState;
|
meta |= MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState;
|
||||||
|
|
||||||
return new ApplicationRules(equip, customize, crest, ComputeParameters(state.ModelData, state.BaseData, parameters), meta);
|
var collection = new ApplicationCollection(equip, bonus, customize, crest,
|
||||||
|
ComputeParameters(state.ModelData, state.BaseData, parameters), meta);
|
||||||
|
return new ApplicationRules(collection, equip != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Apply(DesignBase design)
|
public void Apply(DesignBase design)
|
||||||
{
|
=> design.Application = application;
|
||||||
design.ApplyEquip = Equip;
|
|
||||||
design.ApplyCustomize = Customize;
|
|
||||||
design.ApplyCrest = Crest;
|
|
||||||
design.ApplyParameters = Parameters;
|
|
||||||
design.ApplyMeta = Meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EquipFlag Equip
|
public EquipFlag Equip
|
||||||
=> equip & EquipFlagExtensions.All;
|
=> application.Equip & EquipFlagExtensions.All;
|
||||||
|
|
||||||
public CustomizeFlag Customize
|
|
||||||
=> customize & CustomizeFlagExtensions.AllRelevant;
|
|
||||||
|
|
||||||
public CrestFlag Crest
|
|
||||||
=> crest & CrestExtensions.AllRelevant;
|
|
||||||
|
|
||||||
public CustomizeParameterFlag Parameters
|
public CustomizeParameterFlag Parameters
|
||||||
=> parameters & CustomizeParameterExtensions.All;
|
=> application.Parameters & CustomizeParameterExtensions.All;
|
||||||
|
|
||||||
public MetaFlag Meta
|
public bool Materials
|
||||||
=> meta & MetaExtensions.All;
|
=> materials;
|
||||||
|
|
||||||
public static CustomizeParameterFlag ComputeParameters(in DesignData model, in DesignData game,
|
private static CustomizeParameterFlag ComputeParameters(in DesignData model, in DesignData game,
|
||||||
CustomizeParameterFlag baseFlags = CustomizeParameterExtensions.All)
|
CustomizeParameterFlag baseFlags = CustomizeParameterExtensions.All)
|
||||||
{
|
{
|
||||||
foreach (var flag in baseFlags.Iterate())
|
foreach (var flag in baseFlags.Iterate())
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Glamourer.Automation;
|
using Glamourer.Automation;
|
||||||
using Glamourer.Designs.Links;
|
using Glamourer.Designs.Links;
|
||||||
using Glamourer.Interop.Material;
|
using Glamourer.Interop.Material;
|
||||||
|
|
@ -8,6 +8,8 @@ using Glamourer.State;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using Notification = OtterGui.Classes.Notification;
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
|
|
@ -26,32 +28,40 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
||||||
internal Design(Design other)
|
internal Design(Design other)
|
||||||
: base(other)
|
: base(other)
|
||||||
{
|
{
|
||||||
Tags = [.. other.Tags];
|
Tags = [.. other.Tags];
|
||||||
Description = other.Description;
|
Description = other.Description;
|
||||||
QuickDesign = other.QuickDesign;
|
QuickDesign = other.QuickDesign;
|
||||||
AssociatedMods = new SortedList<Mod, ModSettings>(other.AssociatedMods);
|
ForcedRedraw = other.ForcedRedraw;
|
||||||
|
ResetAdvancedDyes = other.ResetAdvancedDyes;
|
||||||
|
ResetTemporarySettings = other.ResetTemporarySettings;
|
||||||
|
Color = other.Color;
|
||||||
|
AssociatedMods = new SortedList<Mod, ModSettings>(other.AssociatedMods);
|
||||||
|
Links = Links.Clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
public new const int FileVersion = 1;
|
public new const int FileVersion = 2;
|
||||||
|
|
||||||
public Guid Identifier { get; internal init; }
|
public Guid Identifier { get; internal init; }
|
||||||
public DateTimeOffset CreationDate { get; internal init; }
|
public DateTimeOffset CreationDate { get; internal init; }
|
||||||
public DateTimeOffset LastEdit { get; internal set; }
|
public DateTimeOffset LastEdit { get; internal set; }
|
||||||
public LowerString Name { get; internal set; } = LowerString.Empty;
|
public LowerString Name { get; internal set; } = LowerString.Empty;
|
||||||
public string Description { get; internal set; } = string.Empty;
|
public string Description { get; internal set; } = string.Empty;
|
||||||
public string[] Tags { get; internal set; } = [];
|
public string[] Tags { get; internal set; } = [];
|
||||||
public int Index { get; internal set; }
|
public int Index { get; internal set; }
|
||||||
public bool QuickDesign { get; internal set; } = true;
|
public bool ForcedRedraw { get; internal set; }
|
||||||
public string Color { get; internal set; } = string.Empty;
|
public bool ResetAdvancedDyes { get; internal set; }
|
||||||
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = [];
|
public bool ResetTemporarySettings { get; internal set; }
|
||||||
public LinkContainer Links { get; private set; } = [];
|
public bool QuickDesign { get; internal set; } = true;
|
||||||
|
public string Color { get; internal set; } = string.Empty;
|
||||||
|
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = [];
|
||||||
|
public LinkContainer Links { get; private set; } = [];
|
||||||
|
|
||||||
public string Incognito
|
public string Incognito
|
||||||
=> Identifier.ToString()[..8];
|
=> Identifier.ToString()[..8];
|
||||||
|
|
||||||
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks
|
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks(bool newApplication)
|
||||||
=> LinkContainer.GetAllLinks(this).Select(t => ((IDesignStandIn)t.Link.Link, t.Link.Type));
|
=> LinkContainer.GetAllLinks(this).Select(t => ((IDesignStandIn)t.Link.Link, t.Link.Type, JobFlag.All));
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
@ -90,24 +100,28 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
||||||
|
|
||||||
public new JObject JsonSerialize()
|
public new JObject JsonSerialize()
|
||||||
{
|
{
|
||||||
var ret = new JObject()
|
var ret = new JObject
|
||||||
{
|
{
|
||||||
["FileVersion"] = FileVersion,
|
["FileVersion"] = FileVersion,
|
||||||
["Identifier"] = Identifier,
|
["Identifier"] = Identifier,
|
||||||
["CreationDate"] = CreationDate,
|
["CreationDate"] = CreationDate,
|
||||||
["LastEdit"] = LastEdit,
|
["LastEdit"] = LastEdit,
|
||||||
["Name"] = Name.Text,
|
["Name"] = Name.Text,
|
||||||
["Description"] = Description,
|
["Description"] = Description,
|
||||||
["Color"] = Color,
|
["ForcedRedraw"] = ForcedRedraw,
|
||||||
["QuickDesign"] = QuickDesign,
|
["ResetAdvancedDyes"] = ResetAdvancedDyes,
|
||||||
["Tags"] = JArray.FromObject(Tags),
|
["ResetTemporarySettings"] = ResetTemporarySettings,
|
||||||
["WriteProtected"] = WriteProtected(),
|
["Color"] = Color,
|
||||||
["Equipment"] = SerializeEquipment(),
|
["QuickDesign"] = QuickDesign,
|
||||||
["Customize"] = SerializeCustomize(),
|
["Tags"] = JArray.FromObject(Tags),
|
||||||
["Parameters"] = SerializeParameters(),
|
["WriteProtected"] = WriteProtected(),
|
||||||
["Materials"] = SerializeMaterials(),
|
["Equipment"] = SerializeEquipment(),
|
||||||
["Mods"] = SerializeMods(),
|
["Bonus"] = SerializeBonusItems(),
|
||||||
["Links"] = Links.Serialize(),
|
["Customize"] = SerializeCustomize(),
|
||||||
|
["Parameters"] = SerializeParameters(),
|
||||||
|
["Materials"] = SerializeMaterials(),
|
||||||
|
["Mods"] = SerializeMods(),
|
||||||
|
["Links"] = Links.Serialize(),
|
||||||
};
|
};
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -117,12 +131,17 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
||||||
var ret = new JArray();
|
var ret = new JArray();
|
||||||
foreach (var (mod, settings) in AssociatedMods)
|
foreach (var (mod, settings) in AssociatedMods)
|
||||||
{
|
{
|
||||||
var obj = new JObject()
|
var obj = new JObject
|
||||||
{
|
{
|
||||||
["Name"] = mod.Name,
|
["Name"] = mod.Name,
|
||||||
["Directory"] = mod.DirectoryName,
|
["Directory"] = mod.DirectoryName,
|
||||||
["Enabled"] = settings.Enabled,
|
|
||||||
};
|
};
|
||||||
|
if (settings.Remove)
|
||||||
|
obj["Remove"] = true;
|
||||||
|
else if (settings.ForceInherit)
|
||||||
|
obj["Inherit"] = true;
|
||||||
|
else
|
||||||
|
obj["Enabled"] = settings.Enabled;
|
||||||
if (settings.Enabled)
|
if (settings.Enabled)
|
||||||
{
|
{
|
||||||
obj["Priority"] = settings.Priority;
|
obj["Priority"] = settings.Priority;
|
||||||
|
|
@ -139,17 +158,83 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
||||||
|
|
||||||
#region Deserialization
|
#region Deserialization
|
||||||
|
|
||||||
public static Design LoadDesign(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json)
|
public static Design LoadDesign(SaveService saveService, CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader,
|
||||||
|
JObject json)
|
||||||
{
|
{
|
||||||
var version = json["FileVersion"]?.ToObject<int>() ?? 0;
|
var version = json["FileVersion"]?.ToObject<int>() ?? 0;
|
||||||
return version switch
|
return version switch
|
||||||
{
|
{
|
||||||
FileVersion => LoadDesignV1(customizations, items, linkLoader, json),
|
1 => LoadDesignV1(saveService, customizations, items, linkLoader, json),
|
||||||
|
FileVersion => LoadDesignV2(customizations, items, linkLoader, json),
|
||||||
_ => throw new Exception("The design to be loaded has no valid Version."),
|
_ => throw new Exception("The design to be loaded has no valid Version."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json)
|
/// <summary> The values for gloss and specular strength were switched. Swap them for all appropriate designs. </summary>
|
||||||
|
private static Design LoadDesignV1(SaveService saveService, CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader,
|
||||||
|
JObject json)
|
||||||
|
{
|
||||||
|
var design = LoadDesignV2(customizations, items, linkLoader, json);
|
||||||
|
var materialDesignData = design.GetMaterialDataRef();
|
||||||
|
if (materialDesignData.Values.Count == 0)
|
||||||
|
return design;
|
||||||
|
|
||||||
|
var materialData = materialDesignData.Clone();
|
||||||
|
// Guesstimate whether to migrate material rows:
|
||||||
|
// Update 1.3.0.10 released at that time, so any design last updated before that can be migrated.
|
||||||
|
if (design.LastEdit <= new DateTime(2024, 8, 7, 16, 0, 0, DateTimeKind.Utc))
|
||||||
|
{
|
||||||
|
Migrate("because it was saved the wrong way around before 1.3.0.10, and this design was not changed since that release.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var hasNegativeGloss = false;
|
||||||
|
var hasNonPositiveGloss = false;
|
||||||
|
var specularLarger = 0;
|
||||||
|
foreach (var (key, value) in materialData.GetValues(MaterialValueIndex.Min(), MaterialValueIndex.Max()))
|
||||||
|
{
|
||||||
|
hasNegativeGloss |= value.Value.GlossStrength < 0;
|
||||||
|
hasNonPositiveGloss |= value.Value.GlossStrength <= 0;
|
||||||
|
if (value.Value.SpecularStrength > value.Value.GlossStrength)
|
||||||
|
++specularLarger;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is any negative gloss, this is wrong and can be migrated.
|
||||||
|
if (hasNegativeGloss)
|
||||||
|
Migrate("because it had a negative Gloss value, which is not supported and thus probably outdated.");
|
||||||
|
// If there is any non-positive Gloss and some specular values that are larger, it is probably wrong and can be migrated.
|
||||||
|
else if (hasNonPositiveGloss && specularLarger > 0)
|
||||||
|
Migrate("because it had a zero Gloss value, and at least one Specular Strength larger than the Gloss, which is unusual.");
|
||||||
|
// If most of the specular strengths are larger, it is probably wrong and can be migrated.
|
||||||
|
else if (specularLarger > materialData.Values.Count / 2)
|
||||||
|
Migrate("because most of its Specular Strength values were larger than the Gloss values, which is unusual.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return design;
|
||||||
|
|
||||||
|
void Migrate(string reason)
|
||||||
|
{
|
||||||
|
materialDesignData.Clear();
|
||||||
|
foreach (var (key, value) in materialData.GetValues(MaterialValueIndex.Min(), MaterialValueIndex.Max()))
|
||||||
|
{
|
||||||
|
var gloss = Math.Clamp(value.Value.SpecularStrength, 0, (float)Half.MaxValue);
|
||||||
|
var specularStrength = Math.Clamp(value.Value.GlossStrength, 0, (float)Half.MaxValue);
|
||||||
|
var colorRow = value.Value with
|
||||||
|
{
|
||||||
|
GlossStrength = gloss,
|
||||||
|
SpecularStrength = specularStrength,
|
||||||
|
};
|
||||||
|
materialDesignData.AddOrUpdateValue(MaterialValueIndex.FromKey(key), value with { Value = colorRow });
|
||||||
|
}
|
||||||
|
|
||||||
|
Glamourer.Messager.AddMessage(new Notification(
|
||||||
|
$"Swapped Gloss and Specular Strength in {materialDesignData.Values.Count} Rows in design {design.Incognito} {reason}",
|
||||||
|
NotificationType.Info));
|
||||||
|
saveService.Save(SaveType.ImmediateSync, design);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Design LoadDesignV2(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json)
|
||||||
{
|
{
|
||||||
var creationDate = json["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate");
|
var creationDate = json["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate");
|
||||||
|
|
||||||
|
|
@ -168,16 +253,20 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
||||||
design.SetWriteProtected(json["WriteProtected"]?.ToObject<bool>() ?? false);
|
design.SetWriteProtected(json["WriteProtected"]?.ToObject<bool>() ?? false);
|
||||||
LoadCustomize(customizations, json["Customize"], design, design.Name, true, false);
|
LoadCustomize(customizations, json["Customize"], design, design.Name, true, false);
|
||||||
LoadEquip(items, json["Equipment"], design, design.Name, true);
|
LoadEquip(items, json["Equipment"], design, design.Name, true);
|
||||||
|
LoadBonus(items, design, json["Bonus"]);
|
||||||
LoadMods(json["Mods"], design);
|
LoadMods(json["Mods"], design);
|
||||||
LoadParameters(json["Parameters"], design, design.Name);
|
LoadParameters(json["Parameters"], design, design.Name);
|
||||||
LoadMaterials(json["Materials"], design, design.Name);
|
LoadMaterials(json["Materials"], design, design.Name);
|
||||||
LoadLinks(linkLoader, json["Links"], design);
|
LoadLinks(linkLoader, json["Links"], design);
|
||||||
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
|
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
|
||||||
|
design.ForcedRedraw = json["ForcedRedraw"]?.ToObject<bool>() ?? false;
|
||||||
|
design.ResetAdvancedDyes = json["ResetAdvancedDyes"]?.ToObject<bool>() ?? false;
|
||||||
|
design.ResetTemporarySettings = json["ResetTemporarySettings"]?.ToObject<bool>() ?? false;
|
||||||
return design;
|
return design;
|
||||||
|
|
||||||
static string[] ParseTags(JObject json)
|
static string[] ParseTags(JObject json)
|
||||||
{
|
{
|
||||||
var tags = json["Tags"]?.ToObject<string[]>() ?? Array.Empty<string>();
|
var tags = json["Tags"]?.ToObject<string[]>() ?? [];
|
||||||
return tags.OrderBy(t => t).Distinct().ToArray();
|
return tags.OrderBy(t => t).Distinct().ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -191,19 +280,22 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
||||||
{
|
{
|
||||||
var name = tok["Name"]?.ToObject<string>();
|
var name = tok["Name"]?.ToObject<string>();
|
||||||
var directory = tok["Directory"]?.ToObject<string>();
|
var directory = tok["Directory"]?.ToObject<string>();
|
||||||
var enabled = tok["Enabled"]?.ToObject<bool>();
|
var enabled = tok["Enabled"]?.ToObject<bool>() ?? false;
|
||||||
if (name == null || directory == null || enabled == null)
|
if (name == null || directory == null)
|
||||||
{
|
{
|
||||||
Glamourer.Messager.NotificationMessage("The loaded design contains an invalid mod, skipped.", NotificationType.Warning);
|
Glamourer.Messager.NotificationMessage("The loaded design contains an invalid mod, skipped.", NotificationType.Warning);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var settingsDict = tok["Settings"]?.ToObject<Dictionary<string, string[]>>() ?? new Dictionary<string, string[]>();
|
var forceInherit = tok["Inherit"]?.ToObject<bool>() ?? false;
|
||||||
var settings = new SortedList<string, IList<string>>(settingsDict.Count);
|
var removeSetting = tok["Remove"]?.ToObject<bool>() ?? false;
|
||||||
|
var settingsDict = tok["Settings"]?.ToObject<Dictionary<string, List<string>>>() ?? [];
|
||||||
|
var settings = new Dictionary<string, List<string>>(settingsDict.Count);
|
||||||
foreach (var (key, value) in settingsDict)
|
foreach (var (key, value) in settingsDict)
|
||||||
settings.Add(key, value);
|
settings.Add(key, value);
|
||||||
var priority = tok["Priority"]?.ToObject<int>() ?? 0;
|
var priority = tok["Priority"]?.ToObject<int>() ?? 0;
|
||||||
if (!design.AssociatedMods.TryAdd(new Mod(name, directory), new ModSettings(settings, priority, enabled.Value)))
|
if (!design.AssociatedMods.TryAdd(new Mod(name, directory),
|
||||||
|
new ModSettings(settings, priority, enabled, forceInherit, removeSetting)))
|
||||||
Glamourer.Messager.NotificationMessage("The loaded design contains a mod more than once, skipped.", NotificationType.Warning);
|
Glamourer.Messager.NotificationMessage("The loaded design contains a mod more than once, skipped.", NotificationType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -222,10 +314,10 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
|
||||||
if (array == null)
|
if (array == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var obj in array.OfType<JObject>())
|
foreach (var jObj in array.OfType<JObject>())
|
||||||
{
|
{
|
||||||
var identifier = obj["Design"]?.ToObject<Guid>() ?? throw new ArgumentNullException("Design");
|
var identifier = jObj["Design"]?.ToObject<Guid>() ?? throw new ArgumentNullException(nameof(design));
|
||||||
var type = (ApplicationType)(obj["Type"]?.ToObject<uint>() ?? 0);
|
var type = (ApplicationType)(jObj["Type"]?.ToObject<uint>() ?? 0);
|
||||||
linkLoader.AddObject(design, new LinkData(identifier, type, order));
|
linkLoader.AddObject(design, new LinkData(identifier, type, order));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
using Glamourer.Interop.Material;
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
|
|
@ -40,25 +40,23 @@ public class DesignBase
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Used when importing .cma or .chara files. </summary>
|
/// <summary> Used when importing .cma or .chara files. </summary>
|
||||||
internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags)
|
internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags,
|
||||||
|
BonusItemFlag bonusFlags)
|
||||||
{
|
{
|
||||||
_designData = designData;
|
_designData = designData;
|
||||||
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
|
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
|
||||||
ApplyEquip = equipFlags & EquipFlagExtensions.All;
|
Application.Equip = equipFlags & EquipFlagExtensions.All;
|
||||||
ApplyMeta = 0;
|
Application.BonusItem = bonusFlags & BonusExtensions.All;
|
||||||
CustomizeSet = SetCustomizationSet(customize);
|
Application.Meta = 0;
|
||||||
|
CustomizeSet = SetCustomizationSet(customize);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal DesignBase(DesignBase clone)
|
internal DesignBase(DesignBase clone)
|
||||||
{
|
{
|
||||||
_designData = clone._designData;
|
_designData = clone._designData;
|
||||||
_materials = clone._materials.Clone();
|
_materials = clone._materials.Clone();
|
||||||
CustomizeSet = clone.CustomizeSet;
|
CustomizeSet = clone.CustomizeSet;
|
||||||
ApplyCustomize = clone.ApplyCustomizeRaw;
|
Application = clone.Application.CloneSecure();
|
||||||
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
|
|
||||||
ApplyParameters = clone.ApplyParameters & CustomizeParameterExtensions.All;
|
|
||||||
ApplyCrest = clone.ApplyCrest & CrestExtensions.All;
|
|
||||||
ApplyMeta = clone.ApplyMeta & MetaExtensions.All;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Ensure that the customization set is updated when the design data changes. </summary>
|
/// <summary> Ensure that the customization set is updated when the design data changes. </summary>
|
||||||
|
|
@ -70,27 +68,20 @@ public class DesignBase
|
||||||
|
|
||||||
#region Application Data
|
#region Application Data
|
||||||
|
|
||||||
private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant;
|
public CustomizeSet CustomizeSet { get; private set; }
|
||||||
public CustomizeSet CustomizeSet { get; private set; }
|
|
||||||
|
|
||||||
public CustomizeParameterFlag ApplyParameters { get; internal set; }
|
public ApplicationCollection Application = ApplicationCollection.Default;
|
||||||
|
|
||||||
internal CustomizeFlag ApplyCustomize
|
internal CustomizeFlag ApplyCustomize
|
||||||
{
|
{
|
||||||
get => _applyCustomize.FixApplication(CustomizeSet);
|
get => Application.Customize.FixApplication(CustomizeSet);
|
||||||
set => _applyCustomize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType;
|
set => Application.Customize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal CustomizeFlag ApplyCustomizeExcludingBodyType
|
internal CustomizeFlag ApplyCustomizeExcludingBodyType
|
||||||
=> _applyCustomize.FixApplication(CustomizeSet) & ~CustomizeFlag.BodyType;
|
=> Application.Customize.FixApplication(CustomizeSet) & ~CustomizeFlag.BodyType;
|
||||||
|
|
||||||
internal CustomizeFlag ApplyCustomizeRaw
|
private bool _writeProtected;
|
||||||
=> _applyCustomize;
|
|
||||||
|
|
||||||
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
|
||||||
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
|
||||||
internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
|
|
||||||
private bool _writeProtected;
|
|
||||||
|
|
||||||
public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
|
public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
|
||||||
{
|
{
|
||||||
|
|
@ -103,18 +94,18 @@ public class DesignBase
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DoApplyMeta(MetaIndex index)
|
public bool DoApplyMeta(MetaIndex index)
|
||||||
=> ApplyMeta.HasFlag(index.ToFlag());
|
=> Application.Meta.HasFlag(index.ToFlag());
|
||||||
|
|
||||||
public bool WriteProtected()
|
public bool WriteProtected()
|
||||||
=> _writeProtected;
|
=> _writeProtected;
|
||||||
|
|
||||||
public bool SetApplyMeta(MetaIndex index, bool value)
|
public bool SetApplyMeta(MetaIndex index, bool value)
|
||||||
{
|
{
|
||||||
var newFlag = value ? ApplyMeta | index.ToFlag() : ApplyMeta & ~index.ToFlag();
|
var newFlag = value ? Application.Meta | index.ToFlag() : Application.Meta & ~index.ToFlag();
|
||||||
if (newFlag == ApplyMeta)
|
if (newFlag == Application.Meta)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ApplyMeta = newFlag;
|
Application.Meta = newFlag;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,103 +119,103 @@ public class DesignBase
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DoApplyEquip(EquipSlot slot)
|
public bool DoApplyEquip(EquipSlot slot)
|
||||||
=> ApplyEquip.HasFlag(slot.ToFlag());
|
=> Application.Equip.HasFlag(slot.ToFlag());
|
||||||
|
|
||||||
public bool DoApplyStain(EquipSlot slot)
|
public bool DoApplyStain(EquipSlot slot)
|
||||||
=> ApplyEquip.HasFlag(slot.ToStainFlag());
|
=> Application.Equip.HasFlag(slot.ToStainFlag());
|
||||||
|
|
||||||
public bool DoApplyCustomize(CustomizeIndex idx)
|
public bool DoApplyCustomize(CustomizeIndex idx)
|
||||||
=> ApplyCustomize.HasFlag(idx.ToFlag());
|
=> Application.Customize.HasFlag(idx.ToFlag());
|
||||||
|
|
||||||
public bool DoApplyCrest(CrestFlag slot)
|
public bool DoApplyCrest(CrestFlag slot)
|
||||||
=> ApplyCrest.HasFlag(slot);
|
=> Application.Crest.HasFlag(slot);
|
||||||
|
|
||||||
public bool DoApplyParameter(CustomizeParameterFlag flag)
|
public bool DoApplyParameter(CustomizeParameterFlag flag)
|
||||||
=> ApplyParameters.HasFlag(flag);
|
=> Application.Parameters.HasFlag(flag);
|
||||||
|
|
||||||
|
public bool DoApplyBonusItem(BonusItemFlag slot)
|
||||||
|
=> Application.BonusItem.HasFlag(slot);
|
||||||
|
|
||||||
internal bool SetApplyEquip(EquipSlot slot, bool value)
|
internal bool SetApplyEquip(EquipSlot slot, bool value)
|
||||||
{
|
{
|
||||||
var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag();
|
var newValue = value ? Application.Equip | slot.ToFlag() : Application.Equip & ~slot.ToFlag();
|
||||||
if (newValue == ApplyEquip)
|
if (newValue == Application.Equip)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ApplyEquip = newValue;
|
Application.Equip = newValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool SetApplyBonusItem(BonusItemFlag slot, bool value)
|
||||||
|
{
|
||||||
|
var newValue = value ? Application.BonusItem | slot : Application.BonusItem & ~slot;
|
||||||
|
if (newValue == Application.BonusItem)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Application.BonusItem = newValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool SetApplyStain(EquipSlot slot, bool value)
|
internal bool SetApplyStain(EquipSlot slot, bool value)
|
||||||
{
|
{
|
||||||
var newValue = value ? ApplyEquip | slot.ToStainFlag() : ApplyEquip & ~slot.ToStainFlag();
|
var newValue = value ? Application.Equip | slot.ToStainFlag() : Application.Equip & ~slot.ToStainFlag();
|
||||||
if (newValue == ApplyEquip)
|
if (newValue == Application.Equip)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ApplyEquip = newValue;
|
Application.Equip = newValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool SetApplyCustomize(CustomizeIndex idx, bool value)
|
internal bool SetApplyCustomize(CustomizeIndex idx, bool value)
|
||||||
{
|
{
|
||||||
var newValue = value ? _applyCustomize | idx.ToFlag() : _applyCustomize & ~idx.ToFlag();
|
var newValue = value ? Application.Customize | idx.ToFlag() : Application.Customize & ~idx.ToFlag();
|
||||||
if (newValue == _applyCustomize)
|
if (newValue == Application.Customize)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
_applyCustomize = newValue;
|
Application.Customize = newValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool SetApplyCrest(CrestFlag slot, bool value)
|
internal bool SetApplyCrest(CrestFlag slot, bool value)
|
||||||
{
|
{
|
||||||
var newValue = value ? ApplyCrest | slot : ApplyCrest & ~slot;
|
var newValue = value ? Application.Crest | slot : Application.Crest & ~slot;
|
||||||
if (newValue == ApplyCrest)
|
if (newValue == Application.Crest)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ApplyCrest = newValue;
|
Application.Crest = newValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool SetApplyParameter(CustomizeParameterFlag flag, bool value)
|
internal bool SetApplyParameter(CustomizeParameterFlag flag, bool value)
|
||||||
{
|
{
|
||||||
var newValue = value ? ApplyParameters | flag : ApplyParameters & ~flag;
|
var newValue = value ? Application.Parameters | flag : Application.Parameters & ~flag;
|
||||||
if (newValue == ApplyParameters)
|
if (newValue == Application.Parameters)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ApplyParameters = newValue;
|
Application.Parameters = newValue;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags,
|
public IEnumerable<string> FilteredItemNames
|
||||||
CustomizeParameterFlag parameterFlags)
|
=> _designData.FilteredItemNames(Application.Equip, Application.BonusItem);
|
||||||
=> new(this, equipFlags, customizeFlags, crestFlags, parameterFlags);
|
|
||||||
|
internal FlagRestrictionResetter TemporarilyRestrictApplication(ApplicationCollection restrictions)
|
||||||
|
=> new(this, restrictions);
|
||||||
|
|
||||||
internal readonly struct FlagRestrictionResetter : IDisposable
|
internal readonly struct FlagRestrictionResetter : IDisposable
|
||||||
{
|
{
|
||||||
private readonly DesignBase _design;
|
private readonly DesignBase _design;
|
||||||
private readonly EquipFlag _oldEquipFlags;
|
private readonly ApplicationCollection _oldFlags;
|
||||||
private readonly CustomizeFlag _oldCustomizeFlags;
|
|
||||||
private readonly CrestFlag _oldCrestFlags;
|
|
||||||
private readonly CustomizeParameterFlag _oldParameterFlags;
|
|
||||||
|
|
||||||
public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags,
|
public FlagRestrictionResetter(DesignBase d, ApplicationCollection restrictions)
|
||||||
CustomizeParameterFlag parameterFlags)
|
|
||||||
{
|
{
|
||||||
_design = d;
|
_design = d;
|
||||||
_oldEquipFlags = d.ApplyEquip;
|
_oldFlags = d.Application;
|
||||||
_oldCustomizeFlags = d.ApplyCustomizeRaw;
|
_design.Application = restrictions.Restrict(_oldFlags);
|
||||||
_oldCrestFlags = d.ApplyCrest;
|
|
||||||
_oldParameterFlags = d.ApplyParameters;
|
|
||||||
d.ApplyEquip &= equipFlags;
|
|
||||||
d.ApplyCustomize &= customizeFlags;
|
|
||||||
d.ApplyCrest &= crestFlags;
|
|
||||||
d.ApplyParameters &= parameterFlags;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
=> _design.Application = _oldFlags;
|
||||||
_design.ApplyEquip = _oldEquipFlags;
|
|
||||||
_design.ApplyCustomize = _oldCustomizeFlags;
|
|
||||||
_design.ApplyCrest = _oldCrestFlags;
|
|
||||||
_design.ApplyParameters = _oldParameterFlags;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CustomizeSet SetCustomizationSet(CustomizeService customize)
|
private CustomizeSet SetCustomizationSet(CustomizeService customize)
|
||||||
|
|
@ -242,6 +233,7 @@ public class DesignBase
|
||||||
{
|
{
|
||||||
["FileVersion"] = FileVersion,
|
["FileVersion"] = FileVersion,
|
||||||
["Equipment"] = SerializeEquipment(),
|
["Equipment"] = SerializeEquipment(),
|
||||||
|
["Bonus"] = SerializeBonusItems(),
|
||||||
["Customize"] = SerializeCustomize(),
|
["Customize"] = SerializeCustomize(),
|
||||||
["Parameters"] = SerializeParameters(),
|
["Parameters"] = SerializeParameters(),
|
||||||
["Materials"] = SerializeMaterials(),
|
["Materials"] = SerializeMaterials(),
|
||||||
|
|
@ -257,15 +249,16 @@ public class DesignBase
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
||||||
{
|
{
|
||||||
var item = _designData.Item(slot);
|
var item = _designData.Item(slot);
|
||||||
var stain = _designData.Stain(slot);
|
var stains = _designData.Stain(slot);
|
||||||
var crestSlot = slot.ToCrestFlag();
|
var crestSlot = slot.ToCrestFlag();
|
||||||
var crest = _designData.Crest(crestSlot);
|
var crest = _designData.Crest(crestSlot);
|
||||||
ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot));
|
ret[slot.ToString()] = Serialize(item.Id, stains, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot));
|
||||||
}
|
}
|
||||||
|
|
||||||
ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply");
|
ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply");
|
||||||
ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyMeta(MetaIndex.VisorState)).ToJObject("IsToggled", "Apply");
|
ret["VieraEars"] = new QuadBool(_designData.AreEarsVisible(), DoApplyMeta(MetaIndex.EarState)).ToJObject("Show", "Apply");
|
||||||
ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyMeta(MetaIndex.WeaponState)).ToJObject("Show", "Apply");
|
ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyMeta(MetaIndex.VisorState)).ToJObject("IsToggled", "Apply");
|
||||||
|
ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyMeta(MetaIndex.WeaponState)).ToJObject("Show", "Apply");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -274,16 +267,31 @@ public class DesignBase
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest)
|
static JObject Serialize(CustomItemId id, StainIds stains, bool crest, bool apply, bool applyStain, bool applyCrest)
|
||||||
=> new()
|
=> stains.AddToObject(new JObject
|
||||||
{
|
{
|
||||||
["ItemId"] = id.Id,
|
["ItemId"] = id.Id,
|
||||||
["Stain"] = stain.Id,
|
|
||||||
["Crest"] = crest,
|
["Crest"] = crest,
|
||||||
["Apply"] = apply,
|
["Apply"] = apply,
|
||||||
["ApplyStain"] = applyStain,
|
["ApplyStain"] = applyStain,
|
||||||
["ApplyCrest"] = applyCrest,
|
["ApplyCrest"] = applyCrest,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JObject SerializeBonusItems()
|
||||||
|
{
|
||||||
|
var ret = new JObject();
|
||||||
|
foreach (var slot in BonusExtensions.AllFlags)
|
||||||
|
{
|
||||||
|
var item = _designData.BonusItem(slot);
|
||||||
|
ret[slot.ToString()] = new JObject()
|
||||||
|
{
|
||||||
|
["BonusId"] = item.Id.Id,
|
||||||
|
["Apply"] = DoApplyBonusItem(slot),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected JObject SerializeCustomize()
|
protected JObject SerializeCustomize()
|
||||||
|
|
@ -300,7 +308,7 @@ public class DesignBase
|
||||||
ret[idx.ToString()] = new JObject()
|
ret[idx.ToString()] = new JObject()
|
||||||
{
|
{
|
||||||
["Value"] = customize[idx].Value,
|
["Value"] = customize[idx].Value,
|
||||||
["Apply"] = ApplyCustomizeRaw.HasFlag(idx.ToFlag()),
|
["Apply"] = Application.Customize.HasFlag(idx.ToFlag()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -383,7 +391,7 @@ public class DesignBase
|
||||||
{
|
{
|
||||||
var k = uint.Parse(key.Name, NumberStyles.HexNumber);
|
var k = uint.Parse(key.Name, NumberStyles.HexNumber);
|
||||||
var v = value.ToObject<MaterialValueDesign>();
|
var v = value.ToObject<MaterialValueDesign>();
|
||||||
if (!MaterialValueIndex.FromKey(k, out var idx))
|
if (!MaterialValueIndex.FromKey(k, out _))
|
||||||
{
|
{
|
||||||
Glamourer.Messager.NotificationMessage($"Invalid material value key {k} for design {name}, skipped.",
|
Glamourer.Messager.NotificationMessage($"Invalid material value key {k} for design {name}, skipped.",
|
||||||
NotificationType.Warning);
|
NotificationType.Warning);
|
||||||
|
|
@ -423,19 +431,43 @@ public class DesignBase
|
||||||
LoadEquip(items, json["Equipment"], ret, "Temporary Design", true);
|
LoadEquip(items, json["Equipment"], ret, "Temporary Design", true);
|
||||||
LoadParameters(json["Parameters"], ret, "Temporary Design");
|
LoadParameters(json["Parameters"], ret, "Temporary Design");
|
||||||
LoadMaterials(json["Materials"], ret, "Temporary Design");
|
LoadMaterials(json["Materials"], ret, "Temporary Design");
|
||||||
|
LoadBonus(items, ret, json["Bonus"]);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static void LoadBonus(ItemManager items, DesignBase design, JToken? json)
|
||||||
|
{
|
||||||
|
if (json is not JObject)
|
||||||
|
{
|
||||||
|
design.Application.BonusItem = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var slot in BonusExtensions.AllFlags)
|
||||||
|
{
|
||||||
|
if (json[slot.ToString()] is not JObject itemJson)
|
||||||
|
{
|
||||||
|
design.Application.BonusItem &= ~slot;
|
||||||
|
design.GetDesignDataRef().SetBonusItem(slot, EquipItem.BonusItemNothing(slot));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
design.SetApplyBonusItem(slot, itemJson["Apply"]?.ToObject<bool>() ?? false);
|
||||||
|
var id = itemJson["BonusId"]?.ToObject<ulong>() ?? 0;
|
||||||
|
var item = items.Resolve(slot, id);
|
||||||
|
design.GetDesignDataRef().SetBonusItem(slot, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected static void LoadParameters(JToken? parameters, DesignBase design, string name)
|
protected static void LoadParameters(JToken? parameters, DesignBase design, string name)
|
||||||
{
|
{
|
||||||
if (parameters == null)
|
if (parameters == null)
|
||||||
{
|
{
|
||||||
design.ApplyParameters = 0;
|
design.Application.Parameters = 0;
|
||||||
design.GetDesignDataRef().Parameters = default;
|
design.GetDesignDataRef().Parameters = default;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
foreach (var flag in CustomizeParameterExtensions.ValueFlags)
|
foreach (var flag in CustomizeParameterExtensions.ValueFlags)
|
||||||
{
|
{
|
||||||
if (!TryGetToken(flag, out var token))
|
if (!TryGetToken(flag, out var token))
|
||||||
|
|
@ -491,7 +523,7 @@ public class DesignBase
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
design.ApplyParameters &= ~flag;
|
design.Application.Parameters &= ~flag;
|
||||||
design.GetDesignDataRef().Parameters[flag] = CustomizeParameterValue.Zero;
|
design.GetDesignDataRef().Parameters[flag] = CustomizeParameterValue.Zero;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -522,32 +554,15 @@ public class DesignBase
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static (CustomItemId, StainId, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item)
|
|
||||||
{
|
|
||||||
var id = item?["ItemId"]?.ToObject<ulong>() ?? ItemManager.NothingId(slot).Id;
|
|
||||||
var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0);
|
|
||||||
var crest = item?["Crest"]?.ToObject<bool>() ?? false;
|
|
||||||
var apply = item?["Apply"]?.ToObject<bool>() ?? false;
|
|
||||||
var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false;
|
|
||||||
var applyCrest = item?["ApplyCrest"]?.ToObject<bool>() ?? false;
|
|
||||||
return (id, stain, crest, apply, applyStain, applyCrest);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintWarning(string msg)
|
|
||||||
{
|
|
||||||
if (msg.Length > 0 && name != "Temporary Design")
|
|
||||||
Glamourer.Messager.NotificationMessage($"{msg} ({name})", NotificationType.Warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
{
|
{
|
||||||
var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(slot, equip[slot.ToString()]);
|
var (id, stains, crest, apply, applyStain, applyCrest) = ParseItem(slot, equip[slot.ToString()]);
|
||||||
|
|
||||||
PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown));
|
PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown));
|
||||||
PrintWarning(items.ValidateStain(stain, out stain, allowUnknown));
|
PrintWarning(items.ValidateStain(stains, out stains, allowUnknown));
|
||||||
var crestSlot = slot.ToCrestFlag();
|
var crestSlot = slot.ToCrestFlag();
|
||||||
design._designData.SetItem(slot, item);
|
design._designData.SetItem(slot, item);
|
||||||
design._designData.SetStain(slot, stain);
|
design._designData.SetStain(slot, stains);
|
||||||
design._designData.SetCrest(crestSlot, crest);
|
design._designData.SetCrest(crestSlot, crest);
|
||||||
design.SetApplyEquip(slot, apply);
|
design.SetApplyEquip(slot, apply);
|
||||||
design.SetApplyStain(slot, applyStain);
|
design.SetApplyStain(slot, applyStain);
|
||||||
|
|
@ -555,21 +570,21 @@ public class DesignBase
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]);
|
var (id, stains, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]);
|
||||||
if (id == ItemManager.NothingId(EquipSlot.MainHand))
|
if (id == ItemManager.NothingId(EquipSlot.MainHand))
|
||||||
id = items.DefaultSword.ItemId;
|
id = items.DefaultSword.ItemId;
|
||||||
var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) =
|
var (idOff, stainsOff, crestOff, applyOff, applyStainOff, applyCrestOff) =
|
||||||
ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]);
|
ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]);
|
||||||
if (id == ItemManager.NothingId(EquipSlot.OffHand))
|
if (id == ItemManager.NothingId(EquipSlot.OffHand))
|
||||||
id = ItemManager.NothingId(FullEquipType.Shield);
|
id = ItemManager.NothingId(FullEquipType.Shield);
|
||||||
|
|
||||||
PrintWarning(items.ValidateWeapons(id, idOff, out var main, out var off, allowUnknown));
|
PrintWarning(items.ValidateWeapons(id, idOff, out var main, out var off, allowUnknown));
|
||||||
PrintWarning(items.ValidateStain(stain, out stain, allowUnknown));
|
PrintWarning(items.ValidateStain(stains, out stains, allowUnknown));
|
||||||
PrintWarning(items.ValidateStain(stainOff, out stainOff, allowUnknown));
|
PrintWarning(items.ValidateStain(stainsOff, out stainsOff, allowUnknown));
|
||||||
design._designData.SetItem(EquipSlot.MainHand, main);
|
design._designData.SetItem(EquipSlot.MainHand, main);
|
||||||
design._designData.SetItem(EquipSlot.OffHand, off);
|
design._designData.SetItem(EquipSlot.OffHand, off);
|
||||||
design._designData.SetStain(EquipSlot.MainHand, stain);
|
design._designData.SetStain(EquipSlot.MainHand, stains);
|
||||||
design._designData.SetStain(EquipSlot.OffHand, stainOff);
|
design._designData.SetStain(EquipSlot.OffHand, stainsOff);
|
||||||
design._designData.SetCrest(CrestFlag.MainHand, crest);
|
design._designData.SetCrest(CrestFlag.MainHand, crest);
|
||||||
design._designData.SetCrest(CrestFlag.OffHand, crestOff);
|
design._designData.SetCrest(CrestFlag.OffHand, crestOff);
|
||||||
design.SetApplyEquip(EquipSlot.MainHand, apply);
|
design.SetApplyEquip(EquipSlot.MainHand, apply);
|
||||||
|
|
@ -590,6 +605,28 @@ public class DesignBase
|
||||||
metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse);
|
metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse);
|
||||||
design.SetApplyMeta(MetaIndex.VisorState, metaValue.Enabled);
|
design.SetApplyMeta(MetaIndex.VisorState, metaValue.Enabled);
|
||||||
design._designData.SetVisor(metaValue.ForcedValue);
|
design._designData.SetVisor(metaValue.ForcedValue);
|
||||||
|
|
||||||
|
metaValue = QuadBool.FromJObject(equip["VieraEars"], "Show", "Apply", QuadBool.NullTrue);
|
||||||
|
design.SetApplyMeta(MetaIndex.EarState, metaValue.Enabled);
|
||||||
|
design._designData.SetEarsVisible(metaValue.ForcedValue);
|
||||||
|
return;
|
||||||
|
|
||||||
|
void PrintWarning(string msg)
|
||||||
|
{
|
||||||
|
if (msg.Length > 0 && name != "Temporary Design")
|
||||||
|
Glamourer.Messager.NotificationMessage($"{msg} ({name})", NotificationType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
static (CustomItemId, StainIds, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item)
|
||||||
|
{
|
||||||
|
var id = item?["ItemId"]?.ToObject<ulong>() ?? ItemManager.NothingId(slot).Id;
|
||||||
|
var stains = StainIds.ParseFromObject(item as JObject);
|
||||||
|
var crest = item?["Crest"]?.ToObject<bool>() ?? false;
|
||||||
|
var apply = item?["Apply"]?.ToObject<bool>() ?? false;
|
||||||
|
var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false;
|
||||||
|
var applyCrest = item?["ApplyCrest"]?.ToObject<bool>() ?? false;
|
||||||
|
return (id, stains, crest, apply, applyStain, applyCrest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void LoadCustomize(CustomizeService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman,
|
protected static void LoadCustomize(CustomizeService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman,
|
||||||
|
|
@ -670,11 +707,12 @@ public class DesignBase
|
||||||
{
|
{
|
||||||
_designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags,
|
_designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags,
|
||||||
out var writeProtected, out var applyMeta);
|
out var writeProtected, out var applyMeta);
|
||||||
ApplyEquip = equipFlags;
|
Application.Equip = equipFlags;
|
||||||
ApplyCustomize = customizeFlags;
|
ApplyCustomize = customizeFlags;
|
||||||
ApplyParameters = 0;
|
Application.Parameters = 0;
|
||||||
ApplyCrest = 0;
|
Application.Crest = 0;
|
||||||
ApplyMeta = applyMeta;
|
Application.Meta = applyMeta;
|
||||||
|
Application.BonusItem = 0;
|
||||||
SetWriteProtected(writeProtected);
|
SetWriteProtected(writeProtected);
|
||||||
CustomizeSet = SetCustomizationSet(customize);
|
CustomizeSet = SetCustomizationSet(customize);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Glamourer.Services;
|
using Glamourer.Api.Enums;
|
||||||
using Glamourer.State;
|
using Glamourer.Services;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
using OtterGui.Extensions;
|
||||||
using Penumbra.GameData.DataContainers;
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
@ -96,8 +97,8 @@ public class DesignBase64Migration
|
||||||
|
|
||||||
fixed (byte* ptr = bytes)
|
fixed (byte* ptr = bytes)
|
||||||
{
|
{
|
||||||
var cur = (CharacterWeapon*)(ptr + 30);
|
var cur = (LegacyCharacterWeapon*)(ptr + 30);
|
||||||
var eq = (CharacterArmor*)(cur + 2);
|
var eq = (LegacyCharacterArmor*)(cur + 2);
|
||||||
|
|
||||||
if (!humans.IsHuman(data.ModelId))
|
if (!humans.IsHuman(data.ModelId))
|
||||||
{
|
{
|
||||||
|
|
@ -163,7 +164,8 @@ public class DesignBase64Migration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static unsafe string CreateOldBase64(in DesignData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, MetaFlag meta, bool writeProtected, float alpha = 1.0f)
|
public static unsafe string CreateOldBase64(in DesignData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, MetaFlag meta,
|
||||||
|
bool writeProtected, float alpha = 1.0f)
|
||||||
{
|
{
|
||||||
var data = stackalloc byte[Base64SizeV4];
|
var data = stackalloc byte[Base64SizeV4];
|
||||||
data[0] = 5;
|
data[0] = 5;
|
||||||
|
|
@ -186,10 +188,12 @@ public class DesignBase64Migration
|
||||||
| (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0)
|
| (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0)
|
||||||
| (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0));
|
| (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0));
|
||||||
save.Customize.Write(data + 4);
|
save.Customize.Write(data + 4);
|
||||||
((CharacterWeapon*)(data + 30))[0] = save.Item(EquipSlot.MainHand).Weapon(save.Stain(EquipSlot.MainHand));
|
((LegacyCharacterWeapon*)(data + 30))[0] =
|
||||||
((CharacterWeapon*)(data + 30))[1] = save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand));
|
new LegacyCharacterWeapon(save.Item(EquipSlot.MainHand).Weapon(save.Stain(EquipSlot.MainHand)));
|
||||||
|
((LegacyCharacterWeapon*)(data + 30))[1] =
|
||||||
|
new LegacyCharacterWeapon(save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand)));
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
((CharacterArmor*)(data + 44))[slot.ToIndex()] = save.Item(slot).Armor(save.Stain(slot));
|
((LegacyCharacterArmor*)(data + 44))[slot.ToIndex()] = new LegacyCharacterArmor(save.Item(slot).Armor(save.Stain(slot)));
|
||||||
*(ushort*)(data + 84) = 1; // IsSet.
|
*(ushort*)(data + 84) = 1; // IsSet.
|
||||||
*(float*)(data + 86) = 1f;
|
*(float*)(data + 86) = 1f;
|
||||||
data[90] = (byte)((save.IsHatVisible() ? 0x00 : 0x01)
|
data[90] = (byte)((save.IsHatVisible() ? 0x00 : 0x01)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Extensions;
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
|
|
@ -270,7 +271,7 @@ public class DesignColors : ISavable, IReadOnlyDictionary<string, uint>
|
||||||
public static uint AutoColor(DesignBase design)
|
public static uint AutoColor(DesignBase design)
|
||||||
{
|
{
|
||||||
var customize = design.ApplyCustomizeExcludingBodyType == 0;
|
var customize = design.ApplyCustomizeExcludingBodyType == 0;
|
||||||
var equip = design.ApplyEquip == 0;
|
var equip = design.Application.Equip == 0;
|
||||||
return (customize, equip) switch
|
return (customize, equip) switch
|
||||||
{
|
{
|
||||||
(true, true) => ColorId.StateDesign.Value(),
|
(true, true) => ColorId.StateDesign.Value(),
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,13 @@ using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.DataContainers;
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files.MaterialStructs;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
public class DesignConverter(
|
public class DesignConverter(
|
||||||
|
SaveService saveService,
|
||||||
ItemManager _items,
|
ItemManager _items,
|
||||||
DesignManager _designs,
|
DesignManager _designs,
|
||||||
CustomizeService _customize,
|
CustomizeService _customize,
|
||||||
|
|
@ -34,10 +35,10 @@ public class DesignConverter(
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ShareBase64(Design design)
|
public string ShareBase64(Design design)
|
||||||
=> ShareBase64(ShareJObject(design));
|
=> ToBase64(ShareJObject(design));
|
||||||
|
|
||||||
public string ShareBase64(DesignBase design)
|
public string ShareBase64(DesignBase design)
|
||||||
=> ShareBase64(ShareJObject(design));
|
=> ToBase64(ShareJObject(design));
|
||||||
|
|
||||||
public string ShareBase64(ActorState state, in ApplicationRules rules)
|
public string ShareBase64(ActorState state, in ApplicationRules rules)
|
||||||
=> ShareBase64(state.ModelData, state.Materials, rules);
|
=> ShareBase64(state.ModelData, state.Materials, rules);
|
||||||
|
|
@ -45,7 +46,7 @@ public class DesignConverter(
|
||||||
public string ShareBase64(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules)
|
public string ShareBase64(in DesignData data, in StateMaterialManager materials, in ApplicationRules rules)
|
||||||
{
|
{
|
||||||
var design = Convert(data, materials, rules);
|
var design = Convert(data, materials, rules);
|
||||||
return ShareBase64(ShareJObject(design));
|
return ToBase64(ShareJObject(design));
|
||||||
}
|
}
|
||||||
|
|
||||||
public DesignBase Convert(ActorState state, in ApplicationRules rules)
|
public DesignBase Convert(ActorState state, in ApplicationRules rules)
|
||||||
|
|
@ -56,10 +57,37 @@ public class DesignConverter(
|
||||||
var design = _designs.CreateTemporary();
|
var design = _designs.CreateTemporary();
|
||||||
rules.Apply(design);
|
rules.Apply(design);
|
||||||
design.SetDesignData(_customize, data);
|
design.SetDesignData(_customize, data);
|
||||||
ComputeMaterials(design.GetMaterialDataRef(), materials, rules.Equip);
|
if (rules.Materials)
|
||||||
|
ComputeMaterials(design.GetMaterialDataRef(), materials, rules.Equip);
|
||||||
return design;
|
return design;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DesignBase? FromJObject(JObject? jObject, bool customize, bool equip)
|
||||||
|
{
|
||||||
|
if (jObject == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ret = jObject["Identifier"] != null
|
||||||
|
? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObject)
|
||||||
|
: DesignBase.LoadDesignBase(_customize, _items, jObject);
|
||||||
|
|
||||||
|
if (!customize)
|
||||||
|
ret.Application.RemoveCustomize();
|
||||||
|
|
||||||
|
if (!equip)
|
||||||
|
ret.Application.RemoveEquip();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Glamourer.Log.Warning($"Failure to parse JObject to design:\n{ex}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public DesignBase? FromBase64(string base64, bool customize, bool equip, out byte version)
|
public DesignBase? FromBase64(string base64, bool customize, bool equip, out byte version)
|
||||||
{
|
{
|
||||||
DesignBase ret;
|
DesignBase ret;
|
||||||
|
|
@ -73,7 +101,7 @@ public class DesignConverter(
|
||||||
case (byte)'{':
|
case (byte)'{':
|
||||||
var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes));
|
var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes));
|
||||||
ret = jObj1["Identifier"] != null
|
ret = jObj1["Identifier"] != null
|
||||||
? Design.LoadDesign(_customize, _items, _linkLoader, jObj1)
|
? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj1)
|
||||||
: DesignBase.LoadDesignBase(_customize, _items, jObj1);
|
: DesignBase.LoadDesignBase(_customize, _items, jObj1);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
|
|
@ -88,7 +116,7 @@ public class DesignConverter(
|
||||||
var jObj2 = JObject.Parse(decompressed);
|
var jObj2 = JObject.Parse(decompressed);
|
||||||
Debug.Assert(version == 3);
|
Debug.Assert(version == 3);
|
||||||
ret = jObj2["Identifier"] != null
|
ret = jObj2["Identifier"] != null
|
||||||
? Design.LoadDesign(_customize, _items, _linkLoader, jObj2)
|
? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2)
|
||||||
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
|
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +127,7 @@ public class DesignConverter(
|
||||||
var jObj2 = JObject.Parse(decompressed);
|
var jObj2 = JObject.Parse(decompressed);
|
||||||
Debug.Assert(version == 5);
|
Debug.Assert(version == 5);
|
||||||
ret = jObj2["Identifier"] != null
|
ret = jObj2["Identifier"] != null
|
||||||
? Design.LoadDesign(_customize, _items, _linkLoader, jObj2)
|
? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2)
|
||||||
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
|
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +137,7 @@ public class DesignConverter(
|
||||||
var jObj2 = JObject.Parse(decompressed);
|
var jObj2 = JObject.Parse(decompressed);
|
||||||
Debug.Assert(version == 6);
|
Debug.Assert(version == 6);
|
||||||
ret = jObj2["Identifier"] != null
|
ret = jObj2["Identifier"] != null
|
||||||
? Design.LoadDesign(_customize, _items, _linkLoader, jObj2)
|
? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2)
|
||||||
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
|
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -123,28 +151,23 @@ public class DesignConverter(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.SetApplyMeta(MetaIndex.Wetness, customize);
|
|
||||||
if (!customize)
|
if (!customize)
|
||||||
ret.ApplyCustomize = 0;
|
ret.Application.RemoveCustomize();
|
||||||
|
|
||||||
if (!equip)
|
if (!equip)
|
||||||
{
|
ret.Application.RemoveEquip();
|
||||||
ret.ApplyEquip = 0;
|
|
||||||
ret.ApplyCrest = 0;
|
|
||||||
ret.ApplyMeta &= ~(MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ShareBase64(JToken jObject)
|
public static string ToBase64(JToken jObject)
|
||||||
{
|
{
|
||||||
var json = jObject.ToString(Formatting.None);
|
var json = jObject.ToString(Formatting.None);
|
||||||
var compressed = json.Compress(Version);
|
var compressed = json.Compress(Version);
|
||||||
return System.Convert.ToBase64String(compressed);
|
return System.Convert.ToBase64String(compressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<(EquipSlot Slot, EquipItem Item, StainId Stain)> FromDrawData(IReadOnlyList<CharacterArmor> armors,
|
public IEnumerable<(EquipSlot Slot, EquipItem Item, StainIds Stains)> FromDrawData(IReadOnlyList<CharacterArmor> armors,
|
||||||
CharacterWeapon mainhand, CharacterWeapon offhand, bool skipWarnings)
|
CharacterWeapon mainhand, CharacterWeapon offhand, bool skipWarnings)
|
||||||
{
|
{
|
||||||
if (armors.Count != 10)
|
if (armors.Count != 10)
|
||||||
|
|
@ -162,7 +185,7 @@ public class DesignConverter(
|
||||||
item = ItemManager.NothingItem(slot);
|
item = ItemManager.NothingItem(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return (slot, item, armor.Stain);
|
yield return (slot, item, armor.Stains);
|
||||||
}
|
}
|
||||||
|
|
||||||
var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant);
|
var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant);
|
||||||
|
|
@ -172,7 +195,7 @@ public class DesignConverter(
|
||||||
mh = _items.DefaultSword;
|
mh = _items.DefaultSword;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return (EquipSlot.MainHand, mh, mainhand.Stain);
|
yield return (EquipSlot.MainHand, mh, mainhand.Stains);
|
||||||
|
|
||||||
var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type);
|
var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type);
|
||||||
if (!skipWarnings && !oh.Valid)
|
if (!skipWarnings && !oh.Valid)
|
||||||
|
|
@ -183,29 +206,47 @@ public class DesignConverter(
|
||||||
oh = ItemManager.NothingItem(FullEquipType.Shield);
|
oh = ItemManager.NothingItem(FullEquipType.Shield);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return (EquipSlot.OffHand, oh, offhand.Stain);
|
yield return (EquipSlot.OffHand, oh, offhand.Stains);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials,
|
private static void ComputeMaterials(DesignMaterialManager manager, in StateMaterialManager materials,
|
||||||
EquipFlag equipFlags = EquipFlagExtensions.All)
|
EquipFlag equipFlags = EquipFlagExtensions.All, BonusItemFlag bonusFlags = BonusExtensions.All)
|
||||||
{
|
{
|
||||||
foreach (var (key, value) in materials.Values)
|
foreach (var (key, value) in materials.Values)
|
||||||
{
|
{
|
||||||
var idx = MaterialValueIndex.FromKey(key);
|
var idx = MaterialValueIndex.FromKey(key);
|
||||||
if (idx.RowIndex >= MtrlFile.ColorTable.NumRows)
|
if (idx.RowIndex >= ColorTable.NumRows)
|
||||||
continue;
|
continue;
|
||||||
if (idx.MaterialIndex >= MaterialService.MaterialsPerModel)
|
if (idx.MaterialIndex >= MaterialService.MaterialsPerModel)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var slot = idx.DrawObject switch
|
switch (idx.DrawObject)
|
||||||
{
|
{
|
||||||
MaterialValueIndex.DrawObjectType.Human => idx.SlotIndex < 10 ? ((uint)idx.SlotIndex).ToEquipSlot() : EquipSlot.Unknown,
|
case MaterialValueIndex.DrawObjectType.Mainhand when idx.SlotIndex == 0:
|
||||||
MaterialValueIndex.DrawObjectType.Mainhand when idx.SlotIndex == 0 => EquipSlot.MainHand,
|
if ((equipFlags & (EquipFlag.Mainhand | EquipFlag.MainhandStain)) == 0)
|
||||||
MaterialValueIndex.DrawObjectType.Offhand when idx.SlotIndex == 0 => EquipSlot.OffHand,
|
continue;
|
||||||
_ => EquipSlot.Unknown,
|
|
||||||
};
|
break;
|
||||||
if (slot is EquipSlot.Unknown || (slot.ToBothFlags() & equipFlags) == 0)
|
case MaterialValueIndex.DrawObjectType.Offhand when idx.SlotIndex == 0:
|
||||||
continue;
|
if ((equipFlags & (EquipFlag.Offhand | EquipFlag.OffhandStain)) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case MaterialValueIndex.DrawObjectType.Human:
|
||||||
|
if (idx.SlotIndex < 10)
|
||||||
|
{
|
||||||
|
if ((((uint)idx.SlotIndex).ToEquipSlot().ToBothFlags() & equipFlags) == 0)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (idx.SlotIndex >= 16)
|
||||||
|
{
|
||||||
|
if (((idx.SlotIndex - 16u).ToBonusSlot() & bonusFlags) == 0)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default: continue;
|
||||||
|
}
|
||||||
|
|
||||||
manager.AddOrUpdateValue(idx, value.Convert());
|
manager.AddOrUpdateValue(idx, value.Convert());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,27 +9,35 @@ namespace Glamourer.Designs;
|
||||||
|
|
||||||
public unsafe struct DesignData
|
public unsafe struct DesignData
|
||||||
{
|
{
|
||||||
private string _nameHead = string.Empty;
|
public const int NumEquipment = 10;
|
||||||
private string _nameBody = string.Empty;
|
public const int EquipmentByteSize = NumEquipment * CharacterArmor.Size;
|
||||||
private string _nameHands = string.Empty;
|
public const int NumBonusItems = 1;
|
||||||
private string _nameLegs = string.Empty;
|
public const int NumWeapons = 2;
|
||||||
private string _nameFeet = string.Empty;
|
|
||||||
private string _nameEars = string.Empty;
|
private string _nameHead = string.Empty;
|
||||||
private string _nameNeck = string.Empty;
|
private string _nameBody = string.Empty;
|
||||||
private string _nameWrists = string.Empty;
|
private string _nameHands = string.Empty;
|
||||||
private string _nameRFinger = string.Empty;
|
private string _nameLegs = string.Empty;
|
||||||
private string _nameLFinger = string.Empty;
|
private string _nameFeet = string.Empty;
|
||||||
private string _nameMainhand = string.Empty;
|
private string _nameEars = string.Empty;
|
||||||
private string _nameOffhand = string.Empty;
|
private string _nameNeck = string.Empty;
|
||||||
private fixed uint _itemIds[12];
|
private string _nameWrists = string.Empty;
|
||||||
private fixed ushort _iconIds[12];
|
private string _nameRFinger = string.Empty;
|
||||||
private fixed byte _equipmentBytes[48];
|
private string _nameLFinger = string.Empty;
|
||||||
|
private string _nameMainhand = string.Empty;
|
||||||
|
private string _nameOffhand = string.Empty;
|
||||||
|
private string _nameGlasses = string.Empty;
|
||||||
|
|
||||||
|
private fixed uint _itemIds[NumEquipment + NumWeapons];
|
||||||
|
private fixed uint _iconIds[NumEquipment + NumWeapons + NumBonusItems];
|
||||||
|
private fixed byte _equipmentBytes[EquipmentByteSize + NumWeapons * CharacterWeapon.Size];
|
||||||
|
private fixed ushort _bonusIds[NumBonusItems];
|
||||||
|
private fixed ushort _bonusModelIds[NumBonusItems];
|
||||||
|
private fixed byte _bonusVariants[NumBonusItems];
|
||||||
public CustomizeParameterData Parameters;
|
public CustomizeParameterData Parameters;
|
||||||
public CustomizeArray Customize = CustomizeArray.Default;
|
public CustomizeArray Customize = CustomizeArray.Default;
|
||||||
public uint ModelId;
|
public uint ModelId;
|
||||||
public CrestFlag CrestVisibility;
|
public CrestFlag CrestVisibility;
|
||||||
private SecondaryId _secondaryMainhand;
|
|
||||||
private SecondaryId _secondaryOffhand;
|
|
||||||
private FullEquipType _typeMainhand;
|
private FullEquipType _typeMainhand;
|
||||||
private FullEquipType _typeOffhand;
|
private FullEquipType _typeOffhand;
|
||||||
private byte _states;
|
private byte _states;
|
||||||
|
|
@ -38,29 +46,76 @@ public unsafe struct DesignData
|
||||||
public DesignData()
|
public DesignData()
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly bool ContainsName(LowerString name)
|
public readonly bool ContainsName(LowerString name)
|
||||||
=> name.IsContained(_nameHead)
|
=> ItemNames.Any(name.IsContained);
|
||||||
|| name.IsContained(_nameBody)
|
|
||||||
|| name.IsContained(_nameHands)
|
|
||||||
|| name.IsContained(_nameLegs)
|
|
||||||
|| name.IsContained(_nameFeet)
|
|
||||||
|| name.IsContained(_nameEars)
|
|
||||||
|| name.IsContained(_nameNeck)
|
|
||||||
|| name.IsContained(_nameWrists)
|
|
||||||
|| name.IsContained(_nameRFinger)
|
|
||||||
|| name.IsContained(_nameLFinger)
|
|
||||||
|| name.IsContained(_nameMainhand)
|
|
||||||
|| name.IsContained(_nameOffhand);
|
|
||||||
|
|
||||||
public readonly StainId Stain(EquipSlot slot)
|
public readonly StainIds Stain(EquipSlot slot)
|
||||||
{
|
{
|
||||||
var index = slot.ToIndex();
|
var index = slot.ToIndex();
|
||||||
return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3];
|
return index switch
|
||||||
|
{
|
||||||
|
< 10 => new StainIds(_equipmentBytes[CharacterArmor.Size * index + 3], _equipmentBytes[CharacterArmor.Size * index + 4]),
|
||||||
|
10 => new StainIds(_equipmentBytes[EquipmentByteSize + 6], _equipmentBytes[EquipmentByteSize + 7]),
|
||||||
|
11 => new StainIds(_equipmentBytes[EquipmentByteSize + 14], _equipmentBytes[EquipmentByteSize + 15]),
|
||||||
|
_ => StainIds.None,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly bool Crest(CrestFlag slot)
|
public readonly bool Crest(CrestFlag slot)
|
||||||
=> CrestVisibility.HasFlag(slot);
|
=> CrestVisibility.HasFlag(slot);
|
||||||
|
|
||||||
|
public readonly IEnumerable<string> ItemNames
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return _nameHead;
|
||||||
|
yield return _nameBody;
|
||||||
|
yield return _nameHands;
|
||||||
|
yield return _nameLegs;
|
||||||
|
yield return _nameFeet;
|
||||||
|
yield return _nameEars;
|
||||||
|
yield return _nameNeck;
|
||||||
|
yield return _nameWrists;
|
||||||
|
yield return _nameRFinger;
|
||||||
|
yield return _nameLFinger;
|
||||||
|
yield return _nameMainhand;
|
||||||
|
yield return _nameOffhand;
|
||||||
|
yield return _nameGlasses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly IEnumerable<string> FilteredItemNames(EquipFlag item, BonusItemFlag bonusItem)
|
||||||
|
{
|
||||||
|
if (item.HasFlag(EquipFlag.Head))
|
||||||
|
yield return _nameHead;
|
||||||
|
if (item.HasFlag(EquipFlag.Body))
|
||||||
|
yield return _nameBody;
|
||||||
|
if (item.HasFlag(EquipFlag.Hands))
|
||||||
|
yield return _nameHands;
|
||||||
|
if (item.HasFlag(EquipFlag.Legs))
|
||||||
|
yield return _nameLegs;
|
||||||
|
if (item.HasFlag(EquipFlag.Feet))
|
||||||
|
yield return _nameFeet;
|
||||||
|
if (item.HasFlag(EquipFlag.Ears))
|
||||||
|
yield return _nameEars;
|
||||||
|
if (item.HasFlag(EquipFlag.Neck))
|
||||||
|
yield return _nameNeck;
|
||||||
|
if (item.HasFlag(EquipFlag.Wrist))
|
||||||
|
yield return _nameWrists;
|
||||||
|
if (item.HasFlag(EquipFlag.RFinger))
|
||||||
|
yield return _nameRFinger;
|
||||||
|
if (item.HasFlag(EquipFlag.LFinger))
|
||||||
|
yield return _nameLFinger;
|
||||||
|
if (item.HasFlag(EquipFlag.Mainhand))
|
||||||
|
yield return _nameMainhand;
|
||||||
|
if (item.HasFlag(EquipFlag.Offhand))
|
||||||
|
yield return _nameOffhand;
|
||||||
|
if (bonusItem.HasFlag(BonusItemFlag.Glasses))
|
||||||
|
yield return _nameGlasses;
|
||||||
|
}
|
||||||
|
|
||||||
public readonly FullEquipType MainhandType
|
public readonly FullEquipType MainhandType
|
||||||
=> _typeMainhand;
|
=> _typeMainhand;
|
||||||
|
|
@ -69,22 +124,36 @@ public unsafe struct DesignData
|
||||||
=> _typeOffhand;
|
=> _typeOffhand;
|
||||||
|
|
||||||
public readonly EquipItem Item(EquipSlot slot)
|
public readonly EquipItem Item(EquipSlot slot)
|
||||||
=> slot.ToIndex() switch
|
{
|
||||||
|
fixed (byte* ptr = _equipmentBytes)
|
||||||
|
{
|
||||||
|
return slot.ToIndex() switch
|
||||||
|
{
|
||||||
|
// @formatter:off
|
||||||
|
0 => EquipItem.FromIds(_itemIds[ 0], _iconIds[ 0], ((CharacterArmor*)ptr)[0].Set, 0, ((CharacterArmor*)ptr)[0].Variant, FullEquipType.Head, name: _nameHead ),
|
||||||
|
1 => EquipItem.FromIds(_itemIds[ 1], _iconIds[ 1], ((CharacterArmor*)ptr)[1].Set, 0, ((CharacterArmor*)ptr)[1].Variant, FullEquipType.Body, name: _nameBody ),
|
||||||
|
2 => EquipItem.FromIds(_itemIds[ 2], _iconIds[ 2], ((CharacterArmor*)ptr)[2].Set, 0, ((CharacterArmor*)ptr)[2].Variant, FullEquipType.Hands, name: _nameHands ),
|
||||||
|
3 => EquipItem.FromIds(_itemIds[ 3], _iconIds[ 3], ((CharacterArmor*)ptr)[3].Set, 0, ((CharacterArmor*)ptr)[3].Variant, FullEquipType.Legs, name: _nameLegs ),
|
||||||
|
4 => EquipItem.FromIds(_itemIds[ 4], _iconIds[ 4], ((CharacterArmor*)ptr)[4].Set, 0, ((CharacterArmor*)ptr)[4].Variant, FullEquipType.Feet, name: _nameFeet ),
|
||||||
|
5 => EquipItem.FromIds(_itemIds[ 5], _iconIds[ 5], ((CharacterArmor*)ptr)[5].Set, 0, ((CharacterArmor*)ptr)[5].Variant, FullEquipType.Ears, name: _nameEars ),
|
||||||
|
6 => EquipItem.FromIds(_itemIds[ 6], _iconIds[ 6], ((CharacterArmor*)ptr)[6].Set, 0, ((CharacterArmor*)ptr)[6].Variant, FullEquipType.Neck, name: _nameNeck ),
|
||||||
|
7 => EquipItem.FromIds(_itemIds[ 7], _iconIds[ 7], ((CharacterArmor*)ptr)[7].Set, 0, ((CharacterArmor*)ptr)[7].Variant, FullEquipType.Wrists, name: _nameWrists ),
|
||||||
|
8 => EquipItem.FromIds(_itemIds[ 8], _iconIds[ 8], ((CharacterArmor*)ptr)[8].Set, 0, ((CharacterArmor*)ptr)[8].Variant, FullEquipType.Finger, name: _nameRFinger ),
|
||||||
|
9 => EquipItem.FromIds(_itemIds[ 9], _iconIds[ 9], ((CharacterArmor*)ptr)[9].Set, 0, ((CharacterArmor*)ptr)[9].Variant, FullEquipType.Finger, name: _nameLFinger ),
|
||||||
|
10 => EquipItem.FromIds(_itemIds[10], _iconIds[10], *(PrimaryId*)(ptr + EquipmentByteSize + 0), *(SecondaryId*)(ptr + EquipmentByteSize + 2), *(Variant*)(ptr + EquipmentByteSize + 4), _typeMainhand, name: _nameMainhand),
|
||||||
|
11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], *(PrimaryId*)(ptr + EquipmentByteSize + 8), *(SecondaryId*)(ptr + EquipmentByteSize + 10), *(Variant*)(ptr + EquipmentByteSize + 12), _typeOffhand, name: _nameOffhand ),
|
||||||
|
_ => new EquipItem(),
|
||||||
|
// @formatter:on
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly EquipItem BonusItem(BonusItemFlag slot)
|
||||||
|
=> slot switch
|
||||||
{
|
{
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
0 => EquipItem.FromIds((ItemId)_itemIds[ 0], (IconId)_iconIds[ 0], (PrimaryId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 2], FullEquipType.Head, name: _nameHead ),
|
BonusItemFlag.Glasses => EquipItem.FromBonusIds(_bonusIds[0], _iconIds[12], _bonusModelIds[0], _bonusVariants[0], BonusItemFlag.Glasses, _nameGlasses),
|
||||||
1 => EquipItem.FromIds((ItemId)_itemIds[ 1], (IconId)_iconIds[ 1], (PrimaryId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 6], FullEquipType.Body, name: _nameBody ),
|
_ => EquipItem.BonusItemNothing(slot),
|
||||||
2 => EquipItem.FromIds((ItemId)_itemIds[ 2], (IconId)_iconIds[ 2], (PrimaryId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[10], FullEquipType.Hands, name: _nameHands ),
|
|
||||||
3 => EquipItem.FromIds((ItemId)_itemIds[ 3], (IconId)_iconIds[ 3], (PrimaryId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[14], FullEquipType.Legs, name: _nameLegs ),
|
|
||||||
4 => EquipItem.FromIds((ItemId)_itemIds[ 4], (IconId)_iconIds[ 4], (PrimaryId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[18], FullEquipType.Feet, name: _nameFeet ),
|
|
||||||
5 => EquipItem.FromIds((ItemId)_itemIds[ 5], (IconId)_iconIds[ 5], (PrimaryId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[22], FullEquipType.Ears, name: _nameEars ),
|
|
||||||
6 => EquipItem.FromIds((ItemId)_itemIds[ 6], (IconId)_iconIds[ 6], (PrimaryId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[26], FullEquipType.Neck, name: _nameNeck ),
|
|
||||||
7 => EquipItem.FromIds((ItemId)_itemIds[ 7], (IconId)_iconIds[ 7], (PrimaryId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[30], FullEquipType.Wrists, name: _nameWrists ),
|
|
||||||
8 => EquipItem.FromIds((ItemId)_itemIds[ 8], (IconId)_iconIds[ 8], (PrimaryId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[34], FullEquipType.Finger, name: _nameRFinger ),
|
|
||||||
9 => EquipItem.FromIds((ItemId)_itemIds[ 9], (IconId)_iconIds[ 9], (PrimaryId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[38], FullEquipType.Finger, name: _nameLFinger ),
|
|
||||||
10 => EquipItem.FromIds((ItemId)_itemIds[10], (IconId)_iconIds[10], (PrimaryId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, (Variant)_equipmentBytes[42], _typeMainhand, name: _nameMainhand),
|
|
||||||
11 => EquipItem.FromIds((ItemId)_itemIds[11], (IconId)_iconIds[11], (PrimaryId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, (Variant)_equipmentBytes[46], _typeOffhand, name: _nameOffhand ),
|
|
||||||
_ => new EquipItem(),
|
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -113,22 +182,22 @@ public unsafe struct DesignData
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = _equipmentBytes)
|
fixed (byte* ptr = _equipmentBytes)
|
||||||
{
|
{
|
||||||
var armorPtr = (CharacterArmor*)ptr;
|
var weaponPtr = (CharacterWeapon*)(ptr + EquipmentByteSize);
|
||||||
return slot is EquipSlot.MainHand ? armorPtr[10].ToWeapon(_secondaryMainhand) : armorPtr[11].ToWeapon(_secondaryOffhand);
|
return weaponPtr[slot is EquipSlot.MainHand ? 0 : 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SetItem(EquipSlot slot, EquipItem item)
|
public bool SetItem(EquipSlot slot, EquipItem item)
|
||||||
{
|
{
|
||||||
var index = slot.ToIndex();
|
var index = slot.ToIndex();
|
||||||
if (index > 11)
|
if (index > NumEquipment + NumWeapons)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
_itemIds[index] = item.ItemId.Id;
|
_itemIds[index] = item.ItemId.Id;
|
||||||
_iconIds[index] = item.IconId.Id;
|
_iconIds[index] = item.IconId.Id;
|
||||||
_equipmentBytes[4 * index + 0] = (byte)item.PrimaryId.Id;
|
_equipmentBytes[CharacterArmor.Size * index + 0] = (byte)item.PrimaryId.Id;
|
||||||
_equipmentBytes[4 * index + 1] = (byte)(item.PrimaryId.Id >> 8);
|
_equipmentBytes[CharacterArmor.Size * index + 1] = (byte)(item.PrimaryId.Id >> 8);
|
||||||
_equipmentBytes[4 * index + 2] = item.Variant.Id;
|
_equipmentBytes[CharacterArmor.Size * index + 2] = item.Variant.Id;
|
||||||
switch (index)
|
switch (index)
|
||||||
{
|
{
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
|
@ -144,36 +213,61 @@ public unsafe struct DesignData
|
||||||
case 9: _nameLFinger = item.Name; return true;
|
case 9: _nameLFinger = item.Name; return true;
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
case 10:
|
case 10:
|
||||||
_nameMainhand = item.Name;
|
_nameMainhand = item.Name;
|
||||||
_secondaryMainhand = item.SecondaryId;
|
_equipmentBytes[EquipmentByteSize + 2] = (byte)item.SecondaryId.Id;
|
||||||
_typeMainhand = item.Type;
|
_equipmentBytes[EquipmentByteSize + 3] = (byte)(item.SecondaryId.Id >> 8);
|
||||||
|
_equipmentBytes[EquipmentByteSize + 4] = item.Variant.Id;
|
||||||
|
_typeMainhand = item.Type;
|
||||||
return true;
|
return true;
|
||||||
case 11:
|
case 11:
|
||||||
_nameOffhand = item.Name;
|
_nameOffhand = item.Name;
|
||||||
_secondaryOffhand = item.SecondaryId;
|
_equipmentBytes[EquipmentByteSize + 10] = (byte)item.SecondaryId.Id;
|
||||||
_typeOffhand = item.Type;
|
_equipmentBytes[EquipmentByteSize + 11] = (byte)(item.SecondaryId.Id >> 8);
|
||||||
|
_equipmentBytes[EquipmentByteSize + 12] = item.Variant.Id;
|
||||||
|
_typeOffhand = item.Type;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SetStain(EquipSlot slot, StainId stain)
|
public bool SetBonusItem(BonusItemFlag slot, EquipItem item)
|
||||||
|
{
|
||||||
|
var index = slot.ToIndex();
|
||||||
|
if (index > NumBonusItems)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_iconIds[NumEquipment + NumWeapons + index] = item.IconId.Id;
|
||||||
|
_bonusIds[index] = item.Id.BonusItem.Id;
|
||||||
|
_bonusModelIds[index] = item.PrimaryId.Id;
|
||||||
|
_bonusVariants[index] = item.Variant.Id;
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
_nameGlasses = item.Name;
|
||||||
|
return true;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SetStain(EquipSlot slot, StainIds stains)
|
||||||
=> slot.ToIndex() switch
|
=> slot.ToIndex() switch
|
||||||
{
|
{
|
||||||
0 => SetIfDifferent(ref _equipmentBytes[3], stain.Id),
|
// @formatter:off
|
||||||
1 => SetIfDifferent(ref _equipmentBytes[7], stain.Id),
|
0 => SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[0 * CharacterArmor.Size + 4], stains.Stain2.Id),
|
||||||
2 => SetIfDifferent(ref _equipmentBytes[11], stain.Id),
|
1 => SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[1 * CharacterArmor.Size + 4], stains.Stain2.Id),
|
||||||
3 => SetIfDifferent(ref _equipmentBytes[15], stain.Id),
|
2 => SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[2 * CharacterArmor.Size + 4], stains.Stain2.Id),
|
||||||
4 => SetIfDifferent(ref _equipmentBytes[19], stain.Id),
|
3 => SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[3 * CharacterArmor.Size + 4], stains.Stain2.Id),
|
||||||
5 => SetIfDifferent(ref _equipmentBytes[23], stain.Id),
|
4 => SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[4 * CharacterArmor.Size + 4], stains.Stain2.Id),
|
||||||
6 => SetIfDifferent(ref _equipmentBytes[27], stain.Id),
|
5 => SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[5 * CharacterArmor.Size + 4], stains.Stain2.Id),
|
||||||
7 => SetIfDifferent(ref _equipmentBytes[31], stain.Id),
|
6 => SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[6 * CharacterArmor.Size + 4], stains.Stain2.Id),
|
||||||
8 => SetIfDifferent(ref _equipmentBytes[35], stain.Id),
|
7 => SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[7 * CharacterArmor.Size + 4], stains.Stain2.Id),
|
||||||
9 => SetIfDifferent(ref _equipmentBytes[39], stain.Id),
|
8 => SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[8 * CharacterArmor.Size + 4], stains.Stain2.Id),
|
||||||
10 => SetIfDifferent(ref _equipmentBytes[43], stain.Id),
|
9 => SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 3], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[9 * CharacterArmor.Size + 4], stains.Stain2.Id),
|
||||||
11 => SetIfDifferent(ref _equipmentBytes[47], stain.Id),
|
10 => SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 6], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 7], stains.Stain2.Id),
|
||||||
_ => false,
|
11 => SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 14], stains.Stain1.Id) | SetIfDifferent(ref _equipmentBytes[EquipmentByteSize + 15], stains.Stain2.Id),
|
||||||
|
_ => false,
|
||||||
|
// @formatter:on
|
||||||
};
|
};
|
||||||
|
|
||||||
public bool SetCrest(CrestFlag slot, bool visible)
|
public bool SetCrest(CrestFlag slot, bool visible)
|
||||||
|
|
@ -193,6 +287,7 @@ public unsafe struct DesignData
|
||||||
MetaIndex.HatState => IsHatVisible(),
|
MetaIndex.HatState => IsHatVisible(),
|
||||||
MetaIndex.VisorState => IsVisorToggled(),
|
MetaIndex.VisorState => IsVisorToggled(),
|
||||||
MetaIndex.WeaponState => IsWeaponVisible(),
|
MetaIndex.WeaponState => IsWeaponVisible(),
|
||||||
|
MetaIndex.EarState => AreEarsVisible(),
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -203,6 +298,7 @@ public unsafe struct DesignData
|
||||||
MetaIndex.HatState => SetHatVisible(value),
|
MetaIndex.HatState => SetHatVisible(value),
|
||||||
MetaIndex.VisorState => SetVisor(value),
|
MetaIndex.VisorState => SetVisor(value),
|
||||||
MetaIndex.WeaponState => SetWeaponVisible(value),
|
MetaIndex.WeaponState => SetWeaponVisible(value),
|
||||||
|
MetaIndex.EarState => SetEarsVisible(value),
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -246,6 +342,9 @@ public unsafe struct DesignData
|
||||||
public readonly bool IsWeaponVisible()
|
public readonly bool IsWeaponVisible()
|
||||||
=> (_states & 0x08) == 0x08;
|
=> (_states & 0x08) == 0x08;
|
||||||
|
|
||||||
|
public readonly bool AreEarsVisible()
|
||||||
|
=> (_states & 0x10) == 0x00;
|
||||||
|
|
||||||
public bool SetWeaponVisible(bool value)
|
public bool SetWeaponVisible(bool value)
|
||||||
{
|
{
|
||||||
if (value == IsWeaponVisible())
|
if (value == IsWeaponVisible())
|
||||||
|
|
@ -255,21 +354,37 @@ public unsafe struct DesignData
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool SetEarsVisible(bool value)
|
||||||
|
{
|
||||||
|
if (value == AreEarsVisible())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_states = (byte)(value ? _states & ~0x10 : _states | 0x10);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetDefaultEquipment(ItemManager items)
|
public void SetDefaultEquipment(ItemManager items)
|
||||||
{
|
{
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
{
|
{
|
||||||
SetItem(slot, ItemManager.NothingItem(slot));
|
SetItem(slot, ItemManager.NothingItem(slot));
|
||||||
SetStain(slot, 0);
|
SetStain(slot, StainIds.None);
|
||||||
SetCrest(slot.ToCrestFlag(), false);
|
SetCrest(slot.ToCrestFlag(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetItem(EquipSlot.MainHand, items.DefaultSword);
|
SetItem(EquipSlot.MainHand, items.DefaultSword);
|
||||||
SetStain(EquipSlot.MainHand, 0);
|
SetStain(EquipSlot.MainHand, StainIds.None);
|
||||||
SetCrest(CrestFlag.MainHand, false);
|
SetCrest(CrestFlag.MainHand, false);
|
||||||
SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield));
|
SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield));
|
||||||
SetStain(EquipSlot.OffHand, 0);
|
SetStain(EquipSlot.OffHand, StainIds.None);
|
||||||
SetCrest(CrestFlag.OffHand, false);
|
SetCrest(CrestFlag.OffHand, false);
|
||||||
|
SetDefaultBonusItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetDefaultBonusItems()
|
||||||
|
{
|
||||||
|
foreach (var slot in BonusExtensions.AllFlags)
|
||||||
|
SetBonusItem(slot, EquipItem.BonusItemNothing(slot));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -285,13 +400,14 @@ public unsafe struct DesignData
|
||||||
|
|
||||||
SetHatVisible(true);
|
SetHatVisible(true);
|
||||||
SetWeaponVisible(true);
|
SetWeaponVisible(true);
|
||||||
|
SetEarsVisible(true);
|
||||||
SetVisor(false);
|
SetVisor(false);
|
||||||
fixed (uint* ptr = _itemIds)
|
fixed (uint* ptr = _itemIds)
|
||||||
{
|
{
|
||||||
MemoryUtility.MemSet(ptr, 0, 10 * 4);
|
MemoryUtility.MemSet(ptr, 0, 10 * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
fixed (ushort* ptr = _iconIds)
|
fixed (uint* ptr = _iconIds)
|
||||||
{
|
{
|
||||||
MemoryUtility.MemSet(ptr, 0, 10 * 2);
|
MemoryUtility.MemSet(ptr, 0, 10 * 2);
|
||||||
}
|
}
|
||||||
|
|
@ -306,6 +422,7 @@ public unsafe struct DesignData
|
||||||
_nameWrists = string.Empty;
|
_nameWrists = string.Empty;
|
||||||
_nameRFinger = string.Empty;
|
_nameRFinger = string.Empty;
|
||||||
_nameLFinger = string.Empty;
|
_nameLFinger = string.Empty;
|
||||||
|
_nameGlasses = string.Empty;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -322,7 +439,7 @@ public unsafe struct DesignData
|
||||||
|
|
||||||
public readonly byte[] GetEquipmentBytes()
|
public readonly byte[] GetEquipmentBytes()
|
||||||
{
|
{
|
||||||
var ret = new byte[40];
|
var ret = new byte[80];
|
||||||
fixed (byte* retPtr = ret, inPtr = _equipmentBytes)
|
fixed (byte* retPtr = ret, inPtr = _equipmentBytes)
|
||||||
{
|
{
|
||||||
MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length);
|
MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length);
|
||||||
|
|
@ -343,8 +460,8 @@ public unsafe struct DesignData
|
||||||
{
|
{
|
||||||
fixed (byte* dataPtr = _equipmentBytes)
|
fixed (byte* dataPtr = _equipmentBytes)
|
||||||
{
|
{
|
||||||
var data = new Span<byte>(dataPtr, 40);
|
var data = new Span<byte>(dataPtr, 80);
|
||||||
return Convert.TryFromBase64String(base64, data, out var written) && written == 40;
|
return Convert.TryFromBase64String(base64, data, out var written) && written == 80;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
|
using Glamourer.Designs.History;
|
||||||
using Glamourer.Designs.Links;
|
using Glamourer.Designs.Links;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
using Glamourer.Interop.Material;
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.State;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
|
@ -73,7 +73,7 @@ public class DesignEditor(
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}.");
|
Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}.");
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx));
|
DesignChanged.Invoke(DesignChanged.Type.Customize, design, new CustomizeTransaction(idx, oldValue, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -89,7 +89,7 @@ public class DesignEditor(
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
Glamourer.Log.Debug($"Changed entire customize with resulting flags {applied} and {changed}.");
|
Glamourer.Log.Debug($"Changed entire customize with resulting flags {applied} and {changed}.");
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
DesignChanged.Invoke(DesignChanged.Type.EntireCustomize, design, (oldCustomize, applied, changed));
|
DesignChanged.Invoke(DesignChanged.Type.EntireCustomize, design, new EntireCustomizeTransaction(changed, oldCustomize, newCustomize));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -104,7 +104,7 @@ public class DesignEditor(
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}.");
|
Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}.");
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Parameter, design, (old, @new, flag));
|
DesignChanged.Invoke(DesignChanged.Type.Parameter, design, new ParameterTransaction(flag, old, @new));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -123,11 +123,14 @@ public class DesignEditor(
|
||||||
if (!ChangeMainhandPeriphery(design, currentMain, currentOff, item, out var newOff, out var newGauntlets))
|
if (!ChangeMainhandPeriphery(design, currentMain, currentOff, item, out var newOff, out var newGauntlets))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var currentGauntlets = design.DesignData.Item(EquipSlot.Hands);
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug(
|
Glamourer.Log.Debug(
|
||||||
$"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId}).");
|
$"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId}).");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff, newGauntlets));
|
DesignChanged.Invoke(DesignChanged.Type.Weapon, design,
|
||||||
|
new WeaponTransaction(currentMain, currentOff, currentGauntlets, item, newOff ?? currentOff,
|
||||||
|
newGauntlets ?? currentGauntlets));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case EquipSlot.OffHand:
|
case EquipSlot.OffHand:
|
||||||
|
|
@ -140,11 +143,13 @@ public class DesignEditor(
|
||||||
if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item))
|
if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var currentGauntlets = design.DesignData.Item(EquipSlot.Hands);
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug(
|
Glamourer.Log.Debug(
|
||||||
$"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId}).");
|
$"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId}).");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item));
|
DesignChanged.Invoke(DesignChanged.Type.Weapon, design,
|
||||||
|
new WeaponTransaction(currentMain, currentOff, currentGauntlets, currentMain, item, currentGauntlets));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
@ -160,36 +165,53 @@ public class DesignEditor(
|
||||||
Glamourer.Log.Debug(
|
Glamourer.Log.Debug(
|
||||||
$"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}).");
|
$"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}).");
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Equip, design, (old, item, slot));
|
DesignChanged.Invoke(DesignChanged.Type.Equip, design, new EquipTransaction(slot, old, item));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings _ = default)
|
public void ChangeBonusItem(object data, BonusItemFlag slot, EquipItem item, ApplySettings settings = default)
|
||||||
{
|
{
|
||||||
var design = (Design)data;
|
var design = (Design)data;
|
||||||
if (Items.ValidateStain(stain, out var _, false).Length > 0)
|
if (item.Type.ToBonus() != slot)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var oldStain = design.DesignData.Stain(slot);
|
var oldItem = design.DesignData.BonusItem(slot);
|
||||||
if (!design.GetDesignDataRef().SetStain(slot, stain))
|
if (!design.GetDesignDataRef().SetBonusItem(slot, item))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}.");
|
Glamourer.Log.Debug($"Set {slot} bonus item to {item}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot));
|
DesignChanged.Invoke(DesignChanged.Type.BonusItem, design, new BonusItemTransaction(slot, oldItem, item));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings _ = default)
|
public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings _ = default)
|
||||||
|
{
|
||||||
|
var design = (Design)data;
|
||||||
|
if (Items.ValidateStain(stains, out var _, false).Length > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var oldStain = design.DesignData.Stain(slot);
|
||||||
|
if (!design.GetDesignDataRef().SetStain(slot, stains))
|
||||||
|
return;
|
||||||
|
|
||||||
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
SaveService.QueueSave(design);
|
||||||
|
Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stains}.");
|
||||||
|
DesignChanged.Invoke(DesignChanged.Type.Stains, design, new StainTransaction(slot, oldStain, stains));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings _ = default)
|
||||||
{
|
{
|
||||||
if (item.HasValue)
|
if (item.HasValue)
|
||||||
ChangeItem(data, slot, item.Value, _);
|
ChangeItem(data, slot, item.Value, _);
|
||||||
if (stain.HasValue)
|
if (stains.HasValue)
|
||||||
ChangeStain(data, slot, stain.Value, _);
|
ChangeStains(data, slot, stains.Value, _);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -203,7 +225,7 @@ public class DesignEditor(
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}.");
|
Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot));
|
DesignChanged.Invoke(DesignChanged.Type.Crest, design, new CrestTransaction(slot, oldCrest, crest));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -216,7 +238,7 @@ public class DesignEditor(
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Set value of {metaIndex} to {value}.");
|
Glamourer.Log.Debug($"Set value of {metaIndex} to {value}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value));
|
DesignChanged.Invoke(DesignChanged.Type.Other, design, new MetaTransaction(metaIndex, !value, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeMaterialRevert(Design design, MaterialValueIndex index, bool revert)
|
public void ChangeMaterialRevert(Design design, MaterialValueIndex index, bool revert)
|
||||||
|
|
@ -229,7 +251,7 @@ public class DesignEditor(
|
||||||
Glamourer.Log.Debug($"Changed advanced dye value for {index} to {(revert ? "Revert." : "no longer Revert.")}");
|
Glamourer.Log.Debug($"Changed advanced dye value for {index} to {(revert ? "Revert." : "no longer Revert.")}");
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
DesignChanged.Invoke(DesignChanged.Type.MaterialRevert, design, index);
|
DesignChanged.Invoke(DesignChanged.Type.MaterialRevert, design, new MaterialRevertTransaction(index, !revert, revert));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row)
|
public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row)
|
||||||
|
|
@ -264,7 +286,7 @@ public class DesignEditor(
|
||||||
|
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.DelaySave(design);
|
SaveService.DelaySave(design);
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Material, design, (oldValue, row, index));
|
DesignChanged.Invoke(DesignChanged.Type.Material, design, new MaterialTransaction(index, oldValue.Value, row));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeApplyMaterialValue(Design design, MaterialValueIndex index, bool value)
|
public void ChangeApplyMaterialValue(Design design, MaterialValueIndex index, bool value)
|
||||||
|
|
@ -277,13 +299,13 @@ public class DesignEditor(
|
||||||
Glamourer.Log.Debug($"Changed application of advanced dye for {index} to {value}.");
|
Glamourer.Log.Debug($"Changed application of advanced dye for {index} to {value}.");
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
DesignChanged.Invoke(DesignChanged.Type.ApplyMaterial, design, index);
|
DesignChanged.Invoke(DesignChanged.Type.ApplyMaterial, design, new ApplicationTransaction(index, !value, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void ApplyDesign(object data, MergedDesign other, ApplySettings _ = default)
|
public void ApplyDesign(object data, MergedDesign other, ApplySettings settings = default)
|
||||||
=> ApplyDesign(data, other.Design);
|
=> ApplyDesign(data, other.Design, settings);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void ApplyDesign(object data, DesignBase other, ApplySettings _ = default)
|
public void ApplyDesign(object data, DesignBase other, ApplySettings _ = default)
|
||||||
|
|
@ -308,6 +330,12 @@ public class DesignEditor(
|
||||||
|
|
||||||
_forceFullItemOff = false;
|
_forceFullItemOff = false;
|
||||||
|
|
||||||
|
foreach (var slot in BonusExtensions.AllFlags)
|
||||||
|
{
|
||||||
|
if (other.DoApplyBonusItem(slot))
|
||||||
|
ChangeBonusItem(design, slot, other.DesignData.BonusItem(slot));
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var slot in Enum.GetValues<CrestFlag>().Where(other.DoApplyCrest))
|
foreach (var slot in Enum.GetValues<CrestFlag>().Where(other.DoApplyCrest))
|
||||||
ChangeCrest(design, slot, other.DesignData.Crest(slot));
|
ChangeCrest(design, slot, other.DesignData.Crest(slot));
|
||||||
|
|
||||||
|
|
@ -338,7 +366,7 @@ public class DesignEditor(
|
||||||
|
|
||||||
newOff = o;
|
newOff = o;
|
||||||
}
|
}
|
||||||
else if (!_forceFullItemOff && Config.ChangeEntireItem)
|
else if (!_forceFullItemOff && Config.ChangeEntireItem && newMain.Type is not FullEquipType.Sword) // Skip applying shields.
|
||||||
{
|
{
|
||||||
var defaultOffhand = Items.GetDefaultOffhand(newMain);
|
var defaultOffhand = Items.GetDefaultOffhand(newMain);
|
||||||
if (Items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o))
|
if (Items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
|
using Glamourer.Designs.History;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
@ -40,11 +41,11 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
||||||
|
|
||||||
public struct CreationDate : ISortMode<Design>
|
public struct CreationDate : ISortMode<Design>
|
||||||
{
|
{
|
||||||
public string Name
|
public ReadOnlySpan<byte> Name
|
||||||
=> "Creation Date (Older First)";
|
=> "Creation Date (Older First)"u8;
|
||||||
|
|
||||||
public string Description
|
public ReadOnlySpan<byte> Description
|
||||||
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date.";
|
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."u8;
|
||||||
|
|
||||||
public IEnumerable<IPath> GetChildren(Folder f)
|
public IEnumerable<IPath> GetChildren(Folder f)
|
||||||
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.CreationDate));
|
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.CreationDate));
|
||||||
|
|
@ -52,11 +53,11 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
||||||
|
|
||||||
public struct UpdateDate : ISortMode<Design>
|
public struct UpdateDate : ISortMode<Design>
|
||||||
{
|
{
|
||||||
public string Name
|
public ReadOnlySpan<byte> Name
|
||||||
=> "Update Date (Older First)";
|
=> "Update Date (Older First)"u8;
|
||||||
|
|
||||||
public string Description
|
public ReadOnlySpan<byte> Description
|
||||||
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date.";
|
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."u8;
|
||||||
|
|
||||||
public IEnumerable<IPath> GetChildren(Folder f)
|
public IEnumerable<IPath> GetChildren(Folder f)
|
||||||
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.LastEdit));
|
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.LastEdit));
|
||||||
|
|
@ -64,11 +65,11 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
||||||
|
|
||||||
public struct InverseCreationDate : ISortMode<Design>
|
public struct InverseCreationDate : ISortMode<Design>
|
||||||
{
|
{
|
||||||
public string Name
|
public ReadOnlySpan<byte> Name
|
||||||
=> "Creation Date (Newer First)";
|
=> "Creation Date (Newer First)"u8;
|
||||||
|
|
||||||
public string Description
|
public ReadOnlySpan<byte> Description
|
||||||
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date.";
|
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."u8;
|
||||||
|
|
||||||
public IEnumerable<IPath> GetChildren(Folder f)
|
public IEnumerable<IPath> GetChildren(Folder f)
|
||||||
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.CreationDate));
|
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.CreationDate));
|
||||||
|
|
@ -76,11 +77,11 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
||||||
|
|
||||||
public struct InverseUpdateDate : ISortMode<Design>
|
public struct InverseUpdateDate : ISortMode<Design>
|
||||||
{
|
{
|
||||||
public string Name
|
public ReadOnlySpan<byte> Name
|
||||||
=> "Update Date (Newer First)";
|
=> "Update Date (Newer First)"u8;
|
||||||
|
|
||||||
public string Description
|
public ReadOnlySpan<byte> Description
|
||||||
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date.";
|
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."u8;
|
||||||
|
|
||||||
public IEnumerable<IPath> GetChildren(Folder f)
|
public IEnumerable<IPath> GetChildren(Folder f)
|
||||||
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.LastEdit));
|
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.LastEdit));
|
||||||
|
|
@ -92,13 +93,13 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
||||||
_saveService.QueueSave(this);
|
_saveService.QueueSave(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDesignChange(DesignChanged.Type type, Design design, object? data)
|
private void OnDesignChange(DesignChanged.Type type, Design design, ITransaction? data)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case DesignChanged.Type.Created:
|
case DesignChanged.Type.Created:
|
||||||
var parent = Root;
|
var parent = Root;
|
||||||
if (data is string path)
|
if ((data as CreationTransaction?)?.Path is { } path)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
parent = FindOrCreateAllFolders(path);
|
parent = FindOrCreateAllFolders(path);
|
||||||
|
|
@ -113,14 +114,14 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
||||||
|
|
||||||
return;
|
return;
|
||||||
case DesignChanged.Type.Deleted:
|
case DesignChanged.Type.Deleted:
|
||||||
if (FindLeaf(design, out var leaf1))
|
if (TryGetValue(design, out var leaf1))
|
||||||
Delete(leaf1);
|
Delete(leaf1);
|
||||||
return;
|
return;
|
||||||
case DesignChanged.Type.ReloadedAll:
|
case DesignChanged.Type.ReloadedAll:
|
||||||
Reload();
|
Reload();
|
||||||
return;
|
return;
|
||||||
case DesignChanged.Type.Renamed when data is string oldName:
|
case DesignChanged.Type.Renamed when (data as RenameTransaction?)?.Old is { } oldName:
|
||||||
if (!FindLeaf(design, out var leaf2))
|
if (!TryGetValue(design, out var leaf2))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var old = oldName.FixName();
|
var old = oldName.FixName();
|
||||||
|
|
@ -149,15 +150,6 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
||||||
? (string.Empty, false)
|
? (string.Empty, false)
|
||||||
: (DesignToIdentifier(design), true);
|
: (DesignToIdentifier(design), true);
|
||||||
|
|
||||||
// Search the entire filesystem for the leaf corresponding to a design.
|
|
||||||
public bool FindLeaf(Design design, [NotNullWhen(true)] out Leaf? leaf)
|
|
||||||
{
|
|
||||||
leaf = Root.GetAllDescendants(ISortMode<Design>.Lexicographical)
|
|
||||||
.OfType<Leaf>()
|
|
||||||
.FirstOrDefault(l => l.Value == design);
|
|
||||||
return leaf != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void MigrateOldPaths(SaveService saveService, Dictionary<string, string> oldPaths)
|
internal static void MigrateOldPaths(SaveService saveService, Dictionary<string, string> oldPaths)
|
||||||
{
|
{
|
||||||
if (oldPaths.Count == 0)
|
if (oldPaths.Count == 0)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
using Glamourer.Designs.History;
|
||||||
using Glamourer.Designs.Links;
|
using Glamourer.Designs.Links;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Interop.Penumbra;
|
using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
|
using OtterGui.Extensions;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui;
|
|
||||||
using Penumbra.GameData.DataContainers;
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
public sealed class DesignManager : DesignEditor
|
public sealed class DesignManager : DesignEditor
|
||||||
|
|
@ -52,7 +55,7 @@ public sealed class DesignManager : DesignEditor
|
||||||
{
|
{
|
||||||
var text = File.ReadAllText(f.FullName);
|
var text = File.ReadAllText(f.FullName);
|
||||||
var data = JObject.Parse(text);
|
var data = JObject.Parse(text);
|
||||||
var design = Design.LoadDesign(Customizations, Items, linkLoader, data);
|
var design = Design.LoadDesign(SaveService, Customizations, Items, linkLoader, data);
|
||||||
designs.Value!.Add((design, f.FullName));
|
designs.Value!.Add((design, f.FullName));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -98,16 +101,21 @@ public sealed class DesignManager : DesignEditor
|
||||||
var (actualName, path) = ParseName(name, handlePath);
|
var (actualName, path) = ParseName(name, handlePath);
|
||||||
var design = new Design(Customizations, Items)
|
var design = new Design(Customizations, Items)
|
||||||
{
|
{
|
||||||
CreationDate = DateTimeOffset.UtcNow,
|
CreationDate = DateTimeOffset.UtcNow,
|
||||||
LastEdit = DateTimeOffset.UtcNow,
|
LastEdit = DateTimeOffset.UtcNow,
|
||||||
Identifier = CreateNewGuid(),
|
Identifier = CreateNewGuid(),
|
||||||
Name = actualName,
|
Name = actualName,
|
||||||
Index = Designs.Count,
|
Index = Designs.Count,
|
||||||
|
ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing,
|
||||||
|
ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes,
|
||||||
|
QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar,
|
||||||
|
ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings,
|
||||||
};
|
};
|
||||||
|
design.SetWriteProtected(Config.DefaultDesignSettings.Locked);
|
||||||
Designs.Add(design);
|
Designs.Add(design);
|
||||||
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
|
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
|
||||||
SaveService.ImmediateSave(design);
|
SaveService.ImmediateSave(design);
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Created, design, path);
|
DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path));
|
||||||
return design;
|
return design;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,17 +125,22 @@ public sealed class DesignManager : DesignEditor
|
||||||
var (actualName, path) = ParseName(name, handlePath);
|
var (actualName, path) = ParseName(name, handlePath);
|
||||||
var design = new Design(clone)
|
var design = new Design(clone)
|
||||||
{
|
{
|
||||||
CreationDate = DateTimeOffset.UtcNow,
|
CreationDate = DateTimeOffset.UtcNow,
|
||||||
LastEdit = DateTimeOffset.UtcNow,
|
LastEdit = DateTimeOffset.UtcNow,
|
||||||
Identifier = CreateNewGuid(),
|
Identifier = CreateNewGuid(),
|
||||||
Name = actualName,
|
Name = actualName,
|
||||||
Index = Designs.Count,
|
Index = Designs.Count,
|
||||||
|
ForcedRedraw = Config.DefaultDesignSettings.AlwaysForceRedrawing,
|
||||||
|
ResetAdvancedDyes = Config.DefaultDesignSettings.ResetAdvancedDyes,
|
||||||
|
QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar,
|
||||||
|
ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
design.SetWriteProtected(Config.DefaultDesignSettings.Locked);
|
||||||
Designs.Add(design);
|
Designs.Add(design);
|
||||||
Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design.");
|
Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design.");
|
||||||
SaveService.ImmediateSave(design);
|
SaveService.ImmediateSave(design);
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Created, design, path);
|
DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path));
|
||||||
return design;
|
return design;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,11 +156,12 @@ public sealed class DesignManager : DesignEditor
|
||||||
Name = actualName,
|
Name = actualName,
|
||||||
Index = Designs.Count,
|
Index = Designs.Count,
|
||||||
};
|
};
|
||||||
|
design.SetWriteProtected(Config.DefaultDesignSettings.Locked);
|
||||||
Designs.Add(design);
|
Designs.Add(design);
|
||||||
Glamourer.Log.Debug(
|
Glamourer.Log.Debug(
|
||||||
$"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}.");
|
$"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}.");
|
||||||
SaveService.ImmediateSave(design);
|
SaveService.ImmediateSave(design);
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Created, design, path);
|
DesignChanged.Invoke(DesignChanged.Type.Created, design, new CreationTransaction(actualName, path));
|
||||||
return design;
|
return design;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,7 +190,7 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Renamed design {design.Identifier}.");
|
Glamourer.Log.Debug($"Renamed design {design.Identifier}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Renamed, design, oldName);
|
DesignChanged.Invoke(DesignChanged.Type.Renamed, design, new RenameTransaction(oldName, newName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Change the description of a design. </summary>
|
/// <summary> Change the description of a design. </summary>
|
||||||
|
|
@ -190,9 +204,10 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Changed description of design {design.Identifier}.");
|
Glamourer.Log.Debug($"Changed description of design {design.Identifier}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription);
|
DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, new DescriptionTransaction(oldDescription, description));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Change the associated color of a design. </summary>
|
||||||
public void ChangeColor(Design design, string newColor)
|
public void ChangeColor(Design design, string newColor)
|
||||||
{
|
{
|
||||||
var oldColor = design.Color;
|
var oldColor = design.Color;
|
||||||
|
|
@ -203,7 +218,7 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Changed color of design {design.Identifier}.");
|
Glamourer.Log.Debug($"Changed color of design {design.Identifier}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.ChangedColor, design, oldColor);
|
DesignChanged.Invoke(DesignChanged.Type.ChangedColor, design, new DesignColorTransaction(oldColor, newColor));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Add a new tag to a design. The tags remain sorted. </summary>
|
/// <summary> Add a new tag to a design. The tags remain sorted. </summary>
|
||||||
|
|
@ -214,10 +229,10 @@ public sealed class DesignManager : DesignEditor
|
||||||
|
|
||||||
design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray();
|
design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray();
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
var idx = design.Tags.IndexOf(tag);
|
var idx = design.Tags.AsEnumerable().IndexOf(tag);
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}.");
|
Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, (tag, idx));
|
DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Remove a tag from a design by its index. </summary>
|
/// <summary> Remove a tag from a design by its index. </summary>
|
||||||
|
|
@ -231,7 +246,7 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}.");
|
Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.RemovedTag, design, (oldTag, tagIdx));
|
DesignChanged.Invoke(DesignChanged.Type.RemovedTag, design, new TagRemovedTransaction(oldTag, tagIdx));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Rename a tag from a design by its index. The tags stay sorted.</summary>
|
/// <summary> Rename a tag from a design by its index. The tags stay sorted.</summary>
|
||||||
|
|
@ -246,7 +261,8 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags.");
|
Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx));
|
DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design,
|
||||||
|
new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.AsEnumerable().IndexOf(newTag)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Add an associated mod to a design. </summary>
|
/// <summary> Add an associated mod to a design. </summary>
|
||||||
|
|
@ -258,7 +274,7 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} to design {design.Identifier}.");
|
Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} to design {design.Identifier}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings));
|
DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Remove an associated mod from a design. </summary>
|
/// <summary> Remove an associated mod from a design. </summary>
|
||||||
|
|
@ -270,17 +286,26 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Removed associated mod {mod.DirectoryName} from design {design.Identifier}.");
|
Glamourer.Log.Debug($"Removed associated mod {mod.DirectoryName} from design {design.Identifier}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings));
|
DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, new ModRemovedTransaction(mod, settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateMod(Design design, Mod mod, ModSettings settings) {
|
/// <summary> Add or update an associated mod to a design. </summary>
|
||||||
if (!design.AssociatedMods.ContainsKey(mod))
|
public void UpdateMod(Design design, Mod mod, ModSettings settings)
|
||||||
return;
|
{
|
||||||
|
var hasOldSettings = design.AssociatedMods.TryGetValue(mod, out var oldSettings);
|
||||||
design.AssociatedMods[mod] = settings;
|
design.AssociatedMods[mod] = settings;
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Updated associated mod {mod.DirectoryName} from design {design.Identifier}.");
|
if (hasOldSettings)
|
||||||
DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings));
|
{
|
||||||
|
Glamourer.Log.Debug($"Updated associated mod {mod.DirectoryName} from design {design.Identifier}.");
|
||||||
|
DesignChanged.Invoke(DesignChanged.Type.UpdatedMod, design, new ModUpdatedTransaction(mod, oldSettings, settings));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} from design {design.Identifier}.");
|
||||||
|
DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, new ModAddedTransaction(mod, settings));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Set the write protection status of a design. </summary>
|
/// <summary> Set the write protection status of a design. </summary>
|
||||||
|
|
@ -291,7 +316,7 @@ public sealed class DesignManager : DesignEditor
|
||||||
|
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Set design {design.Identifier} to {(value ? "no longer be " : string.Empty)} write-protected.");
|
Glamourer.Log.Debug($"Set design {design.Identifier} to {(value ? "no longer be " : string.Empty)} write-protected.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, value);
|
DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Set the quick design bar display status of a design. </summary>
|
/// <summary> Set the quick design bar display status of a design. </summary>
|
||||||
|
|
@ -302,14 +327,48 @@ public sealed class DesignManager : DesignEditor
|
||||||
|
|
||||||
design.QuickDesign = value;
|
design.QuickDesign = value;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Set design {design.Identifier} to {(!value ? "no longer be " : string.Empty)} displayed in the quick design bar.");
|
Glamourer.Log.Debug(
|
||||||
DesignChanged.Invoke(DesignChanged.Type.QuickDesignBar, design, value);
|
$"Set design {design.Identifier} to {(!value ? "no longer be " : string.Empty)} displayed in the quick design bar.");
|
||||||
|
DesignChanged.Invoke(DesignChanged.Type.QuickDesignBar, design, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Edit Application Rules
|
#region Edit Application Rules
|
||||||
|
|
||||||
|
public void ChangeForcedRedraw(Design design, bool forcedRedraw)
|
||||||
|
{
|
||||||
|
if (design.ForcedRedraw == forcedRedraw)
|
||||||
|
return;
|
||||||
|
|
||||||
|
design.ForcedRedraw = forcedRedraw;
|
||||||
|
SaveService.QueueSave(design);
|
||||||
|
Glamourer.Log.Debug($"Set {design.Identifier} to {(forcedRedraw ? string.Empty : "not")} force redraws.");
|
||||||
|
DesignChanged.Invoke(DesignChanged.Type.ForceRedraw, design, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeResetAdvancedDyes(Design design, bool resetAdvancedDyes)
|
||||||
|
{
|
||||||
|
if (design.ResetAdvancedDyes == resetAdvancedDyes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
design.ResetAdvancedDyes = resetAdvancedDyes;
|
||||||
|
SaveService.QueueSave(design);
|
||||||
|
Glamourer.Log.Debug($"Set {design.Identifier} to {(resetAdvancedDyes ? string.Empty : "not")} reset advanced dyes.");
|
||||||
|
DesignChanged.Invoke(DesignChanged.Type.ResetAdvancedDyes, design, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeResetTemporarySettings(Design design, bool resetTemporarySettings)
|
||||||
|
{
|
||||||
|
if (design.ResetTemporarySettings == resetTemporarySettings)
|
||||||
|
return;
|
||||||
|
|
||||||
|
design.ResetTemporarySettings = resetTemporarySettings;
|
||||||
|
SaveService.QueueSave(design);
|
||||||
|
Glamourer.Log.Debug($"Set {design.Identifier} to {(resetTemporarySettings ? string.Empty : "not")} reset temporary settings.");
|
||||||
|
DesignChanged.Invoke(DesignChanged.Type.ResetTemporarySettings, design, null);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Change whether to apply a specific customize value. </summary>
|
/// <summary> Change whether to apply a specific customize value. </summary>
|
||||||
public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
|
public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
|
||||||
{
|
{
|
||||||
|
|
@ -319,7 +378,7 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Set applying of customization {idx.ToDefaultName()} to {value}.");
|
Glamourer.Log.Debug($"Set applying of customization {idx.ToDefaultName()} to {value}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.ApplyCustomize, design, idx);
|
DesignChanged.Invoke(DesignChanged.Type.ApplyCustomize, design, new ApplicationTransaction(idx, !value, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Change whether to apply a specific equipment piece. </summary>
|
/// <summary> Change whether to apply a specific equipment piece. </summary>
|
||||||
|
|
@ -331,11 +390,23 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}.");
|
Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, slot);
|
DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, new ApplicationTransaction((slot, false), !value, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Change whether to apply a specific equipment piece. </summary>
|
||||||
|
public void ChangeApplyBonusItem(Design design, BonusItemFlag slot, bool value)
|
||||||
|
{
|
||||||
|
if (!design.SetApplyBonusItem(slot, value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
|
SaveService.QueueSave(design);
|
||||||
|
Glamourer.Log.Debug($"Set applying of {slot} bonus item to {value}.");
|
||||||
|
DesignChanged.Invoke(DesignChanged.Type.ApplyBonusItem, design, new ApplicationTransaction(slot, !value, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Change whether to apply a specific stain. </summary>
|
/// <summary> Change whether to apply a specific stain. </summary>
|
||||||
public void ChangeApplyStain(Design design, EquipSlot slot, bool value)
|
public void ChangeApplyStains(Design design, EquipSlot slot, bool value)
|
||||||
{
|
{
|
||||||
if (!design.SetApplyStain(slot, value))
|
if (!design.SetApplyStain(slot, value))
|
||||||
return;
|
return;
|
||||||
|
|
@ -343,7 +414,7 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}.");
|
Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.ApplyStain, design, slot);
|
DesignChanged.Invoke(DesignChanged.Type.ApplyStain, design, new ApplicationTransaction((slot, true), !value, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Change whether to apply a specific crest visibility. </summary>
|
/// <summary> Change whether to apply a specific crest visibility. </summary>
|
||||||
|
|
@ -355,7 +426,7 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}.");
|
Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.ApplyCrest, design, slot);
|
DesignChanged.Invoke(DesignChanged.Type.ApplyCrest, design, new ApplicationTransaction(slot, !value, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Change the application value of one of the meta flags. </summary>
|
/// <summary> Change the application value of one of the meta flags. </summary>
|
||||||
|
|
@ -367,7 +438,7 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Set applying of {metaIndex} to {value}.");
|
Glamourer.Log.Debug($"Set applying of {metaIndex} to {value}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value));
|
DesignChanged.Invoke(DesignChanged.Type.Other, design, new ApplicationTransaction(metaIndex, !value, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Change the application value of a customize parameter. </summary>
|
/// <summary> Change the application value of a customize parameter. </summary>
|
||||||
|
|
@ -379,7 +450,40 @@ public sealed class DesignManager : DesignEditor
|
||||||
design.LastEdit = DateTimeOffset.UtcNow;
|
design.LastEdit = DateTimeOffset.UtcNow;
|
||||||
SaveService.QueueSave(design);
|
SaveService.QueueSave(design);
|
||||||
Glamourer.Log.Debug($"Set applying of parameter {flag} to {value}.");
|
Glamourer.Log.Debug($"Set applying of parameter {flag} to {value}.");
|
||||||
DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, flag);
|
DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, new ApplicationTransaction(flag, !value, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Change multiple application values at once. </summary>
|
||||||
|
public void ChangeApplyMulti(Design design, bool? equipment, bool? customization, bool? bonus, bool? parameters, bool? meta, bool? stains,
|
||||||
|
bool? materials, bool? crest)
|
||||||
|
{
|
||||||
|
if (equipment is { } e)
|
||||||
|
foreach (var f in EquipSlotExtensions.FullSlots)
|
||||||
|
ChangeApplyItem(design, f, e);
|
||||||
|
if (stains is { } s)
|
||||||
|
foreach (var f in EquipSlotExtensions.FullSlots)
|
||||||
|
ChangeApplyStains(design, f, s);
|
||||||
|
if (customization is { } c)
|
||||||
|
foreach (var f in CustomizationExtensions.All.Where(design.CustomizeSet.IsAvailable).Prepend(CustomizeIndex.Clan)
|
||||||
|
.Prepend(CustomizeIndex.Gender))
|
||||||
|
ChangeApplyCustomize(design, f, c);
|
||||||
|
if (bonus is { } b)
|
||||||
|
foreach (var f in BonusExtensions.AllFlags)
|
||||||
|
ChangeApplyBonusItem(design, f, b);
|
||||||
|
if (meta is { } m)
|
||||||
|
foreach (var f in MetaExtensions.AllRelevant)
|
||||||
|
ChangeApplyMeta(design, f, m);
|
||||||
|
if (crest is { } cr)
|
||||||
|
foreach (var f in CrestExtensions.AllRelevantSet)
|
||||||
|
ChangeApplyCrest(design, f, cr);
|
||||||
|
|
||||||
|
if (parameters is { } p)
|
||||||
|
foreach (var f in CustomizeParameterExtensions.AllFlags)
|
||||||
|
ChangeApplyParameter(design, f, p);
|
||||||
|
|
||||||
|
if (materials is { } ma)
|
||||||
|
foreach (var (key, _) in design.GetMaterialData().ToArray())
|
||||||
|
ChangeApplyMaterialValue(design, MaterialValueIndex.FromKey(key), ma);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -453,7 +557,7 @@ public sealed class DesignManager : DesignEditor
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.Move(SaveService.FileNames.MigrationDesignFile,
|
File.Move(SaveService.FileNames.MigrationDesignFile,
|
||||||
Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak"));
|
Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak"), true);
|
||||||
Glamourer.Log.Information($"Moved migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file.");
|
Glamourer.Log.Information($"Moved migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
185
Glamourer/Designs/History/DesignTransaction.cs
Normal file
185
Glamourer/Designs/History/DesignTransaction.cs
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
using Glamourer.GameData;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
|
using Glamourer.Interop.Penumbra;
|
||||||
|
using Glamourer.State;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Glamourer.Designs.History;
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. Can not be reverted. </remarks>
|
||||||
|
public readonly record struct CreationTransaction(string Name, string? Path)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction other)
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct RenameTransaction(string Old, string New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is RenameTransaction other ? new RenameTransaction(other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> ((DesignManager)editor).Rename((Design)data, Old);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct DescriptionTransaction(string Old, string New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is DescriptionTransaction other ? new DescriptionTransaction(other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> ((DesignManager)editor).ChangeDescription((Design)data, Old);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct DesignColorTransaction(string Old, string New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is DesignColorTransaction other ? new DesignColorTransaction(other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> ((DesignManager)editor).ChangeColor((Design)data, Old);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct TagAddedTransaction(string New, int Index)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction other)
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> ((DesignManager)editor).RemoveTag((Design)data, Index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct TagRemovedTransaction(string Old, int Index)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction other)
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> ((DesignManager)editor).AddTag((Design)data, Old);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct TagChangedTransaction(string Old, string New, int IndexOld, int IndexNew)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is TagChangedTransaction other && other.IndexNew == IndexOld
|
||||||
|
? new TagChangedTransaction(other.Old, New, other.IndexOld, IndexNew)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> ((DesignManager)editor).RenameTag((Design)data, IndexNew, Old);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct ModAddedTransaction(Mod Mod, ModSettings Settings)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction other)
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> ((DesignManager)editor).RemoveMod((Design)data, Mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct ModRemovedTransaction(Mod Mod, ModSettings Settings)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction other)
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> ((DesignManager)editor).AddMod((Design)data, Mod, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct ModUpdatedTransaction(Mod Mod, ModSettings Old, ModSettings New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is ModUpdatedTransaction other && Mod == other.Mod ? new ModUpdatedTransaction(Mod, other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> ((DesignManager)editor).UpdateMod((Design)data, Mod, Old);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct MaterialTransaction(MaterialValueIndex Index, ColorRow? Old, ColorRow? New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is MaterialTransaction other && Index == other.Index ? new MaterialTransaction(Index, other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
{
|
||||||
|
if (editor is DesignManager e)
|
||||||
|
e.ChangeMaterialValue((Design)data, Index, Old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct MaterialRevertTransaction(MaterialValueIndex Index, bool Old, bool New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction other)
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> ((DesignManager)editor).ChangeMaterialRevert((Design)data, Index, Old);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks> Only Designs. </remarks>
|
||||||
|
public readonly record struct ApplicationTransaction(object Index, bool Old, bool New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction other)
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
{
|
||||||
|
var manager = (DesignManager)editor;
|
||||||
|
var design = (Design)data;
|
||||||
|
switch (Index)
|
||||||
|
{
|
||||||
|
case CustomizeIndex idx:
|
||||||
|
manager.ChangeApplyCustomize(design, idx, Old);
|
||||||
|
break;
|
||||||
|
case (EquipSlot slot, true):
|
||||||
|
manager.ChangeApplyStains(design, slot, Old);
|
||||||
|
break;
|
||||||
|
case (EquipSlot slot, _):
|
||||||
|
manager.ChangeApplyItem(design, slot, Old);
|
||||||
|
break;
|
||||||
|
case BonusItemFlag slot:
|
||||||
|
manager.ChangeApplyBonusItem(design, slot, Old);
|
||||||
|
break;
|
||||||
|
case CrestFlag slot:
|
||||||
|
manager.ChangeApplyCrest(design, slot, Old);
|
||||||
|
break;
|
||||||
|
case MetaIndex slot:
|
||||||
|
manager.ChangeApplyMeta(design, slot, Old);
|
||||||
|
break;
|
||||||
|
case CustomizeParameterFlag slot:
|
||||||
|
manager.ChangeApplyParameter(design, slot, Old);
|
||||||
|
break;
|
||||||
|
case MaterialValueIndex slot:
|
||||||
|
manager.ChangeApplyMaterialValue(design, slot, Old);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
191
Glamourer/Designs/History/EditorHistory.cs
Normal file
191
Glamourer/Designs/History/EditorHistory.cs
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.Events;
|
||||||
|
using Glamourer.State;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
|
||||||
|
namespace Glamourer.Designs.History;
|
||||||
|
|
||||||
|
public class EditorHistory : IDisposable, IService
|
||||||
|
{
|
||||||
|
public const int MaxUndo = 16;
|
||||||
|
|
||||||
|
private sealed class Queue : IReadOnlyList<ITransaction>
|
||||||
|
{
|
||||||
|
private DateTime _lastAdd = DateTime.UtcNow;
|
||||||
|
|
||||||
|
private readonly ITransaction[] _data = new ITransaction[MaxUndo];
|
||||||
|
public int Offset { get; private set; }
|
||||||
|
public int Count { get; private set; }
|
||||||
|
|
||||||
|
public void Add(ITransaction transaction)
|
||||||
|
{
|
||||||
|
if (!TryMerge(transaction))
|
||||||
|
{
|
||||||
|
if (Count == MaxUndo)
|
||||||
|
{
|
||||||
|
_data[Offset] = transaction;
|
||||||
|
Offset = (Offset + 1) % MaxUndo;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Offset > 0)
|
||||||
|
{
|
||||||
|
_data[(Count + Offset) % MaxUndo] = transaction;
|
||||||
|
++Count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_data[Count] = transaction;
|
||||||
|
++Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastAdd = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryMerge(ITransaction newTransaction)
|
||||||
|
{
|
||||||
|
if (Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var time = DateTime.UtcNow;
|
||||||
|
if (time - _lastAdd > TimeSpan.FromMilliseconds(250))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var lastIdx = (Offset + Count - 1) % MaxUndo;
|
||||||
|
if (newTransaction.Merge(_data[lastIdx]) is not { } transaction)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_data[lastIdx] = transaction;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITransaction? RemoveLast()
|
||||||
|
{
|
||||||
|
if (Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
--Count;
|
||||||
|
var idx = (Offset + Count) % MaxUndo;
|
||||||
|
return _data[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<ITransaction> GetEnumerator()
|
||||||
|
{
|
||||||
|
var end = Offset + (Offset + Count) % MaxUndo;
|
||||||
|
for (var i = Offset; i < end; ++i)
|
||||||
|
yield return _data[i];
|
||||||
|
|
||||||
|
end = Count - end;
|
||||||
|
for (var i = 0; i < end; ++i)
|
||||||
|
yield return _data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
=> GetEnumerator();
|
||||||
|
|
||||||
|
public ITransaction this[int index]
|
||||||
|
=> index < 0 || index >= Count
|
||||||
|
? throw new IndexOutOfRangeException()
|
||||||
|
: _data[(Offset + index) % MaxUndo];
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly DesignEditor _designEditor;
|
||||||
|
private readonly StateEditor _stateEditor;
|
||||||
|
private readonly DesignChanged _designChanged;
|
||||||
|
private readonly StateChanged _stateChanged;
|
||||||
|
|
||||||
|
private readonly Dictionary<ActorState, Queue> _stateEntries = [];
|
||||||
|
private readonly Dictionary<Design, Queue> _designEntries = [];
|
||||||
|
|
||||||
|
private bool _undoMode;
|
||||||
|
|
||||||
|
public EditorHistory(DesignManager designEditor, StateManager stateEditor, DesignChanged designChanged, StateChanged stateChanged)
|
||||||
|
{
|
||||||
|
_designEditor = designEditor;
|
||||||
|
_stateEditor = stateEditor;
|
||||||
|
_designChanged = designChanged;
|
||||||
|
_stateChanged = stateChanged;
|
||||||
|
|
||||||
|
_designChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.EditorHistory);
|
||||||
|
_stateChanged.Subscribe(OnStateChanged, StateChanged.Priority.EditorHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_designChanged.Unsubscribe(OnDesignChanged);
|
||||||
|
_stateChanged.Unsubscribe(OnStateChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanUndo(ActorState state)
|
||||||
|
=> _stateEntries.TryGetValue(state, out var list) && list.Count > 0;
|
||||||
|
|
||||||
|
public bool CanUndo(Design design)
|
||||||
|
=> _designEntries.TryGetValue(design, out var list) && list.Count > 0;
|
||||||
|
|
||||||
|
public bool Undo(ActorState state)
|
||||||
|
{
|
||||||
|
if (!_stateEntries.TryGetValue(state, out var list) || list.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_undoMode = true;
|
||||||
|
list.RemoveLast()!.Revert(_stateEditor, state);
|
||||||
|
_undoMode = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Undo(Design design)
|
||||||
|
{
|
||||||
|
if (!_designEntries.TryGetValue(design, out var list) || list.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_undoMode = true;
|
||||||
|
list.RemoveLast()!.Revert(_designEditor, design);
|
||||||
|
_undoMode = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void AddStateTransaction(ActorState state, ITransaction transaction)
|
||||||
|
{
|
||||||
|
if (!_stateEntries.TryGetValue(state, out var list))
|
||||||
|
{
|
||||||
|
list = [];
|
||||||
|
_stateEntries.Add(state, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddDesignTransaction(Design design, ITransaction transaction)
|
||||||
|
{
|
||||||
|
if (!_designEntries.TryGetValue(design, out var list))
|
||||||
|
{
|
||||||
|
list = [];
|
||||||
|
_designEntries.Add(design, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnStateChanged(StateChangeType type, StateSource source, ActorState state, ActorData actors, ITransaction? data)
|
||||||
|
{
|
||||||
|
if (_undoMode || source is not StateSource.Manual)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (data is not null)
|
||||||
|
AddStateTransaction(state, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? data)
|
||||||
|
{
|
||||||
|
if (_undoMode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (data is not null)
|
||||||
|
AddDesignTransaction(design, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
113
Glamourer/Designs/History/Transaction.cs
Normal file
113
Glamourer/Designs/History/Transaction.cs
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using Glamourer.GameData;
|
||||||
|
|
||||||
|
namespace Glamourer.Designs.History;
|
||||||
|
|
||||||
|
public interface ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction other);
|
||||||
|
public void Revert(IDesignEditor editor, object data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct CustomizeTransaction(CustomizeIndex Slot, CustomizeValue Old, CustomizeValue New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is CustomizeTransaction other && Slot == other.Slot ? new CustomizeTransaction(Slot, other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> editor.ChangeCustomize(data, Slot, Old, ApplySettings.Manual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct EntireCustomizeTransaction(CustomizeFlag Apply, CustomizeArray Old, CustomizeArray New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is EntireCustomizeTransaction other ? new EntireCustomizeTransaction(Apply | other.Apply, other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> editor.ChangeEntireCustomize(data, Old, Apply, ApplySettings.Manual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct EquipTransaction(EquipSlot Slot, EquipItem Old, EquipItem New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is EquipTransaction other && Slot == other.Slot ? new EquipTransaction(Slot, other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> editor.ChangeItem(data, Slot, Old, ApplySettings.Manual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct BonusItemTransaction(BonusItemFlag Slot, EquipItem Old, EquipItem New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is BonusItemTransaction other && Slot == other.Slot ? new BonusItemTransaction(Slot, other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> editor.ChangeBonusItem(data, Slot, Old, ApplySettings.Manual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct WeaponTransaction(
|
||||||
|
EquipItem OldMain,
|
||||||
|
EquipItem OldOff,
|
||||||
|
EquipItem OldGauntlets,
|
||||||
|
EquipItem NewMain,
|
||||||
|
EquipItem NewOff,
|
||||||
|
EquipItem NewGauntlets)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is WeaponTransaction other
|
||||||
|
? new WeaponTransaction(other.OldMain, other.OldOff, other.OldGauntlets, NewMain, NewOff, NewGauntlets)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
{
|
||||||
|
editor.ChangeItem(data, EquipSlot.MainHand, OldMain, ApplySettings.Manual);
|
||||||
|
editor.ChangeItem(data, EquipSlot.OffHand, OldOff, ApplySettings.Manual);
|
||||||
|
editor.ChangeItem(data, EquipSlot.Hands, OldGauntlets, ApplySettings.Manual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct StainTransaction(EquipSlot Slot, StainIds Old, StainIds New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is StainTransaction other && Slot == other.Slot ? new StainTransaction(Slot, other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> editor.ChangeStains(data, Slot, Old, ApplySettings.Manual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct CrestTransaction(CrestFlag Slot, bool Old, bool New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is CrestTransaction other && Slot == other.Slot ? new CrestTransaction(Slot, other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> editor.ChangeCrest(data, Slot, Old, ApplySettings.Manual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct ParameterTransaction(CustomizeParameterFlag Slot, CustomizeParameterValue Old, CustomizeParameterValue New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> older is ParameterTransaction other && Slot == other.Slot ? new ParameterTransaction(Slot, other.Old, New) : null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> editor.ChangeCustomizeParameter(data, Slot, Old, ApplySettings.Manual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct MetaTransaction(MetaIndex Slot, bool Old, bool New)
|
||||||
|
: ITransaction
|
||||||
|
{
|
||||||
|
public ITransaction? Merge(ITransaction older)
|
||||||
|
=> null;
|
||||||
|
|
||||||
|
public void Revert(IDesignEditor editor, object data)
|
||||||
|
=> editor.ChangeMetaState(data, Slot, Old, ApplySettings.Manual);
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,8 @@ public readonly record struct ApplySettings(
|
||||||
bool FromJobChange = false,
|
bool FromJobChange = false,
|
||||||
bool UseSingleSource = false,
|
bool UseSingleSource = false,
|
||||||
bool MergeLinks = false,
|
bool MergeLinks = false,
|
||||||
bool ResetMaterials = false)
|
bool ResetMaterials = false,
|
||||||
|
bool IsFinal = false)
|
||||||
{
|
{
|
||||||
public static readonly ApplySettings Manual = new()
|
public static readonly ApplySettings Manual = new()
|
||||||
{
|
{
|
||||||
|
|
@ -24,6 +25,7 @@ public readonly record struct ApplySettings(
|
||||||
UseSingleSource = false,
|
UseSingleSource = false,
|
||||||
MergeLinks = false,
|
MergeLinks = false,
|
||||||
ResetMaterials = false,
|
ResetMaterials = false,
|
||||||
|
IsFinal = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static readonly ApplySettings ManualWithLinks = new()
|
public static readonly ApplySettings ManualWithLinks = new()
|
||||||
|
|
@ -35,6 +37,7 @@ public readonly record struct ApplySettings(
|
||||||
UseSingleSource = false,
|
UseSingleSource = false,
|
||||||
MergeLinks = true,
|
MergeLinks = true,
|
||||||
ResetMaterials = false,
|
ResetMaterials = false,
|
||||||
|
IsFinal = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static readonly ApplySettings Game = new()
|
public static readonly ApplySettings Game = new()
|
||||||
|
|
@ -46,6 +49,7 @@ public readonly record struct ApplySettings(
|
||||||
UseSingleSource = false,
|
UseSingleSource = false,
|
||||||
MergeLinks = false,
|
MergeLinks = false,
|
||||||
ResetMaterials = true,
|
ResetMaterials = true,
|
||||||
|
IsFinal = false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,12 +68,15 @@ public interface IDesignEditor
|
||||||
public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default)
|
public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default)
|
||||||
=> ChangeEquip(data, slot, item, null, settings);
|
=> ChangeEquip(data, slot, item, null, settings);
|
||||||
|
|
||||||
|
/// <summary> Change a bonus item. </summary>
|
||||||
|
public void ChangeBonusItem(object data, BonusItemFlag slot, EquipItem item, ApplySettings settings = default);
|
||||||
|
|
||||||
/// <summary> Change the stain for any equipment piece. </summary>
|
/// <summary> Change the stain for any equipment piece. </summary>
|
||||||
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings = default)
|
public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings = default)
|
||||||
=> ChangeEquip(data, slot, null, stain, settings);
|
=> ChangeEquip(data, slot, null, stains, settings);
|
||||||
|
|
||||||
/// <summary> Change an equipment piece and its stain at the same time. </summary>
|
/// <summary> Change an equipment piece and its stain at the same time. </summary>
|
||||||
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings = default);
|
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings settings = default);
|
||||||
|
|
||||||
/// <summary> Change the crest visibility for any equipment piece. </summary>
|
/// <summary> Change the crest visibility for any equipment piece. </summary>
|
||||||
public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings = default);
|
public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings = default);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using Glamourer.Interop.Material;
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
|
|
@ -15,11 +16,16 @@ public interface IDesignStandIn : IEquatable<IDesignStandIn>
|
||||||
public string SerializeName();
|
public string SerializeName();
|
||||||
public StateSource AssociatedSource();
|
public StateSource AssociatedSource();
|
||||||
|
|
||||||
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks { get; }
|
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks(bool newApplication);
|
||||||
|
|
||||||
public void AddData(JObject jObj);
|
public void AddData(JObject jObj);
|
||||||
|
|
||||||
public void ParseData(JObject jObj);
|
public void ParseData(JObject jObj);
|
||||||
|
|
||||||
public bool ChangeData(object data);
|
public bool ChangeData(object data);
|
||||||
|
|
||||||
|
public bool ForcedRedraw { get; }
|
||||||
|
|
||||||
|
public bool ResetAdvancedDyes { get; }
|
||||||
|
public bool ResetTemporarySettings { get; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using OtterGui;
|
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Extensions;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
using Notification = OtterGui.Classes.Notification;
|
||||||
|
|
||||||
namespace Glamourer.Designs.Links;
|
namespace Glamourer.Designs.Links;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Glamourer.Automation;
|
using Glamourer.Automation;
|
||||||
|
using Glamourer.Designs.History;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
|
@ -67,7 +68,7 @@ public sealed class DesignLinkManager : IService, IDisposable
|
||||||
_event.Invoke(DesignChanged.Type.ChangedLink, parent, null);
|
_event.Invoke(DesignChanged.Type.ChangedLink, parent, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDesignChanged(DesignChanged.Type type, Design deletedDesign, object? _)
|
private void OnDesignChanged(DesignChanged.Type type, Design deletedDesign, ITransaction? _)
|
||||||
{
|
{
|
||||||
if (type is not DesignChanged.Type.Deleted)
|
if (type is not DesignChanged.Type.Deleted)
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using Glamourer.Automation;
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.Automation;
|
||||||
|
using Glamourer.Designs.Special;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
using Glamourer.Interop.Material;
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
|
|
@ -19,16 +21,18 @@ public class DesignMerger(
|
||||||
{
|
{
|
||||||
public MergedDesign Merge(LinkContainer designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership,
|
public MergedDesign Merge(LinkContainer designs, in CustomizeArray currentCustomize, in DesignData baseRef, bool respectOwnership,
|
||||||
bool modAssociations)
|
bool modAssociations)
|
||||||
=> Merge(designs.Select(d => ((IDesignStandIn)d.Link, d.Type)), currentCustomize, baseRef, respectOwnership, modAssociations);
|
=> Merge(designs.Select(d => ((IDesignStandIn)d.Link, d.Type, JobFlag.All)), currentCustomize, baseRef, respectOwnership,
|
||||||
|
modAssociations);
|
||||||
|
|
||||||
public MergedDesign Merge(IEnumerable<(IDesignStandIn, ApplicationType)> designs, in CustomizeArray currentCustomize, in DesignData baseRef,
|
public MergedDesign Merge(IEnumerable<(IDesignStandIn, ApplicationType, JobFlag)> designs, in CustomizeArray currentCustomize,
|
||||||
bool respectOwnership, bool modAssociations)
|
in DesignData baseRef, bool respectOwnership, bool modAssociations)
|
||||||
{
|
{
|
||||||
var ret = new MergedDesign(designManager);
|
var ret = new MergedDesign(designManager);
|
||||||
ret.Design.SetCustomize(_customize, currentCustomize);
|
ret.Design.SetCustomize(_customize, currentCustomize);
|
||||||
CustomizeFlag fixFlags = 0;
|
var startBodyType = currentCustomize.BodyType;
|
||||||
|
CustomizeFlag fixFlags = 0;
|
||||||
respectOwnership &= _config.UnlockedItemMode;
|
respectOwnership &= _config.UnlockedItemMode;
|
||||||
foreach (var (design, type) in designs)
|
foreach (var (design, type, jobs) in designs)
|
||||||
{
|
{
|
||||||
if (type is 0)
|
if (type is 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -39,17 +43,24 @@ public class DesignMerger(
|
||||||
if (!data.IsHuman)
|
if (!data.IsHuman)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyMeta) = type.ApplyWhat(design);
|
var collection = type.ApplyWhat(design);
|
||||||
ReduceMeta(data, applyMeta, ret, source);
|
ReduceMeta(data, collection.Meta, ret, source);
|
||||||
ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source, respectOwnership);
|
ReduceCustomize(data, collection.Customize, ref fixFlags, ret, source, respectOwnership, startBodyType);
|
||||||
ReduceEquip(data, equipFlags, ret, source, respectOwnership);
|
ReduceEquip(data, collection.Equip, ret, source, respectOwnership);
|
||||||
ReduceMainhands(data, equipFlags, ret, source, respectOwnership);
|
ReduceBonusItems(data, collection.BonusItem, ret, source, respectOwnership);
|
||||||
ReduceOffhands(data, equipFlags, ret, source, respectOwnership);
|
ReduceMainhands(data, jobs, collection.Equip, ret, source, respectOwnership);
|
||||||
ReduceCrests(data, crestFlags, ret, source);
|
ReduceOffhands(data, jobs, collection.Equip, ret, source, respectOwnership);
|
||||||
ReduceParameters(data, parameterFlags, ret, source);
|
ReduceCrests(data, collection.Crest, ret, source);
|
||||||
|
ReduceParameters(data, collection.Parameters, ret, source);
|
||||||
ReduceMods(design as Design, ret, modAssociations);
|
ReduceMods(design as Design, ret, modAssociations);
|
||||||
if (type.HasFlag(ApplicationType.GearCustomization))
|
if (type.HasFlag(ApplicationType.GearCustomization))
|
||||||
ReduceMaterials(design, ret);
|
ReduceMaterials(design, ret);
|
||||||
|
if (design.ForcedRedraw)
|
||||||
|
ret.ForcedRedraw = true;
|
||||||
|
if (design.ResetAdvancedDyes)
|
||||||
|
ret.ResetAdvancedDyes = true;
|
||||||
|
if (design.ResetTemporarySettings)
|
||||||
|
ret.ResetTemporarySettings = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyFixFlags(ret, fixFlags);
|
ApplyFixFlags(ret, fixFlags);
|
||||||
|
|
@ -78,7 +89,7 @@ public class DesignMerger(
|
||||||
|
|
||||||
private static void ReduceMeta(in DesignData design, MetaFlag applyMeta, MergedDesign ret, StateSource source)
|
private static void ReduceMeta(in DesignData design, MetaFlag applyMeta, MergedDesign ret, StateSource source)
|
||||||
{
|
{
|
||||||
applyMeta &= ~ret.Design.ApplyMeta;
|
applyMeta &= ~ret.Design.Application.Meta;
|
||||||
if (applyMeta == 0)
|
if (applyMeta == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -95,7 +106,7 @@ public class DesignMerger(
|
||||||
|
|
||||||
private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateSource source)
|
private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateSource source)
|
||||||
{
|
{
|
||||||
crestFlags &= ~ret.Design.ApplyCrest;
|
crestFlags &= ~ret.Design.Application.Crest;
|
||||||
if (crestFlags == 0)
|
if (crestFlags == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -113,7 +124,7 @@ public class DesignMerger(
|
||||||
private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret,
|
private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret,
|
||||||
StateSource source)
|
StateSource source)
|
||||||
{
|
{
|
||||||
parameterFlags &= ~ret.Design.ApplyParameters;
|
parameterFlags &= ~ret.Design.Application.Parameters;
|
||||||
if (parameterFlags == 0)
|
if (parameterFlags == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -131,7 +142,7 @@ public class DesignMerger(
|
||||||
private void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source,
|
private void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source,
|
||||||
bool respectOwnership)
|
bool respectOwnership)
|
||||||
{
|
{
|
||||||
equipFlags &= ~ret.Design.ApplyEquip;
|
equipFlags &= ~ret.Design.Application.Equip;
|
||||||
if (equipFlags == 0)
|
if (equipFlags == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -169,7 +180,23 @@ public class DesignMerger(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source,
|
private void ReduceBonusItems(in DesignData design, BonusItemFlag bonusItems, MergedDesign ret, StateSource source, bool respectOwnership)
|
||||||
|
{
|
||||||
|
bonusItems &= ~ret.Design.Application.BonusItem;
|
||||||
|
if (bonusItems == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var slot in BonusExtensions.AllFlags.Where(b => bonusItems.HasFlag(b)))
|
||||||
|
{
|
||||||
|
var item = design.BonusItem(slot);
|
||||||
|
if (!respectOwnership || true) // TODO: maybe check unlocks
|
||||||
|
ret.Design.GetDesignDataRef().SetBonusItem(slot, item);
|
||||||
|
ret.Design.SetApplyBonusItem(slot, true);
|
||||||
|
ret.Sources[slot] = source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReduceMainhands(in DesignData design, JobFlag allowedJobs, EquipFlag equipFlags, MergedDesign ret, StateSource source,
|
||||||
bool respectOwnership)
|
bool respectOwnership)
|
||||||
{
|
{
|
||||||
if (!equipFlags.HasFlag(EquipFlag.Mainhand))
|
if (!equipFlags.HasFlag(EquipFlag.Mainhand))
|
||||||
|
|
@ -185,10 +212,11 @@ public class DesignMerger(
|
||||||
ret.Design.GetDesignDataRef().SetItem(EquipSlot.MainHand, weapon);
|
ret.Design.GetDesignDataRef().SetItem(EquipSlot.MainHand, weapon);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.Weapons.TryAdd(weapon.Type, (weapon, source));
|
ret.Weapons.TryAdd(weapon.Type, weapon, source, allowedJobs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReduceOffhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source, bool respectOwnership)
|
private void ReduceOffhands(in DesignData design, JobFlag allowedJobs, EquipFlag equipFlags, MergedDesign ret, StateSource source,
|
||||||
|
bool respectOwnership)
|
||||||
{
|
{
|
||||||
if (!equipFlags.HasFlag(EquipFlag.Offhand))
|
if (!equipFlags.HasFlag(EquipFlag.Offhand))
|
||||||
return;
|
return;
|
||||||
|
|
@ -204,14 +232,14 @@ public class DesignMerger(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (weapon.Valid)
|
if (weapon.Valid)
|
||||||
ret.Weapons.TryAdd(weapon.Type, (weapon, source));
|
ret.Weapons.TryAdd(weapon.Type, weapon, source, allowedJobs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret,
|
private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret,
|
||||||
StateSource source, bool respectOwnership)
|
StateSource source, bool respectOwnership, CustomizeValue startBodyType)
|
||||||
{
|
{
|
||||||
customizeFlags &= ~ret.Design.ApplyCustomizeExcludingBodyType;
|
customizeFlags &= ~ret.Design.ApplyCustomizeExcludingBodyType;
|
||||||
if (ret.Design.DesignData.Customize.BodyType != 1)
|
if (ret.Design.DesignData.Customize.BodyType != startBodyType)
|
||||||
customizeFlags &= ~CustomizeFlag.BodyType;
|
customizeFlags &= ~CustomizeFlag.BodyType;
|
||||||
|
|
||||||
if (customizeFlags == 0)
|
if (customizeFlags == 0)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,16 @@ public sealed class LinkContainer : List<DesignLink>
|
||||||
public new int Count
|
public new int Count
|
||||||
=> base.Count + After.Count;
|
=> base.Count + After.Count;
|
||||||
|
|
||||||
|
public LinkContainer Clone()
|
||||||
|
{
|
||||||
|
var ret = new LinkContainer();
|
||||||
|
ret.EnsureCapacity(base.Count);
|
||||||
|
ret.After.EnsureCapacity(After.Count);
|
||||||
|
ret.AddRange(this);
|
||||||
|
ret.After.AddRange(After);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Reorder(int fromIndex, LinkOrder fromOrder, int toIndex, LinkOrder toOrder)
|
public bool Reorder(int fromIndex, LinkOrder fromOrder, int toIndex, LinkOrder toOrder)
|
||||||
{
|
{
|
||||||
var fromList = fromOrder switch
|
var fromList = fromOrder switch
|
||||||
|
|
@ -89,13 +99,15 @@ public sealed class LinkContainer : List<DesignLink>
|
||||||
|
|
||||||
if (GetAllLinks(parent).Any(l => l.Link.Link == child && l.Order != order))
|
if (GetAllLinks(parent).Any(l => l.Link.Link == child && l.Order != order))
|
||||||
{
|
{
|
||||||
error = $"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the parent already links to the child in the opposite direction.";
|
error =
|
||||||
|
$"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the parent already links to the child in the opposite direction.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetAllLinks(child).Any(l => l.Link.Link == parent && l.Order == order))
|
if (GetAllLinks(child).Any(l => l.Link.Link == parent && l.Order == order))
|
||||||
{
|
{
|
||||||
error = $"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the child already links to the parent in the opposite direction.";
|
error =
|
||||||
|
$"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the child already links to the parent in the opposite direction.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,71 @@
|
||||||
using Glamourer.Events;
|
using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.GameData;
|
|
||||||
using Glamourer.Interop.Penumbra;
|
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Designs.Links;
|
namespace Glamourer.Designs.Links;
|
||||||
|
|
||||||
|
public readonly struct WeaponList
|
||||||
|
{
|
||||||
|
private readonly Dictionary<FullEquipType, List<(EquipItem, StateSource, JobFlag)>> _list = new(4);
|
||||||
|
|
||||||
|
public IEnumerable<(EquipItem, StateSource, JobFlag)> Values
|
||||||
|
=> _list.Values.SelectMany(t => t);
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
=> _list.Clear();
|
||||||
|
|
||||||
|
public bool TryAdd(FullEquipType type, EquipItem item, StateSource source, JobFlag flags)
|
||||||
|
{
|
||||||
|
if (!_list.TryGetValue(type, out var list))
|
||||||
|
{
|
||||||
|
list = new List<(EquipItem, StateSource, JobFlag)>(2);
|
||||||
|
_list.Add(type, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingFlags = list.Count == 0 ? 0 : list.Select(t => t.Item3).Aggregate((t, existing) => t | existing);
|
||||||
|
var remainingFlags = flags & ~existingFlags;
|
||||||
|
|
||||||
|
if (remainingFlags == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
list.Add((item, source, remainingFlags));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet(FullEquipType type, JobId id, bool gameStateAllowed, out (EquipItem, StateSource) ret)
|
||||||
|
{
|
||||||
|
if (!_list.TryGetValue(type, out var list))
|
||||||
|
{
|
||||||
|
ret = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var flag = (JobFlag)(1ul << id.Id);
|
||||||
|
|
||||||
|
foreach (var (item, source, flags) in list)
|
||||||
|
{
|
||||||
|
if (flags.HasFlag(flag) && (gameStateAllowed || source is not StateSource.Game))
|
||||||
|
{
|
||||||
|
ret = (item, source);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WeaponList()
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class MergedDesign
|
public sealed class MergedDesign
|
||||||
{
|
{
|
||||||
public MergedDesign(DesignManager designManager)
|
public MergedDesign(DesignManager designManager)
|
||||||
{
|
{
|
||||||
Design = designManager.CreateTemporary();
|
Design = designManager.CreateTemporary();
|
||||||
Design.ApplyEquip = 0;
|
Design.Application = ApplicationCollection.None;
|
||||||
Design.ApplyCustomize = 0;
|
|
||||||
Design.ApplyCrest = 0;
|
|
||||||
Design.ApplyParameters = 0;
|
|
||||||
Design.ApplyMeta = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MergedDesign(DesignBase design)
|
public MergedDesign(DesignBase design)
|
||||||
|
|
@ -26,15 +75,17 @@ public sealed class MergedDesign
|
||||||
{
|
{
|
||||||
var weapon = design.DesignData.Item(EquipSlot.MainHand);
|
var weapon = design.DesignData.Item(EquipSlot.MainHand);
|
||||||
if (weapon.Valid)
|
if (weapon.Valid)
|
||||||
Weapons.TryAdd(weapon.Type, (weapon, StateSource.Manual));
|
Weapons.TryAdd(weapon.Type, weapon, StateSource.Manual, JobFlag.All);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (design.DoApplyEquip(EquipSlot.OffHand))
|
if (design.DoApplyEquip(EquipSlot.OffHand))
|
||||||
{
|
{
|
||||||
var weapon = design.DesignData.Item(EquipSlot.OffHand);
|
var weapon = design.DesignData.Item(EquipSlot.OffHand);
|
||||||
if (weapon.Valid)
|
if (weapon.Valid)
|
||||||
Weapons.TryAdd(weapon.Type, (weapon, StateSource.Manual));
|
Weapons.TryAdd(weapon.Type, weapon, StateSource.Manual, JobFlag.All);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ForcedRedraw = design is IDesignStandIn { ForcedRedraw: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
public MergedDesign(Design design)
|
public MergedDesign(Design design)
|
||||||
|
|
@ -44,8 +95,11 @@ public sealed class MergedDesign
|
||||||
AssociatedMods[mod] = settings;
|
AssociatedMods[mod] = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly DesignBase Design;
|
public readonly DesignBase Design;
|
||||||
public readonly Dictionary<FullEquipType, (EquipItem, StateSource)> Weapons = new(4);
|
public readonly WeaponList Weapons = new();
|
||||||
public readonly SortedList<Mod, ModSettings> AssociatedMods = [];
|
public readonly SortedList<Mod, ModSettings> AssociatedMods = [];
|
||||||
public StateSources Sources = new();
|
public StateSources Sources = new();
|
||||||
|
public bool ForcedRedraw;
|
||||||
|
public bool ResetAdvancedDyes;
|
||||||
|
public bool ResetTemporarySettings;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Glamourer.State;
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.State;
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
|
|
@ -9,23 +10,15 @@ public enum MetaIndex
|
||||||
VisorState = StateIndex.MetaVisorState,
|
VisorState = StateIndex.MetaVisorState,
|
||||||
WeaponState = StateIndex.MetaWeaponState,
|
WeaponState = StateIndex.MetaWeaponState,
|
||||||
ModelId = StateIndex.MetaModelId,
|
ModelId = StateIndex.MetaModelId,
|
||||||
}
|
EarState = StateIndex.MetaEarState,
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum MetaFlag : byte
|
|
||||||
{
|
|
||||||
Wetness = 0x01,
|
|
||||||
HatState = 0x02,
|
|
||||||
VisorState = 0x04,
|
|
||||||
WeaponState = 0x08,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MetaExtensions
|
public static class MetaExtensions
|
||||||
{
|
{
|
||||||
public static readonly IReadOnlyList<MetaIndex> AllRelevant =
|
public static readonly IReadOnlyList<MetaIndex> AllRelevant =
|
||||||
[MetaIndex.Wetness, MetaIndex.HatState, MetaIndex.VisorState, MetaIndex.WeaponState];
|
[MetaIndex.Wetness, MetaIndex.HatState, MetaIndex.VisorState, MetaIndex.WeaponState, MetaIndex.EarState];
|
||||||
|
|
||||||
public const MetaFlag All = MetaFlag.Wetness | MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
|
public const MetaFlag All = MetaFlag.Wetness | MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState | MetaFlag.EarState;
|
||||||
|
|
||||||
public static MetaFlag ToFlag(this MetaIndex index)
|
public static MetaFlag ToFlag(this MetaIndex index)
|
||||||
=> index switch
|
=> index switch
|
||||||
|
|
@ -34,6 +27,7 @@ public static class MetaExtensions
|
||||||
MetaIndex.HatState => MetaFlag.HatState,
|
MetaIndex.HatState => MetaFlag.HatState,
|
||||||
MetaIndex.VisorState => MetaFlag.VisorState,
|
MetaIndex.VisorState => MetaFlag.VisorState,
|
||||||
MetaIndex.WeaponState => MetaFlag.WeaponState,
|
MetaIndex.WeaponState => MetaFlag.WeaponState,
|
||||||
|
MetaIndex.EarState => MetaFlag.EarState,
|
||||||
_ => (MetaFlag)byte.MaxValue,
|
_ => (MetaFlag)byte.MaxValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -44,9 +38,24 @@ public static class MetaExtensions
|
||||||
MetaFlag.HatState => MetaIndex.HatState,
|
MetaFlag.HatState => MetaIndex.HatState,
|
||||||
MetaFlag.VisorState => MetaIndex.VisorState,
|
MetaFlag.VisorState => MetaIndex.VisorState,
|
||||||
MetaFlag.WeaponState => MetaIndex.WeaponState,
|
MetaFlag.WeaponState => MetaIndex.WeaponState,
|
||||||
|
MetaFlag.EarState => MetaIndex.EarState,
|
||||||
_ => (MetaIndex)byte.MaxValue,
|
_ => (MetaIndex)byte.MaxValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static IEnumerable<MetaIndex> ToIndices(this MetaFlag index)
|
||||||
|
{
|
||||||
|
if (index.HasFlag(MetaFlag.Wetness))
|
||||||
|
yield return MetaIndex.Wetness;
|
||||||
|
if (index.HasFlag(MetaFlag.HatState))
|
||||||
|
yield return MetaIndex.HatState;
|
||||||
|
if (index.HasFlag(MetaFlag.VisorState))
|
||||||
|
yield return MetaIndex.VisorState;
|
||||||
|
if (index.HasFlag(MetaFlag.WeaponState))
|
||||||
|
yield return MetaIndex.WeaponState;
|
||||||
|
if (index.HasFlag(MetaFlag.EarState))
|
||||||
|
yield return MetaIndex.EarState;
|
||||||
|
}
|
||||||
|
|
||||||
public static string ToName(this MetaIndex index)
|
public static string ToName(this MetaIndex index)
|
||||||
=> index switch
|
=> index switch
|
||||||
{
|
{
|
||||||
|
|
@ -54,6 +63,7 @@ public static class MetaExtensions
|
||||||
MetaIndex.VisorState => "Visor Toggled",
|
MetaIndex.VisorState => "Visor Toggled",
|
||||||
MetaIndex.WeaponState => "Weapon Visible",
|
MetaIndex.WeaponState => "Weapon Visible",
|
||||||
MetaIndex.Wetness => "Force Wetness",
|
MetaIndex.Wetness => "Force Wetness",
|
||||||
|
MetaIndex.EarState => "Ears Visible",
|
||||||
_ => "Unknown Meta",
|
_ => "Unknown Meta",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -64,6 +74,7 @@ public static class MetaExtensions
|
||||||
MetaIndex.VisorState => "Toggle the visor state of the characters head gear.",
|
MetaIndex.VisorState => "Toggle the visor state of the characters head gear.",
|
||||||
MetaIndex.WeaponState => "Hide or show the characters weapons when not drawn.",
|
MetaIndex.WeaponState => "Hide or show the characters weapons when not drawn.",
|
||||||
MetaIndex.Wetness => "Force the character to be wet or not.",
|
MetaIndex.Wetness => "Force the character to be wet or not.",
|
||||||
|
MetaIndex.EarState => "Hide or show the characters ears through the head gear. (Viera only)",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
Glamourer/Designs/Special/QuickSelectedDesign.cs
Normal file
62
Glamourer/Designs/Special/QuickSelectedDesign.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
using Glamourer.Automation;
|
||||||
|
using Glamourer.Gui;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
|
using Glamourer.State;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using OtterGui.Services;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Designs.Special;
|
||||||
|
|
||||||
|
public class QuickSelectedDesign(QuickDesignCombo combo) : IDesignStandIn, IService
|
||||||
|
{
|
||||||
|
public const string SerializedName = "//QuickSelection";
|
||||||
|
public const string ResolvedName = "Quick Design Bar Selection";
|
||||||
|
|
||||||
|
public bool Equals(IDesignStandIn? other)
|
||||||
|
=> other is QuickSelectedDesign;
|
||||||
|
|
||||||
|
public string ResolveName(bool incognito)
|
||||||
|
=> ResolvedName;
|
||||||
|
|
||||||
|
public Design? CurrentDesign
|
||||||
|
=> combo.Design as Design;
|
||||||
|
|
||||||
|
public ref readonly DesignData GetDesignData(in DesignData baseRef)
|
||||||
|
{
|
||||||
|
if (combo.Design != null)
|
||||||
|
return ref combo.Design.GetDesignData(baseRef);
|
||||||
|
|
||||||
|
return ref baseRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<(uint, MaterialValueDesign)> GetMaterialData()
|
||||||
|
=> combo.Design?.GetMaterialData() ?? [];
|
||||||
|
|
||||||
|
public string SerializeName()
|
||||||
|
=> SerializedName;
|
||||||
|
|
||||||
|
public StateSource AssociatedSource()
|
||||||
|
=> StateSource.Manual;
|
||||||
|
|
||||||
|
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks(bool newApplication)
|
||||||
|
=> combo.Design?.AllLinks(newApplication) ?? [];
|
||||||
|
|
||||||
|
public void AddData(JObject jObj)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void ParseData(JObject jObj)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public bool ChangeData(object data)
|
||||||
|
=> false;
|
||||||
|
|
||||||
|
public bool ForcedRedraw
|
||||||
|
=> combo.Design?.ForcedRedraw ?? false;
|
||||||
|
|
||||||
|
public bool ResetAdvancedDyes
|
||||||
|
=> combo.Design?.ResetAdvancedDyes ?? false;
|
||||||
|
|
||||||
|
public bool ResetTemporarySettings
|
||||||
|
=> combo.Design?.ResetTemporarySettings ?? false;
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using Glamourer.Interop.Material;
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Designs.Special;
|
namespace Glamourer.Designs.Special;
|
||||||
|
|
||||||
|
|
@ -11,7 +12,8 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn
|
||||||
public const string ResolvedName = "Random";
|
public const string ResolvedName = "Random";
|
||||||
private Design? _currentDesign;
|
private Design? _currentDesign;
|
||||||
|
|
||||||
public IReadOnlyList<IDesignPredicate> Predicates { get; private set; } = [];
|
public IReadOnlyList<IDesignPredicate> Predicates { get; private set; } = [];
|
||||||
|
public bool ResetOnRedraw { get; set; } = false;
|
||||||
|
|
||||||
public string ResolveName(bool _)
|
public string ResolveName(bool _)
|
||||||
=> ResolvedName;
|
=> ResolvedName;
|
||||||
|
|
@ -39,42 +41,62 @@ public class RandomDesign(RandomDesignGenerator rng) : IDesignStandIn
|
||||||
|
|
||||||
public bool Equals(IDesignStandIn? other)
|
public bool Equals(IDesignStandIn? other)
|
||||||
=> other is RandomDesign r
|
=> other is RandomDesign r
|
||||||
|
&& r.ResetOnRedraw == ResetOnRedraw
|
||||||
&& string.Equals(RandomPredicate.GeneratePredicateString(r.Predicates), RandomPredicate.GeneratePredicateString(Predicates),
|
&& string.Equals(RandomPredicate.GeneratePredicateString(r.Predicates), RandomPredicate.GeneratePredicateString(Predicates),
|
||||||
StringComparison.OrdinalIgnoreCase);
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
public StateSource AssociatedSource()
|
public StateSource AssociatedSource()
|
||||||
=> StateSource.Manual;
|
=> StateSource.Manual;
|
||||||
|
|
||||||
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks
|
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks(bool newApplication)
|
||||||
{
|
{
|
||||||
get
|
if (newApplication || ResetOnRedraw)
|
||||||
{
|
|
||||||
_currentDesign = rng.Design(Predicates);
|
_currentDesign = rng.Design(Predicates);
|
||||||
if (_currentDesign == null)
|
else
|
||||||
yield break;
|
_currentDesign ??= rng.Design(Predicates);
|
||||||
|
if (_currentDesign == null)
|
||||||
|
yield break;
|
||||||
|
|
||||||
foreach (var (link, type) in _currentDesign.AllLinks)
|
foreach (var (link, type, jobs) in _currentDesign.AllLinks(newApplication))
|
||||||
yield return (link, type);
|
yield return (link, type, jobs);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddData(JObject jObj)
|
public void AddData(JObject jObj)
|
||||||
{
|
{
|
||||||
jObj["Restrictions"] = RandomPredicate.GeneratePredicateString(Predicates);
|
jObj["Restrictions"] = RandomPredicate.GeneratePredicateString(Predicates);
|
||||||
|
jObj["ResetOnRedraw"] = ResetOnRedraw;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ParseData(JObject jObj)
|
public void ParseData(JObject jObj)
|
||||||
{
|
{
|
||||||
var restrictions = jObj["Restrictions"]?.ToObject<string>() ?? string.Empty;
|
var restrictions = jObj["Restrictions"]?.ToObject<string>() ?? string.Empty;
|
||||||
Predicates = RandomPredicate.GeneratePredicates(restrictions);
|
Predicates = RandomPredicate.GeneratePredicates(restrictions);
|
||||||
|
ResetOnRedraw = jObj["ResetOnRedraw"]?.ToObject<bool>() ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ChangeData(object data)
|
public bool ChangeData(object data)
|
||||||
{
|
{
|
||||||
if (data is not List<IDesignPredicate> predicates)
|
if (data is List<IDesignPredicate> predicates)
|
||||||
return false;
|
{
|
||||||
|
Predicates = predicates;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Predicates = predicates;
|
if (data is bool resetOnRedraw)
|
||||||
return true;
|
{
|
||||||
|
ResetOnRedraw = resetOnRedraw;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ForcedRedraw
|
||||||
|
=> _currentDesign?.ForcedRedraw ?? false;
|
||||||
|
|
||||||
|
public bool ResetAdvancedDyes
|
||||||
|
=> _currentDesign?.ResetAdvancedDyes ?? false;
|
||||||
|
|
||||||
|
public bool ResetTemporarySettings
|
||||||
|
=> _currentDesign?.ResetTemporarySettings ?? false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,33 @@
|
||||||
using OtterGui.Services;
|
using OtterGui;
|
||||||
|
using OtterGui.Services;
|
||||||
|
|
||||||
namespace Glamourer.Designs.Special;
|
namespace Glamourer.Designs.Special;
|
||||||
|
|
||||||
public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem) : IService
|
public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileSystem, Configuration config) : IService
|
||||||
{
|
{
|
||||||
private readonly Random _rng = new();
|
private readonly Random _rng = new();
|
||||||
|
private readonly WeakReference<Design> _lastDesign = new(null!, false);
|
||||||
|
|
||||||
public Design? Design(IReadOnlyList<Design> localDesigns)
|
public Design? Design(IReadOnlyList<Design> localDesigns)
|
||||||
{
|
{
|
||||||
if (localDesigns.Count == 0)
|
if (localDesigns.Count is 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var idx = _rng.Next(0, localDesigns.Count - 1);
|
var idx = _rng.Next(0, localDesigns.Count);
|
||||||
Glamourer.Log.Verbose($"[Random Design] Chose design {idx} out of {localDesigns.Count}: {localDesigns[idx].Incognito}.");
|
if (localDesigns.Count is 1)
|
||||||
return localDesigns[idx];
|
{
|
||||||
|
_lastDesign.SetTarget(localDesigns[idx]);
|
||||||
|
return localDesigns[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.PreventRandomRepeats && _lastDesign.TryGetTarget(out var lastDesign))
|
||||||
|
while (lastDesign == localDesigns[idx])
|
||||||
|
idx = _rng.Next(0, localDesigns.Count);
|
||||||
|
|
||||||
|
var design = localDesigns[idx];
|
||||||
|
Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {design.Incognito}.");
|
||||||
|
_lastDesign.SetTarget(design);
|
||||||
|
return design;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Design? Design()
|
public Design? Design()
|
||||||
|
|
@ -24,12 +38,12 @@ public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileS
|
||||||
|
|
||||||
public Design? Design(IReadOnlyList<IDesignPredicate> predicates)
|
public Design? Design(IReadOnlyList<IDesignPredicate> predicates)
|
||||||
{
|
{
|
||||||
if (predicates.Count == 0)
|
return predicates.Count switch
|
||||||
return Design();
|
{
|
||||||
if (predicates.Count == 1)
|
0 => Design(),
|
||||||
return Design(predicates[0]);
|
1 => Design(predicates[0]),
|
||||||
|
_ => Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList()),
|
||||||
return Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList());
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Design? Design(string restrictions)
|
public Design? Design(string restrictions)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ public interface IDesignPredicate
|
||||||
: designs;
|
: designs;
|
||||||
|
|
||||||
private static (Design Design, string LowerName, string Identifier, string LowerPath) Transform(Design d, DesignFileSystem fs)
|
private static (Design Design, string LowerName, string Identifier, string LowerPath) Transform(Design d, DesignFileSystem fs)
|
||||||
=> (d, d.Name.Lower, d.Identifier.ToString(), fs.FindLeaf(d, out var l) ? l.FullName().ToLowerInvariant() : string.Empty);
|
=> (d, d.Name.Lower, d.Identifier.ToString(), fs.TryGetValue(d, out var l) ? l.FullName().ToLowerInvariant() : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class RandomPredicate
|
public static class RandomPredicate
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using Glamourer.Interop.Material;
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Designs.Special;
|
namespace Glamourer.Designs.Special;
|
||||||
|
|
||||||
|
|
@ -28,9 +29,9 @@ public class RevertDesign : IDesignStandIn
|
||||||
public StateSource AssociatedSource()
|
public StateSource AssociatedSource()
|
||||||
=> StateSource.Game;
|
=> StateSource.Game;
|
||||||
|
|
||||||
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags)> AllLinks
|
public IEnumerable<(IDesignStandIn Design, ApplicationType Flags, JobFlag Jobs)> AllLinks(bool _)
|
||||||
{
|
{
|
||||||
get { yield return (this, ApplicationType.All); }
|
yield return (this, ApplicationType.All, JobFlag.All);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddData(JObject jObj)
|
public void AddData(JObject jObj)
|
||||||
|
|
@ -41,4 +42,13 @@ public class RevertDesign : IDesignStandIn
|
||||||
|
|
||||||
public bool ChangeData(object data)
|
public bool ChangeData(object data)
|
||||||
=> false;
|
=> false;
|
||||||
|
|
||||||
|
public bool ForcedRedraw
|
||||||
|
=> false;
|
||||||
|
|
||||||
|
public bool ResetAdvancedDyes
|
||||||
|
=> true;
|
||||||
|
|
||||||
|
public bool ResetTemporarySettings
|
||||||
|
=> true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
@ -9,15 +9,20 @@ namespace Glamourer;
|
||||||
|
|
||||||
public class EphemeralConfig : ISavable
|
public class EphemeralConfig : ISavable
|
||||||
{
|
{
|
||||||
public int Version { get; set; } = Configuration.Constants.CurrentVersion;
|
public int Version { get; set; } = Configuration.Constants.CurrentVersion;
|
||||||
public bool IncognitoMode { get; set; } = false;
|
public bool IncognitoMode { get; set; } = false;
|
||||||
public bool UnlockDetailMode { get; set; } = true;
|
public bool UnlockDetailMode { get; set; } = true;
|
||||||
public bool ShowDesignQuickBar { get; set; } = false;
|
public bool ShowDesignQuickBar { get; set; } = false;
|
||||||
public bool LockDesignQuickBar { get; set; } = false;
|
public bool LockDesignQuickBar { get; set; } = false;
|
||||||
public bool LockMainWindow { get; set; } = false;
|
public bool LockMainWindow { get; set; } = false;
|
||||||
public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings;
|
public MainWindow.TabType SelectedTab { get; set; } = MainWindow.TabType.Settings;
|
||||||
public Guid SelectedDesign { get; set; } = Guid.Empty;
|
public Guid SelectedDesign { get; set; } = Guid.Empty;
|
||||||
public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion;
|
public Guid SelectedQuickDesign { get; set; } = Guid.Empty;
|
||||||
|
public int LastSeenVersion { get; set; } = GlamourerChangelog.LastChangelogVersion;
|
||||||
|
|
||||||
|
public float CurrentDesignSelectorWidth { get; set; } = 200f;
|
||||||
|
public float DesignSelectorMinimumScale { get; set; } = 0.1f;
|
||||||
|
public float DesignSelectorMaximumScale { get; set; } = 0.5f;
|
||||||
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
|
|
|
||||||
16
Glamourer/Events/AutoRedrawChanged.cs
Normal file
16
Glamourer/Events/AutoRedrawChanged.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
using OtterGui.Classes;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when the auto-reload gear setting is changed in glamourer configuration.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AutoRedrawChanged()
|
||||||
|
: EventWrapper<bool, AutoRedrawChanged.Priority>(nameof(AutoRedrawChanged))
|
||||||
|
{
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
/// <seealso cref="Api.StateApi.OnGPoseChange"/>
|
||||||
|
StateApi = int.MinValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,9 @@ public sealed class AutomationChanged()
|
||||||
/// <summary> Change the used base state of a given set. Additional data is prior and new base. [(AutoDesignSet.Base, AutoDesignSet.Base)]. </summary>
|
/// <summary> Change the used base state of a given set. Additional data is prior and new base. [(AutoDesignSet.Base, AutoDesignSet.Base)]. </summary>
|
||||||
ChangedBase,
|
ChangedBase,
|
||||||
|
|
||||||
|
/// <summary> Change the resetting of temporary settings for a given set. Additional data is the new value. </summary>
|
||||||
|
ChangedTemporarySettingsReset,
|
||||||
|
|
||||||
/// <summary> Add a new associated design to a given set. Additional data is the index it got added at [int]. </summary>
|
/// <summary> Add a new associated design to a given set. Additional data is the index it got added at [int]. </summary>
|
||||||
AddedDesign,
|
AddedDesign,
|
||||||
|
|
||||||
|
|
|
||||||
25
Glamourer/Events/BonusSlotUpdating.cs
Normal file
25
Glamourer/Events/BonusSlotUpdating.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when a model flags a bonus slot for an update.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Parameter is the model with a flagged slot. </item>
|
||||||
|
/// <item>Parameter is the bonus slot changed. </item>
|
||||||
|
/// <item>Parameter is the model values to change the bonus piece to. </item>
|
||||||
|
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class BonusSlotUpdating()
|
||||||
|
: EventWrapperRef34<Model, BonusItemFlag, CharacterArmor, ulong, BonusSlotUpdating.Priority>(nameof(BonusSlotUpdating))
|
||||||
|
{
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
/// <seealso cref="State.StateListener.OnBonusSlotUpdating"/>
|
||||||
|
StateListener = 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Designs.History;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
|
||||||
|
|
@ -13,104 +14,122 @@ namespace Glamourer.Events;
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class DesignChanged()
|
public sealed class DesignChanged()
|
||||||
: EventWrapper<DesignChanged.Type, Design, object?, DesignChanged.Priority>(nameof(DesignChanged))
|
: EventWrapper<DesignChanged.Type, Design, ITransaction?, DesignChanged.Priority>(nameof(DesignChanged))
|
||||||
{
|
{
|
||||||
public enum Type
|
public enum Type
|
||||||
{
|
{
|
||||||
/// <summary> A new design was created. Data is a potential path to move it to [string?]. </summary>
|
/// <summary> A new design was created. </summary>
|
||||||
Created,
|
Created,
|
||||||
|
|
||||||
/// <summary> An existing design was deleted. Data is null. </summary>
|
/// <summary> An existing design was deleted. </summary>
|
||||||
Deleted,
|
Deleted,
|
||||||
|
|
||||||
/// <summary> Invoked on full reload. Design and Data are null. </summary>
|
/// <summary> Invoked on full reload. </summary>
|
||||||
ReloadedAll,
|
ReloadedAll,
|
||||||
|
|
||||||
/// <summary> An existing design was renamed. Data is the prior name [string]. </summary>
|
/// <summary> An existing design was renamed. </summary>
|
||||||
Renamed,
|
Renamed,
|
||||||
|
|
||||||
/// <summary> An existing design had its description changed. Data is the prior description [string]. </summary>
|
/// <summary> An existing design had its description changed. </summary>
|
||||||
ChangedDescription,
|
ChangedDescription,
|
||||||
|
|
||||||
/// <summary> An existing design had its associated color changed. Data is the prior color [string]. </summary>
|
/// <summary> An existing design had its associated color changed. </summary>
|
||||||
ChangedColor,
|
ChangedColor,
|
||||||
|
|
||||||
/// <summary> An existing design had a new tag added. Data is the new tag and the index it was added at [(string, int)]. </summary>
|
/// <summary> An existing design had a new tag added. </summary>
|
||||||
AddedTag,
|
AddedTag,
|
||||||
|
|
||||||
/// <summary> An existing design had an existing tag removed. Data is the removed tag and the index it had before removal [(string, int)]. </summary>
|
/// <summary> An existing design had an existing tag removed. </summary>
|
||||||
RemovedTag,
|
RemovedTag,
|
||||||
|
|
||||||
/// <summary> An existing design had an existing tag renamed. Data is the old name of the tag, the new name of the tag, and the index it had before being resorted [(string, string, int)]. </summary>
|
/// <summary> An existing design had an existing tag renamed. </summary>
|
||||||
ChangedTag,
|
ChangedTag,
|
||||||
|
|
||||||
/// <summary> An existing design had a new associated mod added. Data is the Mod and its Settings [(Mod, ModSettings)]. </summary>
|
/// <summary> An existing design had a new associated mod added. </summary>
|
||||||
AddedMod,
|
AddedMod,
|
||||||
|
|
||||||
/// <summary> An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. </summary>
|
/// <summary> An existing design had an existing associated mod removed. </summary>
|
||||||
RemovedMod,
|
RemovedMod,
|
||||||
|
|
||||||
/// <summary> An existing design had a link to a different design added, removed or moved. Data is null. </summary>
|
/// <summary> An existing design had an existing associated mod updated. </summary>
|
||||||
|
UpdatedMod,
|
||||||
|
|
||||||
|
/// <summary> An existing design had a link to a different design added, removed or moved. </summary>
|
||||||
ChangedLink,
|
ChangedLink,
|
||||||
|
|
||||||
/// <summary> An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
|
/// <summary> An existing design had a customization changed. </summary>
|
||||||
Customize,
|
Customize,
|
||||||
|
|
||||||
/// <summary> An existing design had its entire customize array changed. Data is the old array, the applied flags and the changed flags. [(CustomizeArray, CustomizeFlag, CustomizeFlag)]. </summary>
|
/// <summary> An existing design had its entire customize array changed. </summary>
|
||||||
EntireCustomize,
|
EntireCustomize,
|
||||||
|
|
||||||
/// <summary> An existing design had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. </summary>
|
/// <summary> An existing design had an equipment piece changed. </summary>
|
||||||
Equip,
|
Equip,
|
||||||
|
|
||||||
/// <summary> An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand, the new offhand (if any) and the new gauntlets (if any). [(EquipItem, EquipItem, EquipItem, EquipItem?, EquipItem?)]. </summary>
|
/// <summary> An existing design had a bonus item changed. </summary>
|
||||||
|
BonusItem,
|
||||||
|
|
||||||
|
/// <summary> An existing design had its weapons changed. </summary>
|
||||||
Weapon,
|
Weapon,
|
||||||
|
|
||||||
/// <summary> An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
/// <summary> An existing design had a stain changed. </summary>
|
||||||
Stain,
|
Stains,
|
||||||
|
|
||||||
/// <summary> An existing design had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary>
|
/// <summary> An existing design had a crest visibility changed. </summary>
|
||||||
Crest,
|
Crest,
|
||||||
|
|
||||||
/// <summary> An existing design had a customize parameter changed. Data is the old value, the new value and the flag [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. </summary>
|
/// <summary> An existing design had a customize parameter changed. </summary>
|
||||||
Parameter,
|
Parameter,
|
||||||
|
|
||||||
/// <summary> An existing design had an advanced dye row added, changed, or deleted. Data is the old value, the new value and the index [(ColorRow?, ColorRow?, MaterialValueIndex)]. </summary>
|
/// <summary> An existing design had an advanced dye row added, changed, or deleted. </summary>
|
||||||
Material,
|
Material,
|
||||||
|
|
||||||
/// <summary> An existing design had an advanced dye rows Revert state changed. Data is the index [MaterialValueIndex]. </summary>
|
/// <summary> An existing design had an advanced dye rows Revert state changed. </summary>
|
||||||
MaterialRevert,
|
MaterialRevert,
|
||||||
|
|
||||||
/// <summary> An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. </summary>
|
/// <summary> An existing design had changed whether it always forces a redraw or not. </summary>
|
||||||
|
ForceRedraw,
|
||||||
|
|
||||||
|
/// <summary> An existing design had changed whether it always resets advanced dyes or not. </summary>
|
||||||
|
ResetAdvancedDyes,
|
||||||
|
|
||||||
|
/// <summary> An existing design had changed whether it always resets all prior temporary settings or not. </summary>
|
||||||
|
ResetTemporarySettings,
|
||||||
|
|
||||||
|
/// <summary> An existing design changed whether a specific customization is applied. </summary>
|
||||||
ApplyCustomize,
|
ApplyCustomize,
|
||||||
|
|
||||||
/// <summary> An existing design changed whether a specific equipment piece is applied. Data is the slot of the equipment [EquipSlot]. </summary>
|
/// <summary> An existing design changed whether a specific equipment piece is applied. </summary>
|
||||||
ApplyEquip,
|
ApplyEquip,
|
||||||
|
|
||||||
/// <summary> An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. </summary>
|
/// <summary> An existing design changed whether a specific bonus item is applied. </summary>
|
||||||
|
ApplyBonusItem,
|
||||||
|
|
||||||
|
/// <summary> An existing design changed whether a specific stain is applied. </summary>
|
||||||
ApplyStain,
|
ApplyStain,
|
||||||
|
|
||||||
/// <summary> An existing design changed whether a specific crest visibility is applied. Data is the slot of the equipment [EquipSlot]. </summary>
|
/// <summary> An existing design changed whether a specific crest visibility is applied. </summary>
|
||||||
ApplyCrest,
|
ApplyCrest,
|
||||||
|
|
||||||
/// <summary> An existing design changed whether a specific customize parameter is applied. Data is the flag for the parameter [CustomizeParameterFlag]. </summary>
|
/// <summary> An existing design changed whether a specific customize parameter is applied. </summary>
|
||||||
ApplyParameter,
|
ApplyParameter,
|
||||||
|
|
||||||
/// <summary> An existing design changed whether an advanced dye row is applied. Data is the index [MaterialValueIndex]. </summary>
|
/// <summary> An existing design changed whether an advanced dye row is applied. </summary>
|
||||||
ApplyMaterial,
|
ApplyMaterial,
|
||||||
|
|
||||||
/// <summary> An existing design changed its write protection status. Data is the new value [bool]. </summary>
|
/// <summary> An existing design changed its write protection status. </summary>
|
||||||
WriteProtection,
|
WriteProtection,
|
||||||
|
|
||||||
/// <summary> An existing design changed its display status for the quick design bar. Data is the new value [bool]. </summary>
|
/// <summary> An existing design changed its display status for the quick design bar. </summary>
|
||||||
QuickDesignBar,
|
QuickDesignBar,
|
||||||
|
|
||||||
/// <summary> An existing design changed one of the meta flags. Data is the flag, whether it was about their applying and the new value [(MetaFlag, bool, bool)]. </summary>
|
/// <summary> An existing design changed one of the meta flags. </summary>
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
/// <seealso cref="Designs.Links.DesignLinkManager.OnDesignChange"/>
|
/// <seealso cref="Designs.Links.DesignLinkManager.OnDesignChanged"/>
|
||||||
DesignLinkManager = 1,
|
DesignLinkManager = 1,
|
||||||
|
|
||||||
/// <seealso cref="Automation.AutoDesignManager.OnDesignChange"/>
|
/// <seealso cref="Automation.AutoDesignManager.OnDesignChange"/>
|
||||||
|
|
@ -122,7 +141,10 @@ public sealed class DesignChanged()
|
||||||
/// <seealso cref="Gui.Tabs.DesignTab.DesignFileSystemSelector.OnDesignChange"/>
|
/// <seealso cref="Gui.Tabs.DesignTab.DesignFileSystemSelector.OnDesignChange"/>
|
||||||
DesignFileSystemSelector = -1,
|
DesignFileSystemSelector = -1,
|
||||||
|
|
||||||
/// <seealso cref="SpecialDesignCombo.OnDesignChange"/>
|
/// <seealso cref="DesignComboBase.OnDesignChanged"/>
|
||||||
DesignCombo = -2,
|
DesignCombo = -2,
|
||||||
|
|
||||||
|
/// <seealso cref="EditorHistory.OnDesignChanged" />
|
||||||
|
EditorHistory = -1000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Glamourer.Interop.Structs;
|
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Events;
|
namespace Glamourer.Events;
|
||||||
|
|
@ -14,12 +14,12 @@ namespace Glamourer.Events;
|
||||||
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
|
/// <item>Parameter is the return value the function should return, if it is ulong.MaxValue, the original will be called and returned. </item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SlotUpdating()
|
public sealed class EquipSlotUpdating()
|
||||||
: EventWrapperRef34<Model, EquipSlot, CharacterArmor, ulong, SlotUpdating.Priority>(nameof(SlotUpdating))
|
: EventWrapperRef34<Model, EquipSlot, CharacterArmor, ulong, EquipSlotUpdating.Priority>(nameof(EquipSlotUpdating))
|
||||||
{
|
{
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
/// <seealso cref="State.StateListener.OnSlotUpdating"/>
|
/// <seealso cref="State.StateListener.OnEquipSlotUpdating"/>
|
||||||
StateListener = 0,
|
StateListener = 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,8 +13,8 @@ public sealed class GPoseService : EventWrapper<bool, GPoseService.Priority>
|
||||||
|
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
/// <seealso cref="Api.GlamourerIpc.OnGPoseChanged"/>
|
/// <seealso cref="Api.StateApi.OnGPoseChange"/>
|
||||||
GlamourerIpc = int.MinValue,
|
StateApi = int.MinValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool InGPose { get; private set; }
|
public bool InGPose { get; private set; }
|
||||||
|
|
|
||||||
21
Glamourer/Events/GearsetDataLoaded.cs
Normal file
21
Glamourer/Events/GearsetDataLoaded.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers when the equipped gearset finished all LoadEquipment, LoadWeapon, and LoadCrest calls. (All Non-MetaData)
|
||||||
|
/// This defines an endpoint for when the gameState is updated.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>The model draw object associated with the finished load (Also fired by other players on render) </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GearsetDataLoaded()
|
||||||
|
: EventWrapper<Actor, Model, GearsetDataLoaded.Priority>(nameof(GearsetDataLoaded))
|
||||||
|
{
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
/// <seealso cref="State.StateListener.OnGearsetDataLoaded"/>
|
||||||
|
StateListener = 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Glamourer.Interop.Structs;
|
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
|
||||||
namespace Glamourer.Events;
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Glamourer.Events;
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class MovedEquipment()
|
public sealed class MovedEquipment()
|
||||||
: EventWrapper<(EquipSlot, uint, StainId)[], MovedEquipment.Priority>(nameof(MovedEquipment))
|
: EventWrapper<(EquipSlot, uint, StainIds)[], MovedEquipment.Priority>(nameof(MovedEquipment))
|
||||||
{
|
{
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,11 @@ public sealed class PenumbraReloaded()
|
||||||
{
|
{
|
||||||
/// <seealso cref="Interop.ChangeCustomizeService.Restore"/>
|
/// <seealso cref="Interop.ChangeCustomizeService.Restore"/>
|
||||||
ChangeCustomizeService = 0,
|
ChangeCustomizeService = 0,
|
||||||
|
|
||||||
|
/// <seealso cref="Interop.VisorService.Restore"/>
|
||||||
|
VisorService = 0,
|
||||||
|
|
||||||
|
/// <seealso cref="Interop.VieraEarService.Restore"/>
|
||||||
|
VieraEarService = 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,67 +1,33 @@
|
||||||
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.Designs.History;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
|
||||||
namespace Glamourer.Events
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when a Design is edited in any way.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Parameter is the type of the change </item>
|
||||||
|
/// <item>Parameter is the changed saved state. </item>
|
||||||
|
/// <item>Parameter is the existing actors using this saved state. </item>
|
||||||
|
/// <item>Parameter is any additional data depending on the type of change. </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class StateChanged()
|
||||||
|
: EventWrapper<StateChangeType, StateSource, ActorState, ActorData, ITransaction?, StateChanged.Priority>(nameof(StateChanged))
|
||||||
{
|
{
|
||||||
/// <summary>
|
public enum Priority
|
||||||
/// Triggered when a Design is edited in any way.
|
|
||||||
/// <list type="number">
|
|
||||||
/// <item>Parameter is the type of the change </item>
|
|
||||||
/// <item>Parameter is the changed saved state. </item>
|
|
||||||
/// <item>Parameter is the existing actors using this saved state. </item>
|
|
||||||
/// <item>Parameter is any additional data depending on the type of change. </item>
|
|
||||||
/// </list>
|
|
||||||
/// </summary>
|
|
||||||
public sealed class StateChanged()
|
|
||||||
: EventWrapper<StateChanged.Type, StateSource, ActorState, ActorData, object?, StateChanged.Priority>(nameof(StateChanged))
|
|
||||||
{
|
{
|
||||||
public enum Type
|
/// <seealso cref="Api.StateApi.OnStateChanged" />
|
||||||
{
|
GlamourerIpc = int.MinValue,
|
||||||
/// <summary> A characters saved state had the model id changed. This means everything may have changed. Data is the old model id and the new model id. [(uint, uint)] </summary>
|
|
||||||
Model,
|
|
||||||
|
|
||||||
/// <summary> A characters saved state had multiple customization values changed. TData is the old customize array and the applied changes. [(Customize, CustomizeFlag)] </summary>
|
/// <seealso cref="Interop.Penumbra.PenumbraAutoRedraw.OnStateChanged" />
|
||||||
EntireCustomize,
|
PenumbraAutoRedraw = 0,
|
||||||
|
|
||||||
/// <summary> A characters saved state had a customization value changed. Data is the old value, the new value and the type. [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
|
/// <seealso cref="EditorHistory.OnStateChanged" />
|
||||||
Customize,
|
EditorHistory = -1000,
|
||||||
|
|
||||||
/// <summary> A characters saved state had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. </summary>
|
|
||||||
Equip,
|
|
||||||
|
|
||||||
/// <summary> A characters saved state had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. </summary>
|
|
||||||
Weapon,
|
|
||||||
|
|
||||||
/// <summary> A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
|
||||||
Stain,
|
|
||||||
|
|
||||||
/// <summary> A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary>
|
|
||||||
Crest,
|
|
||||||
|
|
||||||
/// <summary> A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. </summary>
|
|
||||||
Parameter,
|
|
||||||
|
|
||||||
/// <summary> A characters saved state had a material color table value changed. Data is the old value, the new value and the index [(Vector3, Vector3, MaterialValueIndex)] or just the index for resets. </summary>
|
|
||||||
MaterialValue,
|
|
||||||
|
|
||||||
/// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>
|
|
||||||
Design,
|
|
||||||
|
|
||||||
/// <summary> A characters saved state had its state reset to its game values. This means everything may have changed. Data is null. </summary>
|
|
||||||
Reset,
|
|
||||||
|
|
||||||
/// <summary> A characters saved state had a meta toggle changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
|
||||||
Other,
|
|
||||||
|
|
||||||
/// <summary> A characters state was reapplied. Data is null. </summary>
|
|
||||||
Reapply,
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Priority
|
|
||||||
{
|
|
||||||
GlamourerIpc = int.MinValue,
|
|
||||||
PenumbraAutoRedraw = 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
Glamourer/Events/StateFinalized.cs
Normal file
24
Glamourer/Events/StateFinalized.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
using Glamourer.Api;
|
||||||
|
using Glamourer.Api.Enums;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when a set of grouped changes finishes being applied to a Glamourer state.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Parameter is the operation that finished updating the saved state. </item>
|
||||||
|
/// <item>Parameter is the existing actors using this saved state. </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class StateFinalized()
|
||||||
|
: EventWrapper<StateFinalizationType, ActorData, StateFinalized.Priority>(nameof(StateFinalized))
|
||||||
|
{
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
/// <seealso cref="StateApi.OnStateFinalized"/>
|
||||||
|
StateApi = int.MinValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Glamourer/Events/VieraEarStateChanged.cs
Normal file
22
Glamourer/Events/VieraEarStateChanged.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when the state of viera ear visibility for any draw object is changed.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Parameter is the model with a changed viera ear visibility state. </item>
|
||||||
|
/// <item>Parameter is the new state. </item>
|
||||||
|
/// <item>Parameter is whether to call the original function. </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class VieraEarStateChanged()
|
||||||
|
: EventWrapperRef2<Actor, bool, VieraEarStateChanged.Priority>(nameof(VieraEarStateChanged))
|
||||||
|
{
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
/// <seealso cref="State.StateListener.OnVieraEarChange"/>
|
||||||
|
StateListener = 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Glamourer.Interop.Structs;
|
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
|
||||||
namespace Glamourer.Events;
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
|
@ -12,11 +12,11 @@ namespace Glamourer.Events;
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class VisorStateChanged()
|
public sealed class VisorStateChanged()
|
||||||
: EventWrapperRef2<Model, bool, VisorStateChanged.Priority>(nameof(VisorStateChanged))
|
: EventWrapperRef3<Model, bool, bool, VisorStateChanged.Priority>(nameof(VisorStateChanged))
|
||||||
{
|
{
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
/// <seealso cref="State.StateListener.OnVisorChange"/>
|
/// <seealso cref="State.StateListener.OnVisorChange"/>
|
||||||
StateListener = 0,
|
StateListener = 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Glamourer.Interop.Structs;
|
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Events;
|
namespace Glamourer.Events;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Glamourer.Interop.Structs;
|
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
|
||||||
namespace Glamourer.Events;
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
using Lumina.Data;
|
|
||||||
using Lumina.Excel;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
|
|
||||||
namespace Glamourer.GameData;
|
|
||||||
|
|
||||||
/// <summary> A custom version of CharaMakeParams that is easier to parse. </summary>
|
|
||||||
[Sheet("CharaMakeParams")]
|
|
||||||
public class CharaMakeParams : ExcelRow
|
|
||||||
{
|
|
||||||
public const int NumMenus = 28;
|
|
||||||
public const int NumVoices = 12;
|
|
||||||
public const int NumGraphics = 10;
|
|
||||||
public const int MaxNumValues = 100;
|
|
||||||
public const int NumFaces = 8;
|
|
||||||
public const int NumFeatures = 7;
|
|
||||||
public const int NumEquip = 3;
|
|
||||||
|
|
||||||
public enum MenuType
|
|
||||||
{
|
|
||||||
ListSelector = 0,
|
|
||||||
IconSelector = 1,
|
|
||||||
ColorPicker = 2,
|
|
||||||
DoubleColorPicker = 3,
|
|
||||||
IconCheckmark = 4,
|
|
||||||
Percentage = 5,
|
|
||||||
Checkmark = 6, // custom
|
|
||||||
Nothing = 7, // custom
|
|
||||||
List1Selector = 8, // custom, 1-indexed lists
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Menu
|
|
||||||
{
|
|
||||||
public uint Id;
|
|
||||||
public byte InitVal;
|
|
||||||
public MenuType Type;
|
|
||||||
public byte Size;
|
|
||||||
public byte LookAt;
|
|
||||||
public uint Mask;
|
|
||||||
public uint Customize;
|
|
||||||
public uint[] Values;
|
|
||||||
public byte[] Graphic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct FacialFeatures
|
|
||||||
{
|
|
||||||
public uint[] Icons;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LazyRow<Race> Race { get; set; } = null!;
|
|
||||||
public LazyRow<Tribe> Tribe { get; set; } = null!;
|
|
||||||
public sbyte Gender { get; set; }
|
|
||||||
|
|
||||||
public Menu[] Menus { get; set; } = new Menu[NumMenus];
|
|
||||||
public byte[] Voices { get; set; } = new byte[NumVoices];
|
|
||||||
public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces];
|
|
||||||
|
|
||||||
public CharaMakeType.CharaMakeTypeUnkData3347Obj[] Equip { get; set; } = new CharaMakeType.CharaMakeTypeUnkData3347Obj[NumEquip];
|
|
||||||
|
|
||||||
public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language)
|
|
||||||
{
|
|
||||||
RowId = parser.RowId;
|
|
||||||
SubRowId = parser.SubRowId;
|
|
||||||
Race = new LazyRow<Race>(gameData, parser.ReadColumn<uint>(0), language);
|
|
||||||
Tribe = new LazyRow<Tribe>(gameData, parser.ReadColumn<uint>(1), language);
|
|
||||||
Gender = parser.ReadColumn<sbyte>(2);
|
|
||||||
int currentOffset;
|
|
||||||
for (var i = 0; i < NumMenus; ++i)
|
|
||||||
{
|
|
||||||
currentOffset = 3 + i;
|
|
||||||
Menus[i].Id = parser.ReadColumn<uint>(0 * NumMenus + currentOffset);
|
|
||||||
Menus[i].InitVal = parser.ReadColumn<byte>(1 * NumMenus + currentOffset);
|
|
||||||
Menus[i].Type = (MenuType)parser.ReadColumn<byte>(2 * NumMenus + currentOffset);
|
|
||||||
Menus[i].Size = parser.ReadColumn<byte>(3 * NumMenus + currentOffset);
|
|
||||||
Menus[i].LookAt = parser.ReadColumn<byte>(4 * NumMenus + currentOffset);
|
|
||||||
Menus[i].Mask = parser.ReadColumn<uint>(5 * NumMenus + currentOffset);
|
|
||||||
Menus[i].Customize = parser.ReadColumn<uint>(6 * NumMenus + currentOffset);
|
|
||||||
Menus[i].Values = new uint[Menus[i].Size];
|
|
||||||
|
|
||||||
switch (Menus[i].Type)
|
|
||||||
{
|
|
||||||
case MenuType.ColorPicker:
|
|
||||||
case MenuType.DoubleColorPicker:
|
|
||||||
case MenuType.Percentage:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
currentOffset += 7 * NumMenus;
|
|
||||||
for (var j = 0; j < Menus[i].Size; ++j)
|
|
||||||
Menus[i].Values[j] = parser.ReadColumn<uint>(j * NumMenus + currentOffset);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Menus[i].Graphic = new byte[NumGraphics];
|
|
||||||
currentOffset = 3 + (MaxNumValues + 7) * NumMenus + i;
|
|
||||||
for (var j = 0; j < NumGraphics; ++j)
|
|
||||||
Menus[i].Graphic[j] = parser.ReadColumn<byte>(j * NumMenus + currentOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus;
|
|
||||||
for (var i = 0; i < NumVoices; ++i)
|
|
||||||
Voices[i] = parser.ReadColumn<byte>(currentOffset++);
|
|
||||||
|
|
||||||
for (var i = 0; i < NumFaces; ++i)
|
|
||||||
{
|
|
||||||
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + i;
|
|
||||||
FacialFeatureByFace[i].Icons = new uint[NumFeatures];
|
|
||||||
for (var j = 0; j < NumFeatures; ++j)
|
|
||||||
FacialFeatureByFace[i].Icons[j] = (uint)parser.ReadColumn<int>(j * NumFaces + currentOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < NumEquip; ++i)
|
|
||||||
{
|
|
||||||
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7;
|
|
||||||
Equip[i] = new CharaMakeType.CharaMakeTypeUnkData3347Obj()
|
|
||||||
{
|
|
||||||
Helmet = parser.ReadColumn<ulong>(currentOffset + 0),
|
|
||||||
Top = parser.ReadColumn<ulong>(currentOffset + 1),
|
|
||||||
Gloves = parser.ReadColumn<ulong>(currentOffset + 2),
|
|
||||||
Legs = parser.ReadColumn<ulong>(currentOffset + 3),
|
|
||||||
Shoes = parser.ReadColumn<ulong>(currentOffset + 4),
|
|
||||||
Weapon = parser.ReadColumn<ulong>(currentOffset + 5),
|
|
||||||
SubWeapon = parser.ReadColumn<ulong>(currentOffset + 6),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Textures;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
|
@ -32,8 +32,8 @@ public class CustomizeManager : IAsyncDataContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Get specific icons. </summary>
|
/// <summary> Get specific icons. </summary>
|
||||||
public IDalamudTextureWrap GetIcon(uint id)
|
public ISharedImmediateTexture GetIcon(uint id)
|
||||||
=> _icons.LoadIcon(id)!;
|
=> _icons.TextureProvider.GetFromGameIcon(id);
|
||||||
|
|
||||||
/// <summary> Iterate over all supported genders and clans. </summary>
|
/// <summary> Iterate over all supported genders and clans. </summary>
|
||||||
public static IEnumerable<(SubRace Clan, Gender Gender)> AllSets()
|
public static IEnumerable<(SubRace Clan, Gender Gender)> AllSets()
|
||||||
|
|
@ -47,8 +47,8 @@ public class CustomizeManager : IAsyncDataContainer
|
||||||
|
|
||||||
public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet)
|
public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet)
|
||||||
{
|
{
|
||||||
_icons = new IconStorage(textures, gameData);
|
_icons = new TextureCache(gameData, textures);
|
||||||
var stopwatch = new Stopwatch();
|
var stopwatch = new Stopwatch();
|
||||||
var tmpTask = Task.Run(() =>
|
var tmpTask = Task.Run(() =>
|
||||||
{
|
{
|
||||||
stopwatch.Start();
|
stopwatch.Start();
|
||||||
|
|
@ -72,7 +72,7 @@ public class CustomizeManager : IAsyncDataContainer
|
||||||
public bool Finished
|
public bool Finished
|
||||||
=> Awaiter.IsCompletedSuccessfully;
|
=> Awaiter.IsCompletedSuccessfully;
|
||||||
|
|
||||||
private readonly IconStorage _icons;
|
private readonly TextureCache _icons;
|
||||||
private static readonly int ListSize = Clans.Count * Genders.Count;
|
private static readonly int ListSize = Clans.Count * Genders.Count;
|
||||||
private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize];
|
private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ public struct CustomizeParameterData
|
||||||
public Vector3 HairSpecular;
|
public Vector3 HairSpecular;
|
||||||
public Vector3 HairHighlight;
|
public Vector3 HairHighlight;
|
||||||
public Vector3 LeftEye;
|
public Vector3 LeftEye;
|
||||||
|
public float LeftLimbalIntensity;
|
||||||
public Vector3 RightEye;
|
public Vector3 RightEye;
|
||||||
|
public float RightLimbalIntensity;
|
||||||
public Vector3 FeatureColor;
|
public Vector3 FeatureColor;
|
||||||
public float FacePaintUvMultiplier;
|
public float FacePaintUvMultiplier;
|
||||||
public float FacePaintUvOffset;
|
public float FacePaintUvOffset;
|
||||||
|
|
@ -33,7 +35,9 @@ public struct CustomizeParameterData
|
||||||
CustomizeParameterFlag.HairSpecular => new CustomizeParameterValue(HairSpecular),
|
CustomizeParameterFlag.HairSpecular => new CustomizeParameterValue(HairSpecular),
|
||||||
CustomizeParameterFlag.HairHighlight => new CustomizeParameterValue(HairHighlight),
|
CustomizeParameterFlag.HairHighlight => new CustomizeParameterValue(HairHighlight),
|
||||||
CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye),
|
CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye),
|
||||||
|
CustomizeParameterFlag.LeftLimbalIntensity => new CustomizeParameterValue(LeftLimbalIntensity),
|
||||||
CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye),
|
CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye),
|
||||||
|
CustomizeParameterFlag.RightLimbalIntensity => new CustomizeParameterValue(RightLimbalIntensity),
|
||||||
CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(FeatureColor),
|
CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(FeatureColor),
|
||||||
CustomizeParameterFlag.DecalColor => new CustomizeParameterValue(DecalColor),
|
CustomizeParameterFlag.DecalColor => new CustomizeParameterValue(DecalColor),
|
||||||
CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(FacePaintUvMultiplier),
|
CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(FacePaintUvMultiplier),
|
||||||
|
|
@ -57,7 +61,9 @@ public struct CustomizeParameterData
|
||||||
CustomizeParameterFlag.HairSpecular => SetIfDifferent(ref HairSpecular, value.InternalTriple),
|
CustomizeParameterFlag.HairSpecular => SetIfDifferent(ref HairSpecular, value.InternalTriple),
|
||||||
CustomizeParameterFlag.HairHighlight => SetIfDifferent(ref HairHighlight, value.InternalTriple),
|
CustomizeParameterFlag.HairHighlight => SetIfDifferent(ref HairHighlight, value.InternalTriple),
|
||||||
CustomizeParameterFlag.LeftEye => SetIfDifferent(ref LeftEye, value.InternalTriple),
|
CustomizeParameterFlag.LeftEye => SetIfDifferent(ref LeftEye, value.InternalTriple),
|
||||||
|
CustomizeParameterFlag.LeftLimbalIntensity => SetIfDifferent(ref LeftLimbalIntensity, value.Single),
|
||||||
CustomizeParameterFlag.RightEye => SetIfDifferent(ref RightEye, value.InternalTriple),
|
CustomizeParameterFlag.RightEye => SetIfDifferent(ref RightEye, value.InternalTriple),
|
||||||
|
CustomizeParameterFlag.RightLimbalIntensity => SetIfDifferent(ref RightLimbalIntensity, value.Single),
|
||||||
CustomizeParameterFlag.FeatureColor => SetIfDifferent(ref FeatureColor, value.InternalTriple),
|
CustomizeParameterFlag.FeatureColor => SetIfDifferent(ref FeatureColor, value.InternalTriple),
|
||||||
CustomizeParameterFlag.DecalColor => SetIfDifferent(ref DecalColor, value.InternalQuadruple),
|
CustomizeParameterFlag.DecalColor => SetIfDifferent(ref DecalColor, value.InternalQuadruple),
|
||||||
CustomizeParameterFlag.FacePaintUvMultiplier => SetIfDifferent(ref FacePaintUvMultiplier, value.Single),
|
CustomizeParameterFlag.FacePaintUvMultiplier => SetIfDifferent(ref FacePaintUvMultiplier, value.Single),
|
||||||
|
|
@ -77,30 +83,48 @@ public struct CustomizeParameterData
|
||||||
_ => new CustomizeParameterValue(SkinDiffuse, MuscleTone).XivQuadruple,
|
_ => new CustomizeParameterValue(SkinDiffuse, MuscleTone).XivQuadruple,
|
||||||
};
|
};
|
||||||
|
|
||||||
parameters.LeftColor = (flags & (CustomizeParameterFlag.LeftEye | CustomizeParameterFlag.FacePaintUvMultiplier)) switch
|
parameters.LeftColor = (flags & (CustomizeParameterFlag.LeftEye | CustomizeParameterFlag.LeftLimbalIntensity)) switch
|
||||||
{
|
{
|
||||||
0 => parameters.LeftColor,
|
0 => parameters.LeftColor,
|
||||||
CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple,
|
CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple,
|
||||||
CustomizeParameterFlag.FacePaintUvMultiplier => parameters.LeftColor with { W = FacePaintUvMultiplier },
|
CustomizeParameterFlag.LeftLimbalIntensity => parameters.LeftColor with { W = LeftLimbalIntensity },
|
||||||
_ => new CustomizeParameterValue(LeftEye, FacePaintUvMultiplier).XivQuadruple,
|
_ => new CustomizeParameterValue(LeftEye, LeftLimbalIntensity).XivQuadruple,
|
||||||
};
|
};
|
||||||
|
|
||||||
parameters.RightColor = (flags & (CustomizeParameterFlag.RightEye | CustomizeParameterFlag.FacePaintUvOffset)) switch
|
parameters.RightColor = (flags & (CustomizeParameterFlag.RightEye | CustomizeParameterFlag.RightLimbalIntensity)) switch
|
||||||
{
|
{
|
||||||
0 => parameters.RightColor,
|
0 => parameters.RightColor,
|
||||||
CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye, parameters.RightColor.W).XivQuadruple,
|
CustomizeParameterFlag.RightEye => new CustomizeParameterValue(RightEye, parameters.RightColor.W).XivQuadruple,
|
||||||
CustomizeParameterFlag.FacePaintUvOffset => parameters.RightColor with { W = FacePaintUvOffset },
|
CustomizeParameterFlag.RightLimbalIntensity => parameters.RightColor with { W = RightLimbalIntensity },
|
||||||
_ => new CustomizeParameterValue(RightEye, FacePaintUvOffset).XivQuadruple,
|
_ => new CustomizeParameterValue(RightEye, RightLimbalIntensity).XivQuadruple,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (flags.HasFlag(CustomizeParameterFlag.SkinSpecular))
|
if (flags.HasFlag(CustomizeParameterFlag.SkinSpecular))
|
||||||
parameters.SkinFresnelValue0 = new CustomizeParameterValue(SkinSpecular).XivQuadruple;
|
parameters.SkinFresnelValue0 = new CustomizeParameterValue(SkinSpecular).XivQuadruple;
|
||||||
if (flags.HasFlag(CustomizeParameterFlag.HairDiffuse))
|
if (flags.HasFlag(CustomizeParameterFlag.HairDiffuse))
|
||||||
parameters.MainColor = new CustomizeParameterValue(HairDiffuse).XivTriple;
|
{
|
||||||
|
// Vector3 is 0x10 byte for some reason.
|
||||||
|
var triple = new CustomizeParameterValue(HairDiffuse).XivTriple;
|
||||||
|
parameters.MainColor.X = triple.X;
|
||||||
|
parameters.MainColor.Y = triple.Y;
|
||||||
|
parameters.MainColor.Z = triple.Z;
|
||||||
|
}
|
||||||
|
|
||||||
if (flags.HasFlag(CustomizeParameterFlag.HairSpecular))
|
if (flags.HasFlag(CustomizeParameterFlag.HairSpecular))
|
||||||
parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple;
|
parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple;
|
||||||
if (flags.HasFlag(CustomizeParameterFlag.HairHighlight))
|
if (flags.HasFlag(CustomizeParameterFlag.HairHighlight))
|
||||||
parameters.MeshColor = new CustomizeParameterValue(HairHighlight).XivTriple;
|
{
|
||||||
|
// Vector3 is 0x10 byte for some reason.
|
||||||
|
var triple = new CustomizeParameterValue(HairHighlight).XivTriple;
|
||||||
|
parameters.MeshColor.X = triple.X;
|
||||||
|
parameters.MeshColor.Y = triple.Y;
|
||||||
|
parameters.MeshColor.Z = triple.Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvMultiplier))
|
||||||
|
GetUvMultiplierWrite(ref parameters) = FacePaintUvMultiplier;
|
||||||
|
if (flags.HasFlag(CustomizeParameterFlag.FacePaintUvOffset))
|
||||||
|
GetUvOffsetWrite(ref parameters) = FacePaintUvOffset;
|
||||||
if (flags.HasFlag(CustomizeParameterFlag.LipDiffuse))
|
if (flags.HasFlag(CustomizeParameterFlag.LipDiffuse))
|
||||||
parameters.LipColor = new CustomizeParameterValue(LipDiffuse).XivQuadruple;
|
parameters.LipColor = new CustomizeParameterValue(LipDiffuse).XivQuadruple;
|
||||||
if (flags.HasFlag(CustomizeParameterFlag.FeatureColor))
|
if (flags.HasFlag(CustomizeParameterFlag.FeatureColor))
|
||||||
|
|
@ -132,13 +156,21 @@ public struct CustomizeParameterData
|
||||||
parameters.LipColor = new CustomizeParameterValue(LipDiffuse).XivQuadruple;
|
parameters.LipColor = new CustomizeParameterValue(LipDiffuse).XivQuadruple;
|
||||||
break;
|
break;
|
||||||
case CustomizeParameterFlag.HairDiffuse:
|
case CustomizeParameterFlag.HairDiffuse:
|
||||||
parameters.MainColor = new CustomizeParameterValue(HairDiffuse).XivTriple;
|
// Vector3 is 0x10 byte for some reason.
|
||||||
|
var triple1 = new CustomizeParameterValue(HairDiffuse).XivTriple;
|
||||||
|
parameters.MainColor.X = triple1.X;
|
||||||
|
parameters.MainColor.Y = triple1.Y;
|
||||||
|
parameters.MainColor.Z = triple1.Z;
|
||||||
break;
|
break;
|
||||||
case CustomizeParameterFlag.HairSpecular:
|
case CustomizeParameterFlag.HairSpecular:
|
||||||
parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple;
|
parameters.HairFresnelValue0 = new CustomizeParameterValue(HairSpecular).XivTriple;
|
||||||
break;
|
break;
|
||||||
case CustomizeParameterFlag.HairHighlight:
|
case CustomizeParameterFlag.HairHighlight:
|
||||||
parameters.MeshColor = new CustomizeParameterValue(HairHighlight).XivTriple;
|
// Vector3 is 0x10 byte for some reason.
|
||||||
|
var triple2 = new CustomizeParameterValue(HairHighlight).XivTriple;
|
||||||
|
parameters.MeshColor.X = triple2.X;
|
||||||
|
parameters.MeshColor.Y = triple2.Y;
|
||||||
|
parameters.MeshColor.Z = triple2.Z;
|
||||||
break;
|
break;
|
||||||
case CustomizeParameterFlag.LeftEye:
|
case CustomizeParameterFlag.LeftEye:
|
||||||
parameters.LeftColor = new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple;
|
parameters.LeftColor = new CustomizeParameterValue(LeftEye, parameters.LeftColor.W).XivQuadruple;
|
||||||
|
|
@ -150,10 +182,16 @@ public struct CustomizeParameterData
|
||||||
parameters.OptionColor = new CustomizeParameterValue(FeatureColor).XivTriple;
|
parameters.OptionColor = new CustomizeParameterValue(FeatureColor).XivTriple;
|
||||||
break;
|
break;
|
||||||
case CustomizeParameterFlag.FacePaintUvMultiplier:
|
case CustomizeParameterFlag.FacePaintUvMultiplier:
|
||||||
parameters.LeftColor.W = FacePaintUvMultiplier;
|
GetUvMultiplierWrite(ref parameters) = FacePaintUvMultiplier;
|
||||||
break;
|
break;
|
||||||
case CustomizeParameterFlag.FacePaintUvOffset:
|
case CustomizeParameterFlag.FacePaintUvOffset:
|
||||||
parameters.RightColor.W = FacePaintUvOffset;
|
GetUvOffsetWrite(ref parameters) = FacePaintUvOffset;
|
||||||
|
break;
|
||||||
|
case CustomizeParameterFlag.LeftLimbalIntensity:
|
||||||
|
parameters.LeftColor.W = LeftLimbalIntensity;
|
||||||
|
break;
|
||||||
|
case CustomizeParameterFlag.RightLimbalIntensity:
|
||||||
|
parameters.RightColor.W = RightLimbalIntensity;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,8 +199,8 @@ public struct CustomizeParameterData
|
||||||
public static CustomizeParameterData FromParameters(in CustomizeParameter parameter, in DecalParameters decal)
|
public static CustomizeParameterData FromParameters(in CustomizeParameter parameter, in DecalParameters decal)
|
||||||
=> new()
|
=> new()
|
||||||
{
|
{
|
||||||
FacePaintUvOffset = parameter.RightColor.W,
|
FacePaintUvOffset = GetUvOffset(parameter),
|
||||||
FacePaintUvMultiplier = parameter.LeftColor.W,
|
FacePaintUvMultiplier = GetUvMultiplier(parameter),
|
||||||
MuscleTone = parameter.SkinColor.W,
|
MuscleTone = parameter.SkinColor.W,
|
||||||
SkinDiffuse = new CustomizeParameterValue(parameter.SkinColor).InternalTriple,
|
SkinDiffuse = new CustomizeParameterValue(parameter.SkinColor).InternalTriple,
|
||||||
SkinSpecular = new CustomizeParameterValue(parameter.SkinFresnelValue0).InternalTriple,
|
SkinSpecular = new CustomizeParameterValue(parameter.SkinFresnelValue0).InternalTriple,
|
||||||
|
|
@ -171,7 +209,9 @@ public struct CustomizeParameterData
|
||||||
HairSpecular = new CustomizeParameterValue(parameter.HairFresnelValue0).InternalTriple,
|
HairSpecular = new CustomizeParameterValue(parameter.HairFresnelValue0).InternalTriple,
|
||||||
HairHighlight = new CustomizeParameterValue(parameter.MeshColor).InternalTriple,
|
HairHighlight = new CustomizeParameterValue(parameter.MeshColor).InternalTriple,
|
||||||
LeftEye = new CustomizeParameterValue(parameter.LeftColor).InternalTriple,
|
LeftEye = new CustomizeParameterValue(parameter.LeftColor).InternalTriple,
|
||||||
|
LeftLimbalIntensity = new CustomizeParameterValue(parameter.LeftColor.W).Single,
|
||||||
RightEye = new CustomizeParameterValue(parameter.RightColor).InternalTriple,
|
RightEye = new CustomizeParameterValue(parameter.RightColor).InternalTriple,
|
||||||
|
RightLimbalIntensity = new CustomizeParameterValue(parameter.RightColor.W).Single,
|
||||||
FeatureColor = new CustomizeParameterValue(parameter.OptionColor).InternalTriple,
|
FeatureColor = new CustomizeParameterValue(parameter.OptionColor).InternalTriple,
|
||||||
DecalColor = FromParameter(decal),
|
DecalColor = FromParameter(decal),
|
||||||
};
|
};
|
||||||
|
|
@ -189,8 +229,8 @@ public struct CustomizeParameterData
|
||||||
CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(parameter.LeftColor),
|
CustomizeParameterFlag.LeftEye => new CustomizeParameterValue(parameter.LeftColor),
|
||||||
CustomizeParameterFlag.RightEye => new CustomizeParameterValue(parameter.RightColor),
|
CustomizeParameterFlag.RightEye => new CustomizeParameterValue(parameter.RightColor),
|
||||||
CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(parameter.OptionColor),
|
CustomizeParameterFlag.FeatureColor => new CustomizeParameterValue(parameter.OptionColor),
|
||||||
CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(parameter.LeftColor.W),
|
CustomizeParameterFlag.FacePaintUvMultiplier => new CustomizeParameterValue(GetUvMultiplier(parameter)),
|
||||||
CustomizeParameterFlag.FacePaintUvOffset => new CustomizeParameterValue(parameter.RightColor.W),
|
CustomizeParameterFlag.FacePaintUvOffset => new CustomizeParameterValue(GetUvOffset(parameter)),
|
||||||
_ => CustomizeParameterValue.Zero,
|
_ => CustomizeParameterValue.Zero,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -223,4 +263,41 @@ public struct CustomizeParameterData
|
||||||
val = @new;
|
val = @new;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static unsafe float GetUvOffset(in CustomizeParameter parameter)
|
||||||
|
{
|
||||||
|
// TODO CS Update
|
||||||
|
fixed (CustomizeParameter* ptr = ¶meter)
|
||||||
|
{
|
||||||
|
return ((float*)ptr)[23];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe ref float GetUvOffsetWrite(ref CustomizeParameter parameter)
|
||||||
|
{
|
||||||
|
// TODO CS Update
|
||||||
|
fixed (CustomizeParameter* ptr = ¶meter)
|
||||||
|
{
|
||||||
|
return ref ((float*)ptr)[23];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe float GetUvMultiplier(in CustomizeParameter parameter)
|
||||||
|
{
|
||||||
|
// TODO CS Update
|
||||||
|
fixed (CustomizeParameter* ptr = ¶meter)
|
||||||
|
{
|
||||||
|
return ((float*)ptr)[15];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe ref float GetUvMultiplierWrite(ref CustomizeParameter parameter)
|
||||||
|
{
|
||||||
|
// TODO CS Update
|
||||||
|
fixed (CustomizeParameter* ptr = ¶meter)
|
||||||
|
{
|
||||||
|
return ref ((float*)ptr)[15];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,20 +16,27 @@ public enum CustomizeParameterFlag : ushort
|
||||||
FacePaintUvMultiplier = 0x0400,
|
FacePaintUvMultiplier = 0x0400,
|
||||||
FacePaintUvOffset = 0x0800,
|
FacePaintUvOffset = 0x0800,
|
||||||
DecalColor = 0x1000,
|
DecalColor = 0x1000,
|
||||||
|
LeftLimbalIntensity = 0x2000,
|
||||||
|
RightLimbalIntensity = 0x4000,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CustomizeParameterExtensions
|
public static class CustomizeParameterExtensions
|
||||||
{
|
{
|
||||||
public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x1FFF;
|
// Speculars are not available anymore.
|
||||||
|
public const CustomizeParameterFlag All = (CustomizeParameterFlag)0x7FDB;
|
||||||
|
|
||||||
public const CustomizeParameterFlag RgbTriples = All
|
public const CustomizeParameterFlag RgbTriples = All
|
||||||
& ~(RgbaQuadruples | Percentages | Values);
|
& ~(RgbaQuadruples | Percentages | Values);
|
||||||
|
|
||||||
public const CustomizeParameterFlag RgbaQuadruples = CustomizeParameterFlag.DecalColor | CustomizeParameterFlag.LipDiffuse;
|
public const CustomizeParameterFlag RgbaQuadruples = CustomizeParameterFlag.DecalColor | CustomizeParameterFlag.LipDiffuse;
|
||||||
public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone;
|
|
||||||
|
public const CustomizeParameterFlag Percentages = CustomizeParameterFlag.MuscleTone
|
||||||
|
| CustomizeParameterFlag.LeftLimbalIntensity
|
||||||
|
| CustomizeParameterFlag.RightLimbalIntensity;
|
||||||
|
|
||||||
public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier;
|
public const CustomizeParameterFlag Values = CustomizeParameterFlag.FacePaintUvOffset | CustomizeParameterFlag.FacePaintUvMultiplier;
|
||||||
|
|
||||||
public static readonly IReadOnlyList<CustomizeParameterFlag> AllFlags = [.. Enum.GetValues<CustomizeParameterFlag>()];
|
public static readonly IReadOnlyList<CustomizeParameterFlag> AllFlags = [.. Enum.GetValues<CustomizeParameterFlag>().Where(f => All.HasFlag(f))];
|
||||||
public static readonly IReadOnlyList<CustomizeParameterFlag> RgbaFlags = AllFlags.Where(f => RgbaQuadruples.HasFlag(f)).ToArray();
|
public static readonly IReadOnlyList<CustomizeParameterFlag> RgbaFlags = AllFlags.Where(f => RgbaQuadruples.HasFlag(f)).ToArray();
|
||||||
public static readonly IReadOnlyList<CustomizeParameterFlag> RgbFlags = AllFlags.Where(f => RgbTriples.HasFlag(f)).ToArray();
|
public static readonly IReadOnlyList<CustomizeParameterFlag> RgbFlags = AllFlags.Where(f => RgbTriples.HasFlag(f)).ToArray();
|
||||||
public static readonly IReadOnlyList<CustomizeParameterFlag> PercentageFlags = AllFlags.Where(f => Percentages.HasFlag(f)).ToArray();
|
public static readonly IReadOnlyList<CustomizeParameterFlag> PercentageFlags = AllFlags.Where(f => Percentages.HasFlag(f)).ToArray();
|
||||||
|
|
@ -56,10 +63,12 @@ public static class CustomizeParameterExtensions
|
||||||
CustomizeParameterFlag.HairHighlight => "Hair Highlights",
|
CustomizeParameterFlag.HairHighlight => "Hair Highlights",
|
||||||
CustomizeParameterFlag.LeftEye => "Left Eye Color",
|
CustomizeParameterFlag.LeftEye => "Left Eye Color",
|
||||||
CustomizeParameterFlag.RightEye => "Right Eye Color",
|
CustomizeParameterFlag.RightEye => "Right Eye Color",
|
||||||
CustomizeParameterFlag.FeatureColor => "Tattoo Color",
|
CustomizeParameterFlag.FeatureColor => "Feature Color",
|
||||||
CustomizeParameterFlag.FacePaintUvMultiplier => "Multiplier for Face Paint",
|
CustomizeParameterFlag.FacePaintUvMultiplier => "Multiplier for Face Paint",
|
||||||
CustomizeParameterFlag.FacePaintUvOffset => "Offset of Face Paint",
|
CustomizeParameterFlag.FacePaintUvOffset => "Offset of Face Paint",
|
||||||
CustomizeParameterFlag.DecalColor => "Face Paint Color",
|
CustomizeParameterFlag.DecalColor => "Face Paint Color",
|
||||||
|
CustomizeParameterFlag.LeftLimbalIntensity => "Left Limbal Ring Intensity",
|
||||||
|
CustomizeParameterFlag.RightLimbalIntensity => "Right Limbal Ring Intensity",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
using OtterGui.Extensions;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
using Race = Penumbra.GameData.Enums.Race;
|
||||||
|
|
||||||
namespace Glamourer.GameData;
|
namespace Glamourer.GameData;
|
||||||
|
|
||||||
|
|
@ -10,12 +12,15 @@ namespace Glamourer.GameData;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CustomizeSet
|
public class CustomizeSet
|
||||||
{
|
{
|
||||||
internal CustomizeSet(SubRace clan, Gender gender)
|
private readonly NpcCustomizeSet _npcCustomizations;
|
||||||
|
|
||||||
|
internal CustomizeSet(NpcCustomizeSet npcCustomizations, SubRace clan, Gender gender)
|
||||||
{
|
{
|
||||||
Gender = gender;
|
_npcCustomizations = npcCustomizations;
|
||||||
Clan = clan;
|
Gender = gender;
|
||||||
Race = clan.ToRace();
|
Clan = clan;
|
||||||
SettingAvailable = 0;
|
Race = clan.ToRace();
|
||||||
|
SettingAvailable = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Gender Gender { get; }
|
public Gender Gender { get; }
|
||||||
|
|
@ -38,9 +43,9 @@ public class CustomizeSet
|
||||||
public string Option(CustomizeIndex index)
|
public string Option(CustomizeIndex index)
|
||||||
=> OptionName[(int)index];
|
=> OptionName[(int)index];
|
||||||
|
|
||||||
public IReadOnlyList<byte> Voices { get; internal init; } = null!;
|
public IReadOnlyList<byte> Voices { get; internal init; } = null!;
|
||||||
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
public IReadOnlyList<MenuType> Types { get; internal set; } = null!;
|
||||||
public IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizeIndex[]> Order { get; internal set; } = null!;
|
public IReadOnlyDictionary<MenuType, CustomizeIndex[]> Order { get; internal set; } = null!;
|
||||||
|
|
||||||
|
|
||||||
// Always list selector.
|
// Always list selector.
|
||||||
|
|
@ -84,6 +89,7 @@ public class CustomizeSet
|
||||||
{
|
{
|
||||||
if (IsAvailable(index))
|
if (IsAvailable(index))
|
||||||
return DataByValue(index, value, out custom, face) >= 0
|
return DataByValue(index, value, out custom, face) >= 0
|
||||||
|
|| _npcCustomizations.CheckValue(index, value)
|
||||||
|| NpcOptions.Any(t => t.Type == index && t.Value == value);
|
|| NpcOptions.Any(t => t.Type == index && t.Value == value);
|
||||||
|
|
||||||
custom = null;
|
custom = null;
|
||||||
|
|
@ -97,9 +103,9 @@ public class CustomizeSet
|
||||||
|
|
||||||
return type switch
|
return type switch
|
||||||
{
|
{
|
||||||
CharaMakeParams.MenuType.ListSelector => GetInteger0(out custom),
|
MenuType.ListSelector => GetInteger0(out custom),
|
||||||
CharaMakeParams.MenuType.List1Selector => GetInteger1(out custom),
|
MenuType.List1Selector => GetInteger1(out custom),
|
||||||
CharaMakeParams.MenuType.IconSelector => index switch
|
MenuType.IconSelector => index switch
|
||||||
{
|
{
|
||||||
CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom),
|
CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom),
|
||||||
CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles,
|
CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles,
|
||||||
|
|
@ -109,7 +115,7 @@ public class CustomizeSet
|
||||||
CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom),
|
CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom),
|
||||||
_ => Invalid(out custom),
|
_ => Invalid(out custom),
|
||||||
},
|
},
|
||||||
CharaMakeParams.MenuType.ColorPicker => index switch
|
MenuType.ColorPicker => index switch
|
||||||
{
|
{
|
||||||
CustomizeIndex.SkinColor => Get(SkinColors, value, out custom),
|
CustomizeIndex.SkinColor => Get(SkinColors, value, out custom),
|
||||||
CustomizeIndex.EyeColorLeft => Get(EyeColors, value, out custom),
|
CustomizeIndex.EyeColorLeft => Get(EyeColors, value, out custom),
|
||||||
|
|
@ -121,16 +127,16 @@ public class CustomizeSet
|
||||||
CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom),
|
CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom),
|
||||||
_ => Invalid(out custom),
|
_ => Invalid(out custom),
|
||||||
},
|
},
|
||||||
CharaMakeParams.MenuType.DoubleColorPicker => index switch
|
MenuType.DoubleColorPicker => index switch
|
||||||
{
|
{
|
||||||
CustomizeIndex.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, out custom),
|
CustomizeIndex.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, out custom),
|
||||||
CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom),
|
CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom),
|
||||||
_ => Invalid(out custom),
|
_ => Invalid(out custom),
|
||||||
},
|
},
|
||||||
CharaMakeParams.MenuType.IconCheckmark => GetBool(index, value, out custom),
|
MenuType.IconCheckmark => GetBool(index, value, out custom),
|
||||||
CharaMakeParams.MenuType.Percentage => GetInteger0(out custom),
|
MenuType.Percentage => GetInteger0(out custom),
|
||||||
CharaMakeParams.MenuType.Checkmark => GetBool(index, value, out custom),
|
MenuType.Checkmark => GetBool(index, value, out custom),
|
||||||
_ => Invalid(out custom),
|
_ => Invalid(out custom),
|
||||||
};
|
};
|
||||||
|
|
||||||
int Get(IEnumerable<CustomizeData> list, CustomizeValue v, out CustomizeData? output)
|
int Get(IEnumerable<CustomizeData> list, CustomizeValue v, out CustomizeData? output)
|
||||||
|
|
@ -208,10 +214,10 @@ public class CustomizeSet
|
||||||
|
|
||||||
switch (Types[(int)index])
|
switch (Types[(int)index])
|
||||||
{
|
{
|
||||||
case CharaMakeParams.MenuType.Percentage: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
|
case MenuType.Percentage: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
|
||||||
case CharaMakeParams.MenuType.ListSelector: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
|
case MenuType.ListSelector: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
|
||||||
case CharaMakeParams.MenuType.List1Selector: return new CustomizeData(index, (CustomizeValue)(idx + 1), 0, (ushort)idx);
|
case MenuType.List1Selector: return new CustomizeData(index, (CustomizeValue)(idx + 1), 0, (ushort)idx);
|
||||||
case CharaMakeParams.MenuType.Checkmark: return new CustomizeData(index, CustomizeValue.Bool(idx != 0), 0, (ushort)idx);
|
case MenuType.Checkmark: return new CustomizeData(index, CustomizeValue.Bool(idx != 0), 0, (ushort)idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return index switch
|
return index switch
|
||||||
|
|
@ -241,7 +247,7 @@ public class CustomizeSet
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
public CharaMakeParams.MenuType Type(CustomizeIndex index)
|
public MenuType Type(CustomizeIndex index)
|
||||||
=> Types[(int)index];
|
=> Types[(int)index];
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
|
@ -256,9 +262,9 @@ public class CustomizeSet
|
||||||
|
|
||||||
return Type(index) switch
|
return Type(index) switch
|
||||||
{
|
{
|
||||||
CharaMakeParams.MenuType.Percentage => 101,
|
MenuType.Percentage => 101,
|
||||||
CharaMakeParams.MenuType.IconCheckmark => 2,
|
MenuType.IconCheckmark => 2,
|
||||||
CharaMakeParams.MenuType.Checkmark => 2,
|
MenuType.Checkmark => 2,
|
||||||
_ => index switch
|
_ => index switch
|
||||||
{
|
{
|
||||||
CustomizeIndex.Face => Faces.Count,
|
CustomizeIndex.Face => Faces.Count,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
using Dalamud;
|
using Dalamud.Game;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.Sheets;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Race = Penumbra.GameData.Enums.Race;
|
using Race = Penumbra.GameData.Enums.Race;
|
||||||
|
|
@ -13,11 +13,11 @@ namespace Glamourer.GameData;
|
||||||
internal class CustomizeSetFactory(
|
internal class CustomizeSetFactory(
|
||||||
IDataManager _gameData,
|
IDataManager _gameData,
|
||||||
IPluginLog _log,
|
IPluginLog _log,
|
||||||
IconStorage _icons,
|
TextureCache _icons,
|
||||||
NpcCustomizeSet _npcCustomizeSet,
|
NpcCustomizeSet _npcCustomizeSet,
|
||||||
ColorParameters _colors)
|
ColorParameters _colors)
|
||||||
{
|
{
|
||||||
public CustomizeSetFactory(IDataManager gameData, IPluginLog log, IconStorage icons, NpcCustomizeSet npcCustomizeSet)
|
public CustomizeSetFactory(IDataManager gameData, IPluginLog log, TextureCache icons, NpcCustomizeSet npcCustomizeSet)
|
||||||
: this(gameData, log, icons, npcCustomizeSet, new ColorParameters(gameData, log))
|
: this(gameData, log, icons, npcCustomizeSet, new ColorParameters(gameData, log))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
|
@ -28,10 +28,10 @@ internal class CustomizeSetFactory(
|
||||||
var row = _charaMakeSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
var row = _charaMakeSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||||
var hrothgar = race.ToRace() == Race.Hrothgar;
|
var hrothgar = race.ToRace() == Race.Hrothgar;
|
||||||
// Create the initial set with all the easily accessible parameters available for anyone.
|
// Create the initial set with all the easily accessible parameters available for anyone.
|
||||||
var set = new CustomizeSet(race, gender)
|
var set = new CustomizeSet(_npcCustomizeSet, race, gender)
|
||||||
{
|
{
|
||||||
Name = GetName(race, gender),
|
Name = GetName(race, gender),
|
||||||
Voices = row.Voices,
|
Voices = row.VoiceStruct,
|
||||||
HairStyles = GetHairStyles(race, gender),
|
HairStyles = GetHairStyles(race, gender),
|
||||||
HairColors = hair,
|
HairColors = hair,
|
||||||
SkinColors = skin,
|
SkinColors = skin,
|
||||||
|
|
@ -58,7 +58,7 @@ internal class CustomizeSetFactory(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Some data can not be set independently of the rest, so we need a post-processing step to finalize. </summary>
|
/// <summary> Some data can not be set independently of the rest, so we need a post-processing step to finalize. </summary>
|
||||||
private void SetPostProcessing(CustomizeSet set, CharaMakeParams row)
|
private void SetPostProcessing(CustomizeSet set, in CharaMakeType row)
|
||||||
{
|
{
|
||||||
SetAvailability(set, row);
|
SetAvailability(set, row);
|
||||||
SetFacialFeatures(set, row);
|
SetFacialFeatures(set, row);
|
||||||
|
|
@ -76,18 +76,16 @@ internal class CustomizeSetFactory(
|
||||||
CustomizeIndex.Hairstyle,
|
CustomizeIndex.Hairstyle,
|
||||||
CustomizeIndex.LipColor,
|
CustomizeIndex.LipColor,
|
||||||
CustomizeIndex.SkinColor,
|
CustomizeIndex.SkinColor,
|
||||||
CustomizeIndex.FacePaintColor,
|
CustomizeIndex.TailShape,
|
||||||
CustomizeIndex.HighlightsColor,
|
|
||||||
CustomizeIndex.HairColor,
|
|
||||||
CustomizeIndex.FacePaint,
|
|
||||||
CustomizeIndex.TattooColor,
|
|
||||||
CustomizeIndex.EyeColorLeft,
|
|
||||||
CustomizeIndex.EyeColorRight,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>();
|
var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>()
|
||||||
|
{
|
||||||
|
(CustomizeIndex.Height, CustomizeValue.Max),
|
||||||
|
};
|
||||||
_npcCustomizeSet.Awaiter.Wait();
|
_npcCustomizeSet.Awaiter.Wait();
|
||||||
foreach (var customize in _npcCustomizeSet.Select(s => s.Customize).Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1))
|
foreach (var customize in _npcCustomizeSet.Select(s => s.Customize)
|
||||||
|
.Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1))
|
||||||
{
|
{
|
||||||
foreach (var customizeIndex in customizeIndices)
|
foreach (var customizeIndex in customizeIndices)
|
||||||
{
|
{
|
||||||
|
|
@ -106,10 +104,10 @@ internal class CustomizeSetFactory(
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ColorParameters _colorParameters = new(_gameData, _log);
|
private readonly ColorParameters _colorParameters = new(_gameData, _log);
|
||||||
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet = _gameData.GetExcelSheet<CharaMakeCustomize>(ClientLanguage.English)!;
|
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet = _gameData.GetExcelSheet<CharaMakeCustomize>(ClientLanguage.English);
|
||||||
private readonly ExcelSheet<Lobby> _lobbySheet = _gameData.GetExcelSheet<Lobby>(ClientLanguage.English)!;
|
private readonly ExcelSheet<Lobby> _lobbySheet = _gameData.GetExcelSheet<Lobby>(ClientLanguage.English);
|
||||||
private readonly ExcelSheet<HairMakeType> _hairSheet = _gameData.GetExcelSheet<HairMakeType>(ClientLanguage.English)!;
|
private readonly ExcelSheet<RawRow> _hairSheet = _gameData.GetExcelSheet<RawRow>(ClientLanguage.English, "HairMakeType");
|
||||||
private readonly ExcelSheet<Tribe> _tribeSheet = _gameData.GetExcelSheet<Tribe>(ClientLanguage.English)!;
|
private readonly ExcelSheet<Tribe> _tribeSheet = _gameData.GetExcelSheet<Tribe>(ClientLanguage.English);
|
||||||
|
|
||||||
// Those color pickers are shared between all races.
|
// Those color pickers are shared between all races.
|
||||||
private readonly CustomizeData[] _highlightPicker = CreateColors(_colors, CustomizeIndex.HighlightsColor, 256, 192);
|
private readonly CustomizeData[] _highlightPicker = CreateColors(_colors, CustomizeIndex.HighlightsColor, 256, 192);
|
||||||
|
|
@ -120,12 +118,7 @@ internal class CustomizeSetFactory(
|
||||||
private readonly CustomizeData[] _facePaintColorPickerLight = CreateColors(_colors, CustomizeIndex.FacePaintColor, 1152, 96, true);
|
private readonly CustomizeData[] _facePaintColorPickerLight = CreateColors(_colors, CustomizeIndex.FacePaintColor, 1152, 96, true);
|
||||||
private readonly CustomizeData[] _tattooColorPicker = CreateColors(_colors, CustomizeIndex.TattooColor, 0, 192);
|
private readonly CustomizeData[] _tattooColorPicker = CreateColors(_colors, CustomizeIndex.TattooColor, 0, 192);
|
||||||
|
|
||||||
private readonly ExcelSheet<CharaMakeParams> _charaMakeSheet = _gameData.Excel
|
private readonly ExcelSheet<CharaMakeType> _charaMakeSheet = _gameData.Excel.GetSheet<CharaMakeType>();
|
||||||
.GetType()
|
|
||||||
.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)?
|
|
||||||
.MakeGenericMethod(typeof(CharaMakeParams))
|
|
||||||
.Invoke(_gameData.Excel, ["charamaketype", _gameData.Language.ToLumina(), null])! as ExcelSheet<CharaMakeParams>
|
|
||||||
?? null!;
|
|
||||||
|
|
||||||
/// <summary> Obtain available skin and hair colors for the given clan and gender. </summary>
|
/// <summary> Obtain available skin and hair colors for the given clan and gender. </summary>
|
||||||
private (CustomizeData[] Skin, CustomizeData[] Hair) GetSkinHairColors(SubRace race, Gender gender)
|
private (CustomizeData[] Skin, CustomizeData[] Hair) GetSkinHairColors(SubRace race, Gender gender)
|
||||||
|
|
@ -144,29 +137,28 @@ internal class CustomizeSetFactory(
|
||||||
private string GetName(SubRace race, Gender gender)
|
private string GetName(SubRace race, Gender gender)
|
||||||
=> gender switch
|
=> gender switch
|
||||||
{
|
{
|
||||||
Gender.Male => _tribeSheet.GetRow((uint)race)?.Masculine.ToDalamudString().TextValue ?? race.ToName(),
|
Gender.Male => _tribeSheet.TryGetRow((uint)race, out var row) ? row.Masculine.ExtractText() : race.ToName(),
|
||||||
Gender.Female => _tribeSheet.GetRow((uint)race)?.Feminine.ToDalamudString().TextValue ?? race.ToName(),
|
Gender.Female => _tribeSheet.TryGetRow((uint)race, out var row) ? row.Feminine.ExtractText() : race.ToName(),
|
||||||
_ => "Unknown",
|
_ => "Unknown",
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary> Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender. </summary>
|
/// <summary> Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender. </summary>
|
||||||
private CustomizeData[] GetHairStyles(SubRace race, Gender gender)
|
private CustomizeData[] GetHairStyles(SubRace race, Gender gender)
|
||||||
{
|
{
|
||||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender);
|
||||||
// Unknown30 is the number of available hairstyles.
|
// Unknown30 is the number of available hairstyles.
|
||||||
var hairList = new List<CustomizeData>(row.Unknown30);
|
var numHairs = row.ReadUInt8Column(30);
|
||||||
|
var hairList = new List<CustomizeData>(numHairs);
|
||||||
// Hairstyles can be found starting at Unknown66.
|
// Hairstyles can be found starting at Unknown66.
|
||||||
for (var i = 0; i < row.Unknown30; ++i)
|
for (var i = 0; i < numHairs; ++i)
|
||||||
{
|
{
|
||||||
var name = $"Unknown{66 + i * 9}";
|
// Hairs start at Unknown66.
|
||||||
var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
var customizeIdx = row.ReadUInt32Column(66 + i * 9);
|
||||||
?? uint.MaxValue;
|
|
||||||
if (customizeIdx == uint.MaxValue)
|
if (customizeIdx == uint.MaxValue)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
|
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
|
||||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
if (!_customizeSheet.TryGetRow(customizeIdx, out var hairRow))
|
||||||
if (hairRow == null)
|
|
||||||
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
|
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
|
||||||
else if (_icons.IconExists(hairRow.Icon))
|
else if (_icons.IconExists(hairRow.Icon))
|
||||||
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
|
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
|
||||||
|
|
@ -177,45 +169,40 @@ internal class CustomizeSetFactory(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Specific icons for tails or ears. </summary>
|
/// <summary> Specific icons for tails or ears. </summary>
|
||||||
private CustomizeData[] GetTailEarShapes(CharaMakeParams row)
|
private CustomizeData[] GetTailEarShapes(CharaMakeType row)
|
||||||
=> row.Menus.Cast<CharaMakeParams.Menu?>()
|
=> ExtractValues(row, CustomizeIndex.TailShape);
|
||||||
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx)?.Values
|
|
||||||
.Select((v, i) => FromValueAndIndex(CustomizeIndex.TailShape, v, i)).ToArray()
|
|
||||||
?? [];
|
|
||||||
|
|
||||||
/// <summary> Specific icons for faces. </summary>
|
/// <summary> Specific icons for faces. </summary>
|
||||||
private CustomizeData[] GetFaces(CharaMakeParams row)
|
private CustomizeData[] GetFaces(CharaMakeType row)
|
||||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.Face.ToByteAndMask().ByteIdx)
|
=> ExtractValues(row, CustomizeIndex.Face);
|
||||||
?.Values
|
|
||||||
.Select((v, i) => FromValueAndIndex(CustomizeIndex.Face, v, i)).ToArray()
|
|
||||||
?? [];
|
|
||||||
|
|
||||||
/// <summary> Specific icons for Hrothgar patterns. </summary>
|
/// <summary> Specific icons for Hrothgar patterns. </summary>
|
||||||
private CustomizeData[] HrothgarFurPattern(CharaMakeParams row)
|
private CustomizeData[] HrothgarFurPattern(CharaMakeType row)
|
||||||
=> row.Menus.Cast<CharaMakeParams.Menu?>()
|
=> ExtractValues(row, CustomizeIndex.LipColor);
|
||||||
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.LipColor.ToByteAndMask().ByteIdx)?.Values
|
|
||||||
.Select((v, i) => FromValueAndIndex(CustomizeIndex.LipColor, v, i)).ToArray()
|
private CustomizeData[] ExtractValues(CharaMakeType row, CustomizeIndex type)
|
||||||
?? [];
|
{
|
||||||
|
var data = row.CharaMakeStruct.FirstOrNull(m => m.Customize == type.ToByteAndMask().ByteIdx);
|
||||||
|
return data?.SubMenuParam.Take(data.Value.SubMenuNum).Select((v, i) => FromValueAndIndex(type, v, i)).ToArray() ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Get face paints from the hair sheet via reflection since there are also unlockable face paints. </summary>
|
/// <summary> Get face paints from the hair sheet via reflection since there are also unlockable face paints. </summary>
|
||||||
private CustomizeData[] GetFacePaints(SubRace race, Gender gender)
|
private CustomizeData[] GetFacePaints(SubRace race, Gender gender)
|
||||||
{
|
{
|
||||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender);
|
||||||
var paintList = new List<CustomizeData>(row.Unknown37);
|
|
||||||
// Number of available face paints is at Unknown37.
|
// Number of available face paints is at Unknown37.
|
||||||
for (var i = 0; i < row.Unknown37; ++i)
|
var numPaints = row.ReadUInt8Column(37);
|
||||||
|
var paintList = new List<CustomizeData>(numPaints);
|
||||||
|
|
||||||
|
for (var i = 0; i < numPaints; ++i)
|
||||||
{
|
{
|
||||||
// Face paints start at Unknown73.
|
// Face paints start at Unknown73.
|
||||||
var name = $"Unknown{73 + i * 9}";
|
var customizeIdx = row.ReadUInt32Column(73 + i * 9);
|
||||||
var customizeIdx =
|
|
||||||
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
|
||||||
?? uint.MaxValue;
|
|
||||||
if (customizeIdx == uint.MaxValue)
|
if (customizeIdx == uint.MaxValue)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
|
||||||
// Face paint Row from CustomizeSheet might not be set in case of unlockable face paints.
|
// Face paint Row from CustomizeSheet might not be set in case of unlockable face paints.
|
||||||
if (paintRow != null)
|
if (_customizeSheet.TryGetRow(customizeIdx, out var paintRow))
|
||||||
paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon,
|
paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon,
|
||||||
(ushort)paintRow.RowId));
|
(ushort)paintRow.RowId));
|
||||||
else
|
else
|
||||||
|
|
@ -226,21 +213,18 @@ internal class CustomizeSetFactory(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Get List sizes. </summary>
|
/// <summary> Get List sizes. </summary>
|
||||||
private static int GetListSize(CharaMakeParams row, CustomizeIndex index)
|
private static int GetListSize(CharaMakeType row, CustomizeIndex index)
|
||||||
{
|
{
|
||||||
var gameId = index.ToByteAndMask().ByteIdx;
|
var gameId = index.ToByteAndMask().ByteIdx;
|
||||||
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == gameId);
|
var menu = row.CharaMakeStruct.FirstOrNull(m => m.Customize == gameId);
|
||||||
return menu?.Size ?? 0;
|
return menu?.SubMenuNum ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Get generic Features. </summary>
|
/// <summary> Get generic Features. </summary>
|
||||||
private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index)
|
private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index)
|
||||||
{
|
=> _customizeSheet.TryGetRow(value, out var row)
|
||||||
var row = _customizeSheet.GetRow(value);
|
? new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId)
|
||||||
return row == null
|
: new CustomizeData(id, (CustomizeValue)(index + 1), value);
|
||||||
? new CustomizeData(id, (CustomizeValue)(index + 1), value)
|
|
||||||
: new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Create generic color sets from the parameters. </summary>
|
/// <summary> Create generic color sets from the parameters. </summary>
|
||||||
private static CustomizeData[] CreateColors(ColorParameters colorParameters, CustomizeIndex index, int offset, int num,
|
private static CustomizeData[] CreateColors(ColorParameters colorParameters, CustomizeIndex index, int offset, int num,
|
||||||
|
|
@ -258,28 +242,27 @@ internal class CustomizeSetFactory(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Set the specific option names for the given set of parameters. </summary>
|
/// <summary> Set the specific option names for the given set of parameters. </summary>
|
||||||
private string[] GetOptionNames(CharaMakeParams row)
|
private string[] GetOptionNames(CharaMakeType row)
|
||||||
{
|
{
|
||||||
var nameArray = Enum.GetValues<CustomizeIndex>().Select(c =>
|
var nameArray = Enum.GetValues<CustomizeIndex>().Select(c =>
|
||||||
{
|
{
|
||||||
// Find the first menu that corresponds to the Id.
|
// Find the first menu that corresponds to the Id.
|
||||||
var byteId = c.ToByteAndMask().ByteIdx;
|
var byteId = c.ToByteAndMask().ByteIdx;
|
||||||
var menu = row.Menus
|
var menu = row.CharaMakeStruct.FirstOrNull(m => m.Customize == byteId);
|
||||||
.Cast<CharaMakeParams.Menu?>()
|
|
||||||
.FirstOrDefault(m => m!.Value.Customize == byteId);
|
|
||||||
if (menu == null)
|
if (menu == null)
|
||||||
{
|
{
|
||||||
// If none exists and the id corresponds to highlights, set the Highlights name.
|
// If none exists and the id corresponds to highlights, set the Highlights name.
|
||||||
if (c == CustomizeIndex.Highlights)
|
if (c == CustomizeIndex.Highlights)
|
||||||
return string.Intern(_lobbySheet.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights");
|
return string.Intern(_lobbySheet.TryGetRow(237, out var text) ? text.Text.ExtractText() : "Highlights");
|
||||||
|
|
||||||
// Otherwise there is an error and we use the default name.
|
// Otherwise there is an error and we use the default name.
|
||||||
return c.ToDefaultName();
|
return c.ToDefaultName();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise all is normal, get the menu name or if it does not work the default name.
|
// Otherwise all is normal, get the menu name or if it does not work the default name.
|
||||||
var textRow = _lobbySheet.GetRow(menu.Value.Id);
|
return string.Intern(_lobbySheet.TryGetRow(menu.Value.Menu.RowId, out var textRow)
|
||||||
return string.Intern(textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName());
|
? textRow.Text.ExtractText()
|
||||||
|
: c.ToDefaultName());
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
// Add names for both eye colors.
|
// Add names for both eye colors.
|
||||||
|
|
@ -300,7 +283,7 @@ internal class CustomizeSetFactory(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Get the manu types for all available options. </summary>
|
/// <summary> Get the manu types for all available options. </summary>
|
||||||
private static CharaMakeParams.MenuType[] GetMenuTypes(CharaMakeParams row)
|
private static MenuType[] GetMenuTypes(CharaMakeType row)
|
||||||
{
|
{
|
||||||
// Set up the menu types for all customizations.
|
// Set up the menu types for all customizations.
|
||||||
return Enum.GetValues<CustomizeIndex>().Select(c =>
|
return Enum.GetValues<CustomizeIndex>().Select(c =>
|
||||||
|
|
@ -312,13 +295,13 @@ internal class CustomizeSetFactory(
|
||||||
case CustomizeIndex.EyeColorLeft:
|
case CustomizeIndex.EyeColorLeft:
|
||||||
case CustomizeIndex.EyeColorRight:
|
case CustomizeIndex.EyeColorRight:
|
||||||
case CustomizeIndex.FacePaintColor:
|
case CustomizeIndex.FacePaintColor:
|
||||||
return CharaMakeParams.MenuType.ColorPicker;
|
return MenuType.ColorPicker;
|
||||||
case CustomizeIndex.BodyType: return CharaMakeParams.MenuType.Nothing;
|
case CustomizeIndex.BodyType: return MenuType.Nothing;
|
||||||
case CustomizeIndex.FacePaintReversed:
|
case CustomizeIndex.FacePaintReversed:
|
||||||
case CustomizeIndex.Highlights:
|
case CustomizeIndex.Highlights:
|
||||||
case CustomizeIndex.SmallIris:
|
case CustomizeIndex.SmallIris:
|
||||||
case CustomizeIndex.Lipstick:
|
case CustomizeIndex.Lipstick:
|
||||||
return CharaMakeParams.MenuType.Checkmark;
|
return MenuType.Checkmark;
|
||||||
case CustomizeIndex.FacialFeature1:
|
case CustomizeIndex.FacialFeature1:
|
||||||
case CustomizeIndex.FacialFeature2:
|
case CustomizeIndex.FacialFeature2:
|
||||||
case CustomizeIndex.FacialFeature3:
|
case CustomizeIndex.FacialFeature3:
|
||||||
|
|
@ -327,29 +310,23 @@ internal class CustomizeSetFactory(
|
||||||
case CustomizeIndex.FacialFeature6:
|
case CustomizeIndex.FacialFeature6:
|
||||||
case CustomizeIndex.FacialFeature7:
|
case CustomizeIndex.FacialFeature7:
|
||||||
case CustomizeIndex.LegacyTattoo:
|
case CustomizeIndex.LegacyTattoo:
|
||||||
return CharaMakeParams.MenuType.IconCheckmark;
|
return MenuType.IconCheckmark;
|
||||||
}
|
}
|
||||||
|
|
||||||
var gameId = c.ToByteAndMask().ByteIdx;
|
var gameId = c.ToByteAndMask().ByteIdx;
|
||||||
// Otherwise find the first menu corresponding to the id.
|
// Otherwise find the first menu corresponding to the id.
|
||||||
// If there is none, assume a list.
|
// If there is none, assume a list.
|
||||||
var menu = row.Menus
|
var menu = row.CharaMakeStruct.FirstOrNull(m => m.Customize == gameId);
|
||||||
.Cast<CharaMakeParams.Menu?>()
|
var ret = (MenuType)(menu?.SubMenuType ?? (byte)MenuType.ListSelector);
|
||||||
.FirstOrDefault(m => m!.Value.Customize == gameId);
|
if (c is CustomizeIndex.TailShape && ret is MenuType.ListSelector)
|
||||||
var ret = menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
ret = MenuType.List1Selector;
|
||||||
if (c is CustomizeIndex.TailShape && ret is CharaMakeParams.MenuType.ListSelector)
|
|
||||||
ret = CharaMakeParams.MenuType.List1Selector;
|
|
||||||
return ret;
|
return ret;
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Set the availability of options according to actual availability. </summary>
|
/// <summary> Set the availability of options according to actual availability. </summary>
|
||||||
private static void SetAvailability(CustomizeSet set, CharaMakeParams row)
|
private static void SetAvailability(CustomizeSet set, CharaMakeType row)
|
||||||
{
|
{
|
||||||
// TODO: Hrothgar female
|
|
||||||
if (set is { Race: Race.Hrothgar, Gender: Gender.Female })
|
|
||||||
return;
|
|
||||||
|
|
||||||
Set(true, CustomizeIndex.Height);
|
Set(true, CustomizeIndex.Height);
|
||||||
Set(set.Faces.Count > 0, CustomizeIndex.Face);
|
Set(set.Faces.Count > 0, CustomizeIndex.Face);
|
||||||
Set(true, CustomizeIndex.Hairstyle);
|
Set(true, CustomizeIndex.Hairstyle);
|
||||||
|
|
@ -399,7 +376,7 @@ internal class CustomizeSetFactory(
|
||||||
ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.TattooColor;
|
ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.TattooColor;
|
||||||
|
|
||||||
var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray());
|
var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray());
|
||||||
foreach (var type in Enum.GetValues<CharaMakeParams.MenuType>())
|
foreach (var type in Enum.GetValues<MenuType>())
|
||||||
dict.TryAdd(type, []);
|
dict.TryAdd(type, []);
|
||||||
set.Order = dict;
|
set.Order = dict;
|
||||||
}
|
}
|
||||||
|
|
@ -423,7 +400,7 @@ internal class CustomizeSetFactory(
|
||||||
|
|
||||||
bool Valid(CustomizeData c)
|
bool Valid(CustomizeData c)
|
||||||
{
|
{
|
||||||
var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0;
|
var data = _customizeSheet.TryGetRow(c.CustomizeId, out var customize) ? customize.Unknown0 : 0;
|
||||||
return data == 0 || data == i + set.Faces.Count;
|
return data == 0 || data == i + set.Faces.Count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -435,7 +412,7 @@ internal class CustomizeSetFactory(
|
||||||
/// Create a list of lists of facial features and the legacy tattoo.
|
/// Create a list of lists of facial features and the legacy tattoo.
|
||||||
/// Facial Features are bools in a bitfield, so we supply an "off" and an "on" value for simplicity of use.
|
/// Facial Features are bools in a bitfield, so we supply an "off" and an "on" value for simplicity of use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void SetFacialFeatures(CustomizeSet set, CharaMakeParams row)
|
private static void SetFacialFeatures(CustomizeSet set, in CharaMakeType row)
|
||||||
{
|
{
|
||||||
var count = set.Faces.Count;
|
var count = set.Faces.Count;
|
||||||
set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count);
|
set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count);
|
||||||
|
|
@ -444,14 +421,14 @@ internal class CustomizeSetFactory(
|
||||||
var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray();
|
var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray();
|
||||||
for (var i = 0; i < count; ++i)
|
for (var i = 0; i < count; ++i)
|
||||||
{
|
{
|
||||||
var data = row.FacialFeatureByFace[i].Icons;
|
var data = row.FacialFeatureOption[i];
|
||||||
tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, data[0]);
|
tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, (uint)data.Option1);
|
||||||
tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, data[1]);
|
tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, (uint)data.Option2);
|
||||||
tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, data[2]);
|
tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, (uint)data.Option3);
|
||||||
tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, data[3]);
|
tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, (uint)data.Option4);
|
||||||
tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, data[4]);
|
tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, (uint)data.Option5);
|
||||||
tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, data[5]);
|
tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, (uint)data.Option6);
|
||||||
tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, data[6]);
|
tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, (uint)data.Option7);
|
||||||
}
|
}
|
||||||
|
|
||||||
set.FacialFeature1 = tmp[0];
|
set.FacialFeature1 = tmp[0];
|
||||||
|
|
|
||||||
14
Glamourer/GameData/MenuType.cs
Normal file
14
Glamourer/GameData/MenuType.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
namespace Glamourer.GameData;
|
||||||
|
|
||||||
|
public enum MenuType
|
||||||
|
{
|
||||||
|
ListSelector = 0,
|
||||||
|
IconSelector = 1,
|
||||||
|
ColorPicker = 2,
|
||||||
|
DoubleColorPicker = 3,
|
||||||
|
IconCheckmark = 4,
|
||||||
|
Percentage = 5,
|
||||||
|
Checkmark = 6, // custom
|
||||||
|
Nothing = 7, // custom
|
||||||
|
List1Selector = 8, // custom, 1-indexed lists
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.Sheets;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.DataContainers;
|
using Penumbra.GameData.DataContainers;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.GameData;
|
namespace Glamourer.GameData;
|
||||||
|
|
@ -35,32 +37,51 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
|
||||||
/// <summary> The list of data. </summary>
|
/// <summary> The list of data. </summary>
|
||||||
private readonly List<NpcData> _data = [];
|
private readonly List<NpcData> _data = [];
|
||||||
|
|
||||||
|
private readonly BitArray _hairColors = new(256);
|
||||||
|
private readonly BitArray _eyeColors = new(256);
|
||||||
|
private readonly BitArray _facepaintColors = new(256);
|
||||||
|
private readonly BitArray _tattooColors = new(256);
|
||||||
|
private readonly BitArray _facepaints = new(128);
|
||||||
|
|
||||||
|
public bool CheckValue(CustomizeIndex type, CustomizeValue value)
|
||||||
|
=> type switch
|
||||||
|
{
|
||||||
|
CustomizeIndex.HairColor => _hairColors[value.Value],
|
||||||
|
CustomizeIndex.HighlightsColor => _hairColors[value.Value],
|
||||||
|
CustomizeIndex.EyeColorLeft => _eyeColors[value.Value],
|
||||||
|
CustomizeIndex.EyeColorRight => _eyeColors[value.Value],
|
||||||
|
CustomizeIndex.FacePaintColor => _facepaintColors[value.Value],
|
||||||
|
CustomizeIndex.TattooColor => _tattooColors[value.Value],
|
||||||
|
CustomizeIndex.FacePaint when value.Value < 128 => _facepaints[value.Value],
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary> Create the data when ready. </summary>
|
/// <summary> Create the data when ready. </summary>
|
||||||
public NpcCustomizeSet(IDataManager data, DictENpc eNpcs, DictBNpc bNpcs, DictBNpcNames bNpcNames)
|
public NpcCustomizeSet(IDataManager data, DictENpc eNpcs, DictBNpc bNpcs, DictBNpcNames bNpcNames)
|
||||||
{
|
{
|
||||||
var waitTask = Task.WhenAll(eNpcs.Awaiter, bNpcs.Awaiter, bNpcNames.Awaiter);
|
var waitTask = Task.WhenAll(eNpcs.Awaiter, bNpcs.Awaiter, bNpcNames.Awaiter);
|
||||||
Awaiter = waitTask.ContinueWith(_ =>
|
Awaiter = waitTask.ContinueWith(_ =>
|
||||||
{
|
{
|
||||||
var watch = Stopwatch.StartNew();
|
var watch = Stopwatch.StartNew();
|
||||||
var eNpcTask = Task.Run(() => CreateEnpcData(data, eNpcs));
|
var eNpcTask = Task.Run(() => CreateEnpcData(data, eNpcs));
|
||||||
var bNpcTask = Task.Run(() => CreateBnpcData(data, bNpcs, bNpcNames));
|
var bNpcTask = Task.Run(() => CreateBnpcData(data, bNpcs, bNpcNames));
|
||||||
FilterAndOrderNpcData(eNpcTask.Result, bNpcTask.Result);
|
FilterAndOrderNpcData(eNpcTask.Result, bNpcTask.Result);
|
||||||
Time = watch.ElapsedMilliseconds;
|
Time = watch.ElapsedMilliseconds;
|
||||||
});
|
})
|
||||||
|
.ContinueWith(_ => CheckFacepaintFiles(data, _facepaints));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Create data from event NPCs. </summary>
|
/// <summary> Create data from event NPCs. </summary>
|
||||||
private static List<NpcData> CreateEnpcData(IDataManager data, DictENpc eNpcs)
|
private static List<NpcData> CreateEnpcData(IDataManager data, DictENpc eNpcs)
|
||||||
{
|
{
|
||||||
var enpcSheet = data.GetExcelSheet<ENpcBase>()!;
|
var enpcSheet = data.GetExcelSheet<ENpcBase>();
|
||||||
var list = new List<NpcData>(eNpcs.Count);
|
var list = new List<NpcData>(eNpcs.Count);
|
||||||
|
|
||||||
// Go through all event NPCs already collected into a dictionary.
|
// Go through all event NPCs already collected into a dictionary.
|
||||||
foreach (var (id, name) in eNpcs)
|
foreach (var (id, name) in eNpcs)
|
||||||
{
|
{
|
||||||
var row = enpcSheet.GetRow(id.Id);
|
|
||||||
// We only accept NPCs with valid names.
|
// We only accept NPCs with valid names.
|
||||||
if (row == null || name.IsNullOrWhitespace())
|
if (!enpcSheet.TryGetRow(id.Id, out var row) || name.IsNullOrWhitespace())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check if the customization is a valid human.
|
// Check if the customization is a valid human.
|
||||||
|
|
@ -72,33 +93,17 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Customize = customize,
|
Customize = customize,
|
||||||
ModelId = row.ModelChara.Row,
|
ModelId = row.ModelChara.RowId,
|
||||||
Id = id,
|
Id = id,
|
||||||
Kind = ObjectKind.EventNpc,
|
Kind = ObjectKind.EventNpc,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Event NPCs have a reference to NpcEquip but also contain the appearance in their own row.
|
// Event NPCs have a reference to NpcEquip but also contain the appearance in their own row.
|
||||||
// Prefer the NpcEquip reference if it is set, otherwise use the own.
|
// Prefer the NpcEquip reference if it is set and the own does not appear to be set, otherwise use the own.
|
||||||
if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip)
|
if (row.NpcEquip.RowId != 0 && row.NpcEquip.Value is { } equip && row is { ModelBody: 0, ModelLegs: 0 })
|
||||||
{
|
|
||||||
ApplyNpcEquip(ref ret, equip);
|
ApplyNpcEquip(ref ret, equip);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
ApplyNpcEquip(ref ret, row);
|
||||||
ret.Set(0, row.ModelHead | (row.DyeHead.Row << 24));
|
|
||||||
ret.Set(1, row.ModelBody | (row.DyeBody.Row << 24));
|
|
||||||
ret.Set(2, row.ModelHands | (row.DyeHands.Row << 24));
|
|
||||||
ret.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24));
|
|
||||||
ret.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24));
|
|
||||||
ret.Set(5, row.ModelEars | (row.DyeEars.Row << 24));
|
|
||||||
ret.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24));
|
|
||||||
ret.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24));
|
|
||||||
ret.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24));
|
|
||||||
ret.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24));
|
|
||||||
ret.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48));
|
|
||||||
ret.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48));
|
|
||||||
ret.VisorToggled = row.Visor;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.Add(ret);
|
list.Add(ret);
|
||||||
}
|
}
|
||||||
|
|
@ -109,14 +114,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
|
||||||
/// <summary> Create data from battle NPCs. </summary>
|
/// <summary> Create data from battle NPCs. </summary>
|
||||||
private static List<NpcData> CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames)
|
private static List<NpcData> CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames)
|
||||||
{
|
{
|
||||||
var bnpcSheet = data.GetExcelSheet<BNpcBase>()!;
|
var bnpcSheet = data.GetExcelSheet<BNpcBase>();
|
||||||
var list = new List<NpcData>((int)bnpcSheet.RowCount);
|
var list = new List<NpcData>(bnpcSheet.Count);
|
||||||
|
|
||||||
// We go through all battle NPCs in the sheet because the dictionary refers to names.
|
// We go through all battle NPCs in the sheet because the dictionary refers to names.
|
||||||
foreach (var baseRow in bnpcSheet)
|
foreach (var baseRow in bnpcSheet)
|
||||||
{
|
{
|
||||||
// Only accept humans.
|
// Only accept humans.
|
||||||
if (baseRow.ModelChara.Value!.Type != 1)
|
if (baseRow.ModelChara.Value.Type != 1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var bnpcNameIds = bNpcNames[baseRow.RowId];
|
var bnpcNameIds = bNpcNames[baseRow.RowId];
|
||||||
|
|
@ -125,15 +130,15 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check if the customization is a valid human.
|
// Check if the customization is a valid human.
|
||||||
var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!);
|
var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value);
|
||||||
if (!valid)
|
if (!valid)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var equip = baseRow.NpcEquip.Value!;
|
var equip = baseRow.NpcEquip.Value;
|
||||||
var ret = new NpcData
|
var ret = new NpcData
|
||||||
{
|
{
|
||||||
Customize = customize,
|
Customize = customize,
|
||||||
ModelId = baseRow.ModelChara.Row,
|
ModelId = baseRow.ModelChara.RowId,
|
||||||
Id = baseRow.RowId,
|
Id = baseRow.RowId,
|
||||||
Kind = ObjectKind.BattleNpc,
|
Kind = ObjectKind.BattleNpc,
|
||||||
};
|
};
|
||||||
|
|
@ -164,6 +169,12 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
|
||||||
for (var i = 0; i < duplicates.Count; ++i)
|
for (var i = 0; i < duplicates.Count; ++i)
|
||||||
{
|
{
|
||||||
var current = duplicates[i];
|
var current = duplicates[i];
|
||||||
|
_hairColors[current.Customize[CustomizeIndex.HairColor].Value] = true;
|
||||||
|
_hairColors[current.Customize[CustomizeIndex.HighlightsColor].Value] = true;
|
||||||
|
_eyeColors[current.Customize[CustomizeIndex.EyeColorLeft].Value] = true;
|
||||||
|
_eyeColors[current.Customize[CustomizeIndex.EyeColorRight].Value] = true;
|
||||||
|
_facepaintColors[current.Customize[CustomizeIndex.FacePaintColor].Value] = true;
|
||||||
|
_tattooColors[current.Customize[CustomizeIndex.TattooColor].Value] = true;
|
||||||
for (var j = 0; j < i; ++j)
|
for (var j = 0; j < i; ++j)
|
||||||
{
|
{
|
||||||
if (current.DataEquals(duplicates[j]))
|
if (current.DataEquals(duplicates[j]))
|
||||||
|
|
@ -202,18 +213,36 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
|
||||||
/// <summary> Apply equipment from a NpcEquip row. </summary>
|
/// <summary> Apply equipment from a NpcEquip row. </summary>
|
||||||
private static void ApplyNpcEquip(ref NpcData data, NpcEquip row)
|
private static void ApplyNpcEquip(ref NpcData data, NpcEquip row)
|
||||||
{
|
{
|
||||||
data.Set(0, row.ModelHead | (row.DyeHead.Row << 24));
|
data.Set(0, row.ModelHead | (row.DyeHead.RowId << 24) | ((ulong)row.Dye2Head.RowId << 32));
|
||||||
data.Set(1, row.ModelBody | (row.DyeBody.Row << 24));
|
data.Set(1, row.ModelBody | (row.DyeBody.RowId << 24) | ((ulong)row.Dye2Body.RowId << 32));
|
||||||
data.Set(2, row.ModelHands | (row.DyeHands.Row << 24));
|
data.Set(2, row.ModelHands | (row.DyeHands.RowId << 24) | ((ulong)row.Dye2Hands.RowId << 32));
|
||||||
data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24));
|
data.Set(3, row.ModelLegs | (row.DyeLegs.RowId << 24) | ((ulong)row.Dye2Legs.RowId << 32));
|
||||||
data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24));
|
data.Set(4, row.ModelFeet | (row.DyeFeet.RowId << 24) | ((ulong)row.Dye2Feet.RowId << 32));
|
||||||
data.Set(5, row.ModelEars | (row.DyeEars.Row << 24));
|
data.Set(5, row.ModelEars | (row.DyeEars.RowId << 24) | ((ulong)row.Dye2Ears.RowId << 32));
|
||||||
data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24));
|
data.Set(6, row.ModelNeck | (row.DyeNeck.RowId << 24) | ((ulong)row.Dye2Neck.RowId << 32));
|
||||||
data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24));
|
data.Set(7, row.ModelWrists | (row.DyeWrists.RowId << 24) | ((ulong)row.Dye2Wrists.RowId << 32));
|
||||||
data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24));
|
data.Set(8, row.ModelRightRing | (row.DyeRightRing.RowId << 24) | ((ulong)row.Dye2RightRing.RowId << 32));
|
||||||
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24));
|
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.RowId << 24) | ((ulong)row.Dye2LeftRing.RowId << 32));
|
||||||
data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48));
|
data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.RowId << 48) | ((ulong)row.Dye2MainHand.RowId << 56));
|
||||||
data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48));
|
data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56));
|
||||||
|
data.VisorToggled = row.Visor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Apply equipment from a ENpcBase Row row. </summary>
|
||||||
|
private static void ApplyNpcEquip(ref NpcData data, ENpcBase row)
|
||||||
|
{
|
||||||
|
data.Set(0, row.ModelHead | (row.DyeHead.RowId << 24) | ((ulong)row.Dye2Head.RowId << 32));
|
||||||
|
data.Set(1, row.ModelBody | (row.DyeBody.RowId << 24) | ((ulong)row.Dye2Body.RowId << 32));
|
||||||
|
data.Set(2, row.ModelHands | (row.DyeHands.RowId << 24) | ((ulong)row.Dye2Hands.RowId << 32));
|
||||||
|
data.Set(3, row.ModelLegs | (row.DyeLegs.RowId << 24) | ((ulong)row.Dye2Legs.RowId << 32));
|
||||||
|
data.Set(4, row.ModelFeet | (row.DyeFeet.RowId << 24) | ((ulong)row.Dye2Feet.RowId << 32));
|
||||||
|
data.Set(5, row.ModelEars | (row.DyeEars.RowId << 24) | ((ulong)row.Dye2Ears.RowId << 32));
|
||||||
|
data.Set(6, row.ModelNeck | (row.DyeNeck.RowId << 24) | ((ulong)row.Dye2Neck.RowId << 32));
|
||||||
|
data.Set(7, row.ModelWrists | (row.DyeWrists.RowId << 24) | ((ulong)row.Dye2Wrists.RowId << 32));
|
||||||
|
data.Set(8, row.ModelRightRing | (row.DyeRightRing.RowId << 24) | ((ulong)row.Dye2RightRing.RowId << 32));
|
||||||
|
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.RowId << 24) | ((ulong)row.Dye2LeftRing.RowId << 32));
|
||||||
|
data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.RowId << 48) | ((ulong)row.Dye2MainHand.RowId << 56));
|
||||||
|
data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56));
|
||||||
data.VisorToggled = row.Visor;
|
data.VisorToggled = row.Visor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,11 +250,11 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
|
||||||
private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize)
|
private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize)
|
||||||
{
|
{
|
||||||
var customize = new CustomizeArray();
|
var customize = new CustomizeArray();
|
||||||
customize.SetByIndex(0, (CustomizeValue)(byte)bnpcCustomize.Race.Row);
|
customize.SetByIndex(0, (CustomizeValue)(byte)bnpcCustomize.Race.RowId);
|
||||||
customize.SetByIndex(1, (CustomizeValue)bnpcCustomize.Gender);
|
customize.SetByIndex(1, (CustomizeValue)bnpcCustomize.Gender);
|
||||||
customize.SetByIndex(2, (CustomizeValue)bnpcCustomize.BodyType);
|
customize.SetByIndex(2, (CustomizeValue)bnpcCustomize.BodyType);
|
||||||
customize.SetByIndex(3, (CustomizeValue)bnpcCustomize.Height);
|
customize.SetByIndex(3, (CustomizeValue)bnpcCustomize.Height);
|
||||||
customize.SetByIndex(4, (CustomizeValue)(byte)bnpcCustomize.Tribe.Row);
|
customize.SetByIndex(4, (CustomizeValue)(byte)bnpcCustomize.Tribe.RowId);
|
||||||
customize.SetByIndex(5, (CustomizeValue)bnpcCustomize.Face);
|
customize.SetByIndex(5, (CustomizeValue)bnpcCustomize.Face);
|
||||||
customize.SetByIndex(6, (CustomizeValue)bnpcCustomize.HairStyle);
|
customize.SetByIndex(6, (CustomizeValue)bnpcCustomize.HairStyle);
|
||||||
customize.SetByIndex(7, (CustomizeValue)bnpcCustomize.HairHighlight);
|
customize.SetByIndex(7, (CustomizeValue)bnpcCustomize.HairHighlight);
|
||||||
|
|
@ -259,15 +288,15 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
|
||||||
/// <summary> Obtain customizations from a ENpcBase row and check if the human is valid. </summary>
|
/// <summary> Obtain customizations from a ENpcBase row and check if the human is valid. </summary>
|
||||||
private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase)
|
private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase)
|
||||||
{
|
{
|
||||||
if (enpcBase.ModelChara.Value?.Type != 1)
|
if (enpcBase.ModelChara.ValueNullable?.Type != 1)
|
||||||
return (false, CustomizeArray.Default);
|
return (false, CustomizeArray.Default);
|
||||||
|
|
||||||
var customize = new CustomizeArray();
|
var customize = new CustomizeArray();
|
||||||
customize.SetByIndex(0, (CustomizeValue)(byte)enpcBase.Race.Row);
|
customize.SetByIndex(0, (CustomizeValue)(byte)enpcBase.Race.RowId);
|
||||||
customize.SetByIndex(1, (CustomizeValue)enpcBase.Gender);
|
customize.SetByIndex(1, (CustomizeValue)enpcBase.Gender);
|
||||||
customize.SetByIndex(2, (CustomizeValue)enpcBase.BodyType);
|
customize.SetByIndex(2, (CustomizeValue)enpcBase.BodyType);
|
||||||
customize.SetByIndex(3, (CustomizeValue)enpcBase.Height);
|
customize.SetByIndex(3, (CustomizeValue)enpcBase.Height);
|
||||||
customize.SetByIndex(4, (CustomizeValue)(byte)enpcBase.Tribe.Row);
|
customize.SetByIndex(4, (CustomizeValue)(byte)enpcBase.Tribe.RowId);
|
||||||
customize.SetByIndex(5, (CustomizeValue)enpcBase.Face);
|
customize.SetByIndex(5, (CustomizeValue)enpcBase.Face);
|
||||||
customize.SetByIndex(6, (CustomizeValue)enpcBase.HairStyle);
|
customize.SetByIndex(6, (CustomizeValue)enpcBase.HairStyle);
|
||||||
customize.SetByIndex(7, (CustomizeValue)enpcBase.HairHighlight);
|
customize.SetByIndex(7, (CustomizeValue)enpcBase.HairHighlight);
|
||||||
|
|
@ -298,6 +327,17 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
|
||||||
return (true, customize);
|
return (true, customize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Check decal files for existence. </summary>
|
||||||
|
private static void CheckFacepaintFiles(IDataManager data, BitArray facepaints)
|
||||||
|
{
|
||||||
|
for (byte i = 0; i < 128; ++i)
|
||||||
|
{
|
||||||
|
var path = GamePaths.Tex.FaceDecal(i);
|
||||||
|
if (data.FileExists(path))
|
||||||
|
facepaints[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerator<NpcData> GetEnumerator()
|
public IEnumerator<NpcData> GetEnumerator()
|
||||||
=> _data.GetEnumerator();
|
=> _data.GetEnumerator();
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ public unsafe struct NpcData
|
||||||
public CustomizeArray Customize;
|
public CustomizeArray Customize;
|
||||||
|
|
||||||
/// <summary> The equipment appearance of the NPC, 10 * CharacterArmor. </summary>
|
/// <summary> The equipment appearance of the NPC, 10 * CharacterArmor. </summary>
|
||||||
private fixed byte _equip[40];
|
private fixed byte _equip[CharacterArmor.Size * 10];
|
||||||
|
|
||||||
/// <summary> The mainhand weapon appearance of the NPC. </summary>
|
/// <summary> The mainhand weapon appearance of the NPC. </summary>
|
||||||
public CharacterWeapon Mainhand;
|
public CharacterWeapon Mainhand;
|
||||||
|
|
@ -54,36 +54,35 @@ public unsafe struct NpcData
|
||||||
{
|
{
|
||||||
sb.Append(span[i].Set.Id.ToString("D4"))
|
sb.Append(span[i].Set.Id.ToString("D4"))
|
||||||
.Append('-')
|
.Append('-')
|
||||||
.Append(span[i].Variant.Id.ToString("D3"))
|
.Append(span[i].Variant.Id.ToString("D3"));
|
||||||
.Append('-')
|
foreach (var stain in span[i].Stains)
|
||||||
.Append(span[i].Stain.Id.ToString("D3"))
|
sb.Append('-').Append(stain.Id.ToString("D3"));
|
||||||
.Append(", ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.Append(Mainhand.Skeleton.Id.ToString("D4"))
|
sb.Append(Mainhand.Skeleton.Id.ToString("D4"))
|
||||||
.Append('-')
|
.Append('-')
|
||||||
.Append(Mainhand.Weapon.Id.ToString("D4"))
|
.Append(Mainhand.Weapon.Id.ToString("D4"))
|
||||||
.Append('-')
|
.Append('-')
|
||||||
.Append(Mainhand.Variant.Id.ToString("D3"))
|
.Append(Mainhand.Variant.Id.ToString("D3"));
|
||||||
.Append('-')
|
foreach (var stain in Mainhand.Stains)
|
||||||
.Append(Mainhand.Stain.Id.ToString("D4"))
|
sb.Append('-').Append(stain.Id.ToString("D3"));
|
||||||
.Append(", ")
|
sb.Append(", ")
|
||||||
.Append(Offhand.Skeleton.Id.ToString("D4"))
|
.Append(Offhand.Skeleton.Id.ToString("D4"))
|
||||||
.Append('-')
|
.Append('-')
|
||||||
.Append(Offhand.Weapon.Id.ToString("D4"))
|
.Append(Offhand.Weapon.Id.ToString("D4"))
|
||||||
.Append('-')
|
.Append('-')
|
||||||
.Append(Offhand.Variant.Id.ToString("D3"))
|
.Append(Offhand.Variant.Id.ToString("D3"));
|
||||||
.Append('-')
|
foreach (var stain in Mainhand.Stains)
|
||||||
.Append(Offhand.Stain.Id.ToString("D3"));
|
sb.Append('-').Append(stain.Id.ToString("D3"));
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Set an equipment piece to a given value. </summary>
|
/// <summary> Set an equipment piece to a given value. </summary>
|
||||||
internal void Set(int idx, uint value)
|
internal void Set(int idx, ulong value)
|
||||||
{
|
{
|
||||||
fixed (byte* ptr = _equip)
|
fixed (byte* ptr = _equip)
|
||||||
{
|
{
|
||||||
((uint*)ptr)[idx] = value;
|
((ulong*)ptr)[idx] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Api;
|
using Glamourer.Api;
|
||||||
|
using Glamourer.Automation;
|
||||||
|
using Glamourer.Designs;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
|
|
@ -7,6 +9,7 @@ using Glamourer.State;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
|
||||||
namespace Glamourer;
|
namespace Glamourer;
|
||||||
|
|
||||||
|
|
@ -23,15 +26,17 @@ public class Glamourer : IDalamudPlugin
|
||||||
|
|
||||||
public static readonly Logger Log = new();
|
public static readonly Logger Log = new();
|
||||||
public static MessageService Messager { get; private set; } = null!;
|
public static MessageService Messager { get; private set; } = null!;
|
||||||
|
public static DynamisIpc Dynamis { get; private set; } = null!;
|
||||||
|
|
||||||
private readonly ServiceManager _services;
|
private readonly ServiceManager _services;
|
||||||
|
|
||||||
public Glamourer(DalamudPluginInterface pluginInterface)
|
public Glamourer(IDalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_services = ServiceManagerA.CreateProvider(pluginInterface, Log);
|
_services = StaticServiceManager.CreateProvider(pluginInterface, Log, this);
|
||||||
Messager = _services.GetService<MessageService>();
|
Messager = _services.GetService<MessageService>();
|
||||||
|
Dynamis = _services.GetService<DynamisIpc>();
|
||||||
_services.EnsureRequiredServices();
|
_services.EnsureRequiredServices();
|
||||||
|
|
||||||
_services.GetService<VisorService>();
|
_services.GetService<VisorService>();
|
||||||
|
|
@ -40,7 +45,7 @@ public class Glamourer : IDalamudPlugin
|
||||||
_services.GetService<StateListener>(); // Initialize State Listener.
|
_services.GetService<StateListener>(); // Initialize State Listener.
|
||||||
_services.GetService<GlamourerWindowSystem>(); // initialize ui.
|
_services.GetService<GlamourerWindowSystem>(); // initialize ui.
|
||||||
_services.GetService<CommandService>(); // initialize commands.
|
_services.GetService<CommandService>(); // initialize commands.
|
||||||
_services.GetService<GlamourerIpc>(); // initialize IPC.
|
_services.GetService<IpcProviders>(); // initialize IPC.
|
||||||
Log.Information($"Glamourer v{Version} loaded successfully.");
|
Log.Information($"Glamourer v{Version} loaded successfully.");
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|
@ -50,6 +55,96 @@ public class Glamourer : IDalamudPlugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GatherSupportInformation()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder(10240);
|
||||||
|
var config = _services.GetService<Configuration>();
|
||||||
|
sb.AppendLine("**Settings**");
|
||||||
|
sb.Append($"> **`Plugin Version: `** {Version}\n");
|
||||||
|
sb.Append($"> **`Commit Hash: `** {CommitHash}\n");
|
||||||
|
sb.Append($"> **`Enable Auto Designs: `** {config.EnableAutoDesigns}\n");
|
||||||
|
sb.Append($"> **`Gear Protection: `** {config.UseRestrictedGearProtection}\n");
|
||||||
|
sb.Append($"> **`Item Restriction: `** {config.UnlockedItemMode}\n");
|
||||||
|
sb.Append($"> **`Keep Manual Changes: `** {config.RespectManualOnAutomationUpdate}\n");
|
||||||
|
sb.Append($"> **`Auto-Reload Gear: `** {config.AutoRedrawEquipOnChanges}\n");
|
||||||
|
sb.Append($"> **`Revert on Zone Change:`** {config.RevertManualChangesOnZoneChange}\n");
|
||||||
|
sb.Append($"> **`Festival Easter-Eggs: `** {config.DisableFestivals}\n");
|
||||||
|
sb.Append($"> **`Apply Entire Weapon: `** {config.ChangeEntireItem}\n");
|
||||||
|
sb.Append($"> **`Apply Associated Mods:`** {config.AlwaysApplyAssociatedMods}\n");
|
||||||
|
sb.Append($"> **`Attach to PCP: `** {config.AttachToPcp}\n");
|
||||||
|
sb.Append($"> **`Hidden Panels: `** {config.HideDesignPanel}\n");
|
||||||
|
sb.Append($"> **`Show QDB: `** {config.Ephemeral.ShowDesignQuickBar}\n");
|
||||||
|
sb.Append($"> **`QDB Hotkey: `** {config.ToggleQuickDesignBar}\n");
|
||||||
|
sb.Append($"> **`Smaller Equip Display:`** {config.SmallEquip}\n");
|
||||||
|
sb.Append($"> **`Debug Mode: `** {config.DebugMode}\n");
|
||||||
|
sb.Append($"> **`Cheat Codes: `** {(ulong)_services.GetService<CodeService>().AllEnabled:X8}\n");
|
||||||
|
sb.AppendLine("**Plugins**");
|
||||||
|
GatherRelevantPlugins(sb);
|
||||||
|
var designManager = _services.GetService<DesignManager>();
|
||||||
|
var autoManager = _services.GetService<AutoDesignManager>();
|
||||||
|
var stateManager = _services.GetService<StateManager>();
|
||||||
|
var objectManager = _services.GetService<ActorObjectManager>();
|
||||||
|
var currentPlayer = objectManager.PlayerData.Identifier;
|
||||||
|
var states = stateManager.Where(kvp => objectManager.ContainsKey(kvp.Key)).ToList();
|
||||||
|
|
||||||
|
sb.AppendLine("**Statistics**");
|
||||||
|
sb.Append($"> **`Current Player: `** {(currentPlayer.IsValid ? currentPlayer.Incognito(null) : "None")}\n");
|
||||||
|
sb.Append($"> **`Saved Designs: `** {designManager.Designs.Count}\n");
|
||||||
|
sb.Append($"> **`Automation Sets: `** {autoManager.Count} ({autoManager.Count(set => set.Enabled)} Enabled)\n");
|
||||||
|
sb.Append(
|
||||||
|
$"> **`Actor States: `** {stateManager.Count} ({states.Count} Visible, {stateManager.Values.Count(s => s.IsLocked)} Locked)\n");
|
||||||
|
|
||||||
|
var enabledAutomation = autoManager.Where(s => s.Enabled).ToList();
|
||||||
|
if (enabledAutomation.Count > 0)
|
||||||
|
{
|
||||||
|
sb.AppendLine("**Enabled Automation**");
|
||||||
|
foreach (var set in enabledAutomation)
|
||||||
|
{
|
||||||
|
sb.Append(
|
||||||
|
$"> **`{set.Identifiers.First().Incognito(null) + ':',-24}`** {(set.Name.Length >= 2 ? $"{set.Name.AsSpan(0, 2)}..." : set.Name)} ({set.Designs.Count} {(set.Designs.Count == 1 ? "Design" : "Designs")})\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (states.Count > 0)
|
||||||
|
{
|
||||||
|
sb.AppendLine("**State**");
|
||||||
|
foreach (var (ident, state) in states)
|
||||||
|
{
|
||||||
|
var sources = Enum.GetValues<StateSource>().Select(s => (0, s)).ToArray();
|
||||||
|
foreach (var source in StateIndex.All.Select(s => state.Sources[s]))
|
||||||
|
++sources[(int)source].Item1;
|
||||||
|
foreach (var material in state.Materials.Values)
|
||||||
|
++sources[(int)material.Value.Source].Item1;
|
||||||
|
var sourcesString = string.Join(", ", sources.Where(s => s.Item1 > 0).Select(s => $"{s.s} {s.Item1}"));
|
||||||
|
sb.Append(
|
||||||
|
$"> **`{ident.Incognito(null) + ':',-24}`** {(state.IsLocked ? "Locked, " : string.Empty)}Job {state.LastJob.Id}, Zone {state.LastTerritory}, Materials {state.Materials.Values.Count}, {sourcesString}\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void GatherRelevantPlugins(StringBuilder sb)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<string> relevantPlugins =
|
||||||
|
[
|
||||||
|
"Penumbra", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge",
|
||||||
|
"LoporritSync", "GagSpeak", "ProjectGagSpeak", "RoleplayingVoiceDalamud",
|
||||||
|
];
|
||||||
|
var plugins = _services.GetService<IDalamudPluginInterface>().InstalledPlugins
|
||||||
|
.GroupBy(p => p.InternalName)
|
||||||
|
.ToDictionary(g => g.Key, g =>
|
||||||
|
{
|
||||||
|
var item = g.OrderByDescending(p => p.IsLoaded).ThenByDescending(p => p.Version).First();
|
||||||
|
return (item.IsLoaded, item.Version, item.Name);
|
||||||
|
});
|
||||||
|
foreach (var plugin in relevantPlugins)
|
||||||
|
{
|
||||||
|
if (plugins.TryGetValue(plugin, out var data))
|
||||||
|
sb.Append($"> **`{data.Name + ':',-22}`** {data.Version}{(data.IsLoaded ? string.Empty : " (Disabled)")}\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
=> _services?.Dispose();
|
=> _services?.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,33 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Dalamud.NET.Sdk/14.0.1">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0-windows</TargetFramework>
|
|
||||||
<LangVersion>preview</LangVersion>
|
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
|
||||||
<RootNamespace>Glamourer</RootNamespace>
|
<RootNamespace>Glamourer</RootNamespace>
|
||||||
<AssemblyName>Glamourer</AssemblyName>
|
<AssemblyName>Glamourer</AssemblyName>
|
||||||
<FileVersion>9.0.0.1</FileVersion>
|
<FileVersion>9.0.0.1</FileVersion>
|
||||||
<AssemblyVersion>9.0.0.1</AssemblyVersion>
|
<AssemblyVersion>9.0.0.1</AssemblyVersion>
|
||||||
<Company>SoftOtter</Company>
|
|
||||||
<Product>Glamourer</Product>
|
<Product>Glamourer</Product>
|
||||||
<Copyright>Copyright © 2023</Copyright>
|
<Copyright>Copyright © 2025</Copyright>
|
||||||
<Deterministic>true</Deterministic>
|
|
||||||
<OutputType>Library</OutputType>
|
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
|
||||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
|
||||||
<DebugSymbols>true</DebugSymbols>
|
|
||||||
<DebugType>full</DebugType>
|
|
||||||
<Optimize>false</Optimize>
|
|
||||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
|
||||||
<DebugType>pdbonly</DebugType>
|
|
||||||
<Optimize>true</Optimize>
|
|
||||||
<DefineConstants>TRACE</DefineConstants>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="LegacyTattoo.raw" />
|
<None Remove="LegacyTattoo.raw" />
|
||||||
|
|
||||||
|
<None Include="Glamourer.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="LegacyTattoo.raw" />
|
<EmbeddedResource Include="LegacyTattoo.raw" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Dalamud">
|
|
||||||
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="FFXIVClientStructs">
|
|
||||||
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="ImGui.NET">
|
|
||||||
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="ImGuiScene">
|
|
||||||
<HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Lumina">
|
|
||||||
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Lumina.Excel">
|
|
||||||
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Newtonsoft.Json">
|
|
||||||
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Glamourer.Api\Glamourer.Api.csproj" />
|
||||||
<ProjectReference Include="..\OtterGui\OtterGui.csproj" />
|
<ProjectReference Include="..\OtterGui\OtterGui.csproj" />
|
||||||
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
|
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
|
||||||
<ProjectReference Include="..\Penumbra.String\Penumbra.string.csproj" />
|
<ProjectReference Include="..\Penumbra.String\Penumbra.String.csproj" />
|
||||||
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
|
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Dalamud.ContextMenu" Version="1.3.1" />
|
|
||||||
<PackageReference Include="Vortice.Direct3D11" Version="3.4.2-beta" />
|
<PackageReference Include="Vortice.Direct3D11" Version="3.4.2-beta" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
@ -117,14 +56,4 @@
|
||||||
<InformationalVersion>$(GitCommitHash)</InformationalVersion>
|
<InformationalVersion>$(GitCommitHash)</InformationalVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="Glamourer.json">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
|
||||||
<Exec Command="if $(Configuration) == Release powershell Copy-Item -Force $(TargetDir)$(SolutionName).json -Destination $(SolutionDir)" />
|
|
||||||
</Target>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
"AssemblyVersion": "9.0.0.1",
|
"AssemblyVersion": "9.0.0.1",
|
||||||
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"DalamudApiLevel": 9,
|
"DalamudApiLevel": 14,
|
||||||
"ImageUrls": null,
|
"ImageUrls": null,
|
||||||
"IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png"
|
"IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png"
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
|
||||||
namespace Glamourer.Gui;
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
|
@ -29,6 +29,10 @@ public enum ColorId
|
||||||
TriStateNeutral,
|
TriStateNeutral,
|
||||||
BattleNpc,
|
BattleNpc,
|
||||||
EventNpc,
|
EventNpc,
|
||||||
|
ModdedItemMarker,
|
||||||
|
ContainsItemsEnabled,
|
||||||
|
ContainsItemsDisabled,
|
||||||
|
AdvancedDyeActive,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Colors
|
public static class Colors
|
||||||
|
|
@ -39,32 +43,36 @@ public static class Colors
|
||||||
=> color switch
|
=> color switch
|
||||||
{
|
{
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
ColorId.NormalDesign => (0xFFFFFFFF, "Normal Design", "A design with no specific traits." ),
|
ColorId.NormalDesign => (0xFFFFFFFF, "Normal Design", "A design with no specific traits." ),
|
||||||
ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ),
|
ColorId.CustomizationDesign => (0xFFC000C0, "Customization Design", "A design that only changes customizations on a character." ),
|
||||||
ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that does not change equipment or customizations on a character." ),
|
ColorId.StateDesign => (0xFF00C0C0, "State Design", "A design that does not change equipment or customizations on a character." ),
|
||||||
ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ),
|
ColorId.EquipmentDesign => (0xFF00C000, "Equipment Design", "A design that only changes equipment on a character." ),
|
||||||
ColorId.ActorAvailable => (0xFF18C018, "Actor Available", "The header in the Actor tab panel if the currently selected actor exists in the game world at least once." ),
|
ColorId.ActorAvailable => (0xFF18C018, "Actor Available", "The header in the Actor tab panel if the currently selected actor exists in the game world at least once." ),
|
||||||
ColorId.ActorUnavailable => (0xFF1818C0, "Actor Unavailable", "The Header in the Actor tab panel if the currently selected actor does not exist in the game world." ),
|
ColorId.ActorUnavailable => (0xFF1818C0, "Actor Unavailable", "The Header in the Actor tab panel if the currently selected actor does not exist in the game world." ),
|
||||||
ColorId.FolderExpanded => (0xFFFFF0C0, "Expanded Design Folder", "A design folder that is currently expanded." ),
|
ColorId.FolderExpanded => (0xFFFFF0C0, "Expanded Design Folder", "A design folder that is currently expanded." ),
|
||||||
ColorId.FolderCollapsed => (0xFFFFF0C0, "Collapsed Design Folder", "A design folder that is currently collapsed." ),
|
ColorId.FolderCollapsed => (0xFFFFF0C0, "Collapsed Design Folder", "A design folder that is currently collapsed." ),
|
||||||
ColorId.FolderLine => (0xFFFFF0C0, "Expanded Design Folder Line", "The line signifying which descendants belong to an expanded design folder." ),
|
ColorId.FolderLine => (0xFFFFF0C0, "Expanded Design Folder Line", "The line signifying which descendants belong to an expanded design folder." ),
|
||||||
ColorId.EnabledAutoSet => (0xFFA0F0A0, "Enabled Automation Set", "An automation set that is currently enabled. Only one set can be enabled for each identifier at once." ),
|
ColorId.EnabledAutoSet => (0xFFA0F0A0, "Enabled Automation Set", "An automation set that is currently enabled. Only one set can be enabled for each identifier at once." ),
|
||||||
ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ),
|
ColorId.DisabledAutoSet => (0xFF808080, "Disabled Automation Set", "An automation set that is currently disabled." ),
|
||||||
ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ),
|
ColorId.AutomationActorAvailable => (0xFFFFFFFF, "Automation Actor Available", "A character associated with the given automated design set is currently visible." ),
|
||||||
ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ),
|
ColorId.AutomationActorUnavailable => (0xFF808080, "Automation Actor Unavailable", "No character associated with the given automated design set is currently visible." ),
|
||||||
ColorId.HeaderButtons => (0xFFFFF0C0, "Header Buttons", "The text and border color of buttons in the header, like the Incognito toggle." ),
|
ColorId.HeaderButtons => (0xFFFFF0C0, "Header Buttons", "The text and border color of buttons in the header, like the Incognito toggle." ),
|
||||||
ColorId.FavoriteStarOn => (0xFF40D0D0, "Favored Item", "The color of the star for favored items and of the border in the unlock overview tab." ),
|
ColorId.FavoriteStarOn => (0xFF40D0D0, "Favored Item", "The color of the star for favored items and of the border in the unlock overview tab." ),
|
||||||
ColorId.FavoriteStarHovered => (0xFFD040D0, "Favorite Star Hovered", "The color of the star for favored items when it is hovered." ),
|
ColorId.FavoriteStarHovered => (0xFFD040D0, "Favorite Star Hovered", "The color of the star for favored items when it is hovered." ),
|
||||||
ColorId.FavoriteStarOff => (0x20808080, "Favorite Star Outline", "The color of the star for items that are not favored when it is not hovered." ),
|
ColorId.FavoriteStarOff => (0x20808080, "Favorite Star Outline", "The color of the star for items that are not favored when it is not hovered." ),
|
||||||
ColorId.QuickDesignButton => (0x900A0A0A, "Quick Design Bar Button Background", "The color of button frames in the quick design bar." ),
|
ColorId.QuickDesignButton => (0x900A0A0A, "Quick Design Bar Button Background", "The color of button frames in the quick design bar." ),
|
||||||
ColorId.QuickDesignFrame => (0x90383838, "Quick Design Bar Combo Background", "The color of the combo background in the quick design bar." ),
|
ColorId.QuickDesignFrame => (0x90383838, "Quick Design Bar Combo Background", "The color of the combo background in the quick design bar." ),
|
||||||
ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ),
|
ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ),
|
||||||
ColorId.TriStateCheck => (0xFF00D000, "Checkmark in Tri-State Checkboxes", "The color of the checkmark indicating positive change in tri-state checkboxes." ),
|
ColorId.TriStateCheck => (0xFF00D000, "Checkmark in Tri-State Checkboxes", "The color of the checkmark indicating positive change in tri-state checkboxes." ),
|
||||||
ColorId.TriStateCross => (0xFF0000D0, "Cross in Tri-State Checkboxes", "The color of the cross indicating negative change in tri-state checkboxes." ),
|
ColorId.TriStateCross => (0xFF0000D0, "Cross in Tri-State Checkboxes", "The color of the cross indicating negative change in tri-state checkboxes." ),
|
||||||
ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes." ),
|
ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes." ),
|
||||||
ColorId.BattleNpc => (0xFFFFFFFF, "Battle NPC in NPC Tab", "The color of the names of battle NPCs in the NPC tab that do not have a more specific color assigned." ),
|
ColorId.BattleNpc => (0xFFFFFFFF, "Battle NPC in NPC Tab", "The color of the names of battle NPCs in the NPC tab that do not have a more specific color assigned." ),
|
||||||
ColorId.EventNpc => (0xFFFFFFFF, "Event NPC in NPC Tab", "The color of the names of event NPCs in the NPC tab that do not have a more specific color assigned." ),
|
ColorId.EventNpc => (0xFFFFFFFF, "Event NPC in NPC Tab", "The color of the names of event NPCs in the NPC tab that do not have a more specific color assigned." ),
|
||||||
_ => (0x00000000, string.Empty, string.Empty ),
|
ColorId.ModdedItemMarker => (0xFFFF20FF, "Modded Item Marker", "The color of dot in the unlocks overview tab signaling that the item is modded in the currently selected Penumbra collection." ),
|
||||||
|
ColorId.ContainsItemsEnabled => (0xFFA0F0A0, "Enabled Mod Contains Design Items", "The color of enabled mods in the associated mod dropdown menu when they contain items used in this design." ),
|
||||||
|
ColorId.ContainsItemsDisabled => (0x80A0F0A0, "Disabled Mod Contains Design Items", "The color of disabled mods in the associated mod dropdown menu when they contain items used in this design." ),
|
||||||
|
ColorId.AdvancedDyeActive => (0xFF58DDFF, "Advanced Dyes Active", "The highlight color for the advanced dye button and marker if any advanced dyes are active for this slot." ),
|
||||||
|
_ => (0x00000000, string.Empty, string.Empty ),
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,83 @@
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Text;
|
||||||
|
using OtterGui.Text.EndObjects;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Customization;
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
public partial class CustomizationDrawer
|
public partial class CustomizationDrawer
|
||||||
{
|
{
|
||||||
private const string ColorPickerPopupName = "ColorPicker";
|
private const string ColorPickerPopupName = "ColorPicker";
|
||||||
|
private CustomizeValue _draggedColorValue;
|
||||||
|
private CustomizeIndex _draggedColorType;
|
||||||
|
|
||||||
|
|
||||||
|
private void DrawDragDropSource(CustomizeIndex index, CustomizeData custom)
|
||||||
|
{
|
||||||
|
using var dragDropSource = ImUtf8.DragDropSource();
|
||||||
|
if (!dragDropSource)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!DragDropSource.SetPayload("##colorDragDrop"u8))
|
||||||
|
_draggedColorValue = _customize[index];
|
||||||
|
ImUtf8.Text(
|
||||||
|
$"Dragging {(custom.Color == 0 ? $"{_currentOption} (NPC)" : _currentOption)} #{_draggedColorValue.Value}...");
|
||||||
|
_draggedColorType = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDragDropTarget(CustomizeIndex index)
|
||||||
|
{
|
||||||
|
using var dragDropTarget = ImUtf8.DragDropTarget();
|
||||||
|
if (!dragDropTarget.Success || !dragDropTarget.IsDropping("##colorDragDrop"u8))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var idx = _set.DataByValue(_draggedColorType, _draggedColorValue, out var draggedData, _customize.Face);
|
||||||
|
var bestMatch = _draggedColorValue;
|
||||||
|
if (draggedData.HasValue)
|
||||||
|
{
|
||||||
|
var draggedColor = draggedData.Value.Color;
|
||||||
|
var targetData = _set.Data(index, idx);
|
||||||
|
if (targetData.Color != draggedColor)
|
||||||
|
{
|
||||||
|
var bestDiff = Diff(targetData.Color, draggedColor);
|
||||||
|
var count = _set.Count(index);
|
||||||
|
for (var i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
targetData = _set.Data(index, i);
|
||||||
|
if (targetData.Color == draggedColor)
|
||||||
|
{
|
||||||
|
UpdateValue(_draggedColorValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var diff = Diff(targetData.Color, draggedColor);
|
||||||
|
if (diff >= bestDiff)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bestDiff = diff;
|
||||||
|
bestMatch = (CustomizeValue)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateValue(bestMatch);
|
||||||
|
return;
|
||||||
|
|
||||||
|
static uint Diff(uint color1, uint color2)
|
||||||
|
{
|
||||||
|
var r = (color1 & 0xFF) - (color2 & 0xFF);
|
||||||
|
var g = ((color1 >> 8) & 0xFF) - ((color2 >> 8) & 0xFF);
|
||||||
|
var b = ((color1 >> 16) & 0xFF) - ((color2 >> 16) & 0xFF);
|
||||||
|
return 30 * r * r + 59 * g * g + 11 * b * b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawColorPicker(CustomizeIndex index)
|
private void DrawColorPicker(CustomizeIndex index)
|
||||||
{
|
{
|
||||||
|
|
@ -21,13 +88,18 @@ public partial class CustomizationDrawer
|
||||||
|
|
||||||
using (_ = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0))
|
using (_ = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0))
|
||||||
{
|
{
|
||||||
if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
|
if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.NoDragDrop, _framedIconSize))
|
||||||
|
{
|
||||||
ImGui.OpenPopup(ColorPickerPopupName);
|
ImGui.OpenPopup(ColorPickerPopupName);
|
||||||
else if (current >= 0 && CaptureMouseWheel(ref current, 0, _currentCount))
|
}
|
||||||
|
else if (current >= 0 && !_locked && CaptureMouseWheel(ref current, 0, _currentCount))
|
||||||
{
|
{
|
||||||
var data = _set.Data(_currentIndex, current, _customize.Face);
|
var data = _set.Data(_currentIndex, current, _customize.Face);
|
||||||
UpdateValue(data.Value);
|
UpdateValue(data.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DrawDragDropSource(index, custom);
|
||||||
|
DrawDragDropTarget(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
var npc = false;
|
var npc = false;
|
||||||
|
|
@ -70,7 +142,7 @@ public partial class CustomizationDrawer
|
||||||
for (var i = 0; i < _currentCount; ++i)
|
for (var i = 0; i < _currentCount; ++i)
|
||||||
{
|
{
|
||||||
var custom = _set.Data(_currentIndex, i, _customize[CustomizeIndex.Face]);
|
var custom = _set.Data(_currentIndex, i, _customize[CustomizeIndex.Face]);
|
||||||
if (ImGui.ColorButton(custom.Value.ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
|
if (ImGui.ColorButton(custom.Value.ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)) && !_locked)
|
||||||
{
|
{
|
||||||
UpdateValue(custom.Value);
|
UpdateValue(custom.Value);
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -34,14 +34,13 @@ public partial class CustomizationDrawer
|
||||||
|
|
||||||
private void DrawGenderSelector()
|
private void DrawGenderSelector()
|
||||||
{
|
{
|
||||||
using (var disabled = ImRaii.Disabled(_locked || _lockedRedraw))
|
using (ImRaii.Disabled(_locked || _lockedRedraw))
|
||||||
{
|
{
|
||||||
var icon = _customize.Gender switch
|
var icon = _customize.Gender switch
|
||||||
{
|
{
|
||||||
Gender.Male when _customize.Race is Race.Hrothgar => FontAwesomeIcon.MarsDouble,
|
Gender.Male => FontAwesomeIcon.Mars,
|
||||||
Gender.Male => FontAwesomeIcon.Mars,
|
Gender.Female => FontAwesomeIcon.Venus,
|
||||||
Gender.Female => FontAwesomeIcon.Venus,
|
_ => FontAwesomeIcon.Question,
|
||||||
_ => FontAwesomeIcon.Question,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty,
|
if (ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty,
|
||||||
|
|
@ -56,7 +55,7 @@ public partial class CustomizationDrawer
|
||||||
|
|
||||||
private void DrawRaceCombo()
|
private void DrawRaceCombo()
|
||||||
{
|
{
|
||||||
using (var disabled = ImRaii.Disabled(_locked || _lockedRedraw))
|
using (ImRaii.Disabled(_locked || _lockedRedraw))
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth(_raceSelectorWidth);
|
ImGui.SetNextItemWidth(_raceSelectorWidth);
|
||||||
using (var combo = ImRaii.Combo("##subRaceCombo", _service.ClanName(_customize.Clan, _customize.Gender)))
|
using (var combo = ImRaii.Combo("##subRaceCombo", _service.ClanName(_customize.Clan, _customize.Gender)))
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
using Glamourer.GameData;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
|
using Glamourer.GameData;
|
||||||
using Glamourer.Unlocks;
|
using Glamourer.Unlocks;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
using OtterGui.Extensions;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
@ -29,10 +31,11 @@ public partial class CustomizationDrawer
|
||||||
npc = true;
|
npc = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var icon = _service.Manager.GetIcon(custom!.Value.IconId);
|
var icon = _service.Manager.GetIcon(custom!.Value.IconId);
|
||||||
|
var hasIcon = icon.TryGetWrap(out var wrap, out _);
|
||||||
using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw))
|
using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw))
|
||||||
{
|
{
|
||||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
if (ImGui.ImageButton(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, _iconSize))
|
||||||
{
|
{
|
||||||
ImGui.OpenPopup(IconSelectorPopup);
|
ImGui.OpenPopup(IconSelectorPopup);
|
||||||
}
|
}
|
||||||
|
|
@ -43,7 +46,8 @@ public partial class CustomizationDrawer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
if (hasIcon)
|
||||||
|
ImGuiUtil.HoverIconTooltip(wrap!, _iconSize);
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using (_ = ImRaii.Group())
|
using (_ = ImRaii.Group())
|
||||||
|
|
@ -83,8 +87,9 @@ public partial class CustomizationDrawer
|
||||||
using var frameColor = current == i
|
using var frameColor = current == i
|
||||||
? ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed)
|
? ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed)
|
||||||
: ImRaii.PushColor(ImGuiCol.Button, ColorId.FavoriteStarOn.Value(), isFavorite);
|
: ImRaii.PushColor(ImGuiCol.Button, ColorId.FavoriteStarOn.Value(), isFavorite);
|
||||||
|
var hasIcon = icon.TryGetWrap(out var wrap, out var _);
|
||||||
|
|
||||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
if (ImGui.ImageButton(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, _iconSize))
|
||||||
{
|
{
|
||||||
UpdateValue(custom.Value);
|
UpdateValue(custom.Value);
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
|
|
@ -96,8 +101,9 @@ public partial class CustomizationDrawer
|
||||||
else
|
else
|
||||||
_favorites.TryAdd(_set.Gender, _set.Clan, _currentIndex, custom.Value);
|
_favorites.TryAdd(_set.Gender, _set.Clan, _currentIndex, custom.Value);
|
||||||
|
|
||||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize,
|
if (hasIcon)
|
||||||
FavoriteManager.TypeAllowed(_currentIndex) ? "Right-Click to toggle favorite." : string.Empty);
|
ImGuiUtil.HoverIconTooltip(wrap!, _iconSize,
|
||||||
|
FavoriteManager.TypeAllowed(_currentIndex) ? "Right-Click to toggle favorite." : string.Empty);
|
||||||
|
|
||||||
var text = custom.Value.ToString();
|
var text = custom.Value.ToString();
|
||||||
var textWidth = ImGui.CalcTextSize(text).X;
|
var textWidth = ImGui.CalcTextSize(text).X;
|
||||||
|
|
@ -188,25 +194,36 @@ public partial class CustomizationDrawer
|
||||||
|
|
||||||
private void DrawMultiIcons()
|
private void DrawMultiIcons()
|
||||||
{
|
{
|
||||||
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
|
var options = _set.Order[MenuType.IconCheckmark];
|
||||||
using var group = ImRaii.Group();
|
using var group = ImRaii.Group();
|
||||||
var face = _set.DataByValue(CustomizeIndex.Face, _customize.Face, out _, _customize.Face) < 0 ? _set.Faces[0].Value : _customize.Face;
|
var face = _set.DataByValue(CustomizeIndex.Face, _customize.Face, out _, _customize.Face) < 0 ? _set.Faces[0].Value : _customize.Face;
|
||||||
foreach (var (featureIdx, idx) in options.WithIndex())
|
foreach (var (featureIdx, idx) in options.WithIndex())
|
||||||
{
|
{
|
||||||
using var id = SetId(featureIdx);
|
using var id = SetId(featureIdx);
|
||||||
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
|
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
|
||||||
var feature = _set.Data(featureIdx, 0, face);
|
var feature = _set.Data(featureIdx, 0, face);
|
||||||
var icon = featureIdx == CustomizeIndex.LegacyTattoo
|
bool hasIcon;
|
||||||
? _legacyTattoo ?? _service.Manager.GetIcon(feature.IconId)
|
IDalamudTextureWrap? wrap;
|
||||||
: _service.Manager.GetIcon(feature.IconId);
|
var icon = _service.Manager.GetIcon(feature.IconId);
|
||||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
|
if (featureIdx is CustomizeIndex.LegacyTattoo)
|
||||||
Vector4.Zero, enabled ? Vector4.One : _redTint))
|
{
|
||||||
|
wrap = _legacyTattoo;
|
||||||
|
hasIcon = wrap != null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasIcon = icon.TryGetWrap(out wrap, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.ImageButton(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, _iconSize, Vector2.Zero, Vector2.One,
|
||||||
|
(int)ImGui.GetStyle().FramePadding.X, Vector4.Zero, enabled ? Vector4.One : _redTint))
|
||||||
{
|
{
|
||||||
_customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max);
|
_customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max);
|
||||||
Changed |= _currentFlag;
|
Changed |= _currentFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
if (hasIcon)
|
||||||
|
ImGuiUtil.HoverIconTooltip(wrap!, _iconSize);
|
||||||
if (idx % 4 != 3)
|
if (idx % 4 != 3)
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGuiInternal;
|
using OtterGuiInternal;
|
||||||
|
|
@ -29,6 +29,29 @@ public partial class CustomizationDrawer
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted(_currentOption);
|
ImGui.TextUnformatted(_currentOption);
|
||||||
|
if (_currentIndex is CustomizeIndex.Height)
|
||||||
|
DrawHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawHeight()
|
||||||
|
{
|
||||||
|
if (_config.HeightDisplayType is HeightDisplayType.None)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var height = _heightService.Height(_customize);
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
var heightString = _config.HeightDisplayType switch
|
||||||
|
{
|
||||||
|
HeightDisplayType.Centimetre => FormattableString.Invariant($"({height * 100:F1} cm)"),
|
||||||
|
HeightDisplayType.Metre => FormattableString.Invariant($"({height:F2} m)"),
|
||||||
|
HeightDisplayType.Wrong => FormattableString.Invariant($"({height * 100 / 2.539:F1} in)"),
|
||||||
|
HeightDisplayType.WrongFoot => $"({(int)(height * 100 / 2.539 / 12)}'{(int)(height * 100 / 2.539) % 12}'')",
|
||||||
|
HeightDisplayType.Corgi => FormattableString.Invariant($"({height * 100 / 40.0:F1} Corgis)"),
|
||||||
|
HeightDisplayType.OlympicPool => FormattableString.Invariant($"({height / 3.0:F3} Pools)"),
|
||||||
|
_ => FormattableString.Invariant($"({height})"),
|
||||||
|
};
|
||||||
|
ImGui.TextUnformatted(heightString);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawPercentageSlider()
|
private void DrawPercentageSlider()
|
||||||
|
|
@ -272,7 +295,7 @@ public partial class CustomizationDrawer
|
||||||
|
|
||||||
private void ApplyCheckbox(CustomizeIndex index)
|
private void ApplyCheckbox(CustomizeIndex index)
|
||||||
{
|
{
|
||||||
SetId(index);
|
using var id = SetId(index);
|
||||||
if (UiHelpers.DrawCheckbox("##apply", $"Apply the {_currentOption} customization in this design.", _currentApply, out _, _locked))
|
if (UiHelpers.DrawCheckbox("##apply", $"Apply the {_currentOption} customization in this design.", _currentApply, out _, _locked))
|
||||||
ToggleApply();
|
ToggleApply();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Textures;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin.Services;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.Unlocks;
|
using Glamourer.Unlocks;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -12,16 +12,21 @@ using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Customization;
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeService _service, CodeService _codes, Configuration _config, FavoriteManager _favorites)
|
public partial class CustomizationDrawer(
|
||||||
|
ITextureProvider textures,
|
||||||
|
CustomizeService _service,
|
||||||
|
Configuration _config,
|
||||||
|
FavoriteManager _favorites,
|
||||||
|
HeightService _heightService)
|
||||||
: IDisposable
|
: IDisposable
|
||||||
{
|
{
|
||||||
private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f);
|
private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f);
|
||||||
private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(pi);
|
private readonly IDalamudTextureWrap? _legacyTattoo = GetLegacyTattooIcon(textures);
|
||||||
|
|
||||||
private Exception? _terminate;
|
private Exception? _terminate;
|
||||||
|
|
||||||
private CustomizeArray _customize = CustomizeArray.Default;
|
private CustomizeArray _customize = CustomizeArray.Default;
|
||||||
private CustomizeSet _set = null!;
|
private CustomizeSet _set = null!;
|
||||||
|
|
||||||
public CustomizeArray Customize
|
public CustomizeArray Customize
|
||||||
=> _customize;
|
=> _customize;
|
||||||
|
|
@ -46,7 +51,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer
|
||||||
|
|
||||||
public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw)
|
public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw)
|
||||||
{
|
{
|
||||||
_withApply = false;
|
_withApply = false;
|
||||||
Init(current, locked, lockedRedraw);
|
Init(current, locked, lockedRedraw);
|
||||||
|
|
||||||
return DrawInternal();
|
return DrawInternal();
|
||||||
|
|
@ -111,30 +116,27 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_codes.Enabled(CodeService.CodeFlag.Artisan))
|
|
||||||
return DrawArtisan();
|
|
||||||
|
|
||||||
DrawRaceGenderSelector();
|
DrawRaceGenderSelector();
|
||||||
DrawBodyType();
|
DrawBodyType();
|
||||||
|
|
||||||
_set = _service.Manager.GetSet(_customize.Clan, _customize.Gender);
|
_set = _service.Manager.GetSet(_customize.Clan, _customize.Gender);
|
||||||
|
|
||||||
foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage])
|
foreach (var id in _set.Order[MenuType.Percentage])
|
||||||
PercentageSelector(id);
|
PercentageSelector(id);
|
||||||
|
|
||||||
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.IconSelector], DrawIconSelector, ImGui.SameLine);
|
Functions.IteratePairwise(_set.Order[MenuType.IconSelector], DrawIconSelector, ImGui.SameLine);
|
||||||
|
|
||||||
DrawMultiIconSelector();
|
DrawMultiIconSelector();
|
||||||
|
|
||||||
foreach (var id in _set.Order[CharaMakeParams.MenuType.ListSelector])
|
foreach (var id in _set.Order[MenuType.ListSelector])
|
||||||
DrawListSelector(id, false);
|
DrawListSelector(id, false);
|
||||||
|
|
||||||
foreach (var id in _set.Order[CharaMakeParams.MenuType.List1Selector])
|
foreach (var id in _set.Order[MenuType.List1Selector])
|
||||||
DrawListSelector(id, true);
|
DrawListSelector(id, true);
|
||||||
|
|
||||||
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine);
|
Functions.IteratePairwise(_set.Order[MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine);
|
||||||
|
|
||||||
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.Checkmark], DrawCheckbox,
|
Functions.IteratePairwise(_set.Order[MenuType.Checkmark], DrawCheckbox,
|
||||||
() => ImGui.SameLine(_comboSelectorSize - _framedIconSize.X + ImGui.GetStyle().WindowPadding.X));
|
() => ImGui.SameLine(_comboSelectorSize - _framedIconSize.X + ImGui.GetStyle().WindowPadding.X));
|
||||||
return Changed != 0 || ChangeApply != _initialApply;
|
return Changed != 0 || ChangeApply != _initialApply;
|
||||||
}
|
}
|
||||||
|
|
@ -148,31 +150,6 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe bool DrawArtisan()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < CustomizeArray.Size; ++i)
|
|
||||||
{
|
|
||||||
using var id = ImRaii.PushId(i);
|
|
||||||
int value = _customize.Data[i];
|
|
||||||
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
|
|
||||||
if (ImGui.InputInt(string.Empty, ref value, 0, 0))
|
|
||||||
{
|
|
||||||
var newValue = (byte)Math.Clamp(value, 0, byte.MaxValue);
|
|
||||||
if (newValue != _customize.Data[i])
|
|
||||||
foreach (var flag in Enum.GetValues<CustomizeIndex>())
|
|
||||||
{
|
|
||||||
var (j, _) = flag.ToByteAndMask();
|
|
||||||
if (j == i)
|
|
||||||
Changed |= flag.ToFlag();
|
|
||||||
}
|
|
||||||
|
|
||||||
_customize.Data[i] = newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Changed != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateSizes()
|
private void UpdateSizes()
|
||||||
{
|
{
|
||||||
_spacing = ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemInnerSpacing.X };
|
_spacing = ImGui.GetStyle().ItemSpacing with { X = ImGui.GetStyle().ItemInnerSpacing.X };
|
||||||
|
|
@ -184,7 +161,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer
|
||||||
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
|
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IDalamudTextureWrap? GetLegacyTattooIcon(DalamudPluginInterface pi)
|
private static IDalamudTextureWrap? GetLegacyTattooIcon(ITextureProvider textures)
|
||||||
{
|
{
|
||||||
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
|
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
|
||||||
if (resource == null)
|
if (resource == null)
|
||||||
|
|
@ -193,7 +170,7 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeSer
|
||||||
var rawImage = new byte[resource.Length];
|
var rawImage = new byte[resource.Length];
|
||||||
var length = resource.Read(rawImage, 0, (int)resource.Length);
|
var length = resource.Read(rawImage, 0, (int)resource.Length);
|
||||||
return length == resource.Length
|
return length == resource.Length
|
||||||
? pi.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4)
|
? textures.CreateFromRaw(RawImageSpecification.Rgba32(192, 192), rawImage, "Glamourer.LegacyTattoo")
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
public struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data)
|
public struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data)
|
||||||
{
|
{
|
||||||
private IDesignEditor _editor;
|
private IDesignEditor _editor = null!;
|
||||||
private object _object;
|
private object _object = null!;
|
||||||
public readonly CustomizeParameterFlag Flag = flag;
|
public readonly CustomizeParameterFlag Flag = flag;
|
||||||
public bool Locked;
|
public bool Locked;
|
||||||
public bool DisplayApplication;
|
public bool DisplayApplication;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using Glamourer.Designs;
|
||||||
using Glamourer.GameData;
|
using Glamourer.GameData;
|
||||||
using Glamourer.Interop.PalettePlus;
|
using Glamourer.Interop.PalettePlus;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
|
@ -287,13 +287,13 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImGuiColorEditFlags GetFlags()
|
private ImGuiColorEditFlags GetFlags()
|
||||||
=> Format | Display | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions;
|
=> Format | Display | ImGuiColorEditFlags.Hdr | ImGuiColorEditFlags.NoOptions;
|
||||||
|
|
||||||
private ImGuiColorEditFlags Format
|
private ImGuiColorEditFlags Format
|
||||||
=> config.UseFloatForColors ? ImGuiColorEditFlags.Float : ImGuiColorEditFlags.Uint8;
|
=> config.UseFloatForColors ? ImGuiColorEditFlags.Float : ImGuiColorEditFlags.Uint8;
|
||||||
|
|
||||||
private ImGuiColorEditFlags Display
|
private ImGuiColorEditFlags Display
|
||||||
=> config.UseRgbForColors ? ImGuiColorEditFlags.DisplayRGB : ImGuiColorEditFlags.DisplayHSV;
|
=> config.UseRgbForColors ? ImGuiColorEditFlags.DisplayRgb : ImGuiColorEditFlags.DisplayHsv;
|
||||||
|
|
||||||
private ImRaii.IEndObject EnsureSize()
|
private ImRaii.IEndObject EnsureSize()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Glamourer.Automation;
|
using Glamourer.Automation;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Designs.History;
|
||||||
using Glamourer.Designs.Special;
|
using Glamourer.Designs.Special;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Extensions;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
|
|
||||||
|
|
@ -14,30 +16,31 @@ namespace Glamourer.Gui;
|
||||||
|
|
||||||
public abstract class DesignComboBase : FilterComboCache<Tuple<IDesignStandIn, string>>, IDisposable
|
public abstract class DesignComboBase : FilterComboCache<Tuple<IDesignStandIn, string>>, IDisposable
|
||||||
{
|
{
|
||||||
private readonly EphemeralConfig _config;
|
protected readonly EphemeralConfig Config;
|
||||||
private readonly DesignChanged _designChanged;
|
protected readonly DesignChanged DesignChanged;
|
||||||
private readonly DesignColors _designColors;
|
protected readonly DesignColors DesignColors;
|
||||||
protected readonly TabSelected TabSelected;
|
protected readonly TabSelected TabSelected;
|
||||||
protected float InnerWidth;
|
protected float InnerWidth;
|
||||||
private IDesignStandIn? _currentDesign;
|
private IDesignStandIn? _currentDesign;
|
||||||
|
private bool _isCurrentSelectionDirty;
|
||||||
|
|
||||||
protected DesignComboBase(Func<IReadOnlyList<Tuple<IDesignStandIn, string>>> generator, Logger log, DesignChanged designChanged,
|
protected DesignComboBase(Func<IReadOnlyList<Tuple<IDesignStandIn, string>>> generator, Logger log, DesignChanged designChanged,
|
||||||
TabSelected tabSelected, EphemeralConfig config, DesignColors designColors)
|
TabSelected tabSelected, EphemeralConfig config, DesignColors designColors)
|
||||||
: base(generator, MouseWheelType.Control, log)
|
: base(generator, MouseWheelType.Control, log)
|
||||||
{
|
{
|
||||||
_designChanged = designChanged;
|
DesignChanged = designChanged;
|
||||||
TabSelected = tabSelected;
|
TabSelected = tabSelected;
|
||||||
_config = config;
|
Config = config;
|
||||||
_designColors = designColors;
|
DesignColors = designColors;
|
||||||
_designChanged.Subscribe(OnDesignChange, DesignChanged.Priority.DesignCombo);
|
DesignChanged.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignCombo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Incognito
|
public bool Incognito
|
||||||
=> _config.IncognitoMode;
|
=> Config.IncognitoMode;
|
||||||
|
|
||||||
void IDisposable.Dispose()
|
void IDisposable.Dispose()
|
||||||
{
|
{
|
||||||
_designChanged.Unsubscribe(OnDesignChange);
|
DesignChanged.Unsubscribe(OnDesignChanged);
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,52 +48,51 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<IDesignStandIn, s
|
||||||
{
|
{
|
||||||
var (design, path) = Items[globalIdx];
|
var (design, path) = Items[globalIdx];
|
||||||
bool ret;
|
bool ret;
|
||||||
if (design is Design realDesign)
|
switch (design)
|
||||||
{
|
{
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(realDesign));
|
case Design realDesign:
|
||||||
ret = base.DrawSelectable(globalIdx, selected);
|
|
||||||
|
|
||||||
if (path.Length > 0 && realDesign.Name != path)
|
|
||||||
{
|
{
|
||||||
var start = ImGui.GetItemRectMin();
|
using var color = ImRaii.PushColor(ImGuiCol.Text, DesignColors.GetColor(realDesign));
|
||||||
var pos = start.X + ImGui.CalcTextSize(realDesign.Name).X;
|
ret = base.DrawSelectable(globalIdx, selected);
|
||||||
var maxSize = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X;
|
DrawPath(path, realDesign);
|
||||||
var remainingSpace = maxSize - pos;
|
return ret;
|
||||||
var requiredSize = ImGui.CalcTextSize(path).X + ImGui.GetStyle().ItemInnerSpacing.X;
|
|
||||||
var offset = remainingSpace - requiredSize;
|
|
||||||
if (ImGui.GetScrollMaxY() == 0)
|
|
||||||
offset -= ImGui.GetStyle().ItemInnerSpacing.X;
|
|
||||||
|
|
||||||
if (offset < ImGui.GetStyle().ItemSpacing.X)
|
|
||||||
ImGuiUtil.HoverTooltip(path);
|
|
||||||
else
|
|
||||||
ImGui.GetWindowDrawList().AddText(start with { X = pos + offset },
|
|
||||||
ImGui.GetColorU32(ImGuiCol.TextDisabled), path);
|
|
||||||
}
|
}
|
||||||
|
case QuickSelectedDesign quickDesign:
|
||||||
|
{
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.NormalDesign.Value());
|
||||||
|
ret = base.DrawSelectable(globalIdx, selected);
|
||||||
|
DrawResolvedDesign(quickDesign);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
default: return base.DrawSelectable(globalIdx, selected);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
ret = base.DrawSelectable(globalIdx, selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override int UpdateCurrentSelected(int currentSelected)
|
private static void DrawPath(string path, Design realDesign)
|
||||||
{
|
{
|
||||||
CurrentSelectionIdx = Items.IndexOf(p => _currentDesign == p.Item1);
|
if (path.Length <= 0 || realDesign.Name == path)
|
||||||
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : null;
|
return;
|
||||||
return CurrentSelectionIdx;
|
|
||||||
|
DrawRightAligned(realDesign.Name, path, ImGui.GetColorU32(ImGuiCol.TextDisabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawResolvedDesign(QuickSelectedDesign quickDesign)
|
||||||
|
{
|
||||||
|
var linkedDesign = quickDesign.CurrentDesign;
|
||||||
|
if (linkedDesign != null)
|
||||||
|
DrawRightAligned(quickDesign.ResolveName(false), linkedDesign.Name.Text, DesignColors.GetColor(linkedDesign));
|
||||||
|
else
|
||||||
|
DrawRightAligned(quickDesign.ResolveName(false), "[Nothing]", DesignColors.MissingColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool Draw(IDesignStandIn? currentDesign, string? label, float width)
|
protected bool Draw(IDesignStandIn? currentDesign, string? label, float width)
|
||||||
{
|
{
|
||||||
_currentDesign = currentDesign;
|
_currentDesign = currentDesign;
|
||||||
InnerWidth = 400 * ImGuiHelpers.GlobalScale;
|
UpdateCurrentSelection();
|
||||||
|
InnerWidth = 400 * ImGuiHelpers.GlobalScale;
|
||||||
var name = label ?? "Select Design Here...";
|
var name = label ?? "Select Design Here...";
|
||||||
bool ret;
|
bool ret;
|
||||||
using (_ = currentDesign != null ? ImRaii.PushColor(ImGuiCol.Text, _designColors.GetColor(currentDesign as Design)) : null)
|
using (_ = currentDesign != null ? ImRaii.PushColor(ImGuiCol.Text, DesignColors.GetColor(currentDesign as Design)) : null)
|
||||||
{
|
{
|
||||||
ret = Draw("##design", name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing())
|
ret = Draw("##design", name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing())
|
||||||
&& CurrentSelection != null;
|
&& CurrentSelection != null;
|
||||||
|
|
@ -103,6 +105,8 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<IDesignStandIn, s
|
||||||
ImGuiUtil.HoverTooltip("Control + Right-Click to move to design.");
|
ImGuiUtil.HoverTooltip("Control + Right-Click to move to design.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QuickSelectedDesignTooltip(currentDesign);
|
||||||
|
|
||||||
_currentDesign = null;
|
_currentDesign = null;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -119,37 +123,101 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<IDesignStandIn, s
|
||||||
return filter.IsContained(path) || filter.IsContained(design.ResolveName(false));
|
return filter.IsContained(path) || filter.IsContained(design.ResolveName(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDesignChange(DesignChanged.Type type, Design design, object? data = null)
|
protected override void OnMouseWheel(string preview, ref int _2, int steps)
|
||||||
{
|
{
|
||||||
switch (type)
|
if (!ReferenceEquals(_currentDesign, CurrentSelection?.Item1))
|
||||||
{
|
CurrentSelectionIdx = -1;
|
||||||
case DesignChanged.Type.Created:
|
|
||||||
case DesignChanged.Type.Renamed:
|
|
||||||
case DesignChanged.Type.ChangedColor:
|
|
||||||
case DesignChanged.Type.Deleted:
|
|
||||||
case DesignChanged.Type.QuickDesignBar:
|
|
||||||
var priorState = IsInitialized;
|
|
||||||
if (priorState)
|
|
||||||
Cleanup();
|
|
||||||
CurrentSelectionIdx = Items.IndexOf(s => ReferenceEquals(s.Item1, CurrentSelection?.Item1));
|
|
||||||
if (CurrentSelectionIdx >= 0)
|
|
||||||
{
|
|
||||||
CurrentSelection = Items[CurrentSelectionIdx];
|
|
||||||
}
|
|
||||||
else if (Items.Count > 0)
|
|
||||||
{
|
|
||||||
CurrentSelectionIdx = 0;
|
|
||||||
CurrentSelection = Items[0];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CurrentSelection = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!priorState)
|
base.OnMouseWheel(preview, ref _2, steps);
|
||||||
Cleanup();
|
}
|
||||||
break;
|
|
||||||
|
private void UpdateCurrentSelection()
|
||||||
|
{
|
||||||
|
if (!_isCurrentSelectionDirty)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var priorState = IsInitialized;
|
||||||
|
if (priorState)
|
||||||
|
Cleanup();
|
||||||
|
CurrentSelectionIdx = Items.IndexOf(s => ReferenceEquals(s.Item1, CurrentSelection?.Item1));
|
||||||
|
if (CurrentSelectionIdx >= 0)
|
||||||
|
{
|
||||||
|
UpdateSelection(Items[CurrentSelectionIdx]);
|
||||||
}
|
}
|
||||||
|
else if (Items.Count > 0)
|
||||||
|
{
|
||||||
|
CurrentSelectionIdx = 0;
|
||||||
|
UpdateSelection(Items[0]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateSelection(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!priorState)
|
||||||
|
Cleanup();
|
||||||
|
_isCurrentSelectionDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int UpdateCurrentSelected(int currentSelected)
|
||||||
|
{
|
||||||
|
CurrentSelectionIdx = Items.IndexOf(p => _currentDesign == p.Item1);
|
||||||
|
UpdateSelection(CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : null);
|
||||||
|
return CurrentSelectionIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDesignChanged(DesignChanged.Type type, Design? _1, ITransaction? _2 = null)
|
||||||
|
{
|
||||||
|
_isCurrentSelectionDirty = type switch
|
||||||
|
{
|
||||||
|
DesignChanged.Type.Created => true,
|
||||||
|
DesignChanged.Type.Renamed => true,
|
||||||
|
DesignChanged.Type.ChangedColor => true,
|
||||||
|
DesignChanged.Type.Deleted => true,
|
||||||
|
DesignChanged.Type.QuickDesignBar => true,
|
||||||
|
_ => _isCurrentSelectionDirty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QuickSelectedDesignTooltip(IDesignStandIn? design)
|
||||||
|
{
|
||||||
|
if (!ImGui.IsItemHovered())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (design is not QuickSelectedDesign q)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var tt = ImRaii.Tooltip();
|
||||||
|
var linkedDesign = q.CurrentDesign;
|
||||||
|
if (linkedDesign != null)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted("Currently resolving to ");
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Text, DesignColors.GetColor(linkedDesign));
|
||||||
|
ImGui.SameLine(0, 0);
|
||||||
|
ImGui.TextUnformatted(linkedDesign.Name.Text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted("No design selected in the Quick Design Bar.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawRightAligned(string leftText, string text, uint color)
|
||||||
|
{
|
||||||
|
var start = ImGui.GetItemRectMin();
|
||||||
|
var pos = start.X + ImGui.CalcTextSize(leftText).X;
|
||||||
|
var maxSize = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X;
|
||||||
|
var remainingSpace = maxSize - pos;
|
||||||
|
var requiredSize = ImGui.CalcTextSize(text).X + ImGui.GetStyle().ItemInnerSpacing.X;
|
||||||
|
var offset = remainingSpace - requiredSize;
|
||||||
|
if (ImGui.GetScrollMaxY() == 0)
|
||||||
|
offset -= ImGui.GetStyle().ItemInnerSpacing.X;
|
||||||
|
|
||||||
|
if (offset < ImGui.GetStyle().ItemSpacing.X)
|
||||||
|
ImGuiUtil.HoverTooltip(text);
|
||||||
|
else
|
||||||
|
ImGui.GetWindowDrawList().AddText(start with { X = pos + offset },
|
||||||
|
color, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,8 +244,7 @@ public abstract class DesignCombo : DesignComboBase
|
||||||
|
|
||||||
public sealed class QuickDesignCombo : DesignCombo
|
public sealed class QuickDesignCombo : DesignCombo
|
||||||
{
|
{
|
||||||
public QuickDesignCombo(DesignManager designs,
|
public QuickDesignCombo(DesignFileSystem fileSystem,
|
||||||
DesignFileSystem fileSystem,
|
|
||||||
Logger log,
|
Logger log,
|
||||||
DesignChanged designChanged,
|
DesignChanged designChanged,
|
||||||
TabSelected tabSelected,
|
TabSelected tabSelected,
|
||||||
|
|
@ -185,16 +252,49 @@ public sealed class QuickDesignCombo : DesignCombo
|
||||||
DesignColors designColors)
|
DesignColors designColors)
|
||||||
: base(log, designChanged, tabSelected, config, designColors, () =>
|
: base(log, designChanged, tabSelected, config, designColors, () =>
|
||||||
[
|
[
|
||||||
.. designs.Designs
|
.. fileSystem
|
||||||
.Where(d => d.QuickDesign)
|
.Where(kvp => kvp.Key.QuickDesign)
|
||||||
.Select(d => new Tuple<IDesignStandIn, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
|
.Select(kvp => new Tuple<IDesignStandIn, string>(kvp.Key, kvp.Value.FullName()))
|
||||||
.OrderBy(d => d.Item2),
|
.OrderBy(d => d.Item2),
|
||||||
])
|
])
|
||||||
=> AllowMouseWheel = MouseWheelType.Unmodified;
|
{
|
||||||
|
if (config.SelectedQuickDesign != Guid.Empty)
|
||||||
|
{
|
||||||
|
CurrentSelectionIdx = Items.IndexOf(t => t.Item1 is Design d && d.Identifier == config.SelectedQuickDesign);
|
||||||
|
if (CurrentSelectionIdx >= 0)
|
||||||
|
CurrentSelection = Items[CurrentSelectionIdx];
|
||||||
|
else if (Items.Count > 0)
|
||||||
|
CurrentSelectionIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AllowMouseWheel = MouseWheelType.Unmodified;
|
||||||
|
SelectionChanged += OnSelectionChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectionChange(Tuple<IDesignStandIn, string>? old, Tuple<IDesignStandIn, string>? @new)
|
||||||
|
{
|
||||||
|
if (old == null)
|
||||||
|
{
|
||||||
|
if (@new?.Item1 is not Design d)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Config.SelectedQuickDesign = d.Identifier;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
else if (@new?.Item1 is not Design d)
|
||||||
|
{
|
||||||
|
Config.SelectedQuickDesign = Guid.Empty;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
else if (!old.Item1.Equals(@new.Item1))
|
||||||
|
{
|
||||||
|
Config.SelectedQuickDesign = d.Identifier;
|
||||||
|
Config.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class LinkDesignCombo(
|
public sealed class LinkDesignCombo(
|
||||||
DesignManager designs,
|
|
||||||
DesignFileSystem fileSystem,
|
DesignFileSystem fileSystem,
|
||||||
Logger log,
|
Logger log,
|
||||||
DesignChanged designChanged,
|
DesignChanged designChanged,
|
||||||
|
|
@ -203,8 +303,8 @@ public sealed class LinkDesignCombo(
|
||||||
DesignColors designColors)
|
DesignColors designColors)
|
||||||
: DesignCombo(log, designChanged, tabSelected, config, designColors, () =>
|
: DesignCombo(log, designChanged, tabSelected, config, designColors, () =>
|
||||||
[
|
[
|
||||||
.. designs.Designs
|
.. fileSystem
|
||||||
.Select(d => new Tuple<IDesignStandIn, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
|
.Select(kvp => new Tuple<IDesignStandIn, string>(kvp.Key, kvp.Value.FullName()))
|
||||||
.OrderBy(d => d.Item2),
|
.OrderBy(d => d.Item2),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -218,8 +318,8 @@ public sealed class RandomDesignCombo(
|
||||||
DesignColors designColors)
|
DesignColors designColors)
|
||||||
: DesignCombo(log, designChanged, tabSelected, config, designColors, () =>
|
: DesignCombo(log, designChanged, tabSelected, config, designColors, () =>
|
||||||
[
|
[
|
||||||
.. designs.Designs
|
.. fileSystem
|
||||||
.Select(d => new Tuple<IDesignStandIn, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
|
.Select(kvp => new Tuple<IDesignStandIn, string>(kvp.Key, kvp.Value.FullName()))
|
||||||
.OrderBy(d => d.Item2),
|
.OrderBy(d => d.Item2),
|
||||||
])
|
])
|
||||||
{
|
{
|
||||||
|
|
@ -245,7 +345,6 @@ public sealed class RandomDesignCombo(
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class SpecialDesignCombo(
|
public sealed class SpecialDesignCombo(
|
||||||
DesignManager designs,
|
|
||||||
DesignFileSystem fileSystem,
|
DesignFileSystem fileSystem,
|
||||||
TabSelected tabSelected,
|
TabSelected tabSelected,
|
||||||
DesignColors designColors,
|
DesignColors designColors,
|
||||||
|
|
@ -253,11 +352,13 @@ public sealed class SpecialDesignCombo(
|
||||||
DesignChanged designChanged,
|
DesignChanged designChanged,
|
||||||
AutoDesignManager autoDesignManager,
|
AutoDesignManager autoDesignManager,
|
||||||
EphemeralConfig config,
|
EphemeralConfig config,
|
||||||
RandomDesignGenerator rng)
|
RandomDesignGenerator rng,
|
||||||
: DesignComboBase(() => designs.Designs
|
QuickSelectedDesign quickSelectedDesign)
|
||||||
.Select(d => new Tuple<IDesignStandIn, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
|
: DesignComboBase(() => fileSystem
|
||||||
|
.Select(kvp => new Tuple<IDesignStandIn, string>(kvp.Key, kvp.Value.FullName()))
|
||||||
.OrderBy(d => d.Item2)
|
.OrderBy(d => d.Item2)
|
||||||
.Prepend(new Tuple<IDesignStandIn, string>(new RandomDesign(rng), string.Empty))
|
.Prepend(new Tuple<IDesignStandIn, string>(new RandomDesign(rng), string.Empty))
|
||||||
|
.Prepend(new Tuple<IDesignStandIn, string>(quickSelectedDesign, string.Empty))
|
||||||
.Prepend(new Tuple<IDesignStandIn, string>(new RevertDesign(), string.Empty))
|
.Prepend(new Tuple<IDesignStandIn, string>(new RevertDesign(), string.Empty))
|
||||||
.ToList(), log, designChanged, tabSelected, config, designColors)
|
.ToList(), log, designChanged, tabSelected, config, designColors)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,25 +6,28 @@ using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Glamourer.Automation;
|
using Glamourer.Automation;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.Interop.Structs;
|
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using OtterGui;
|
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Text;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
|
||||||
namespace Glamourer.Gui;
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum QdbButtons
|
public enum QdbButtons
|
||||||
{
|
{
|
||||||
ApplyDesign = 0x01,
|
ApplyDesign = 0x01,
|
||||||
RevertAll = 0x02,
|
RevertAll = 0x02,
|
||||||
RevertAutomation = 0x04,
|
RevertAutomation = 0x04,
|
||||||
RevertAdvanced = 0x08,
|
RevertAdvancedDyes = 0x08,
|
||||||
RevertEquip = 0x10,
|
RevertEquip = 0x10,
|
||||||
RevertCustomize = 0x20,
|
RevertCustomize = 0x20,
|
||||||
|
ReapplyAutomation = 0x40,
|
||||||
|
ResetSettings = 0x80,
|
||||||
|
RevertAdvancedCustomization = 0x100,
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DesignQuickBar : Window, IDisposable
|
public sealed class DesignQuickBar : Window, IDisposable
|
||||||
|
|
@ -34,19 +37,21 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove
|
? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove
|
||||||
: ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing;
|
: ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing;
|
||||||
|
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly QuickDesignCombo _designCombo;
|
private readonly QuickDesignCombo _designCombo;
|
||||||
private readonly StateManager _stateManager;
|
private readonly StateManager _stateManager;
|
||||||
private readonly AutoDesignApplier _autoDesignApplier;
|
private readonly AutoDesignApplier _autoDesignApplier;
|
||||||
private readonly ObjectManager _objects;
|
private readonly ActorObjectManager _objects;
|
||||||
private readonly IKeyState _keyState;
|
private readonly PenumbraService _penumbra;
|
||||||
private readonly ImRaii.Style _windowPadding = new();
|
private readonly IKeyState _keyState;
|
||||||
private readonly ImRaii.Color _windowColor = new();
|
private readonly ImRaii.Style _windowPadding = new();
|
||||||
private DateTime _keyboardToggle = DateTime.UnixEpoch;
|
private readonly ImRaii.Color _windowColor = new();
|
||||||
private int _numButtons;
|
private DateTime _keyboardToggle = DateTime.UnixEpoch;
|
||||||
|
private int _numButtons;
|
||||||
|
private readonly StringBuilder _tooltipBuilder = new(512);
|
||||||
|
|
||||||
public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState,
|
public DesignQuickBar(Configuration config, QuickDesignCombo designCombo, StateManager stateManager, IKeyState keyState,
|
||||||
ObjectManager objects, AutoDesignApplier autoDesignApplier)
|
ActorObjectManager objects, AutoDesignApplier autoDesignApplier, PenumbraService penumbra)
|
||||||
: base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking)
|
: base("Glamourer Quick Bar", ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
@ -55,9 +60,11 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
_keyState = keyState;
|
_keyState = keyState;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
_autoDesignApplier = autoDesignApplier;
|
_autoDesignApplier = autoDesignApplier;
|
||||||
|
_penumbra = penumbra;
|
||||||
IsOpen = _config.Ephemeral.ShowDesignQuickBar;
|
IsOpen = _config.Ephemeral.ShowDesignQuickBar;
|
||||||
DisableWindowSounds = true;
|
DisableWindowSounds = true;
|
||||||
Size = Vector2.Zero;
|
Size = Vector2.Zero;
|
||||||
|
RespectCloseHotkey = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -69,6 +76,9 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
IsOpen = _config.Ephemeral.ShowDesignQuickBar && _config.QdbButtons != 0;
|
IsOpen = _config.Ephemeral.ShowDesignQuickBar && _config.QdbButtons != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool DrawConditions()
|
||||||
|
=> _objects.Player.Valid;
|
||||||
|
|
||||||
public override void PreDraw()
|
public override void PreDraw()
|
||||||
{
|
{
|
||||||
Flags = GetFlags;
|
Flags = GetFlags;
|
||||||
|
|
@ -99,8 +109,7 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
|
|
||||||
private void Draw(float width)
|
private void Draw(float width)
|
||||||
{
|
{
|
||||||
_objects.Update();
|
using var group = ImUtf8.Group();
|
||||||
using var group = ImRaii.Group();
|
|
||||||
var spacing = ImGui.GetStyle().ItemInnerSpacing;
|
var spacing = ImGui.GetStyle().ItemInnerSpacing;
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
||||||
var buttonSize = new Vector2(ImGui.GetFrameHeight());
|
var buttonSize = new Vector2(ImGui.GetFrameHeight());
|
||||||
|
|
@ -117,7 +126,10 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
DrawRevertEquipButton(buttonSize);
|
DrawRevertEquipButton(buttonSize);
|
||||||
DrawRevertCustomizeButton(buttonSize);
|
DrawRevertCustomizeButton(buttonSize);
|
||||||
DrawRevertAdvancedCustomization(buttonSize);
|
DrawRevertAdvancedCustomization(buttonSize);
|
||||||
|
DrawRevertAdvancedDyes(buttonSize);
|
||||||
DrawRevertAutomationButton(buttonSize);
|
DrawRevertAutomationButton(buttonSize);
|
||||||
|
DrawReapplyAutomationButton(buttonSize);
|
||||||
|
DrawResetSettingsButton(buttonSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ActorIdentifier _playerIdentifier;
|
private ActorIdentifier _playerIdentifier;
|
||||||
|
|
@ -130,7 +142,6 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
|
|
||||||
private void PrepareButtons()
|
private void PrepareButtons()
|
||||||
{
|
{
|
||||||
_objects.Update();
|
|
||||||
(_playerIdentifier, _playerData) = _objects.PlayerData;
|
(_playerIdentifier, _playerData) = _objects.PlayerData;
|
||||||
(_targetIdentifier, _targetData) = _objects.TargetData;
|
(_targetIdentifier, _targetData) = _objects.TargetData;
|
||||||
_playerState = _stateManager.GetValueOrDefault(_playerIdentifier);
|
_playerState = _stateManager.GetValueOrDefault(_playerIdentifier);
|
||||||
|
|
@ -141,33 +152,38 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
{
|
{
|
||||||
var design = _designCombo.Design as Design;
|
var design = _designCombo.Design as Design;
|
||||||
var available = 0;
|
var available = 0;
|
||||||
var tooltip = string.Empty;
|
_tooltipBuilder.Clear();
|
||||||
|
|
||||||
if (design == null)
|
if (design == null)
|
||||||
{
|
{
|
||||||
tooltip = "No design selected.";
|
_tooltipBuilder.Append("No design selected.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (_playerIdentifier.IsValid && _playerData.Valid)
|
if (_playerIdentifier.IsValid && _playerData.Valid)
|
||||||
{
|
{
|
||||||
available |= 1;
|
available |= 1;
|
||||||
tooltip = $"Left-Click: Apply {design.ResolveName(_config.Ephemeral.IncognitoMode)} to yourself.";
|
_tooltipBuilder.Append("Left-Click: Apply ")
|
||||||
|
.Append(design.ResolveName(_config.Ephemeral.IncognitoMode))
|
||||||
|
.Append(" to yourself.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_targetIdentifier.IsValid && _targetData.Valid)
|
if (_targetIdentifier.IsValid && _targetData.Valid)
|
||||||
{
|
{
|
||||||
if (available != 0)
|
if (available != 0)
|
||||||
tooltip += '\n';
|
_tooltipBuilder.Append('\n');
|
||||||
available |= 2;
|
available |= 2;
|
||||||
tooltip += $"Right-Click: Apply {design.ResolveName(_config.Ephemeral.IncognitoMode)} to {_targetIdentifier}.";
|
_tooltipBuilder.Append("Right-Click: Apply ")
|
||||||
|
.Append(design.ResolveName(_config.Ephemeral.IncognitoMode))
|
||||||
|
.Append(" to ").Append(_config.Ephemeral.IncognitoMode ? _targetIdentifier.Incognito(null) : _targetIdentifier.ToName());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (available == 0)
|
if (available == 0)
|
||||||
tooltip = "Neither player character nor target available.";
|
_tooltipBuilder.Append("Neither player character nor target available.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.PlayCircle, size, tooltip, available);
|
var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.PlayCircle, size, available);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (!clicked)
|
if (!clicked)
|
||||||
return;
|
return;
|
||||||
|
|
@ -179,9 +195,8 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
using var _ = design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
|
||||||
using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
|
_stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { IsFinal = true });
|
||||||
_stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRevertButton(Vector2 buttonSize)
|
private void DrawRevertButton(Vector2 buttonSize)
|
||||||
|
|
@ -190,28 +205,32 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var available = 0;
|
var available = 0;
|
||||||
var tooltip = string.Empty;
|
_tooltipBuilder.Clear();
|
||||||
|
|
||||||
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false })
|
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false })
|
||||||
{
|
{
|
||||||
available |= 1;
|
available |= 1;
|
||||||
tooltip = "Left-Click: Revert the player character to their game state.";
|
_tooltipBuilder.Append("Left-Click: Revert the player character to their game state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false })
|
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false })
|
||||||
{
|
{
|
||||||
if (available != 0)
|
if (available != 0)
|
||||||
tooltip += '\n';
|
_tooltipBuilder.Append('\n');
|
||||||
available |= 2;
|
available |= 2;
|
||||||
tooltip += $"Right-Click: Revert {_targetIdentifier} to their game state.";
|
_tooltipBuilder.Append("Right-Click: Revert ")
|
||||||
|
.Append(_targetIdentifier)
|
||||||
|
.Append(" to their game state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (available == 0)
|
if (available == 0)
|
||||||
tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked.";
|
_tooltipBuilder.Append(
|
||||||
|
"Neither player character nor target are available, have state modified by Glamourer, or their state is locked.");
|
||||||
|
|
||||||
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available);
|
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, available);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (clicked)
|
if (clicked)
|
||||||
_stateManager.ResetState(state!, StateSource.Manual);
|
_stateManager.ResetState(state!, StateSource.Manual, isFinal: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRevertAutomationButton(Vector2 buttonSize)
|
private void DrawRevertAutomationButton(Vector2 buttonSize)
|
||||||
|
|
@ -223,69 +242,147 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var available = 0;
|
var available = 0;
|
||||||
var tooltip = string.Empty;
|
_tooltipBuilder.Clear();
|
||||||
|
|
||||||
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
|
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
|
||||||
{
|
{
|
||||||
available |= 1;
|
available |= 1;
|
||||||
tooltip = "Left-Click: Revert the player character to their automation state.";
|
_tooltipBuilder.Append("Left-Click: Revert the player character to their automation state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid)
|
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid)
|
||||||
{
|
{
|
||||||
if (available != 0)
|
if (available != 0)
|
||||||
tooltip += '\n';
|
_tooltipBuilder.Append('\n');
|
||||||
available |= 2;
|
available |= 2;
|
||||||
tooltip += $"Right-Click: Revert {_targetIdentifier} to their automation state.";
|
_tooltipBuilder.Append("Right-Click: Revert ")
|
||||||
|
.Append(_targetIdentifier)
|
||||||
|
.Append(" to their automation state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (available == 0)
|
if (available == 0)
|
||||||
tooltip = "Neither player character nor target are available, have state modified by Glamourer, or their state is locked.";
|
_tooltipBuilder.Append(
|
||||||
|
"Neither player character nor target are available, have state modified by Glamourer, or their state is locked.");
|
||||||
|
|
||||||
var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, tooltip, available);
|
var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.SyncAlt, buttonSize, available);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (!clicked)
|
if (!clicked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var actor in data.Objects)
|
foreach (var actor in data.Objects)
|
||||||
{
|
{
|
||||||
_autoDesignApplier.ReapplyAutomation(actor, id, state!);
|
_autoDesignApplier.ReapplyAutomation(actor, id, state!, true, false, out var forcedRedraw);
|
||||||
_stateManager.ReapplyState(actor, StateSource.Manual);
|
_stateManager.ReapplyAutomationState(actor, forcedRedraw, true, StateSource.Manual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRevertAdvancedCustomization(Vector2 buttonSize)
|
private void DrawReapplyAutomationButton(Vector2 buttonSize)
|
||||||
{
|
{
|
||||||
if (!_config.UseAdvancedParameters)
|
if (!_config.EnableAutoDesigns)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced))
|
if (!_config.QdbButtons.HasFlag(QdbButtons.ReapplyAutomation))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var available = 0;
|
var available = 0;
|
||||||
var tooltip = string.Empty;
|
_tooltipBuilder.Clear();
|
||||||
|
|
||||||
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
|
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
|
||||||
{
|
{
|
||||||
available |= 1;
|
available |= 1;
|
||||||
tooltip = "Left-Click: Revert the advanced customizations and dyes of the player character to their game state.";
|
_tooltipBuilder.Append("Left-Click: Reapply the player character's current automation on top of their current state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid)
|
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid)
|
||||||
{
|
{
|
||||||
if (available != 0)
|
if (available != 0)
|
||||||
tooltip += '\n';
|
_tooltipBuilder.Append('\n');
|
||||||
available |= 2;
|
available |= 2;
|
||||||
tooltip += $"Right-Click: Revert the advanced customizations and dyes of {_targetIdentifier} to their game state.";
|
_tooltipBuilder.Append("Right-Click: Reapply ")
|
||||||
|
.Append(_targetIdentifier)
|
||||||
|
.Append("'s current automation on top of their current state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (available == 0)
|
if (available == 0)
|
||||||
tooltip = "Neither player character nor target are available or their state is locked.";
|
_tooltipBuilder.Append(
|
||||||
|
"Neither player character nor target are available, have state modified by Glamourer, or their state is locked.");
|
||||||
|
|
||||||
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, tooltip, available);
|
var (clicked, id, data, state) = ResolveTarget(FontAwesomeIcon.Repeat, buttonSize, available);
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (!clicked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var actor in data.Objects)
|
||||||
|
{
|
||||||
|
_autoDesignApplier.ReapplyAutomation(actor, id, state!, false, false, out var forcedRedraw);
|
||||||
|
_stateManager.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Manual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRevertAdvancedCustomization(Vector2 buttonSize)
|
||||||
|
{
|
||||||
|
if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var available = 0;
|
||||||
|
_tooltipBuilder.Clear();
|
||||||
|
|
||||||
|
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
|
||||||
|
{
|
||||||
|
available |= 1;
|
||||||
|
_tooltipBuilder.Append("Left-Click: Revert the advanced customizations of the player character to their game state.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid)
|
||||||
|
{
|
||||||
|
if (available != 0)
|
||||||
|
_tooltipBuilder.Append('\n');
|
||||||
|
available |= 2;
|
||||||
|
_tooltipBuilder.Append("Right-Click: Revert the advanced customizations of ")
|
||||||
|
.Append(_targetIdentifier)
|
||||||
|
.Append(" to their game state.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (available == 0)
|
||||||
|
_tooltipBuilder.Append("Neither player character nor target are available or their state is locked.");
|
||||||
|
|
||||||
|
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.PaintBrush, buttonSize, available);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (clicked)
|
if (clicked)
|
||||||
_stateManager.ResetAdvancedState(state!, StateSource.Manual);
|
_stateManager.ResetAdvancedCustomizations(state!, StateSource.Manual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRevertAdvancedDyes(Vector2 buttonSize)
|
||||||
|
{
|
||||||
|
if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedDyes))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var available = 0;
|
||||||
|
_tooltipBuilder.Clear();
|
||||||
|
|
||||||
|
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
|
||||||
|
{
|
||||||
|
available |= 1;
|
||||||
|
_tooltipBuilder.Append("Left-Click: Revert the advanced dyes of the player character to their game state.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid)
|
||||||
|
{
|
||||||
|
if (available != 0)
|
||||||
|
_tooltipBuilder.Append('\n');
|
||||||
|
available |= 2;
|
||||||
|
_tooltipBuilder.Append("Right-Click: Revert the advanced dyes of ")
|
||||||
|
.Append(_targetIdentifier)
|
||||||
|
.Append(" to their game state.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (available == 0)
|
||||||
|
_tooltipBuilder.Append("Neither player character nor target are available or their state is locked.");
|
||||||
|
|
||||||
|
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, available);
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (clicked)
|
||||||
|
_stateManager.ResetAdvancedDyes(state!, StateSource.Manual);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRevertCustomizeButton(Vector2 buttonSize)
|
private void DrawRevertCustomizeButton(Vector2 buttonSize)
|
||||||
|
|
@ -294,26 +391,28 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var available = 0;
|
var available = 0;
|
||||||
var tooltip = string.Empty;
|
_tooltipBuilder.Clear();
|
||||||
|
|
||||||
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
|
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
|
||||||
{
|
{
|
||||||
available |= 1;
|
available |= 1;
|
||||||
tooltip = "Left-Click: Revert the customizations of the player character to their game state.";
|
_tooltipBuilder.Append("Left-Click: Revert the customizations of the player character to their game state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid)
|
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid)
|
||||||
{
|
{
|
||||||
if (available != 0)
|
if (available != 0)
|
||||||
tooltip += '\n';
|
_tooltipBuilder.Append('\n');
|
||||||
available |= 2;
|
available |= 2;
|
||||||
tooltip += $"Right-Click: Revert the customizations of {_targetIdentifier} to their game state.";
|
_tooltipBuilder.Append("Right-Click: Revert the customizations of ")
|
||||||
|
.Append(_targetIdentifier)
|
||||||
|
.Append(" to their game state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (available == 0)
|
if (available == 0)
|
||||||
tooltip = "Neither player character nor target are available or their state is locked.";
|
_tooltipBuilder.Append("Neither player character nor target are available or their state is locked.");
|
||||||
|
|
||||||
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.User, buttonSize, tooltip, available);
|
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.User, buttonSize, available);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (clicked)
|
if (clicked)
|
||||||
_stateManager.ResetCustomize(state!, StateSource.Manual);
|
_stateManager.ResetCustomize(state!, StateSource.Manual);
|
||||||
|
|
@ -325,35 +424,80 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var available = 0;
|
var available = 0;
|
||||||
var tooltip = string.Empty;
|
_tooltipBuilder.Clear();
|
||||||
|
|
||||||
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
|
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
|
||||||
{
|
{
|
||||||
available |= 1;
|
available |= 1;
|
||||||
tooltip = "Left-Click: Revert the equipment of the player character to its game state.";
|
_tooltipBuilder.Append("Left-Click: Revert the equipment of the player character to its game state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid)
|
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid)
|
||||||
{
|
{
|
||||||
if (available != 0)
|
if (available != 0)
|
||||||
tooltip += '\n';
|
_tooltipBuilder.Append('\n');
|
||||||
available |= 2;
|
available |= 2;
|
||||||
tooltip += $"Right-Click: Revert the equipment of {_targetIdentifier} to its game state.";
|
_tooltipBuilder.Append("Right-Click: Revert the equipment of ")
|
||||||
|
.Append(_targetIdentifier)
|
||||||
|
.Append(" to its game state.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (available == 0)
|
if (available == 0)
|
||||||
tooltip = "Neither player character nor target are available or their state is locked.";
|
_tooltipBuilder.Append("Neither player character nor target are available or their state is locked.");
|
||||||
|
|
||||||
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Vest, buttonSize, tooltip, available);
|
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Vest, buttonSize, available);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (clicked)
|
if (clicked)
|
||||||
_stateManager.ResetEquip(state!, StateSource.Manual);
|
_stateManager.ResetEquip(state!, StateSource.Manual);
|
||||||
}
|
}
|
||||||
|
|
||||||
private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip,
|
private void DrawResetSettingsButton(Vector2 buttonSize)
|
||||||
int available)
|
|
||||||
{
|
{
|
||||||
ImGuiUtil.DrawDisabledButton(icon.ToIconString(), buttonSize, tooltip, available == 0, true);
|
if (!_config.QdbButtons.HasFlag(QdbButtons.ResetSettings))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var available = 0;
|
||||||
|
_tooltipBuilder.Clear();
|
||||||
|
|
||||||
|
if (_playerIdentifier.IsValid && _playerData.Valid)
|
||||||
|
{
|
||||||
|
available |= 1;
|
||||||
|
_tooltipBuilder
|
||||||
|
.Append(
|
||||||
|
"Left-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ")
|
||||||
|
.Append(_playerIdentifier)
|
||||||
|
.Append('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_targetIdentifier.IsValid && _targetData.Valid)
|
||||||
|
{
|
||||||
|
if (available != 0)
|
||||||
|
_tooltipBuilder.Append('\n');
|
||||||
|
available |= 2;
|
||||||
|
_tooltipBuilder
|
||||||
|
.Append(
|
||||||
|
"Right-Click: Reset all temporary settings applied by Glamourer (manually or through automation) to the collection affecting ")
|
||||||
|
.Append(_targetIdentifier)
|
||||||
|
.Append('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (available == 0)
|
||||||
|
_tooltipBuilder.Append("Neither player character nor target are available to identify their collections.");
|
||||||
|
|
||||||
|
var (clicked, _, data, _) = ResolveTarget(FontAwesomeIcon.Cog, buttonSize, available);
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (clicked)
|
||||||
|
{
|
||||||
|
_penumbra.RemoveAllTemporarySettings(data.Objects[0].Index, StateSource.Manual);
|
||||||
|
_penumbra.RemoveAllTemporarySettings(data.Objects[0].Index, StateSource.Fixed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, int available)
|
||||||
|
{
|
||||||
|
var enumerator = _tooltipBuilder.GetChunks();
|
||||||
|
var span = enumerator.MoveNext() ? enumerator.Current.Span : [];
|
||||||
|
ImUtf8.IconButton(icon, span, buttonSize, available == 0);
|
||||||
if ((available & 1) == 1 && ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
if ((available & 1) == 1 && ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||||
return (true, _playerIdentifier, _playerData, _playerState);
|
return (true, _playerIdentifier, _playerData, _playerState);
|
||||||
if ((available & 2) == 2 && ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
if ((available & 2) == 2 && ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||||
|
|
@ -385,14 +529,24 @@ public sealed class DesignQuickBar : Window, IDisposable
|
||||||
_numButtons = 0;
|
_numButtons = 0;
|
||||||
if (_config.QdbButtons.HasFlag(QdbButtons.RevertAll))
|
if (_config.QdbButtons.HasFlag(QdbButtons.RevertAll))
|
||||||
++_numButtons;
|
++_numButtons;
|
||||||
if (_config.EnableAutoDesigns && _config.QdbButtons.HasFlag(QdbButtons.RevertAutomation))
|
if (_config.EnableAutoDesigns)
|
||||||
|
{
|
||||||
|
if (_config.QdbButtons.HasFlag(QdbButtons.RevertAutomation))
|
||||||
|
++_numButtons;
|
||||||
|
if (_config.QdbButtons.HasFlag(QdbButtons.ReapplyAutomation))
|
||||||
|
++_numButtons;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization))
|
||||||
++_numButtons;
|
++_numButtons;
|
||||||
if ((_config.UseAdvancedParameters || _config.UseAdvancedDyes) && _config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced))
|
if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedDyes))
|
||||||
++_numButtons;
|
++_numButtons;
|
||||||
if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize))
|
if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize))
|
||||||
++_numButtons;
|
++_numButtons;
|
||||||
if (_config.QdbButtons.HasFlag(QdbButtons.RevertEquip))
|
if (_config.QdbButtons.HasFlag(QdbButtons.RevertEquip))
|
||||||
++_numButtons;
|
++_numButtons;
|
||||||
|
if (_config.UseTemporarySettings && _config.QdbButtons.HasFlag(QdbButtons.ResetSettings))
|
||||||
|
++_numButtons;
|
||||||
if (_config.QdbButtons.HasFlag(QdbButtons.ApplyDesign))
|
if (_config.QdbButtons.HasFlag(QdbButtons.ApplyDesign))
|
||||||
{
|
{
|
||||||
++_numButtons;
|
++_numButtons;
|
||||||
|
|
|
||||||
61
Glamourer/Gui/Equipment/BonusDrawData.cs
Normal file
61
Glamourer/Gui/Equipment/BonusDrawData.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
|
using Glamourer.State;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Equipment;
|
||||||
|
|
||||||
|
public struct BonusDrawData(BonusItemFlag slot, in DesignData designData)
|
||||||
|
{
|
||||||
|
private IDesignEditor _editor = null!;
|
||||||
|
private object _object = null!;
|
||||||
|
public readonly BonusItemFlag Slot = slot;
|
||||||
|
public bool Locked;
|
||||||
|
public bool DisplayApplication;
|
||||||
|
public bool AllowRevert;
|
||||||
|
public bool HasAdvancedDyes;
|
||||||
|
|
||||||
|
public readonly bool IsDesign
|
||||||
|
=> _object is Design;
|
||||||
|
|
||||||
|
public readonly bool IsState
|
||||||
|
=> _object is ActorState;
|
||||||
|
|
||||||
|
public readonly void SetItem(EquipItem item)
|
||||||
|
=> _editor.ChangeBonusItem(_object, Slot, item, ApplySettings.Manual);
|
||||||
|
|
||||||
|
public readonly void SetApplyItem(bool value)
|
||||||
|
{
|
||||||
|
var manager = (DesignManager)_editor;
|
||||||
|
var design = (Design)_object;
|
||||||
|
manager.ChangeApplyBonusItem(design, Slot, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EquipItem CurrentItem = designData.BonusItem(slot);
|
||||||
|
public EquipItem GameItem = default;
|
||||||
|
public bool CurrentApply;
|
||||||
|
|
||||||
|
public static BonusDrawData FromDesign(DesignManager manager, Design design, BonusItemFlag slot)
|
||||||
|
=> new(slot, design.DesignData)
|
||||||
|
{
|
||||||
|
_editor = manager,
|
||||||
|
_object = design,
|
||||||
|
CurrentApply = design.DoApplyBonusItem(slot),
|
||||||
|
Locked = design.WriteProtected(),
|
||||||
|
DisplayApplication = true,
|
||||||
|
HasAdvancedDyes = design.GetMaterialDataRef().CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static BonusDrawData FromState(StateManager manager, ActorState state, BonusItemFlag slot)
|
||||||
|
=> new(slot, state.ModelData)
|
||||||
|
{
|
||||||
|
_editor = manager,
|
||||||
|
_object = state,
|
||||||
|
Locked = state.IsLocked,
|
||||||
|
DisplayApplication = false,
|
||||||
|
GameItem = state.BaseData.BonusItem(slot),
|
||||||
|
AllowRevert = true,
|
||||||
|
HasAdvancedDyes = state.Materials.CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)),
|
||||||
|
};
|
||||||
|
}
|
||||||
121
Glamourer/Gui/Equipment/BonusItemCombo.cs
Normal file
121
Glamourer/Gui/Equipment/BonusItemCombo.cs
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Glamourer.Services;
|
||||||
|
using Glamourer.Unlocks;
|
||||||
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Extensions;
|
||||||
|
using OtterGui.Log;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Widgets;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Equipment;
|
||||||
|
|
||||||
|
public sealed class BonusItemCombo : FilterComboCache<EquipItem>
|
||||||
|
{
|
||||||
|
private readonly FavoriteManager _favorites;
|
||||||
|
public readonly string Label;
|
||||||
|
private CustomItemId _currentItem;
|
||||||
|
private float _innerWidth;
|
||||||
|
|
||||||
|
public PrimaryId CustomSetId { get; private set; }
|
||||||
|
public Variant CustomVariant { get; private set; }
|
||||||
|
|
||||||
|
public BonusItemCombo(IDataManager gameData, ItemManager items, BonusItemFlag slot, Logger log, FavoriteManager favorites)
|
||||||
|
: base(() => GetItems(favorites, items, slot), MouseWheelType.Control, log)
|
||||||
|
{
|
||||||
|
_favorites = favorites;
|
||||||
|
Label = GetLabel(gameData, slot);
|
||||||
|
_currentItem = 0;
|
||||||
|
SearchByParts = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawList(float width, float itemHeight)
|
||||||
|
{
|
||||||
|
base.DrawList(width, itemHeight);
|
||||||
|
if (NewSelection != null && Items.Count > NewSelection.Value)
|
||||||
|
CurrentSelection = Items[NewSelection.Value];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int UpdateCurrentSelected(int currentSelected)
|
||||||
|
{
|
||||||
|
if (CurrentSelection.Id == _currentItem)
|
||||||
|
return currentSelected;
|
||||||
|
|
||||||
|
CurrentSelectionIdx = Items.IndexOf(i => i.Id == _currentItem);
|
||||||
|
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default;
|
||||||
|
return base.UpdateCurrentSelected(CurrentSelectionIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Draw(string previewName, BonusItemId previewIdx, float width, float innerWidth)
|
||||||
|
{
|
||||||
|
_innerWidth = innerWidth;
|
||||||
|
_currentItem = previewIdx;
|
||||||
|
CustomVariant = 0;
|
||||||
|
return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override float GetFilterWidth()
|
||||||
|
=> _innerWidth - 2 * ImGui.GetStyle().FramePadding.X;
|
||||||
|
|
||||||
|
protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||||
|
{
|
||||||
|
var obj = Items[globalIdx];
|
||||||
|
var name = ToString(obj);
|
||||||
|
if (UiHelpers.DrawFavoriteStar(_favorites, obj) && CurrentSelectionIdx == globalIdx)
|
||||||
|
{
|
||||||
|
CurrentSelectionIdx = -1;
|
||||||
|
_currentItem = obj.Id;
|
||||||
|
CurrentSelection = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
var ret = ImGui.Selectable(name, selected);
|
||||||
|
ImGui.SameLine();
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
|
||||||
|
ImGuiUtil.RightAlign($"({obj.PrimaryId.Id}-{obj.Variant.Id})");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsVisible(int globalIndex, LowerString filter)
|
||||||
|
=> base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString());
|
||||||
|
|
||||||
|
protected override string ToString(EquipItem obj)
|
||||||
|
=> obj.Name;
|
||||||
|
|
||||||
|
private static string GetLabel(IDataManager gameData, BonusItemFlag slot)
|
||||||
|
{
|
||||||
|
var sheet = gameData.GetExcelSheet<Addon>()!;
|
||||||
|
|
||||||
|
return slot switch
|
||||||
|
{
|
||||||
|
BonusItemFlag.Glasses => sheet.TryGetRow(16050, out var text) ? text.Text.ToString() : "Facewear",
|
||||||
|
BonusItemFlag.UnkSlot => sheet.TryGetRow(16051, out var text) ? text.Text.ToString() : "Facewear",
|
||||||
|
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<EquipItem> GetItems(FavoriteManager favorites, ItemManager items, BonusItemFlag slot)
|
||||||
|
{
|
||||||
|
var nothing = EquipItem.BonusItemNothing(slot);
|
||||||
|
return items.ItemData.ByType[slot.ToEquipType()].OrderByDescending(favorites.Contains).ThenBy(i => i.Id.Id).Prepend(nothing).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClosePopup()
|
||||||
|
{
|
||||||
|
// If holding control while the popup closes, try to parse the input as a full pair of set id and variant, and set a custom item for that.
|
||||||
|
if (!ImGui.GetIO().KeyCtrl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var split = Filter.Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
if (split.Length != 2 || !ushort.TryParse(split[0], out var setId) || !byte.TryParse(split[1], out var variant))
|
||||||
|
return;
|
||||||
|
|
||||||
|
CustomSetId = setId;
|
||||||
|
CustomVariant = variant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Interop.Material;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
@ -7,9 +8,9 @@ namespace Glamourer.Gui.Equipment;
|
||||||
|
|
||||||
public struct EquipDrawData(EquipSlot slot, in DesignData designData)
|
public struct EquipDrawData(EquipSlot slot, in DesignData designData)
|
||||||
{
|
{
|
||||||
private IDesignEditor _editor;
|
private IDesignEditor _editor = null!;
|
||||||
private object _object;
|
private object _object = null!;
|
||||||
public readonly EquipSlot Slot = slot;
|
public readonly EquipSlot Slot = slot;
|
||||||
public bool Locked;
|
public bool Locked;
|
||||||
public bool DisplayApplication;
|
public bool DisplayApplication;
|
||||||
public bool AllowRevert;
|
public bool AllowRevert;
|
||||||
|
|
@ -23,29 +24,31 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData)
|
||||||
public readonly void SetItem(EquipItem item)
|
public readonly void SetItem(EquipItem item)
|
||||||
=> _editor.ChangeItem(_object, Slot, item, ApplySettings.Manual);
|
=> _editor.ChangeItem(_object, Slot, item, ApplySettings.Manual);
|
||||||
|
|
||||||
public readonly void SetStain(StainId stain)
|
public readonly void SetStains(StainIds stains)
|
||||||
=> _editor.ChangeStain(_object, Slot, stain, ApplySettings.Manual);
|
=> _editor.ChangeStains(_object, Slot, stains, ApplySettings.Manual);
|
||||||
|
|
||||||
|
public readonly void SetStain(int which, StainId stain)
|
||||||
|
=> _editor.ChangeStains(_object, Slot, CurrentStains.With(which, stain), ApplySettings.Manual);
|
||||||
|
|
||||||
public readonly void SetApplyItem(bool value)
|
public readonly void SetApplyItem(bool value)
|
||||||
{
|
{
|
||||||
var manager = (DesignManager)_editor;
|
var manager = (DesignManager)_editor;
|
||||||
var design = (Design)_object;
|
manager.ChangeApplyItem((Design)_object, Slot, value);
|
||||||
manager.ChangeApplyItem(design, Slot, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly void SetApplyStain(bool value)
|
public readonly void SetApplyStain(bool value)
|
||||||
{
|
{
|
||||||
var manager = (DesignManager)_editor;
|
var manager = (DesignManager)_editor;
|
||||||
var design = (Design)_object;
|
manager.ChangeApplyStains((Design)_object, Slot, value);
|
||||||
manager.ChangeApplyStain(design, Slot, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public EquipItem CurrentItem = designData.Item(slot);
|
public EquipItem CurrentItem = designData.Item(slot);
|
||||||
public StainId CurrentStain = designData.Stain(slot);
|
public StainIds CurrentStains = designData.Stain(slot);
|
||||||
public EquipItem GameItem = default;
|
public EquipItem GameItem = default;
|
||||||
public StainId GameStain = default;
|
public StainIds GameStains = default;
|
||||||
public bool CurrentApply;
|
public bool CurrentApply;
|
||||||
public bool CurrentApplyStain;
|
public bool CurrentApplyStain;
|
||||||
|
public bool HasAdvancedDyes;
|
||||||
|
|
||||||
public readonly Gender CurrentGender = designData.Customize.Gender;
|
public readonly Gender CurrentGender = designData.Customize.Gender;
|
||||||
public readonly Race CurrentRace = designData.Customize.Race;
|
public readonly Race CurrentRace = designData.Customize.Race;
|
||||||
|
|
@ -58,6 +61,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData)
|
||||||
CurrentApply = design.DoApplyEquip(slot),
|
CurrentApply = design.DoApplyEquip(slot),
|
||||||
CurrentApplyStain = design.DoApplyStain(slot),
|
CurrentApplyStain = design.DoApplyStain(slot),
|
||||||
Locked = design.WriteProtected(),
|
Locked = design.WriteProtected(),
|
||||||
|
HasAdvancedDyes = design.GetMaterialDataRef().CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)),
|
||||||
DisplayApplication = true,
|
DisplayApplication = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -69,7 +73,8 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData)
|
||||||
Locked = state.IsLocked,
|
Locked = state.IsLocked,
|
||||||
DisplayApplication = false,
|
DisplayApplication = false,
|
||||||
GameItem = state.BaseData.Item(slot),
|
GameItem = state.BaseData.Item(slot),
|
||||||
GameStain = state.BaseData.Stain(slot),
|
GameStains = state.BaseData.Stain(slot),
|
||||||
|
HasAdvancedDyes = state.Materials.CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)),
|
||||||
AllowRevert = true,
|
AllowRevert = true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
83
Glamourer/Gui/Equipment/EquipItemSlotCache.cs
Normal file
83
Glamourer/Gui/Equipment/EquipItemSlotCache.cs
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
using Glamourer.Services;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Equipment;
|
||||||
|
|
||||||
|
[InlineArray(13)]
|
||||||
|
public struct EquipItemSlotCache
|
||||||
|
{
|
||||||
|
private EquipItem _element;
|
||||||
|
|
||||||
|
public EquipItem Dragged
|
||||||
|
{
|
||||||
|
get => this[^1];
|
||||||
|
set => this[^1] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
=> ((Span<EquipItem>)this).Clear();
|
||||||
|
|
||||||
|
public EquipItem this[EquipSlot slot]
|
||||||
|
{
|
||||||
|
get => this[(int)slot.ToIndex()];
|
||||||
|
set => this[(int)slot.ToIndex()] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(ItemManager items, in EquipItem item, EquipSlot startSlot)
|
||||||
|
{
|
||||||
|
if (item.Id == Dragged.Id && item.Type == Dragged.Type)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (startSlot)
|
||||||
|
{
|
||||||
|
case EquipSlot.MainHand:
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
this[EquipSlot.MainHand] = item;
|
||||||
|
if (item.Type is FullEquipType.Sword)
|
||||||
|
this[EquipSlot.OffHand] = items.FindClosestShield(item.ItemId, out var shield) ? shield : default;
|
||||||
|
else
|
||||||
|
this[EquipSlot.OffHand] = items.ItemData.Secondary.GetValueOrDefault(item.ItemId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EquipSlot.OffHand:
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
if (item.Type is FullEquipType.Shield)
|
||||||
|
this[EquipSlot.MainHand] = items.FindClosestSword(item.ItemId, out var sword) ? sword : default;
|
||||||
|
else
|
||||||
|
this[EquipSlot.MainHand] = items.ItemData.Primary.GetValueOrDefault(item.ItemId);
|
||||||
|
this[EquipSlot.OffHand] = item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
this[EquipSlot.MainHand] = default;
|
||||||
|
this[EquipSlot.OffHand] = default;
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
{
|
||||||
|
if (startSlot == slot)
|
||||||
|
{
|
||||||
|
this[slot] = item;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var slotItem = items.Identify(slot, item.PrimaryId, item.Variant);
|
||||||
|
if (!slotItem.Valid || slotItem.ItemId.Id is not 0 != item.ItemId.Id is not 0)
|
||||||
|
{
|
||||||
|
slotItem = items.Identify(EquipSlot.OffHand, item.PrimaryId, item.SecondaryId, 1, item.Type);
|
||||||
|
if (slotItem.ItemId.Id is not 0 != item.ItemId.Id is not 0)
|
||||||
|
slotItem = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
this[slot] = slotItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dragged = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,10 +5,13 @@ using Glamourer.Events;
|
||||||
using Glamourer.Gui.Materials;
|
using Glamourer.Gui.Materials;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.Unlocks;
|
using Glamourer.Unlocks;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using OtterGui;
|
using OtterGui.Extensions;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Text;
|
||||||
|
using OtterGui.Text.EndObjects;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.DataContainers;
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
@ -23,42 +26,49 @@ public class EquipmentDrawer
|
||||||
private readonly GlamourerColorCombo _stainCombo;
|
private readonly GlamourerColorCombo _stainCombo;
|
||||||
private readonly DictStain _stainData;
|
private readonly DictStain _stainData;
|
||||||
private readonly ItemCombo[] _itemCombo;
|
private readonly ItemCombo[] _itemCombo;
|
||||||
|
private readonly BonusItemCombo[] _bonusItemCombo;
|
||||||
private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo;
|
private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo;
|
||||||
private readonly CodeService _codes;
|
|
||||||
private readonly TextureService _textures;
|
private readonly TextureService _textures;
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly GPoseService _gPose;
|
private readonly GPoseService _gPose;
|
||||||
private readonly AdvancedDyePopup _advancedDyes;
|
private readonly AdvancedDyePopup _advancedDyes;
|
||||||
|
private readonly ItemCopyService _itemCopy;
|
||||||
|
|
||||||
private float _requiredComboWidthUnscaled;
|
private float _requiredComboWidthUnscaled;
|
||||||
private float _requiredComboWidth;
|
private float _requiredComboWidth;
|
||||||
|
|
||||||
public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures,
|
private Stain? _draggedStain;
|
||||||
Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes)
|
private EquipItemSlotCache _draggedItem;
|
||||||
|
private EquipSlot _dragTarget;
|
||||||
|
|
||||||
|
public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures,
|
||||||
|
Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy)
|
||||||
{
|
{
|
||||||
_items = items;
|
_items = items;
|
||||||
_codes = codes;
|
_textures = textures;
|
||||||
_textures = textures;
|
_config = config;
|
||||||
_config = config;
|
_gPose = gPose;
|
||||||
_gPose = gPose;
|
_advancedDyes = advancedDyes;
|
||||||
_advancedDyes = advancedDyes;
|
_itemCopy = itemCopy;
|
||||||
_stainData = items.Stains;
|
_stainData = items.Stains;
|
||||||
_stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites);
|
_stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites);
|
||||||
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray();
|
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray();
|
||||||
_weaponCombo = new Dictionary<FullEquipType, WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
|
_bonusItemCombo = BonusExtensions.AllFlags.Select(f => new BonusItemCombo(gameData, items, f, Glamourer.Log, favorites)).ToArray();
|
||||||
|
_weaponCombo = new Dictionary<FullEquipType, WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
|
||||||
foreach (var type in Enum.GetValues<FullEquipType>())
|
foreach (var type in Enum.GetValues<FullEquipType>())
|
||||||
{
|
{
|
||||||
if (type.ToSlot() is EquipSlot.MainHand)
|
if (type.ToSlot() is EquipSlot.MainHand)
|
||||||
_weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log));
|
_weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log, favorites));
|
||||||
else if (type.ToSlot() is EquipSlot.OffHand)
|
else if (type.ToSlot() is EquipSlot.OffHand)
|
||||||
_weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log));
|
_weaponCombo.TryAdd(type, new WeaponCombo(items, type, Glamourer.Log, favorites));
|
||||||
}
|
}
|
||||||
|
|
||||||
_weaponCombo.Add(FullEquipType.Unknown, new WeaponCombo(items, FullEquipType.Unknown, Glamourer.Log));
|
_weaponCombo.Add(FullEquipType.Unknown, new WeaponCombo(items, FullEquipType.Unknown, Glamourer.Log, favorites));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector2 _iconSize;
|
private Vector2 _iconSize;
|
||||||
private float _comboLength;
|
private float _comboLength;
|
||||||
|
private uint _advancedMaterialColor;
|
||||||
|
|
||||||
public void Prepare()
|
public void Prepare()
|
||||||
{
|
{
|
||||||
|
|
@ -70,7 +80,9 @@ public class EquipmentDrawer
|
||||||
.Max(i => ImGui.CalcTextSize($"{i.Item2.Name} ({i.Item2.ModelString})").X)
|
.Max(i => ImGui.CalcTextSize($"{i.Item2.Name} ({i.Item2.ModelString})").X)
|
||||||
/ ImGuiHelpers.GlobalScale;
|
/ ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
_requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale;
|
_requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale;
|
||||||
|
_advancedMaterialColor = ColorId.AdvancedDyeActive.Value();
|
||||||
|
_dragTarget = EquipSlot.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool VerifyRestrictedGear(EquipDrawData data)
|
private bool VerifyRestrictedGear(EquipDrawData data)
|
||||||
|
|
@ -87,21 +99,34 @@ public class EquipmentDrawer
|
||||||
if (_config.HideApplyCheckmarks)
|
if (_config.HideApplyCheckmarks)
|
||||||
equipDrawData.DisplayApplication = false;
|
equipDrawData.DisplayApplication = false;
|
||||||
|
|
||||||
using var id = ImRaii.PushId((int)equipDrawData.Slot);
|
using var id = ImUtf8.PushId((int)equipDrawData.Slot);
|
||||||
var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y };
|
var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y };
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
||||||
|
|
||||||
if (_config.SmallEquip)
|
if (_config.SmallEquip)
|
||||||
DrawEquipSmall(equipDrawData);
|
DrawEquipSmall(equipDrawData);
|
||||||
else if (!equipDrawData.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan))
|
|
||||||
DrawEquipArtisan(equipDrawData);
|
|
||||||
else
|
else
|
||||||
DrawEquipNormal(equipDrawData);
|
DrawEquipNormal(equipDrawData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DrawBonusItem(BonusDrawData bonusDrawData)
|
||||||
|
{
|
||||||
|
if (_config.HideApplyCheckmarks)
|
||||||
|
bonusDrawData.DisplayApplication = false;
|
||||||
|
|
||||||
|
using var id = ImUtf8.PushId(100 + (int)bonusDrawData.Slot);
|
||||||
|
var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y };
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
||||||
|
|
||||||
|
if (_config.SmallEquip)
|
||||||
|
DrawBonusItemSmall(bonusDrawData);
|
||||||
|
else
|
||||||
|
DrawBonusItemNormal(bonusDrawData);
|
||||||
|
}
|
||||||
|
|
||||||
public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
|
public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
|
||||||
{
|
{
|
||||||
if (mainhand.CurrentItem.PrimaryId.Id == 0)
|
if (mainhand.CurrentItem.PrimaryId.Id == 0 && !allWeapons)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_config.HideApplyCheckmarks)
|
if (_config.HideApplyCheckmarks)
|
||||||
|
|
@ -110,14 +135,12 @@ public class EquipmentDrawer
|
||||||
offhand.DisplayApplication = false;
|
offhand.DisplayApplication = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var id = ImRaii.PushId("Weapons");
|
using var id = ImUtf8.PushId("Weapons"u8);
|
||||||
var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y };
|
var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y };
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
||||||
|
|
||||||
if (_config.SmallEquip)
|
if (_config.SmallEquip)
|
||||||
DrawWeaponsSmall(mainhand, offhand, allWeapons);
|
DrawWeaponsSmall(mainhand, offhand, allWeapons);
|
||||||
else if (!mainhand.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan))
|
|
||||||
DrawWeaponsArtisan(mainhand, offhand);
|
|
||||||
else
|
else
|
||||||
DrawWeaponsNormal(mainhand, offhand, allWeapons);
|
DrawWeaponsNormal(mainhand, offhand, allWeapons);
|
||||||
}
|
}
|
||||||
|
|
@ -140,136 +163,31 @@ public class EquipmentDrawer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DrawAllStain(out StainId ret, bool locked)
|
public bool DrawAllStain(out StainIds ret, bool locked)
|
||||||
{
|
{
|
||||||
using var disabled = ImRaii.Disabled(locked);
|
using var disabled = ImRaii.Disabled(locked);
|
||||||
var change = _stainCombo.Draw("Dye All Slots", Stain.None.RgbaColor, string.Empty, false, false, MouseWheelType.None);
|
var change = _stainCombo.Draw("Dye All Slots", Stain.None.RgbaColor, string.Empty, false, false, MouseWheelType.None);
|
||||||
ret = Stain.None.RowIndex;
|
ret = StainIds.None;
|
||||||
if (change)
|
if (change)
|
||||||
if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out var stain))
|
if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out var stain))
|
||||||
ret = stain.RowIndex;
|
ret = StainIds.All(stain.RowIndex);
|
||||||
else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex)
|
else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex)
|
||||||
ret = Stain.None.RowIndex;
|
ret = StainIds.None;
|
||||||
|
|
||||||
if (!locked)
|
if (!locked)
|
||||||
{
|
{
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _config.DeleteDesignModifier.IsActive())
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && _config.DeleteDesignModifier.IsActive())
|
||||||
{
|
{
|
||||||
ret = Stain.None.RowIndex;
|
ret = StainIds.None;
|
||||||
change = true;
|
change = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip($"{_config.DeleteDesignModifier.ToString()} and Right-click to clear.");
|
ImUtf8.HoverTooltip($"{_config.DeleteDesignModifier.ToString()} and Right-click to clear.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return change;
|
return change;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Artisan
|
|
||||||
|
|
||||||
private void DrawEquipArtisan(EquipDrawData data)
|
|
||||||
{
|
|
||||||
DrawStainArtisan(data);
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawArmorArtisan(data);
|
|
||||||
if (!data.DisplayApplication)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawApply(data);
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawApplyStain(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawWeaponsArtisan(in EquipDrawData mainhand, in EquipDrawData offhand)
|
|
||||||
{
|
|
||||||
using (var _ = ImRaii.PushId(0))
|
|
||||||
{
|
|
||||||
DrawStainArtisan(mainhand);
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawWeapon(mainhand);
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var _ = ImRaii.PushId(1))
|
|
||||||
{
|
|
||||||
DrawStainArtisan(offhand);
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawWeapon(offhand);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
void DrawWeapon(in EquipDrawData current)
|
|
||||||
{
|
|
||||||
int setId = current.CurrentItem.PrimaryId.Id;
|
|
||||||
int type = current.CurrentItem.SecondaryId.Id;
|
|
||||||
int variant = current.CurrentItem.Variant.Id;
|
|
||||||
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
|
|
||||||
if (ImGui.InputInt("##setId", ref setId, 0, 0))
|
|
||||||
{
|
|
||||||
var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue);
|
|
||||||
if (newSetId.Id != current.CurrentItem.PrimaryId.Id)
|
|
||||||
current.SetItem(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
|
|
||||||
if (ImGui.InputInt("##type", ref type, 0, 0))
|
|
||||||
{
|
|
||||||
var newType = (SecondaryId)Math.Clamp(type, 0, ushort.MaxValue);
|
|
||||||
if (newType.Id != current.CurrentItem.SecondaryId.Id)
|
|
||||||
current.SetItem(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
|
|
||||||
if (ImGui.InputInt("##variant", ref variant, 0, 0))
|
|
||||||
{
|
|
||||||
var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue);
|
|
||||||
if (newVariant.Id != current.CurrentItem.Variant.Id)
|
|
||||||
current.SetItem(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, current.CurrentItem.SecondaryId,
|
|
||||||
newVariant));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Draw an input for stain that can set arbitrary values instead of choosing valid stains. </summary>
|
|
||||||
private static void DrawStainArtisan(EquipDrawData data)
|
|
||||||
{
|
|
||||||
int stainId = data.CurrentStain.Id;
|
|
||||||
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
|
|
||||||
if (!ImGui.InputInt("##stain", ref stainId, 0, 0))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue);
|
|
||||||
if (newStainId != data.CurrentStain.Id)
|
|
||||||
data.SetStain(newStainId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary>
|
|
||||||
private void DrawArmorArtisan(EquipDrawData data)
|
|
||||||
{
|
|
||||||
int setId = data.CurrentItem.PrimaryId.Id;
|
|
||||||
int variant = data.CurrentItem.Variant.Id;
|
|
||||||
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
|
|
||||||
if (ImGui.InputInt("##setId", ref setId, 0, 0))
|
|
||||||
{
|
|
||||||
var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue);
|
|
||||||
if (newSetId.Id != data.CurrentItem.PrimaryId.Id)
|
|
||||||
data.SetItem(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
|
|
||||||
if (ImGui.InputInt("##variant", ref variant, 0, 0))
|
|
||||||
{
|
|
||||||
var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue);
|
|
||||||
if (newVariant != data.CurrentItem.Variant)
|
|
||||||
data.SetItem(_items.Identify(data.Slot, data.CurrentItem.PrimaryId, newVariant));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Small
|
#region Small
|
||||||
|
|
||||||
|
|
@ -287,14 +205,31 @@ public class EquipmentDrawer
|
||||||
}
|
}
|
||||||
else if (equipDrawData.IsState)
|
else if (equipDrawData.IsState)
|
||||||
{
|
{
|
||||||
_advancedDyes.DrawButton(equipDrawData.Slot);
|
_advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VerifyRestrictedGear(equipDrawData))
|
if (VerifyRestrictedGear(equipDrawData))
|
||||||
label += " (Restricted)";
|
label += " (Restricted)";
|
||||||
|
|
||||||
|
DrawEquipLabel(equipDrawData is { IsDesign: true, HasAdvancedDyes: true }, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawBonusItemSmall(in BonusDrawData bonusDrawData)
|
||||||
|
{
|
||||||
|
ImGui.Dummy(new Vector2(StainId.NumStains * ImUtf8.FrameHeight + (StainId.NumStains - 1) * ImUtf8.ItemSpacing.X, ImUtf8.FrameHeight));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(label);
|
DrawBonusItem(bonusDrawData, out var label, true, false, false);
|
||||||
|
if (bonusDrawData.DisplayApplication)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawApply(bonusDrawData);
|
||||||
|
}
|
||||||
|
else if (bonusDrawData.IsState)
|
||||||
|
{
|
||||||
|
_advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawEquipLabel(bonusDrawData is { IsDesign: true, HasAdvancedDyes: true }, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawWeaponsSmall(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
|
private void DrawWeaponsSmall(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
|
||||||
|
|
@ -311,12 +246,12 @@ public class EquipmentDrawer
|
||||||
}
|
}
|
||||||
else if (mainhand.IsState)
|
else if (mainhand.IsState)
|
||||||
{
|
{
|
||||||
_advancedDyes.DrawButton(EquipSlot.MainHand);
|
_advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : 0u);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allWeapons)
|
if (allWeapons)
|
||||||
mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})";
|
mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})";
|
||||||
WeaponHelpMarker(mainhandLabel);
|
WeaponHelpMarker(mainhand is { IsDesign: true, HasAdvancedDyes: true }, mainhandLabel);
|
||||||
|
|
||||||
if (offhand.CurrentItem.Type is FullEquipType.Unknown)
|
if (offhand.CurrentItem.Type is FullEquipType.Unknown)
|
||||||
return;
|
return;
|
||||||
|
|
@ -333,10 +268,10 @@ public class EquipmentDrawer
|
||||||
}
|
}
|
||||||
else if (offhand.IsState)
|
else if (offhand.IsState)
|
||||||
{
|
{
|
||||||
_advancedDyes.DrawButton(EquipSlot.OffHand);
|
_advancedDyes.DrawButton(EquipSlot.OffHand, offhand.HasAdvancedDyes ? _advancedMaterialColor : 0u);
|
||||||
}
|
}
|
||||||
|
|
||||||
WeaponHelpMarker(offhandLabel);
|
WeaponHelpMarker(offhand is { IsDesign: true, HasAdvancedDyes: true }, offhandLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -357,8 +292,8 @@ public class EquipmentDrawer
|
||||||
DrawApply(equipDrawData);
|
DrawApply(equipDrawData);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
DrawEquipLabel(equipDrawData is { IsDesign: true, HasAdvancedDyes: true }, label);
|
||||||
ImGui.TextUnformatted(label);
|
|
||||||
DrawStain(equipDrawData, false);
|
DrawStain(equipDrawData, false);
|
||||||
if (equipDrawData.DisplayApplication)
|
if (equipDrawData.DisplayApplication)
|
||||||
{
|
{
|
||||||
|
|
@ -367,16 +302,36 @@ public class EquipmentDrawer
|
||||||
}
|
}
|
||||||
else if (equipDrawData.IsState)
|
else if (equipDrawData.IsState)
|
||||||
{
|
{
|
||||||
_advancedDyes.DrawButton(equipDrawData.Slot);
|
_advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VerifyRestrictedGear(equipDrawData))
|
if (VerifyRestrictedGear(equipDrawData))
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted("(Restricted)");
|
ImUtf8.Text("(Restricted)"u8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawBonusItemNormal(in BonusDrawData bonusDrawData)
|
||||||
|
{
|
||||||
|
bonusDrawData.CurrentItem.DrawIcon(_textures, _iconSize, bonusDrawData.Slot);
|
||||||
|
var right = ImGui.IsItemClicked(ImGuiMouseButton.Right);
|
||||||
|
var left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawBonusItem(bonusDrawData, out var label, false, right, left);
|
||||||
|
if (bonusDrawData.DisplayApplication)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawApply(bonusDrawData);
|
||||||
|
}
|
||||||
|
else if (bonusDrawData.IsState)
|
||||||
|
{
|
||||||
|
_advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawEquipLabel(bonusDrawData is { IsDesign: true, HasAdvancedDyes: true }, label);
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawWeaponsNormal(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
|
private void DrawWeaponsNormal(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
|
||||||
{
|
{
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
|
||||||
|
|
@ -385,7 +340,7 @@ public class EquipmentDrawer
|
||||||
mainhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.MainHand);
|
mainhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.MainHand);
|
||||||
var left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
|
var left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using (ImRaii.Group())
|
using (ImUtf8.Group())
|
||||||
{
|
{
|
||||||
DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, false, left);
|
DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, false, left);
|
||||||
if (mainhand.DisplayApplication)
|
if (mainhand.DisplayApplication)
|
||||||
|
|
@ -394,7 +349,8 @@ public class EquipmentDrawer
|
||||||
DrawApply(mainhand);
|
DrawApply(mainhand);
|
||||||
}
|
}
|
||||||
|
|
||||||
WeaponHelpMarker(mainhandLabel, allWeapons ? mainhand.CurrentItem.Type.ToName() : null);
|
WeaponHelpMarker(mainhand is { IsDesign: true, HasAdvancedDyes: true }, mainhandLabel,
|
||||||
|
allWeapons ? mainhand.CurrentItem.Type.ToName() : null);
|
||||||
|
|
||||||
DrawStain(mainhand, false);
|
DrawStain(mainhand, false);
|
||||||
if (mainhand.DisplayApplication)
|
if (mainhand.DisplayApplication)
|
||||||
|
|
@ -404,7 +360,7 @@ public class EquipmentDrawer
|
||||||
}
|
}
|
||||||
else if (mainhand.IsState)
|
else if (mainhand.IsState)
|
||||||
{
|
{
|
||||||
_advancedDyes.DrawButton(EquipSlot.MainHand);
|
_advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : 0u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -415,7 +371,7 @@ public class EquipmentDrawer
|
||||||
var right = ImGui.IsItemClicked(ImGuiMouseButton.Right);
|
var right = ImGui.IsItemClicked(ImGuiMouseButton.Right);
|
||||||
left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
|
left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using (ImRaii.Group())
|
using (ImUtf8.Group())
|
||||||
{
|
{
|
||||||
DrawOffhand(mainhand, offhand, out var offhandLabel, false, right, left);
|
DrawOffhand(mainhand, offhand, out var offhandLabel, false, right, left);
|
||||||
if (offhand.DisplayApplication)
|
if (offhand.DisplayApplication)
|
||||||
|
|
@ -424,7 +380,7 @@ public class EquipmentDrawer
|
||||||
DrawApply(offhand);
|
DrawApply(offhand);
|
||||||
}
|
}
|
||||||
|
|
||||||
WeaponHelpMarker(offhandLabel);
|
WeaponHelpMarker(offhand is { IsDesign: true, HasAdvancedDyes: true }, offhandLabel);
|
||||||
|
|
||||||
DrawStain(offhand, false);
|
DrawStain(offhand, false);
|
||||||
if (offhand.DisplayApplication)
|
if (offhand.DisplayApplication)
|
||||||
|
|
@ -432,28 +388,62 @@ public class EquipmentDrawer
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
DrawApplyStain(offhand);
|
DrawApplyStain(offhand);
|
||||||
}
|
}
|
||||||
else if (mainhand.IsState)
|
else if (offhand.IsState)
|
||||||
{
|
{
|
||||||
_advancedDyes.DrawButton(EquipSlot.OffHand);
|
_advancedDyes.DrawButton(EquipSlot.OffHand, offhand.HasAdvancedDyes ? _advancedMaterialColor : 0u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawStain(in EquipDrawData data, bool small)
|
private void DrawStain(in EquipDrawData data, bool small)
|
||||||
{
|
{
|
||||||
var found = _stainData.TryGetValue(data.CurrentStain, out var stain);
|
|
||||||
using var disabled = ImRaii.Disabled(data.Locked);
|
using var disabled = ImRaii.Disabled(data.Locked);
|
||||||
var change = small
|
var width = (_comboLength - ImUtf8.ItemInnerSpacing.X * (data.CurrentStains.Count - 1)) / data.CurrentStains.Count;
|
||||||
? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss)
|
foreach (var (stainId, index) in data.CurrentStains.WithIndex())
|
||||||
: _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength);
|
{
|
||||||
if (change)
|
using var id = ImUtf8.PushId(index);
|
||||||
if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain))
|
var found = _stainData.TryGetValue(stainId, out var stain);
|
||||||
data.SetStain(stain.RowIndex);
|
var change = small
|
||||||
else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex)
|
? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss)
|
||||||
data.SetStain(Stain.None.RowIndex);
|
: _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, width);
|
||||||
|
|
||||||
if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out _))
|
_itemCopy.HandleCopyPaste(data, index);
|
||||||
data.SetStain(Stain.None.RowIndex);
|
if (!change)
|
||||||
|
DrawStainDragDrop(data, index, stain, found);
|
||||||
|
|
||||||
|
if (index < data.CurrentStains.Count - 1)
|
||||||
|
ImUtf8.SameLineInner();
|
||||||
|
|
||||||
|
if (change)
|
||||||
|
if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain))
|
||||||
|
data.SetStains(data.CurrentStains.With(index, stain.RowIndex));
|
||||||
|
else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex)
|
||||||
|
data.SetStains(data.CurrentStains.With(index, Stain.None.RowIndex));
|
||||||
|
if (ResetOrClear(data.Locked, false, data.AllowRevert, true, stainId, data.GameStains[index], Stain.None.RowIndex,
|
||||||
|
out var newStain))
|
||||||
|
data.SetStains(data.CurrentStains.With(index, newStain));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawStainDragDrop(in EquipDrawData data, int index, Stain stain, bool found)
|
||||||
|
{
|
||||||
|
if (found)
|
||||||
|
{
|
||||||
|
using var dragSource = ImUtf8.DragDropSource();
|
||||||
|
if (dragSource.Success)
|
||||||
|
{
|
||||||
|
DragDropSource.SetPayload("stainDragDrop"u8);
|
||||||
|
_draggedStain = stain;
|
||||||
|
ImUtf8.Text($"Dragging {stain.Name}...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using var dragTarget = ImUtf8.DragDropTarget();
|
||||||
|
if (dragTarget.IsDropping("stainDragDrop"u8) && _draggedStain.HasValue)
|
||||||
|
{
|
||||||
|
data.SetStains(data.CurrentStains.With(index, _draggedStain.Value.RowIndex));
|
||||||
|
_draggedStain = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open)
|
private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open)
|
||||||
|
|
@ -468,16 +458,91 @@ public class EquipmentDrawer
|
||||||
using var disabled = ImRaii.Disabled(data.Locked);
|
using var disabled = ImRaii.Disabled(data.Locked);
|
||||||
var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
|
var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
|
||||||
_requiredComboWidth);
|
_requiredComboWidth);
|
||||||
|
DrawGearDragDrop(data);
|
||||||
|
if (change)
|
||||||
|
data.SetItem(combo.CurrentSelection);
|
||||||
|
else if (combo.CustomVariant.Id > 0)
|
||||||
|
data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant));
|
||||||
|
_itemCopy.HandleCopyPaste(data);
|
||||||
|
|
||||||
|
if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, ItemManager.NothingItem(data.Slot),
|
||||||
|
out var item))
|
||||||
|
data.SetItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawBonusItem(in BonusDrawData data, out string label, bool small, bool clear, bool open)
|
||||||
|
{
|
||||||
|
var combo = _bonusItemCombo[data.Slot.ToIndex()];
|
||||||
|
label = combo.Label;
|
||||||
|
if (!data.Locked && open)
|
||||||
|
UiHelpers.OpenCombo($"##{combo.Label}");
|
||||||
|
|
||||||
|
using var disabled = ImRaii.Disabled(data.Locked);
|
||||||
|
var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id.BonusItem,
|
||||||
|
small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
|
||||||
|
_requiredComboWidth);
|
||||||
|
if (ImGui.IsItemHovered() && ImGui.GetIO().KeyCtrl)
|
||||||
|
{
|
||||||
|
if (ImGui.IsKeyPressed(ImGuiKey.C))
|
||||||
|
_itemCopy.Copy(combo.CurrentSelection);
|
||||||
|
else if (ImGui.IsKeyPressed(ImGuiKey.V))
|
||||||
|
_itemCopy.Paste(data.Slot.ToEquipType(), data.SetItem);
|
||||||
|
}
|
||||||
|
|
||||||
if (change)
|
if (change)
|
||||||
data.SetItem(combo.CurrentSelection);
|
data.SetItem(combo.CurrentSelection);
|
||||||
else if (combo.CustomVariant.Id > 0)
|
else if (combo.CustomVariant.Id > 0)
|
||||||
data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant));
|
data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant));
|
||||||
|
|
||||||
if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, ItemManager.NothingItem(data.Slot),
|
if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, EquipItem.BonusItemNothing(data.Slot),
|
||||||
out var item))
|
out var item))
|
||||||
data.SetItem(item);
|
data.SetItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawGearDragDrop(in EquipDrawData data)
|
||||||
|
{
|
||||||
|
if (data.CurrentItem.Valid)
|
||||||
|
{
|
||||||
|
using var dragSource = ImUtf8.DragDropSource();
|
||||||
|
if (dragSource.Success)
|
||||||
|
{
|
||||||
|
DragDropSource.SetPayload("equipDragDrop"u8);
|
||||||
|
_draggedItem.Update(_items, data.CurrentItem, data.Slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using var dragTarget = ImUtf8.DragDropTarget();
|
||||||
|
if (!dragTarget)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var item = _draggedItem[data.Slot];
|
||||||
|
if (!item.Valid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_dragTarget = data.Slot;
|
||||||
|
if (!dragTarget.IsDropping("equipDragDrop"u8))
|
||||||
|
return;
|
||||||
|
|
||||||
|
data.SetItem(item);
|
||||||
|
_draggedItem.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void DrawDragDropTooltip()
|
||||||
|
{
|
||||||
|
var payload = ImGui.GetDragDropPayload().Handle;
|
||||||
|
if (payload is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)Unsafe.AsPointer(ref payload->DataType_0)).SequenceEqual("equipDragDrop"u8))
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var tt = ImUtf8.Tooltip();
|
||||||
|
if (_dragTarget is EquipSlot.Unknown)
|
||||||
|
ImUtf8.Text($"Dragging {_draggedItem.Dragged.Name}...");
|
||||||
|
else
|
||||||
|
ImUtf8.Text($"Converting to {_draggedItem[_dragTarget].Name}...");
|
||||||
|
}
|
||||||
|
|
||||||
private static bool ResetOrClear<T>(bool locked, bool clicked, bool allowRevert, bool allowClear,
|
private static bool ResetOrClear<T>(bool locked, bool clicked, bool allowRevert, bool allowClear,
|
||||||
in T currentItem, in T revertItem, in T clearItem, out T? item) where T : IEquatable<T>
|
in T currentItem, in T revertItem, in T clearItem, out T? item) where T : IEquatable<T>
|
||||||
{
|
{
|
||||||
|
|
@ -501,7 +566,7 @@ public class EquipmentDrawer
|
||||||
(false, true, _) => ("Right-click to clear.\nControl and mouse wheel to scroll.", clearItem, true),
|
(false, true, _) => ("Right-click to clear.\nControl and mouse wheel to scroll.", clearItem, true),
|
||||||
(false, false, _) => ("Control and mouse wheel to scroll.", default, false),
|
(false, false, _) => ("Control and mouse wheel to scroll.", default, false),
|
||||||
};
|
};
|
||||||
ImGuiUtil.HoverTooltip(tt);
|
ImUtf8.HoverTooltip(tt);
|
||||||
|
|
||||||
return clicked && valid;
|
return clicked && valid;
|
||||||
}
|
}
|
||||||
|
|
@ -526,8 +591,13 @@ public class EquipmentDrawer
|
||||||
if (combo.Draw(mainhand.CurrentItem.Name, mainhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
|
if (combo.Draw(mainhand.CurrentItem.Name, mainhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
|
||||||
_requiredComboWidth))
|
_requiredComboWidth))
|
||||||
changedItem = combo.CurrentSelection;
|
changedItem = combo.CurrentSelection;
|
||||||
else if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem,
|
else if (combo.CustomVariant.Id > 0 && (drawAll || ItemData.ConvertWeaponId(combo.CustomSetId) == mainhand.CurrentItem.Type))
|
||||||
default, out var c))
|
changedItem = _items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant);
|
||||||
|
_itemCopy.HandleCopyPaste(mainhand);
|
||||||
|
DrawGearDragDrop(mainhand);
|
||||||
|
|
||||||
|
if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem,
|
||||||
|
default, out var c))
|
||||||
changedItem = c;
|
changedItem = c;
|
||||||
|
|
||||||
if (changedItem != null)
|
if (changedItem != null)
|
||||||
|
|
@ -543,8 +613,9 @@ public class EquipmentDrawer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unknown && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
if (unknown)
|
||||||
ImGui.SetTooltip("The weapon type could not be identified, thus changing it to other weapons of that type is not possible.");
|
ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled,
|
||||||
|
"The weapon type could not be identified, thus changing it to other weapons of that type is not possible."u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawOffhand(in EquipDrawData mainhand, in EquipDrawData offhand, out string label, bool small, bool clear, bool open)
|
private void DrawOffhand(in EquipDrawData mainhand, in EquipDrawData offhand, out string label, bool small, bool clear, bool open)
|
||||||
|
|
@ -557,13 +628,17 @@ public class EquipmentDrawer
|
||||||
|
|
||||||
label = combo.Label;
|
label = combo.Label;
|
||||||
var locked = offhand.Locked
|
var locked = offhand.Locked
|
||||||
|| !_gPose.InGPose && (offhand.CurrentItem.Type is FullEquipType.Unknown || mainhand.CurrentItem.Type is FullEquipType.Unknown);
|
|| !_gPose.InGPose && (offhand.CurrentItem.Type.IsUnknown() || mainhand.CurrentItem.Type.IsUnknown());
|
||||||
using var disabled = ImRaii.Disabled(locked);
|
using var disabled = ImRaii.Disabled(locked);
|
||||||
if (!locked && open)
|
if (!locked && open)
|
||||||
UiHelpers.OpenCombo($"##{combo.Label}");
|
UiHelpers.OpenCombo($"##{combo.Label}");
|
||||||
if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
|
if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
|
||||||
_requiredComboWidth))
|
_requiredComboWidth))
|
||||||
offhand.SetItem(combo.CurrentSelection);
|
offhand.SetItem(combo.CurrentSelection);
|
||||||
|
else if (combo.CustomVariant.Id > 0 && ItemData.ConvertWeaponId(combo.CustomSetId) == offhand.CurrentItem.Type)
|
||||||
|
offhand.SetItem(_items.Identify(mainhand.Slot, combo.CustomSetId, combo.CustomWeaponId, combo.CustomVariant));
|
||||||
|
_itemCopy.HandleCopyPaste(offhand);
|
||||||
|
DrawGearDragDrop(offhand);
|
||||||
|
|
||||||
var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem);
|
var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem);
|
||||||
if (ResetOrClear(locked, clear, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item))
|
if (ResetOrClear(locked, clear, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item))
|
||||||
|
|
@ -577,9 +652,16 @@ public class EquipmentDrawer
|
||||||
data.SetApplyItem(enabled);
|
data.SetApplyItem(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void DrawApply(in BonusDrawData data)
|
||||||
|
{
|
||||||
|
if (UiHelpers.DrawCheckbox($"##apply{data.Slot}", "Apply this bonus item when applying the Design.", data.CurrentApply, out var enabled,
|
||||||
|
data.Locked))
|
||||||
|
data.SetApplyItem(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
private static void DrawApplyStain(in EquipDrawData data)
|
private static void DrawApplyStain(in EquipDrawData data)
|
||||||
{
|
{
|
||||||
if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this item when applying the Design.", data.CurrentApplyStain,
|
if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this dye to the item when applying the Design.", data.CurrentApplyStain,
|
||||||
out var enabled,
|
out var enabled,
|
||||||
data.Locked))
|
data.Locked))
|
||||||
data.SetApplyStain(enabled);
|
data.SetApplyStain(enabled);
|
||||||
|
|
@ -587,14 +669,14 @@ public class EquipmentDrawer
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private static void WeaponHelpMarker(string label, string? type = null)
|
private void WeaponHelpMarker(bool hasAdvancedDyes, string label, string? type = null)
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGuiComponents.HelpMarker(
|
ImGuiComponents.HelpMarker(
|
||||||
"Changing weapons to weapons of different types can cause crashes, freezes, soft- and hard locks and cheating, "
|
"Changing weapons to weapons of different types can cause crashes, freezes, soft- and hard locks and cheating, "
|
||||||
+ "thus it is only allowed to change weapons to other weapons of the same type.");
|
+ "thus it is only allowed to change weapons to other weapons of the same type.");
|
||||||
ImGui.SameLine();
|
DrawEquipLabel(hasAdvancedDyes, label);
|
||||||
ImGui.TextUnformatted(label);
|
|
||||||
if (type == null)
|
if (type == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -602,4 +684,17 @@ public class EquipmentDrawer
|
||||||
pos.Y += ImGui.GetFrameHeightWithSpacing();
|
pos.Y += ImGui.GetFrameHeightWithSpacing();
|
||||||
ImGui.GetWindowDrawList().AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), $"({type})");
|
ImGui.GetWindowDrawList().AddText(pos, ImGui.GetColorU32(ImGuiCol.Text), $"({type})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void DrawEquipLabel(bool hasAdvancedDyes, string label)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Text, _advancedMaterialColor, hasAdvancedDyes))
|
||||||
|
{
|
||||||
|
ImUtf8.Text(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAdvancedDyes)
|
||||||
|
ImUtf8.HoverTooltip("This design has advanced dyes setup for this slot."u8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Glamourer.Unlocks;
|
using Glamourer.Unlocks;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.GameData.DataContainers;
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using Glamourer.Unlocks;
|
using Glamourer.Unlocks;
|
||||||
using ImGuiNET;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.Sheets;
|
||||||
using OtterGui;
|
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Extensions;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Text;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
@ -75,44 +76,41 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
|
||||||
var ret = ImGui.Selectable(name, selected);
|
var ret = ImGui.Selectable(name, selected);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
|
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
|
||||||
ImGuiUtil.RightAlign($"({obj.ModelString})");
|
ImUtf8.TextRightAligned($"({obj.PrimaryId.Id}-{obj.Variant.Id})");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool IsVisible(int globalIndex, LowerString filter)
|
protected override bool IsVisible(int globalIndex, LowerString filter)
|
||||||
=> base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString());
|
=> base.IsVisible(globalIndex, filter) || Items[globalIndex].ModelString.StartsWith(filter.Lower);
|
||||||
|
|
||||||
protected override string ToString(EquipItem obj)
|
protected override string ToString(EquipItem obj)
|
||||||
=> obj.Name;
|
=> obj.Name;
|
||||||
|
|
||||||
private static string GetLabel(IDataManager gameData, EquipSlot slot)
|
private static string GetLabel(IDataManager gameData, EquipSlot slot)
|
||||||
{
|
{
|
||||||
var sheet = gameData.GetExcelSheet<Addon>()!;
|
var sheet = gameData.GetExcelSheet<Addon>();
|
||||||
|
|
||||||
return slot switch
|
return slot switch
|
||||||
{
|
{
|
||||||
EquipSlot.Head => sheet.GetRow(740)?.Text.ToString() ?? "Head",
|
EquipSlot.Head => sheet.TryGetRow(740, out var text) ? text.Text.ToString() : "Head",
|
||||||
EquipSlot.Body => sheet.GetRow(741)?.Text.ToString() ?? "Body",
|
EquipSlot.Body => sheet.TryGetRow(741, out var text) ? text.Text.ToString() : "Body",
|
||||||
EquipSlot.Hands => sheet.GetRow(742)?.Text.ToString() ?? "Hands",
|
EquipSlot.Hands => sheet.TryGetRow(742, out var text) ? text.Text.ToString() : "Hands",
|
||||||
EquipSlot.Legs => sheet.GetRow(744)?.Text.ToString() ?? "Legs",
|
EquipSlot.Legs => sheet.TryGetRow(744, out var text) ? text.Text.ToString() : "Legs",
|
||||||
EquipSlot.Feet => sheet.GetRow(745)?.Text.ToString() ?? "Feet",
|
EquipSlot.Feet => sheet.TryGetRow(745, out var text) ? text.Text.ToString() : "Feet",
|
||||||
EquipSlot.Ears => sheet.GetRow(746)?.Text.ToString() ?? "Ears",
|
EquipSlot.Ears => sheet.TryGetRow(746, out var text) ? text.Text.ToString() : "Ears",
|
||||||
EquipSlot.Neck => sheet.GetRow(747)?.Text.ToString() ?? "Neck",
|
EquipSlot.Neck => sheet.TryGetRow(747, out var text) ? text.Text.ToString() : "Neck",
|
||||||
EquipSlot.Wrists => sheet.GetRow(748)?.Text.ToString() ?? "Wrists",
|
EquipSlot.Wrists => sheet.TryGetRow(748, out var text) ? text.Text.ToString() : "Wrists",
|
||||||
EquipSlot.RFinger => sheet.GetRow(749)?.Text.ToString() ?? "Right Ring",
|
EquipSlot.RFinger => sheet.TryGetRow(749, out var text) ? text.Text.ToString() : "Right Ring",
|
||||||
EquipSlot.LFinger => sheet.GetRow(750)?.Text.ToString() ?? "Left Ring",
|
EquipSlot.LFinger => sheet.TryGetRow(750, out var text) ? text.Text.ToString() : "Left Ring",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IReadOnlyList<EquipItem> GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot)
|
private static List<EquipItem> GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot)
|
||||||
{
|
{
|
||||||
var nothing = ItemManager.NothingItem(slot);
|
var nothing = ItemManager.NothingItem(slot);
|
||||||
if (!items.ItemData.ByType.TryGetValue(slot.ToEquipType(), out var list))
|
if (!items.ItemData.ByType.TryGetValue(slot.ToEquipType(), out var list))
|
||||||
return new[]
|
return [nothing];
|
||||||
{
|
|
||||||
nothing,
|
|
||||||
};
|
|
||||||
|
|
||||||
var enumerable = list.AsEnumerable();
|
var enumerable = list.AsEnumerable();
|
||||||
if (slot.IsEquipment())
|
if (slot.IsEquipment())
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue