Compare commits

...

223 commits

Author SHA1 Message Date
Actions User
5b6517aae8 [CI] Updating repo.json for 1.5.1.5 2025-11-28 22:09:08 +00:00
Ottermandias
aadcf771e7 1.5.1.5 2025-11-28 23:06:53 +01:00
Ottermandias
bf4673a1d9 Save Remove and Inherit for mod associations. 2025-11-07 23:30:18 +01:00
Ottermandias
76b214c643 Fix try-on interaction with Penumbra for facewear. 2025-11-07 23:30:18 +01:00
Ottermandias
434a5a809e Make old backup files overwrite instead of throwing. 2025-11-07 23:30:18 +01:00
Actions User
88fe25f69e [CI] Updating repo.json for testing_1.5.1.4 2025-10-23 15:40:25 +00:00
Ottermandias
bef1e39ac3 Update Libraries. 2025-10-23 17:37:27 +02:00
Ottermandias
c604d5dbe5 Add DeletePlayerState. 2025-10-23 17:25:59 +02:00
Ottermandias
a0d912a395 Fix issue with reverting state of unavailable actors. 2025-10-23 17:25:59 +02:00
Actions User
a56852f918 [CI] Updating repo.json for 1.5.1.3 2025-10-07 11:02:02 +00:00
Ottermandias
4228fc1b89 fu 2025-10-07 12:59:39 +02:00
Ottermandias
e644b8da28 Fix span issue. 2025-10-07 12:53:55 +02:00
Ottermandias
ace3a8f755 Again. 2025-10-07 12:43:40 +02:00
Ottermandias
76ed347cbf Update signatures. 2025-10-07 12:28:18 +02:00
Ottermandias
c3469a1687 Fix facewear advanced dyes, fix backup service not running in task, update libraries. 2025-09-28 23:55:44 +02:00
Ottermandias
0a9693daea
Update CodeService.cs 2025-09-15 20:29:13 +02:00
Actions User
414bd8bee7 [CI] Updating repo.json for 1.5.1.2 2025-08-28 16:52:43 +00:00
Ottermandias
8e1745d67a Once more with feeling 2025-08-28 18:47:57 +02:00
Actions User
889f01a724 [CI] Updating repo.json for 1.5.1.1 2025-08-26 09:58:08 +00:00
Ottermandias
6e62905fa7 Fix staging incompatibility with CS. 2025-08-26 11:55:00 +02:00
Actions User
654787fa0d [CI] Updating repo.json for 1.5.1.0 2025-08-25 08:45:28 +00:00
Ottermandias
835ba23935 1.5.1.0 2025-08-25 10:43:14 +02:00
Ottermandias
389a8781d6 Update library. 2025-08-25 10:39:38 +02:00
Actions User
3eabe591df [CI] Updating repo.json for testing_1.5.0.9 2025-08-24 13:59:02 +00:00
Ottermandias
487d3b9399 Update PCP Service. 2025-08-24 15:49:29 +02:00
Actions User
4d4e4669dd [CI] Updating repo.json for testing_1.5.0.8 2025-08-22 18:34:49 +00:00
Ottermandias
fb065549e9 Add PCP Service. 2025-08-22 20:32:32 +02:00
Ottermandias
2c34154915 Update API. 2025-08-22 20:32:32 +02:00
Actions User
3704051b0f [CI] Updating repo.json for 1.5.0.7 2025-08-17 08:45:55 +00:00
Ottermandias
b2b8f2b6eb Make glamourers visor toggle trigger static visors. (?!?) 2025-08-17 10:43:26 +02:00
Ottermandias
22e6c0655b Add ear state when toggling meta application via button. 2025-08-16 11:59:08 +02:00
Ottermandias
bb2ba0cf11 Add glasses to advanced dye slot combo. 2025-08-16 11:59:08 +02:00
Ottermandias
e854386b23 Update OtterGui 2025-08-16 11:59:08 +02:00
Actions User
49d24df2e7 [CI] Updating repo.json for 1.5.0.6 2025-08-12 12:53:44 +00:00
Ottermandias
c9b291c2f3 Add new parameter to LoadWeapon hook. 2025-08-12 14:47:09 +02:00
Actions User
65f789880d [CI] Updating repo.json for 1.5.0.5 2025-08-12 10:32:03 +00:00
Ottermandias
abf998a727 Update GameData 2025-08-12 12:29:55 +02:00
Ottermandias
4cc191cb25 Add viera ear visibility to application rules. 2025-08-11 20:53:44 +02:00
Ottermandias
dc431c10a5 Add chat command to toggle automation. 2025-08-11 19:59:27 +02:00
Ottermandias
26862ba78f Update ChangedEquipData. 2025-08-11 19:59:27 +02:00
Actions User
e4374337f2 [CI] Updating repo.json for 1.5.0.4 2025-08-09 18:50:50 +00:00
Ottermandias
240c889fff Fix changed equipment access to use new size. 2025-08-09 20:48:46 +02:00
Ottermandias
612cd31c3e Fix some caravans. 2025-08-09 19:02:32 +02:00
Actions User
304b362002 [CI] Updating repo.json for 1.5.0.3 2025-08-09 16:55:27 +00:00
Ottermandias
8f34f197d0 Another try. 2025-08-09 18:53:22 +02:00
Actions User
1c97266a93 [CI] Updating repo.json for 1.5.0.2 2025-08-09 16:41:32 +00:00
Ottermandias
a9caddafd5 Maybe fix design crashes. 2025-08-09 18:39:14 +02:00
Actions User
34bf95dddb [CI] Updating repo.json for 1.5.0.1 2025-08-09 11:03:48 +00:00
Ottermandias
4761b8f584 Need staging again... 2025-08-09 13:01:48 +02:00
Ottermandias
0d94aae732 Fix popups not working early. 2025-08-09 12:11:42 +02:00
Ottermandias
e83f328cdc Fix resizable child. 2025-08-09 11:58:52 +02:00
Ottermandias
52fd29c478 Woops 2025-08-09 11:52:17 +02:00
Ottermandias
a8b79993df Make QDB ignore close hotkey. 2025-08-09 11:47:11 +02:00
Ottermandias
98574558e5 Set Repo API level to 13 and remove stg from future releases. 2025-08-08 23:07:08 +02:00
Actions User
557cbf23ce [CI] Updating repo.json for 1.5.0.0 2025-08-08 21:06:10 +00:00
Ottermandias
56753ae7ba Use staging for release. 2025-08-08 23:03:58 +02:00
Ottermandias
b66df624f7 Update Gamedata. 2025-08-08 23:01:54 +02:00
Ottermandias
be78f1447b 1.5.0.0 2025-08-08 15:56:08 +02:00
Ottermandias
97e32a3cb7 Update GameData. 2025-08-08 15:48:19 +02:00
Ottermandias
4472920536 Move Viera Ears sig to gamedata. 2025-08-08 15:46:24 +02:00
Ottermandias
ac6a726f57 Remaining API13 updates. 2025-08-08 15:39:05 +02:00
Ottermandias
00d550f4fe Add viera ear flags 2025-08-08 15:38:51 +02:00
Ottermandias
0f98fac157 Add auto-locking to design defaults. 2025-08-08 15:36:47 +02:00
Ottermandias
c25f0f72db Update for ottergui. 2025-08-08 15:36:14 +02:00
Karou
72e05e23bc stupid detached head.... 2025-08-07 21:53:01 -04:00
Karou
2c3bed6ba5 Api 13 grunt work 2025-08-07 21:50:23 -04:00
Ottermandias
d6df9885dc Update GameData. 2025-08-02 00:07:38 +02:00
Ottermandias
e7936500e0
Merge pull request #111 from CordeliaMist/Fix-RevertNotFiringFinalize
Correct StateFinalized not invoking in certain areas
2025-07-28 18:00:26 +02:00
Cordelia Mist
4ef4e65d46 Ensure that reverts called by API invoke their StateFinalizationType upon completing a reset state. 2025-07-28 08:43:13 -07:00
Cordelia Mist
40b4a8fd7a Ensure Revert via Command invokes a StateFinalization type for Reset 2025-07-28 08:37:57 -07:00
Actions User
8a1f03c272 [CI] Updating repo.json for 1.4.0.3 2025-07-14 15:12:49 +00:00
Ottermandias
8ed479eddf Fix issue with invalid bonus items. 2025-07-14 17:09:42 +02:00
Ottermandias
c0a278ca2c Make designs update on mousewheel. 2025-06-15 23:34:00 +02:00
Actions User
2e9a7004c6 [CI] Updating repo.json for 1.4.0.2 2025-06-13 15:21:04 +00:00
Ottermandias
75c76a92b9 Optimize design combos and file system. 2025-06-13 17:17:58 +02:00
Ottermandias
282935c6d6 Allow drag & drop of equipment pieces. 2025-06-03 18:40:41 +02:00
Actions User
d7b189b714 [CI] Updating repo.json for 1.4.0.1 2025-05-29 00:57:24 +00:00
Ottermandias
e3da3f356c Merge branch 'main' of github.com:Ottermandias/Glamourer 2025-05-29 02:55:23 +02:00
Ottermandias
66bed4217f Fix staining template reading. 2025-05-29 02:55:20 +02:00
Ottermandias
56bbf6593a Fix button counting. 2025-05-29 02:55:11 +02:00
Actions User
b8e1e7c384 [CI] Updating repo.json for 1.4.0.0 2025-05-28 11:58:09 +00:00
Ottermandias
a0d2c39f45 1.4.0.0 2025-05-28 13:56:06 +02:00
Ottermandias
07df3186c2 Better. 2025-05-27 14:38:04 +02:00
Ottermandias
5b59e74417 Use CS ReadStainingTemplate again. 2025-05-27 12:06:29 +02:00
Ottermandias
b4485f028d Batch some multi-design changes to skip unnecessary computations. 2025-05-23 15:21:14 +02:00
Ottermandias
74674cfa0c Make combos start from preview selection if possible. 2025-05-23 10:47:47 +02:00
Ottermandias
f192c17c9b Allow drag & drop for color buttons. 2025-05-21 17:46:56 +02:00
Ottermandias
aa1ac29182 Make design selector resizable. 2025-05-21 17:16:55 +02:00
Ottermandias
081ac6bf8b Split ResetAdvanced into two parts. 2025-05-21 17:09:41 +02:00
Ottermandias
e4b32343ae Update libraries. 2025-05-21 17:08:17 +02:00
Ottermandias
c93370ec92 Again. 2025-05-09 00:06:19 +02:00
Ottermandias
9abd7f2767 Make Dynamis IPC working. 2025-05-08 23:44:16 +02:00
Ottermandias
8a9877bb01 Add testing Dynamis IPC for debugging. 2025-05-06 00:31:26 +02:00
Ottermandias
c1e1476fa6 Fix some issues with glamourer not searching mods by name. 2025-05-06 00:30:47 +02:00
Ottermandias
b1abbb8e77 Support model id input in weapon combo, support middle-mouse pipette. 2025-05-06 00:30:27 +02:00
Ottermandias
fcb0660def Implement new IPC methods and API 1.6 2025-05-04 00:36:39 +02:00
Ottermandias
a6073e2a42 Update old PR slightly. 2025-05-03 23:36:03 +02:00
Ottermandias
2c87077918
Merge pull request #107 from Diorik/random_no_repeat
Prevent Random Repeats
2025-05-03 23:29:29 +02:00
Ottermandias
4e0a9f62b9
Merge pull request #109 from Caraxi/api-fix
Fix `SetMetaState` and `SetMetaStateName` not being registered
2025-05-02 17:28:10 +02:00
Actions User
39636f5293 [CI] Updating repo.json for 1.3.8.6 2025-05-01 21:04:23 +00:00
Ottermandias
b53124e708 Temporarily use custom address for ReadStainingTemplate. 2025-05-01 23:02:08 +02:00
Ottermandias
9a684c9ff5 Implement GetDesignListExtended. 2025-04-29 23:50:29 +02:00
Ottermandias
c7d1620c1e Update GameData. 2025-04-19 23:09:48 +02:00
Ottermandias
325b54031c Update libraries. 2025-04-19 22:28:06 +02:00
Caraxi
155a9d6266 Fix SetMetaState and SetMetaStateName not being registered 2025-04-15 14:26:30 +09:30
Actions User
4f6fb44f79 [CI] Updating repo.json for 1.3.8.5 2025-04-10 14:42:24 +00:00
Ottermandias
bfce99859f Update GameData. 2025-04-10 16:39:04 +02:00
Ottermandias
6c556d6a61 Update API. 2025-04-10 16:20:15 +02:00
Ottermandias
7ed42005dd Force higher Penumbra API version and use better IPC for cutscene parents and game objects. 2025-04-09 15:11:04 +02:00
Ottermandias
aad978f5f6 Pass actor in GearsetDataLoaded instead of looking it up. 2025-04-08 22:53:59 +02:00
Ottermandias
c0ad4aab51 Update for new ActorObjectManager. 2025-04-05 18:48:39 +02:00
Ottermandias
8fe0ac8195 Fix weapon color set issue. 2025-04-05 15:12:27 +02:00
Ottermandias
46f8818cee Add Incognito Modifier. 2025-04-04 22:35:48 +02:00
Actions User
118f51cc64 [CI] Updating repo.json for 1.3.8.4 2025-04-02 23:07:10 +00:00
Ottermandias
096d82741d Fix previous fix for weapons. 2025-04-03 01:04:58 +02:00
Actions User
b98cb31fd2 [CI] Updating repo.json for 1.3.8.3 2025-04-02 21:47:24 +00:00
Ottermandias
90813ce030 Update GameData. 2025-04-02 23:45:28 +02:00
Ottermandias
95bc52b2bc Check for valid humanity. 2025-04-02 23:45:07 +02:00
Ottermandias
d79e4b5853
Merge pull request #108 from keifufu/fix-linux-build
Fix linux build
2025-03-31 13:03:43 +02:00
keifufu
a40a6905be
newline be gone 2025-03-31 13:02:19 +02:00
keifufu
6b3a64ce14
fix linux build 2025-03-31 13:00:51 +02:00
Actions User
4fca1600ca [CI] Updating repo.json for 1.3.8.2 2025-03-29 17:06:25 +00:00
Ottermandias
b1e65e6f9d Fix some offsets. 2025-03-29 18:04:18 +01:00
Actions User
381b23fe0c [CI] Updating repo.json for 1.3.8.1 2025-03-28 16:29:25 +00:00
Ottermandias
782c4446b2 Update GameData. 2025-03-28 17:25:34 +01:00
Ottermandias
76eaa75d04 Update Penumbra.Api. 2025-03-28 17:23:25 +01:00
Ottermandias
361ed536a3 Fix issue with NPC automation due to missing job detection. 2025-03-28 17:22:57 +01:00
Ottermandias
b1d00e9812 Update GameData. 2025-03-28 15:54:26 +01:00
Ottermandias
b0abf865cb Change build step. 2025-03-28 15:54:10 +01:00
Ottermandias
d398381b52 Revert Dalamud staging on release, and update api level. 2025-03-28 14:17:16 +01:00
Actions User
d75d70bee5 [CI] Updating repo.json for 1.3.8.0 2025-03-28 13:16:18 +00:00
Ottermandias
296f1e90b5 🤷 2025-03-28 14:13:34 +01:00
Ottermandias
9d3dfbbece use staging build for release for now. 2025-03-28 14:05:53 +01:00
Ottermandias
d6d592f099 1.3.8.0 2025-03-28 13:58:19 +01:00
Ottermandias
00cb1b6643 Use CS sig. 2025-03-28 13:52:15 +01:00
Ottermandias
22babad789 Update. 2025-03-27 18:11:08 +01:00
Ottermandias
18ff905746 Add a chat command to clear temporary settings made by Glamourer. 2025-03-11 18:06:22 +01:00
Ottermandias
fd0d761b92 Fix small issue with invisible customizations applying. 2025-03-11 00:58:05 +01:00
Actions User
750d4f9eca [CI] Updating repo.json for 1.3.7.1 2025-03-10 22:59:41 +00:00
Ottermandias
25517525c9 Keep temporary mod settings manually applied by Glamourer when redrawing with automation changes. 2025-03-10 23:55:29 +01:00
Actions User
99a8e1e8c5 [CI] Updating repo.json for 1.3.7.0 2025-03-09 22:17:20 +00:00
Ottermandias
381b2a1b8b 1.3.7.0 2025-03-09 23:14:19 +01:00
Ottermandias
3dee511b9a Update some obsoletes. 2025-03-09 23:13:33 +01:00
Ottermandias
e93c3b7bb8 Update submodules. 2025-03-09 23:12:21 +01:00
Ottermandias
773682838e Make customize buttons not change advanced customization to On by default. 2025-03-09 13:37:27 +01:00
Ottermandias
c9f00c6369 Mark designs containing advanced dyes, mod associations or links in automation. 2025-03-04 16:28:48 +01:00
Ottermandias
2026069ed3 Make Glamourer able to export and import color sets to and from Penumbra, maybe. 2025-03-04 15:34:39 +01:00
Actions User
ae093506c1 [CI] Updating repo.json for testing_1.3.6.6 2025-03-03 23:21:09 +00:00
Ottermandias
87f1b613f9 Add application shortcuts to multi design panel. 2025-03-04 00:18:41 +01:00
Ottermandias
71e15474b2 Add deletion of advanced dyes to multi design selection. 2025-03-03 18:32:52 +01:00
Ottermandias
3fd6108fa1 Add buttons to remove, enable and disable multiple advanced dyes at once in a design. 2025-03-03 18:12:42 +01:00
Ottermandias
b9e4c144c2 Add some buttons to set design application rules to some presets. 2025-03-03 17:55:38 +01:00
Ottermandias
6e685b96d1 Make all panels configurable. 2025-03-03 16:31:15 +01:00
Ottermandias
c96f009fb4 Slightly speed up QDB drawing. 2025-03-03 16:31:15 +01:00
Actions User
3112079776 [CI] Updating repo.json for testing_1.3.6.5 2025-03-02 12:40:58 +00:00
Ottermandias
7015737d88 Meh. 2025-03-02 13:38:58 +01:00
Ottermandias
94f6e870e6 Update Submodules. 2025-03-02 13:33:41 +01:00
Ottermandias
528aae7eee Remove PalettePlusChecker and config options to disable Advanced Customization and Advanced Dyes. 2025-03-02 13:24:51 +01:00
Ottermandias
425c9471fb Highlight materials with advanced dyes even if inactive, allow removing inactive. 2025-02-23 23:19:24 +01:00
Ottermandias
0c8110e15e Highlight existing advanced dyes in state and design. 2025-02-21 00:10:36 +01:00
Actions User
8a7ec45bbf [CI] Updating repo.json for testing_1.3.6.3 2025-02-19 23:19:21 +00:00
Ottermandias
c1f84b4303 Differentiate between temporary settings through manual and automatic application. 2025-02-20 00:16:55 +01:00
Ottermandias
5a9e9513f4 Skip automatic updates from temporary settings when applied by design. 2025-02-20 00:16:55 +01:00
Ottermandias
9e7679e70f Move check for valid model to actor display instead of identifier list. 2025-02-20 00:16:55 +01:00
Actions User
e5b2114ac2 [CI] Updating repo.json for testing_1.3.6.2 2025-02-18 14:13:13 +00:00
Ottermandias
1168460942 Add button to reset glamourer temporary settings to qdb. 2025-02-17 17:40:56 +01:00
Actions User
f7d6e75a9b [CI] Updating repo.json for testing_1.3.6.1 2025-02-13 15:43:08 +00:00
Ottermandias
d56c2db547 Add more mod association and modded utility. 2025-02-13 16:32:23 +01:00
Ottermandias
ab2a3f5bd9 Add new colors. 2025-02-13 16:30:35 +01:00
Ottermandias
4748d1e211 Allow filtered item names. 2025-02-13 16:30:24 +01:00
Diorik
5ca151b675 PreventRandom use WeakReference, reroll rand instead of changing list 2025-02-06 13:29:47 -06:00
Diorik
67fd65d366 Make PreventRandomc figurable, clean up logic
Will no longer hold design reference or make redundant copy of list
2025-02-06 12:49:23 -06:00
Diorik
45981f2fee
Merge branch 'Ottermandias:main' into random_no_repeat 2025-02-06 11:26:07 -06:00
Ottermandias
1df2a46884 Update Submodule Versions. 2025-02-06 17:00:31 +01:00
Actions User
2c7b7d59be [CI] Updating repo.json for 1.3.6.0 2025-02-06 15:55:34 +00:00
Ottermandias
d849506ecd 1.3.6.0 2025-02-06 16:52:59 +01:00
Ottermandias
47f2cfc210
Merge pull request #105 from Diorik/redraw_command
Add "Reset Design" command
2025-02-06 16:38:10 +01:00
Ottermandias
5d1c65cce7 Fix overwriting with current state keeping old advanced dyes. 2025-02-06 16:27:10 +01:00
Diorik
cf308fc118 Prevent repeating random design
Cache the last selected random design and prevent it from being chosen again.
2025-02-04 01:54:34 -06:00
Diorik
87016419c5 Add "Reset Design" command
A new command to reapply automation while resetting the random design regardless of settings.
2025-02-04 00:46:12 -06:00
Ottermandias
da46705b52 Use new settings API. 2025-01-31 16:06:50 +01:00
Ottermandias
9a1cf3d9e6 Better 2025-01-30 22:45:59 +01:00
Ottermandias
8e0908dbf7 Add more settings to multi design panel. 2025-01-30 22:39:57 +01:00
Ottermandias
1f255a98e6 Fix issue with scaling after zone change. 2025-01-30 13:55:28 +01:00
Ottermandias
2a067ef60b Accept all existing facepaints. 2025-01-30 13:36:13 +01:00
Actions User
b3afa2067c [CI] Updating repo.json for testing_1.3.5.5 2025-01-25 13:02:30 +00:00
Ottermandias
630c4dd894 Update Submodules. 2025-01-25 14:00:36 +01:00
Ottermandias
f1fa5c2fa9 Merge branch 'refs/heads/CordeliaMist/PR-SetMetaState' 2025-01-24 23:29:03 +01:00
Ottermandias
f6c9ecad60 Move MetaFlag completely to API, rename, cleanup. 2025-01-24 23:28:23 +01:00
Cordelia Mist
5eb545f62d Namechange: SetMetaState -> SetMeta
Help clear up confusion on if it is in the StateApi section or the ItemsApi section (Which it is currently in)
2025-01-24 13:09:49 -08:00
Cordelia Mist
0f127a557d Introduced locks, if nessisary for change. 2025-01-24 11:44:09 -08:00
Cordelia Mist
611200d311 Added setMetaState by name 2025-01-24 11:43:26 -08:00
Cordelia Mist
0ed4603ba5 Corrected Paramater fields to include ApplyFlags and removed unessisary using. 2025-01-24 11:42:52 -08:00
Cordelia Mist
439849edfa Append SetMetaState with object index. 2025-01-24 11:35:53 -08:00
Cordelia Mist
091aadd4a6 Add Yield return Indices List for setter helper 2025-01-24 11:25:40 -08:00
Cordelia Mist
356b814102 Append conversion for SetMetaFlag -> MetaIndex for help in moving down the API call to the state editor call. 2025-01-24 11:14:50 -08:00
Actions User
cd6a6a462d [CI] Updating repo.json for testing_1.3.5.4 2025-01-24 16:54:22 +00:00
Ottermandias
0123fe1fbd Increment minor API version. 2025-01-24 17:52:06 +01:00
Ottermandias
8b518d3c6f Merge branch 'refs/heads/CordeliaMist/main' 2025-01-24 17:51:11 +01:00
Ottermandias
d9f9937d41 Continue renames, some cleanup. 2025-01-24 17:50:58 +01:00
Ottermandias
7be283ca30 Apply API renames. 2025-01-24 16:50:29 +01:00
Ottermandias
748c324acf Merge branch 'main' into CordeliaMist/main 2025-01-24 15:13:46 +01:00
Ottermandias
2916669319 Change EquipGearsetInternal to CS. 2025-01-24 15:06:29 +01:00
Ottermandias
30468e0b09 Fix checkbox not working. 2025-01-24 15:04:47 +01:00
Ottermandias
70918e5393 Add AutoDesignSet toggle for resetting settings. 2025-01-24 15:04:47 +01:00
Cordelia Mist
c43ce9d978 Updated new functionality and internal gearset to use FFXIVClientStructs member functions and implemented structs, corrected remaining spacing issues. 2025-01-21 10:47:12 -08:00
Cordelia
2e11481276
Merge branch 'Ottermandias:main' into main 2025-01-20 11:33:55 -08:00
Actions User
f1b335e794 [CI] Updating repo.json for testing_1.3.5.3 2025-01-20 16:49:52 +00:00
Ottermandias
96dca5dbe7 Cherry-Pick from ebdfd3f8 to use EquipGearSetInternal. 2025-01-20 17:47:36 +01:00
Ottermandias
4db0822443 Update GameData. 2025-01-20 15:40:14 +01:00
Ottermandias
cdc67a035c Check for ENPC player copies and treat them as transformations. 2025-01-20 15:40:14 +01:00
Ottermandias
8add6e5519 Fix some .net update issues. 2025-01-20 15:40:14 +01:00
Cordelia Mist
ebdfd3f8bc Correct spacing and formatting further to align with main Glamourer branch preferences. 2025-01-19 09:50:34 -08:00
Cordelia Mist
1cd8e5fb7e Corrected comments on StateApi for PR 2025-01-19 09:35:10 -08:00
Cordelia Mist
1d185e9bfe Added Proper Unsubsribe from OnStateUpdated.
Ensures that ReapplyAutomation is called from OnAutomationChange when change occurs.
Reformatted temporary Signature locations and comments to align with the structure of the respective classes.
2025-01-19 09:07:43 -08:00
Cordelia Mist
9c57935a87 removed unessisary usings and corrected gearsetDataLoaded stateListener ref. 2025-01-17 18:52:16 -08:00
Cordelia Mist
8b609e5f05 corrected comments and formatting to reflect Glamourer's main branch. 2025-01-17 18:43:05 -08:00
Cordelia Mist
e1a41b5f3c Implements true endpoints for all glamourer operations, also correctly marks reverts and gearsets. Replaced back excessive logging to maintain with logging formats expected by glamourer. 2025-01-17 18:43:05 -08:00
Cordelia Mist
c605d19510 Progress made. May be successful! 2025-01-17 18:39:55 -08:00
Actions User
8160f420db [CI] Updating repo.json for testing_1.3.5.2 2025-01-15 17:05:57 +00:00
Ottermandias
60a1ee728a Use current temporary settings for mod associations if they exist. 2025-01-14 14:51:36 +01:00
Ottermandias
c83ddf054a Add counts to multi design selection. 2025-01-12 00:04:00 +01:00
Actions User
02bfd17794 [CI] Updating repo.json for 1.3.5.1 2025-01-11 12:49:40 +00:00
180 changed files with 4795 additions and 2061 deletions

View file

@ -15,12 +15,12 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.x.x'
dotnet-version: '9.x.x'
- name: Restore dependencies
run: dotnet restore
- name: Download Dalamud
run: |
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip
Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev"
- name: Build
run: |

View file

@ -15,7 +15,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.x.x'
dotnet-version: '9.x.x'
- name: Restore dependencies
run: dotnet restore
- name: Download Dalamud

@ -1 +1 @@
Subproject commit b1b90e6ecfeee76a12cb27793753fa87af21083f
Subproject commit 59a7ab5fa9941eb754757b62e4cb189e455e9514

View file

@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.github\workflows\release.yml = .github\workflows\release.yml
Glamourer\Glamourer.json = Glamourer\Glamourer.json
repo.json = repo.json
.github\workflows\test_release.yml = .github\workflows\test_release.yml
EndProjectSection
@ -29,30 +30,30 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.Build.0 = Release|Any CPU
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.Build.0 = Release|Any CPU
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.Build.0 = Release|Any CPU
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.Build.0 = Release|Any CPU
{EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.Build.0 = Release|Any CPU
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B46691B-FAB2-4CC3-9B89-C8B91A590F47}.Release|Any CPU.Build.0 = Release|Any CPU
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|x64
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|x64
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|x64
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.Build.0 = Release|x64
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.ActiveCfg = Debug|x64
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Debug|Any CPU.Build.0 = Debug|x64
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.ActiveCfg = Release|x64
{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}.Release|Any CPU.Build.0 = Release|x64
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.ActiveCfg = Debug|x64
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Debug|Any CPU.Build.0 = Debug|x64
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.ActiveCfg = Release|x64
{C0A2FAF8-C3AE-4B7B-ADDB-4AAC1A855428}.Release|Any CPU.Build.0 = Release|x64
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.ActiveCfg = Debug|x64
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Debug|Any CPU.Build.0 = Debug|x64
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.ActiveCfg = Release|x64
{AAFE22E7-0F9B-462A-AAA3-6EE3B268F3F8}.Release|Any CPU.Build.0 = Release|x64
{EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.ActiveCfg = Debug|x64
{EF233CE2-F243-449E-BE05-72B9D110E419}.Debug|Any CPU.Build.0 = Debug|x64
{EF233CE2-F243-449E-BE05-72B9D110E419}.Release|Any CPU.ActiveCfg = Release|x64
{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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -1,33 +1,43 @@
using Glamourer.Api.Enums;
using Glamourer.Designs;
using Glamourer.State;
using OtterGui;
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;
using ObjectManager = Glamourer.Interop.ObjectManager;
namespace Glamourer.Api;
public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService
public class ApiHelpers(ActorObjectManager objects, StateManager stateManager, ActorManager actors) : IApiService
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal IEnumerable<ActorState> FindExistingStates(string actorName)
internal IEnumerable<ActorState> FindExistingStates(string actorName, ushort worldId = ushort.MaxValue)
{
if (actorName.Length == 0 || !ByteString.FromString(actorName, out var byteString))
yield break;
foreach (var state in stateManager.Values.Where(state
=> state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString))
yield return state;
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[objectIndex];
var actor = objects.Objects[objectIndex];
var identifier = actor.GetIdentifier(actors);
if (!identifier.IsValid)
{
@ -42,7 +52,7 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal ActorState? FindState(int objectIndex)
{
var actor = objects[objectIndex];
var actor = objects.Objects[objectIndex];
var identifier = actor.GetIdentifier(actors);
if (identifier.IsValid && stateManager.GetOrCreate(identifier, actor, out var state))
return state;
@ -73,10 +83,8 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM
if (objectName.Length == 0 || !ByteString.FromString(objectName, out var byteString))
return [];
objects.Update();
return stateManager.Values.Where(state => state.Identifier.Type is IdentifierType.Player && state.Identifier.PlayerName == byteString)
.Concat(objects.Identifiers
.Concat(objects
.Where(kvp => kvp.Key is { IsValid: true, Type: IdentifierType.Player } && kvp.Key.PlayerName == byteString)
.SelectWhere(kvp =>
{

View file

@ -2,15 +2,32 @@
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) : IGlamourerApiDesigns, IApiService
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);
@ -33,7 +50,7 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager
{
var once = (flags & ApplyFlag.Once) != 0;
var settings = new ApplySettings(Source: once ? StateSource.IpcManual : StateSource.IpcFixed, Key: key, MergeLinks: true,
ResetMaterials: !once && key != 0);
ResetMaterials: !once && key != 0, IsFinal: true);
using var restrict = ApiHelpers.Restrict(design, flags);
stateManager.ApplyDesign(state, design, settings);
@ -66,4 +83,56 @@ public class DesignsApi(ApiHelpers helpers, DesignManager designs, StateManager
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;
}

View file

@ -6,7 +6,7 @@ namespace Glamourer.Api;
public class GlamourerApi(DesignsApi designs, StateApi state, ItemsApi items) : IGlamourerApi, IApiService
{
public const int CurrentApiVersionMajor = 1;
public const int CurrentApiVersionMinor = 3;
public const int CurrentApiVersionMinor = 7;
public (int Major, int Minor) ApiVersion
=> (CurrentApiVersionMajor, CurrentApiVersionMinor);

View file

@ -2,7 +2,6 @@ using Dalamud.Plugin;
using Glamourer.Api.Api;
using Glamourer.Api.Helpers;
using OtterGui.Services;
using System.Reflection.Emit;
using Glamourer.Api.Enums;
namespace Glamourer.Api;
@ -25,8 +24,14 @@ public sealed class IpcProviders : IDisposable, IApiService
IpcSubscribers.ApiVersion.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),
@ -37,6 +42,8 @@ public sealed class IpcProviders : IDisposable, IApiService
(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),
@ -47,11 +54,13 @@ public sealed class IpcProviders : IDisposable, IApiService
IpcSubscribers.RevertStateName.Provider(pi, api.State),
IpcSubscribers.UnlockState.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.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();

View file

@ -96,9 +96,9 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager
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 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))
{
@ -115,6 +115,72 @@ public class ItemsApi(ApiHelpers helpers, ItemManager itemManager, StateManager
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);

View file

@ -4,51 +4,52 @@ using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Designs.History;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using Glamourer.State;
using Newtonsoft.Json.Linq;
using OtterGui.Services;
using Penumbra.GameData.Interop;
using ObjectManager = Glamourer.Interop.ObjectManager;
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 Configuration _config;
private readonly AutoDesignApplier _autoDesigns;
private readonly ObjectManager _objects;
private readonly StateChanged _stateChanged;
private readonly GPoseService _gPose;
private readonly ApiHelpers _helpers;
private readonly StateManager _stateManager;
private readonly DesignConverter _converter;
private readonly AutoDesignApplier _autoDesigns;
private readonly ActorObjectManager _objects;
private readonly StateChanged _stateChanged;
private readonly StateFinalized _stateFinalized;
private readonly GPoseService _gPose;
public StateApi(ApiHelpers helpers,
StateManager stateManager,
DesignConverter converter,
Configuration config,
AutoDesignApplier autoDesigns,
ObjectManager objects,
ActorObjectManager objects,
StateChanged stateChanged,
StateFinalized stateFinalized,
GPoseService gPose)
{
_helpers = helpers;
_stateManager = stateManager;
_converter = converter;
_config = config;
_autoDesigns = autoDesigns;
_objects = objects;
_stateChanged = stateChanged;
_gPose = gPose;
_helpers = helpers;
_stateManager = stateManager;
_converter = converter;
_autoDesigns = autoDesigns;
_objects = objects;
_stateChanged = stateChanged;
_stateFinalized = stateFinalized;
_gPose = gPose;
_stateChanged.Subscribe(OnStateChanged, Events.StateChanged.Priority.GlamourerIpc);
_gPose.Subscribe(OnGPoseChange, GPoseService.Priority.GlamourerIpc);
_stateFinalized.Subscribe(OnStateFinalized, Events.StateFinalized.Priority.StateApi);
_gPose.Subscribe(OnGPoseChange, GPoseService.Priority.StateApi);
}
public void Dispose()
{
_stateChanged.Unsubscribe(OnStateChanged);
_stateFinalized.Unsubscribe(OnStateFinalized);
_gPose.Unsubscribe(OnGPoseChange);
}
@ -199,6 +200,27 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
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));
@ -214,7 +236,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
if (!state.CanUnlock(key))
return ApiHelpers.Return(GlamourerApiEc.InvalidKey, args);
RevertToAutomation(_objects[objectIndex], state, key, flags);
RevertToAutomation(_objects.Objects[objectIndex], state, key, flags);
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
@ -248,15 +270,16 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
return ApiHelpers.Return(GlamourerApiEc.Success, args);
}
public event Action<nint>? StateChanged;
public event Action<IntPtr, StateChangeType>? StateChangedWithType;
public event Action<bool>? GPoseChanged;
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);
ResetMaterials: !once && key != 0, IsFinal: true);
_stateManager.ApplyDesign(state, design, settings);
ApiHelpers.Lock(state, key, flags);
}
@ -266,15 +289,9 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
var source = (flags & ApplyFlag.Once) != 0 ? StateSource.IpcManual : StateSource.IpcFixed;
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);
break;
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);
@ -282,7 +299,6 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
private GlamourerApiEc RevertToAutomation(ActorState state, uint key, ApplyFlag flags)
{
_objects.Update();
if (!_objects.TryGetValue(state.Identifier, out var actors) || !actors.Valid)
return GlamourerApiEc.ActorNotFound;
@ -295,8 +311,8 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
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, out var forcedRedraw);
_stateManager.ReapplyState(actor, state, forcedRedraw, source);
_autoDesigns.ReapplyAutomation(actor, state.Identifier, state, true, false, out var forcedRedraw);
_stateManager.ReapplyAutomationState(actor, state, forcedRedraw, true, source);
ApiHelpers.Lock(state, key, flags);
}
@ -308,7 +324,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
if (!state.CanUnlock(key))
return (GlamourerApiEc.InvalidKey, null);
return (GlamourerApiEc.Success, _converter.ShareJObject(state, ApplicationRules.AllWithConfig(_config)));
return (GlamourerApiEc.Success, _converter.ShareJObject(state, ApplicationRules.All));
}
private (GlamourerApiEc, string?) ConvertBase64(ActorState? state, uint key)
@ -333,6 +349,7 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
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);
@ -341,4 +358,12 @@ public sealed class StateApi : IGlamourerApiState, IApiService, IDisposable
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);
}
}

View file

@ -1,4 +1,5 @@
using Glamourer.Designs;
using Glamourer.Api.Enums;
using Glamourer.Designs;
using Glamourer.GameData;
using Penumbra.GameData.Enums;
@ -37,7 +38,7 @@ public static class ApplicationTypeExtensions
var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0;
var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0;
var crestFlags = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0;
var metaFlags = (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.Customizations) ? MetaFlag.Wetness : 0);
var bonusFlags = type.HasFlag(ApplicationType.Armor) ? BonusExtensions.All : 0;
@ -46,7 +47,13 @@ public static class ApplicationTypeExtensions
}
public static ApplicationCollection ApplyWhat(this ApplicationType type, IDesignStandIn designStandIn)
=> designStandIn is not DesignBase design ? type.Collection() : type.Collection().Restrict(design.Application);
{
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 ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet;

View file

@ -11,29 +11,28 @@ using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
using ObjectManager = Glamourer.Interop.ObjectManager;
namespace Glamourer.Automation;
public sealed class AutoDesignApplier : IDisposable
{
private readonly Configuration _config;
private readonly AutoDesignManager _manager;
private readonly StateManager _state;
private readonly JobService _jobs;
private readonly EquippedGearset _equippedGearset;
private readonly ActorManager _actors;
private readonly AutomationChanged _event;
private readonly ObjectManager _objects;
private readonly WeaponLoading _weapons;
private readonly HumanModelList _humans;
private readonly DesignMerger _designMerger;
private readonly IClientState _clientState;
private readonly Configuration _config;
private readonly AutoDesignManager _manager;
private readonly StateManager _state;
private readonly JobService _jobs;
private readonly EquippedGearset _equippedGearset;
private readonly ActorManager _actors;
private readonly AutomationChanged _event;
private readonly ActorObjectManager _objects;
private readonly WeaponLoading _weapons;
private readonly HumanModelList _humans;
private readonly DesignMerger _designMerger;
private readonly IClientState _clientState;
private readonly JobChangeState _jobChangeState;
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)
{
_config = config;
@ -154,7 +153,6 @@ public sealed class AutoDesignApplier : IDisposable
if (newSet is not { Enabled: true })
return;
_objects.Update();
foreach (var id in newSet.Identifiers)
{
if (_objects.TryGetValue(id, out var data))
@ -163,7 +161,7 @@ public sealed class AutoDesignApplier : IDisposable
{
Reduce(data.Objects[0], state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw);
foreach (var actor in data.Objects)
_state.ReapplyState(actor, forcedRedraw, StateSource.Fixed);
_state.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Fixed);
}
}
else if (_objects.TryGetValueAllWorld(id, out data) || _objects.TryGetValueNonOwned(id, out data))
@ -174,7 +172,7 @@ public sealed class AutoDesignApplier : IDisposable
if (_state.GetOrCreate(specificId, actor, out var state))
{
Reduce(actor, state, newSet, _config.RespectManualOnAutomationUpdate, false, true, out var forcedRedraw);
_state.ReapplyState(actor, forcedRedraw, StateSource.Fixed);
_state.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Fixed);
}
}
}
@ -225,7 +223,7 @@ public sealed class AutoDesignApplier : IDisposable
_state.ReapplyState(actor, forcedRedraw, StateSource.Fixed);
}
public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset, out bool forcedRedraw)
public void ReapplyAutomation(Actor actor, ActorIdentifier identifier, ActorState state, bool reset, bool forcedNew, out bool forcedRedraw)
{
forcedRedraw = false;
if (!_config.EnableAutoDesigns)
@ -235,7 +233,7 @@ public sealed class AutoDesignApplier : IDisposable
_state.ResetState(state, StateSource.Game);
if (GetPlayerSet(identifier, out var set))
Reduce(actor, state, set, false, false, false, out forcedRedraw);
Reduce(actor, state, set, false, false, forcedNew, out forcedRedraw);
}
public bool Reduce(Actor actor, ActorIdentifier identifier, [NotNullWhen(true)] out ActorState? state)
@ -293,8 +291,16 @@ public sealed class AutoDesignApplier : IDisposable
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);
if (set.ResetTemporarySettings)
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;

View file

@ -10,6 +10,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Extensions;
using OtterGui.Filesystem;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
@ -234,6 +235,22 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
_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)
{
var newDesign = new AutoDesign()

View file

@ -8,6 +8,7 @@ using Glamourer.Services;
using Newtonsoft.Json;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Extensions;
using OtterGui.Filesystem;
using OtterGui.Widgets;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
@ -31,6 +32,7 @@ public class DefaultDesignSettings
public bool ResetAdvancedDyes = false;
public bool ShowQuickDesignBar = true;
public bool ResetTemporarySettings = false;
public bool Locked = false;
}
public class Configuration : IPluginConfiguration, ISavable
@ -38,35 +40,40 @@ public class Configuration : IPluginConfiguration, ISavable
[JsonIgnore]
public readonly EphemeralConfig Ephemeral;
public bool UseRestrictedGearProtection { get; set; } = false;
public bool OpenFoldersByDefault { get; set; } = false;
public bool AutoRedrawEquipOnChanges { get; set; } = false;
public bool EnableAutoDesigns { get; set; } = true;
public bool HideApplyCheckmarks { get; set; } = false;
public bool SmallEquip { get; set; } = false;
public bool UnlockedItemMode { get; set; } = false;
public byte DisableFestivals { get; set; } = 1;
public bool EnableGameContextMenu { get; set; } = true;
public bool HideWindowInCutscene { get; set; } = false;
public bool ShowAutomationSetEditing { get; set; } = true;
public bool ShowAllAutomatedApplicationRules { get; set; } = true;
public bool ShowUnlockedItemWarnings { get; set; } = true;
public bool RevertManualChangesOnZoneChange { get; set; } = false;
public bool ShowQuickBarInTabs { get; set; } = true;
public bool OpenWindowAtStart { get; set; } = false;
public bool ShowWindowWhenUiHidden { get; set; } = false;
public bool UseAdvancedParameters { get; set; } = true;
public bool UseAdvancedDyes { get; set; } = true;
public bool KeepAdvancedDyesAttached { get; set; } = true;
public bool ShowPalettePlusImport { get; set; } = true;
public bool UseFloatForColors { get; set; } = true;
public bool UseRgbForColors { get; set; } = true;
public bool ShowColorConfig { get; set; } = true;
public bool ChangeEntireItem { get; set; } = false;
public bool AlwaysApplyAssociatedMods { get; set; } = false;
public bool UseTemporarySettings { get; set; } = true;
public bool AllowDoubleClickToApply { get; set; } = false;
public bool RespectManualOnAutomationUpdate { get; set; } = false;
public bool AttachToPcp { get; set; } = true;
public bool UseRestrictedGearProtection { get; set; } = false;
public bool OpenFoldersByDefault { get; set; } = false;
public bool AutoRedrawEquipOnChanges { get; set; } = false;
public bool EnableAutoDesigns { get; set; } = true;
public bool HideApplyCheckmarks { get; set; } = false;
public bool SmallEquip { get; set; } = false;
public bool UnlockedItemMode { get; set; } = false;
public byte DisableFestivals { get; set; } = 1;
public bool EnableGameContextMenu { get; set; } = true;
public bool HideWindowInCutscene { get; set; } = false;
public bool ShowAutomationSetEditing { get; set; } = true;
public bool ShowAllAutomatedApplicationRules { get; set; } = true;
public bool ShowUnlockedItemWarnings { get; set; } = true;
public bool RevertManualChangesOnZoneChange { get; set; } = false;
public bool ShowQuickBarInTabs { get; set; } = true;
public bool OpenWindowAtStart { get; set; } = false;
public bool ShowWindowWhenUiHidden { get; set; } = false;
public bool KeepAdvancedDyesAttached { get; set; } = true;
public bool ShowPalettePlusImport { get; set; } = true;
public bool UseFloatForColors { get; set; } = true;
public bool UseRgbForColors { get; set; } = true;
public bool ShowColorConfig { get; set; } = true;
public bool ChangeEntireItem { get; set; } = false;
public bool AlwaysApplyAssociatedMods { get; set; } = true;
public bool UseTemporarySettings { get; set; } = true;
public bool AllowDoubleClickToApply { get; set; } = false;
public bool RespectManualOnAutomationUpdate { get; set; } = false;
public bool PreventRandomRepeats { get; set; } = false;
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();
@ -74,10 +81,11 @@ public class Configuration : IPluginConfiguration, ISavable
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; } =
QdbButtons.ApplyDesign | QdbButtons.RevertAll | QdbButtons.RevertAutomation | QdbButtons.RevertAdvanced;
QdbButtons.ApplyDesign | QdbButtons.RevertAll | QdbButtons.RevertAutomation | QdbButtons.RevertAdvancedDyes;
[JsonConverter(typeof(SortModeConverter))]
[JsonProperty(Order = int.MaxValue)]
@ -154,7 +162,7 @@ public class Configuration : IPluginConfiguration, ISavable
public static class Constants
{
public const int CurrentVersion = 7;
public const int CurrentVersion = 8;
public static readonly ISortMode<Design>[] ValidSortModes =
[

View 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);
}
}
}

View file

@ -1,5 +1,6 @@
using Glamourer.Api.Enums;
using Glamourer.GameData;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using Penumbra.GameData.Enums;
namespace Glamourer.Designs;
@ -18,13 +19,13 @@ public record struct ApplicationCollection(
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);
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);
CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState | MetaFlag.EarState);
public static ApplicationCollection FromKeys()
=> (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch
@ -46,7 +47,7 @@ public record struct ApplicationCollection(
Equip = 0;
BonusItem = 0;
Crest = 0;
Meta &= ~(MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState);
Meta &= ~(MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState | MetaFlag.EarState);
}
public void RemoveCustomize()

View file

@ -1,6 +1,7 @@
using Glamourer.GameData;
using Glamourer.Api.Enums;
using Glamourer.GameData;
using Glamourer.State;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using Penumbra.GameData.Enums;
namespace Glamourer.Designs;
@ -18,9 +19,6 @@ public readonly struct ApplicationRules(ApplicationCollection application, bool
public static ApplicationRules AllButParameters(ActorState state)
=> new(ApplicationCollection.All with { Parameters = ComputeParameters(state.ModelData, state.BaseData, All.Parameters) }, true);
public static ApplicationRules AllWithConfig(Configuration config)
=> new(ApplicationCollection.All with { Parameters = config.UseAdvancedParameters ? All.Parameters : 0 }, config.UseAdvancedDyes);
public static ApplicationRules NpcFromModifiers(bool ctrl, bool shift)
{
var equip = ctrl || !shift ? EquipFlagExtensions.All : 0;

View file

@ -100,7 +100,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
public new JObject JsonSerialize()
{
var ret = new JObject()
var ret = new JObject
{
["FileVersion"] = FileVersion,
["Identifier"] = Identifier,
@ -131,12 +131,17 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn
var ret = new JArray();
foreach (var (mod, settings) in AssociatedMods)
{
var obj = new JObject()
var obj = new JObject
{
["Name"] = mod.Name,
["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)
{
obj["Priority"] = settings.Priority;

View file

@ -40,7 +40,8 @@ public class DesignBase
}
/// <summary> Used when importing .cma or .chara files. </summary>
internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags, BonusItemFlag bonusFlags)
internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags,
BonusItemFlag bonusFlags)
{
_designData = designData;
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
@ -195,6 +196,9 @@ public class DesignBase
return true;
}
public IEnumerable<string> FilteredItemNames
=> _designData.FilteredItemNames(Application.Equip, Application.BonusItem);
internal FlagRestrictionResetter TemporarilyRestrictApplication(ApplicationCollection restrictions)
=> new(this, restrictions);
@ -251,9 +255,10 @@ public class DesignBase
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["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyMeta(MetaIndex.VisorState)).ToJObject("IsToggled", "Apply");
ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyMeta(MetaIndex.WeaponState)).ToJObject("Show", "Apply");
ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply");
ret["VieraEars"] = new QuadBool(_designData.AreEarsVisible(), DoApplyMeta(MetaIndex.EarState)).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
{
@ -600,6 +605,10 @@ public class DesignBase
metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse);
design.SetApplyMeta(MetaIndex.VisorState, metaValue.Enabled);
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)

View file

@ -1,5 +1,7 @@
using Glamourer.Services;
using Glamourer.Api.Enums;
using Glamourer.Services;
using OtterGui;
using OtterGui.Extensions;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;

View file

@ -3,11 +3,12 @@ using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility.Raii;
using Glamourer.Gui;
using Glamourer.Services;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Extensions;
namespace Glamourer.Designs;

View file

@ -46,20 +46,9 @@ public unsafe struct DesignData
public DesignData()
{ }
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
public readonly bool ContainsName(LowerString name)
=> name.IsContained(_nameHead)
|| 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)
|| name.IsContained(_nameGlasses);
=> ItemNames.Any(name.IsContained);
public readonly StainIds Stain(EquipSlot slot)
{
@ -76,6 +65,57 @@ public unsafe struct DesignData
public readonly bool Crest(CrestFlag 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
=> _typeMainhand;
@ -247,6 +287,7 @@ public unsafe struct DesignData
MetaIndex.HatState => IsHatVisible(),
MetaIndex.VisorState => IsVisorToggled(),
MetaIndex.WeaponState => IsWeaponVisible(),
MetaIndex.EarState => AreEarsVisible(),
_ => false,
};
@ -257,6 +298,7 @@ public unsafe struct DesignData
MetaIndex.HatState => SetHatVisible(value),
MetaIndex.VisorState => SetVisor(value),
MetaIndex.WeaponState => SetWeaponVisible(value),
MetaIndex.EarState => SetEarsVisible(value),
_ => false,
};
@ -300,6 +342,9 @@ public unsafe struct DesignData
public readonly bool IsWeaponVisible()
=> (_states & 0x08) == 0x08;
public readonly bool AreEarsVisible()
=> (_states & 0x10) == 0x00;
public bool SetWeaponVisible(bool value)
{
if (value == IsWeaponVisible())
@ -309,6 +354,15 @@ public unsafe struct DesignData
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)
{
foreach (var slot in EquipSlotExtensions.EqdpSlots)
@ -346,6 +400,7 @@ public unsafe struct DesignData
SetHatVisible(true);
SetWeaponVisible(true);
SetEarsVisible(true);
SetVisor(false);
fixed (uint* ptr = _itemIds)
{

View file

@ -41,11 +41,11 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
public struct CreationDate : ISortMode<Design>
{
public string Name
=> "Creation Date (Older First)";
public ReadOnlySpan<byte> Name
=> "Creation Date (Older First)"u8;
public string Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date.";
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their creation date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.CreationDate));
@ -53,11 +53,11 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
public struct UpdateDate : ISortMode<Design>
{
public string Name
=> "Update Date (Older First)";
public ReadOnlySpan<byte> Name
=> "Update Date (Older First)"u8;
public string Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date.";
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their last update date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderBy(l => l.Value.LastEdit));
@ -65,11 +65,11 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
public struct InverseCreationDate : ISortMode<Design>
{
public string Name
=> "Creation Date (Newer First)";
public ReadOnlySpan<byte> Name
=> "Creation Date (Newer First)"u8;
public string Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date.";
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse creation date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.CreationDate));
@ -77,11 +77,11 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
public struct InverseUpdateDate : ISortMode<Design>
{
public string Name
=> "Update Date (Newer First)";
public ReadOnlySpan<byte> Name
=> "Update Date (Newer First)"u8;
public string Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date.";
public ReadOnlySpan<byte> Description
=> "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse last update date."u8;
public IEnumerable<IPath> GetChildren(Folder f)
=> f.GetSubFolders().Cast<IPath>().Concat(f.GetLeaves().OrderByDescending(l => l.Value.LastEdit));
@ -114,14 +114,14 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
return;
case DesignChanged.Type.Deleted:
if (FindLeaf(design, out var leaf1))
if (TryGetValue(design, out var leaf1))
Delete(leaf1);
return;
case DesignChanged.Type.ReloadedAll:
Reload();
return;
case DesignChanged.Type.Renamed when (data as RenameTransaction?)?.Old is { } oldName:
if (!FindLeaf(design, out var leaf2))
if (!TryGetValue(design, out var leaf2))
return;
var old = oldName.FixName();
@ -150,15 +150,6 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
? (string.Empty, false)
: (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)
{
if (oldPaths.Count == 0)

View file

@ -3,14 +3,16 @@ using Glamourer.Designs.History;
using Glamourer.Designs.Links;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop.Material;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
using OtterGui.Extensions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
namespace Glamourer.Designs;
public sealed class DesignManager : DesignEditor
@ -109,6 +111,7 @@ public sealed class DesignManager : DesignEditor
QuickDesign = Config.DefaultDesignSettings.ShowQuickDesignBar,
ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings,
};
design.SetWriteProtected(Config.DefaultDesignSettings.Locked);
Designs.Add(design);
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
SaveService.ImmediateSave(design);
@ -133,6 +136,7 @@ public sealed class DesignManager : DesignEditor
ResetTemporarySettings = Config.DefaultDesignSettings.ResetTemporarySettings,
};
design.SetWriteProtected(Config.DefaultDesignSettings.Locked);
Designs.Add(design);
Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design.");
SaveService.ImmediateSave(design);
@ -152,6 +156,7 @@ public sealed class DesignManager : DesignEditor
Name = actualName,
Index = Designs.Count,
};
design.SetWriteProtected(Config.DefaultDesignSettings.Locked);
Designs.Add(design);
Glamourer.Log.Debug(
$"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}.");
@ -224,7 +229,7 @@ public sealed class DesignManager : DesignEditor
design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray();
design.LastEdit = DateTimeOffset.UtcNow;
var idx = design.Tags.IndexOf(tag);
var idx = design.Tags.AsEnumerable().IndexOf(tag);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}.");
DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, new TagAddedTransaction(tag, idx));
@ -257,7 +262,7 @@ public sealed class DesignManager : DesignEditor
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags.");
DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design,
new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.IndexOf(newTag)));
new TagChangedTransaction(oldTag, newTag, tagIdx, design.Tags.AsEnumerable().IndexOf(newTag)));
}
/// <summary> Add an associated mod to a design. </summary>
@ -448,6 +453,39 @@ public sealed class DesignManager : DesignEditor
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
public void UndoDesignChange(Design design)
@ -519,7 +557,7 @@ public sealed class DesignManager : DesignEditor
try
{
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.");
}
catch (Exception ex)

View file

@ -1,8 +1,8 @@
using Glamourer.Api.Enums;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using Glamourer.State;
using OtterGui.Services;
using Penumbra.GameData.Interop;
namespace Glamourer.Designs.History;
@ -152,7 +152,7 @@ public class EditorHistory : IDisposable, IService
{
if (!_stateEntries.TryGetValue(state, out var list))
{
list = new Queue();
list = [];
_stateEntries.Add(state, list);
}
@ -163,7 +163,7 @@ public class EditorHistory : IDisposable, IService
{
if (!_designEntries.TryGetValue(design, out var list))
{
list = new Queue();
list = [];
_designEntries.Add(design, list);
}

View file

@ -13,7 +13,8 @@ public readonly record struct ApplySettings(
bool FromJobChange = false,
bool UseSingleSource = false,
bool MergeLinks = false,
bool ResetMaterials = false)
bool ResetMaterials = false,
bool IsFinal = false)
{
public static readonly ApplySettings Manual = new()
{
@ -24,6 +25,7 @@ public readonly record struct ApplySettings(
UseSingleSource = false,
MergeLinks = false,
ResetMaterials = false,
IsFinal = false,
};
public static readonly ApplySettings ManualWithLinks = new()
@ -35,6 +37,7 @@ public readonly record struct ApplySettings(
UseSingleSource = false,
MergeLinks = true,
ResetMaterials = false,
IsFinal = false,
};
public static readonly ApplySettings Game = new()
@ -46,6 +49,7 @@ public readonly record struct ApplySettings(
UseSingleSource = false,
MergeLinks = false,
ResetMaterials = true,
IsFinal = false,
};
}

View file

@ -1,6 +1,6 @@
using Dalamud.Interface.ImGuiNotification;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Extensions;
using OtterGui.Services;
using Notification = OtterGui.Classes.Notification;

View file

@ -1,4 +1,5 @@
using Glamourer.Automation;
using Glamourer.Api.Enums;
using Glamourer.Automation;
using Glamourer.Designs.Special;
using Glamourer.GameData;
using Glamourer.Interop.Material;

View file

@ -1,4 +1,5 @@
using Glamourer.State;
using Glamourer.Api.Enums;
using Glamourer.State;
namespace Glamourer.Designs;
@ -9,23 +10,15 @@ public enum MetaIndex
VisorState = StateIndex.MetaVisorState,
WeaponState = StateIndex.MetaWeaponState,
ModelId = StateIndex.MetaModelId,
}
[Flags]
public enum MetaFlag : byte
{
Wetness = 0x01,
HatState = 0x02,
VisorState = 0x04,
WeaponState = 0x08,
EarState = StateIndex.MetaEarState,
}
public static class MetaExtensions
{
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)
=> index switch
@ -34,6 +27,7 @@ public static class MetaExtensions
MetaIndex.HatState => MetaFlag.HatState,
MetaIndex.VisorState => MetaFlag.VisorState,
MetaIndex.WeaponState => MetaFlag.WeaponState,
MetaIndex.EarState => MetaFlag.EarState,
_ => (MetaFlag)byte.MaxValue,
};
@ -44,9 +38,24 @@ public static class MetaExtensions
MetaFlag.HatState => MetaIndex.HatState,
MetaFlag.VisorState => MetaIndex.VisorState,
MetaFlag.WeaponState => MetaIndex.WeaponState,
MetaFlag.EarState => MetaIndex.EarState,
_ => (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)
=> index switch
{
@ -54,6 +63,7 @@ public static class MetaExtensions
MetaIndex.VisorState => "Visor Toggled",
MetaIndex.WeaponState => "Weapon Visible",
MetaIndex.Wetness => "Force Wetness",
MetaIndex.EarState => "Ears Visible",
_ => "Unknown Meta",
};
@ -64,6 +74,7 @@ public static class MetaExtensions
MetaIndex.VisorState => "Toggle the visor state of the characters head gear.",
MetaIndex.WeaponState => "Hide or show the characters weapons when not drawn.",
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,
};
}

View file

@ -1,19 +1,33 @@
using OtterGui.Services;
using OtterGui;
using OtterGui.Services;
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)
{
if (localDesigns.Count == 0)
if (localDesigns.Count is 0)
return null;
var idx = _rng.Next(0, localDesigns.Count);
Glamourer.Log.Verbose($"[Random Design] Chose design {idx + 1} out of {localDesigns.Count}: {localDesigns[idx].Incognito}.");
return localDesigns[idx];
if (localDesigns.Count is 1)
{
_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()
@ -24,12 +38,12 @@ public class RandomDesignGenerator(DesignStorage designs, DesignFileSystem fileS
public Design? Design(IReadOnlyList<IDesignPredicate> predicates)
{
if (predicates.Count == 0)
return Design();
if (predicates.Count == 1)
return Design(predicates[0]);
return Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList());
return predicates.Count switch
{
0 => Design(),
1 => Design(predicates[0]),
_ => Design(IDesignPredicate.Get(predicates, designs, fileSystem).ToList()),
};
}
public Design? Design(string restrictions)

View file

@ -22,7 +22,7 @@ public interface IDesignPredicate
: designs;
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

View file

@ -20,6 +20,10 @@ public class EphemeralConfig : ISavable
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]
private readonly SaveService _saveService;

View file

@ -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>
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>
AddedDesign,

View file

@ -13,8 +13,8 @@ public sealed class GPoseService : EventWrapper<bool, GPoseService.Priority>
public enum Priority
{
/// <seealso cref="Api.GlamourerIpc.OnGPoseChanged"/>
GlamourerIpc = int.MinValue,
/// <seealso cref="Api.StateApi.OnGPoseChange"/>
StateApi = int.MinValue,
}
public bool InGPose { get; private set; }

View 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,
}
}

View file

@ -15,5 +15,8 @@ public sealed class PenumbraReloaded()
/// <seealso cref="Interop.VisorService.Restore"/>
VisorService = 0,
/// <seealso cref="Interop.VieraEarService.Restore"/>
VieraEarService = 0,
}
}

View file

@ -3,6 +3,7 @@ using Glamourer.Designs.History;
using Glamourer.Interop.Structs;
using Glamourer.State;
using OtterGui.Classes;
using Penumbra.GameData.Interop;
namespace Glamourer.Events;

View 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,
}
}

View 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,
}
}

View file

@ -19,4 +19,4 @@ public sealed class VisorStateChanged()
/// <seealso cref="State.StateListener.OnVisorChange"/>
StateListener = 0,
}
}
}

View file

@ -1,4 +1,5 @@
using OtterGui;
using OtterGui.Extensions;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Race = Penumbra.GameData.Enums.Race;
@ -11,7 +12,7 @@ namespace Glamourer.GameData;
/// </summary>
public class CustomizeSet
{
private NpcCustomizeSet _npcCustomizations;
private readonly NpcCustomizeSet _npcCustomizations;
internal CustomizeSet(NpcCustomizeSet npcCustomizations, SubRace clan, Gender gender)
{
@ -88,7 +89,7 @@ public class CustomizeSet
{
if (IsAvailable(index))
return DataByValue(index, value, out custom, face) >= 0
|| _npcCustomizations.CheckColor(index, value)
|| _npcCustomizations.CheckValue(index, value)
|| NpcOptions.Any(t => t.Type == index && t.Value == value);
custom = null;

View file

@ -76,7 +76,6 @@ internal class CustomizeSetFactory(
CustomizeIndex.Hairstyle,
CustomizeIndex.LipColor,
CustomizeIndex.SkinColor,
CustomizeIndex.FacePaint,
CustomizeIndex.TailShape,
};

View file

@ -3,6 +3,7 @@ using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Lumina.Excel.Sheets;
using OtterGui.Services;
using Penumbra.GameData.Data;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -40,17 +41,19 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
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 CheckColor(CustomizeIndex type, CustomizeValue value)
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],
_ => false,
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>
@ -58,13 +61,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
{
var waitTask = Task.WhenAll(eNpcs.Awaiter, bNpcs.Awaiter, bNpcNames.Awaiter);
Awaiter = waitTask.ContinueWith(_ =>
{
var watch = Stopwatch.StartNew();
var eNpcTask = Task.Run(() => CreateEnpcData(data, eNpcs));
var bNpcTask = Task.Run(() => CreateBnpcData(data, bNpcs, bNpcNames));
FilterAndOrderNpcData(eNpcTask.Result, bNpcTask.Result);
Time = watch.ElapsedMilliseconds;
});
{
var watch = Stopwatch.StartNew();
var eNpcTask = Task.Run(() => CreateEnpcData(data, eNpcs));
var bNpcTask = Task.Run(() => CreateBnpcData(data, bNpcs, bNpcNames));
FilterAndOrderNpcData(eNpcTask.Result, bNpcTask.Result);
Time = watch.ElapsedMilliseconds;
})
.ContinueWith(_ => CheckFacepaintFiles(data, _facepaints));
}
/// <summary> Create data from event NPCs. </summary>
@ -323,6 +327,17 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
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/>
public IEnumerator<NpcData> GetEnumerator()
=> _data.GetEnumerator();

View file

@ -6,12 +6,10 @@ using Glamourer.Gui;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.State;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Log;
using OtterGui.Services;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.GameData.Interop;
namespace Glamourer;
@ -28,6 +26,7 @@ public class Glamourer : IDalamudPlugin
public static readonly Logger Log = new();
public static MessageService Messager { get; private set; } = null!;
public static DynamisIpc Dynamis { get; private set; } = null!;
private readonly ServiceManager _services;
@ -37,6 +36,7 @@ public class Glamourer : IDalamudPlugin
{
_services = StaticServiceManager.CreateProvider(pluginInterface, Log, this);
Messager = _services.GetService<MessageService>();
Dynamis = _services.GetService<DynamisIpc>();
_services.EnsureRequiredServices();
_services.GetService<VisorService>();
@ -47,15 +47,6 @@ public class Glamourer : IDalamudPlugin
_services.GetService<CommandService>(); // initialize commands.
_services.GetService<IpcProviders>(); // initialize IPC.
Log.Information($"Glamourer v{Version} loaded successfully.");
//var text = File.ReadAllBytes(@"C:\FFXIVMods\PBDTest\files\human.pbd");
//var pbd = new PbdFile(text);
//var roundtrip = pbd.Write();
//File.WriteAllBytes(@"C:\FFXIVMods\PBDTest\files\Vanilla resaved save.pbd", roundtrip);
//var deformer = pbd.Deformers.FirstOrDefault(d => d.GenderRace is GenderRace.RoegadynFemale)!.RacialDeformer;
//deformer.DeformMatrices["ya_fukubu_phys"] = deformer.DeformMatrices["j_kosi"];
//var aleks = pbd.Write();
//File.WriteAllBytes(@"C:\FFXIVMods\PBDTest\files\rue.pbd", aleks);
}
catch
{
@ -78,10 +69,10 @@ public class Glamourer : IDalamudPlugin
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($"> **`Advanced Customize: `** {config.UseAdvancedParameters}\n");
sb.Append($"> **`Advanced Dye: `** {config.UseAdvancedDyes}\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");
@ -92,7 +83,7 @@ public class Glamourer : IDalamudPlugin
var designManager = _services.GetService<DesignManager>();
var autoManager = _services.GetService<AutoDesignManager>();
var stateManager = _services.GetService<StateManager>();
var objectManager = _services.GetService<ObjectManager>();
var objectManager = _services.GetService<ActorObjectManager>();
var currentPlayer = objectManager.PlayerData.Identifier;
var states = stateManager.Where(kvp => objectManager.ContainsKey(kvp.Key)).ToList();

View file

@ -1,92 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Dalamud.NET.Sdk/13.1.0">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<LangVersion>preview</LangVersion>
<PlatformTarget>x64</PlatformTarget>
<RootNamespace>Glamourer</RootNamespace>
<AssemblyName>Glamourer</AssemblyName>
<FileVersion>9.0.0.1</FileVersion>
<AssemblyVersion>9.0.0.1</AssemblyVersion>
<Company>SoftOtter</Company>
<Product>Glamourer</Product>
<Copyright>Copyright © 2023</Copyright>
<Deterministic>true</Deterministic>
<OutputType>Library</OutputType>
<Copyright>Copyright © 2025</Copyright>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<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>
<ItemGroup>
<None Remove="LegacyTattoo.raw" />
<None Include="Glamourer.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="LegacyTattoo.raw" />
</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>
<ProjectReference Include="..\Glamourer.Api\Glamourer.Api.csproj" />
<ProjectReference Include="..\OtterGui\OtterGui.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" />
<PackageReference Include="Vortice.Direct3D11" Version="3.4.2-beta" />
</ItemGroup>
@ -116,14 +56,4 @@
<InformationalVersion>$(GitCommitHash)</InformationalVersion>
</PropertyGroup>
</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>

View file

@ -8,7 +8,7 @@
"AssemblyVersion": "9.0.0.1",
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any",
"DalamudApiLevel": 11,
"DalamudApiLevel": 13,
"ImageUrls": null,
"IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png"
}

View file

@ -1,4 +1,4 @@
using ImGuiNET;
using Dalamud.Bindings.ImGui;
namespace Glamourer.Gui;
@ -29,6 +29,10 @@ public enum ColorId
TriStateNeutral,
BattleNpc,
EventNpc,
ModdedItemMarker,
ContainsItemsEnabled,
ContainsItemsDisabled,
AdvancedDyeActive,
}
public static class Colors
@ -39,32 +43,36 @@ public static class Colors
=> color switch
{
// @formatter:off
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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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
};

View file

@ -1,16 +1,83 @@
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Glamourer.GameData;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Text.EndObjects;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using System;
namespace Glamourer.Gui.Customization;
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)
{
@ -21,7 +88,7 @@ public partial class CustomizationDrawer
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);
}
@ -30,6 +97,9 @@ public partial class CustomizationDrawer
var data = _set.Data(_currentIndex, current, _customize.Face);
UpdateValue(data.Value);
}
DrawDragDropSource(index, custom);
DrawDragDropTarget(index);
}
var npc = false;

View file

@ -1,5 +1,5 @@
using Dalamud.Interface;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;

View file

@ -1,8 +1,9 @@
using Dalamud.Interface.Textures.TextureWraps;
using Glamourer.GameData;
using Glamourer.Unlocks;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Extensions;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -34,7 +35,7 @@ public partial class CustomizationDrawer
var hasIcon = icon.TryGetWrap(out var wrap, out _);
using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw))
{
if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize))
if (ImGui.ImageButton(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, _iconSize))
{
ImGui.OpenPopup(IconSelectorPopup);
}
@ -88,7 +89,7 @@ public partial class CustomizationDrawer
: ImRaii.PushColor(ImGuiCol.Button, ColorId.FavoriteStarOn.Value(), isFavorite);
var hasIcon = icon.TryGetWrap(out var wrap, out var _);
if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize))
if (ImGui.ImageButton(wrap?.Handle ?? icon.GetWrapOrEmpty().Handle, _iconSize))
{
UpdateValue(custom.Value);
ImGui.CloseCurrentPopup();
@ -214,7 +215,7 @@ public partial class CustomizationDrawer
hasIcon = icon.TryGetWrap(out wrap, out _);
}
if (ImGui.ImageButton(wrap?.ImGuiHandle ?? icon.GetWrapOrEmpty().ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One,
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);

View file

@ -1,4 +1,4 @@
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using OtterGuiInternal;

View file

@ -4,7 +4,7 @@ using Dalamud.Plugin.Services;
using Glamourer.GameData;
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;

View file

@ -6,8 +6,8 @@ namespace Glamourer.Gui.Customization;
public struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data)
{
private IDesignEditor _editor;
private object _object;
private IDesignEditor _editor = null!;
private object _object = null!;
public readonly CustomizeParameterFlag Flag = flag;
public bool Locked;
public bool DisplayApplication;

View file

@ -3,7 +3,7 @@ using Glamourer.Designs;
using Glamourer.GameData;
using Glamourer.Interop.PalettePlus;
using Glamourer.State;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
@ -287,13 +287,13 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
}
private ImGuiColorEditFlags GetFlags()
=> Format | Display | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.NoOptions;
=> Format | Display | ImGuiColorEditFlags.Hdr | ImGuiColorEditFlags.NoOptions;
private ImGuiColorEditFlags Format
=> config.UseFloatForColors ? ImGuiColorEditFlags.Float : ImGuiColorEditFlags.Uint8;
private ImGuiColorEditFlags Display
=> config.UseRgbForColors ? ImGuiColorEditFlags.DisplayRGB : ImGuiColorEditFlags.DisplayHSV;
=> config.UseRgbForColors ? ImGuiColorEditFlags.DisplayRgb : ImGuiColorEditFlags.DisplayHsv;
private ImRaii.IEndObject EnsureSize()
{

View file

@ -5,9 +5,10 @@ using Glamourer.Designs;
using Glamourer.Designs.History;
using Glamourer.Designs.Special;
using Glamourer.Events;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Extensions;
using OtterGui.Log;
using OtterGui.Widgets;
@ -21,6 +22,7 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<IDesignStandIn, s
protected readonly TabSelected TabSelected;
protected float InnerWidth;
private IDesignStandIn? _currentDesign;
private bool _isCurrentSelectionDirty;
protected DesignComboBase(Func<IReadOnlyList<Tuple<IDesignStandIn, string>>> generator, Logger log, DesignChanged designChanged,
TabSelected tabSelected, EphemeralConfig config, DesignColors designColors)
@ -83,17 +85,11 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<IDesignStandIn, s
DrawRightAligned(quickDesign.ResolveName(false), "[Nothing]", DesignColors.MissingColor);
}
protected override int UpdateCurrentSelected(int currentSelected)
{
CurrentSelectionIdx = Items.IndexOf(p => _currentDesign == p.Item1);
UpdateSelection(CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : null);
return CurrentSelectionIdx;
}
protected bool Draw(IDesignStandIn? currentDesign, string? label, float width)
{
_currentDesign = currentDesign;
InnerWidth = 400 * ImGuiHelpers.GlobalScale;
UpdateCurrentSelection();
InnerWidth = 400 * ImGuiHelpers.GlobalScale;
var name = label ?? "Select Design Here...";
bool ret;
using (_ = currentDesign != null ? ImRaii.PushColor(ImGuiCol.Text, DesignColors.GetColor(currentDesign as Design)) : null)
@ -127,37 +123,60 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<IDesignStandIn, s
return filter.IsContained(path) || filter.IsContained(design.ResolveName(false));
}
private void OnDesignChanged(DesignChanged.Type type, Design design, ITransaction? _ = null)
protected override void OnMouseWheel(string preview, ref int _2, int steps)
{
switch (type)
{
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)
{
UpdateSelection(Items[CurrentSelectionIdx]);
}
else if (Items.Count > 0)
{
CurrentSelectionIdx = 0;
UpdateSelection(Items[0]);
}
else
{
UpdateSelection(null);
}
if (!ReferenceEquals(_currentDesign, CurrentSelection?.Item1))
CurrentSelectionIdx = -1;
if (!priorState)
Cleanup();
break;
base.OnMouseWheel(preview, ref _2, steps);
}
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)
@ -175,7 +194,7 @@ public abstract class DesignComboBase : FilterComboCache<Tuple<IDesignStandIn, s
ImGui.TextUnformatted("Currently resolving to ");
using var color = ImRaii.PushColor(ImGuiCol.Text, DesignColors.GetColor(linkedDesign));
ImGui.SameLine(0, 0);
ImGui.TextUnformatted(linkedDesign.Name);
ImGui.TextUnformatted(linkedDesign.Name.Text);
}
else
{
@ -225,8 +244,7 @@ public abstract class DesignCombo : DesignComboBase
public sealed class QuickDesignCombo : DesignCombo
{
public QuickDesignCombo(DesignManager designs,
DesignFileSystem fileSystem,
public QuickDesignCombo(DesignFileSystem fileSystem,
Logger log,
DesignChanged designChanged,
TabSelected tabSelected,
@ -234,9 +252,9 @@ public sealed class QuickDesignCombo : DesignCombo
DesignColors designColors)
: base(log, designChanged, tabSelected, config, designColors, () =>
[
.. designs.Designs
.Where(d => d.QuickDesign)
.Select(d => new Tuple<IDesignStandIn, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
.. fileSystem
.Where(kvp => kvp.Key.QuickDesign)
.Select(kvp => new Tuple<IDesignStandIn, string>(kvp.Key, kvp.Value.FullName()))
.OrderBy(d => d.Item2),
])
{
@ -277,7 +295,6 @@ public sealed class QuickDesignCombo : DesignCombo
}
public sealed class LinkDesignCombo(
DesignManager designs,
DesignFileSystem fileSystem,
Logger log,
DesignChanged designChanged,
@ -286,8 +303,8 @@ public sealed class LinkDesignCombo(
DesignColors designColors)
: DesignCombo(log, designChanged, tabSelected, config, designColors, () =>
[
.. designs.Designs
.Select(d => new Tuple<IDesignStandIn, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
.. fileSystem
.Select(kvp => new Tuple<IDesignStandIn, string>(kvp.Key, kvp.Value.FullName()))
.OrderBy(d => d.Item2),
]);
@ -301,8 +318,8 @@ public sealed class RandomDesignCombo(
DesignColors designColors)
: DesignCombo(log, designChanged, tabSelected, config, designColors, () =>
[
.. designs.Designs
.Select(d => new Tuple<IDesignStandIn, string>(d, fileSystem.FindLeaf(d, out var l) ? l.FullName() : string.Empty))
.. fileSystem
.Select(kvp => new Tuple<IDesignStandIn, string>(kvp.Key, kvp.Value.FullName()))
.OrderBy(d => d.Item2),
])
{
@ -328,7 +345,6 @@ public sealed class RandomDesignCombo(
}
public sealed class SpecialDesignCombo(
DesignManager designs,
DesignFileSystem fileSystem,
TabSelected tabSelected,
DesignColors designColors,
@ -338,8 +354,8 @@ public sealed class SpecialDesignCombo(
EphemeralConfig config,
RandomDesignGenerator rng,
QuickSelectedDesign quickSelectedDesign)
: DesignComboBase(() => designs.Designs
.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)
.Prepend(new Tuple<IDesignStandIn, string>(new RandomDesign(rng), string.Empty))
.Prepend(new Tuple<IDesignStandIn, string>(quickSelectedDesign, string.Empty))

View file

@ -6,26 +6,28 @@ using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Interop;
using Glamourer.Interop.Structs;
using Glamourer.Interop.Penumbra;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
using Dalamud.Bindings.ImGui;
using OtterGui.Classes;
using OtterGui.Text;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Interop;
namespace Glamourer.Gui;
[Flags]
public enum QdbButtons
{
ApplyDesign = 0x01,
RevertAll = 0x02,
RevertAutomation = 0x04,
RevertAdvanced = 0x08,
RevertEquip = 0x10,
RevertCustomize = 0x20,
ReapplyAutomation = 0x40,
ApplyDesign = 0x01,
RevertAll = 0x02,
RevertAutomation = 0x04,
RevertAdvancedDyes = 0x08,
RevertEquip = 0x10,
RevertCustomize = 0x20,
ReapplyAutomation = 0x40,
ResetSettings = 0x80,
RevertAdvancedCustomization = 0x100,
}
public sealed class DesignQuickBar : Window, IDisposable
@ -35,19 +37,21 @@ public sealed class DesignQuickBar : Window, IDisposable
? ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoMove
: ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoDocking | ImGuiWindowFlags.NoFocusOnAppearing;
private readonly Configuration _config;
private readonly QuickDesignCombo _designCombo;
private readonly StateManager _stateManager;
private readonly AutoDesignApplier _autoDesignApplier;
private readonly ObjectManager _objects;
private readonly IKeyState _keyState;
private readonly ImRaii.Style _windowPadding = new();
private readonly ImRaii.Color _windowColor = new();
private DateTime _keyboardToggle = DateTime.UnixEpoch;
private int _numButtons;
private readonly Configuration _config;
private readonly QuickDesignCombo _designCombo;
private readonly StateManager _stateManager;
private readonly AutoDesignApplier _autoDesignApplier;
private readonly ActorObjectManager _objects;
private readonly PenumbraService _penumbra;
private readonly IKeyState _keyState;
private readonly ImRaii.Style _windowPadding = new();
private readonly ImRaii.Color _windowColor = new();
private DateTime _keyboardToggle = DateTime.UnixEpoch;
private int _numButtons;
private readonly StringBuilder _tooltipBuilder = new(512);
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)
{
_config = config;
@ -56,9 +60,11 @@ public sealed class DesignQuickBar : Window, IDisposable
_keyState = keyState;
_objects = objects;
_autoDesignApplier = autoDesignApplier;
_penumbra = penumbra;
IsOpen = _config.Ephemeral.ShowDesignQuickBar;
DisableWindowSounds = true;
Size = Vector2.Zero;
RespectCloseHotkey = false;
}
public void Dispose()
@ -103,7 +109,7 @@ public sealed class DesignQuickBar : Window, IDisposable
private void Draw(float width)
{
using var group = ImRaii.Group();
using var group = ImUtf8.Group();
var spacing = ImGui.GetStyle().ItemInnerSpacing;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
var buttonSize = new Vector2(ImGui.GetFrameHeight());
@ -120,8 +126,10 @@ public sealed class DesignQuickBar : Window, IDisposable
DrawRevertEquipButton(buttonSize);
DrawRevertCustomizeButton(buttonSize);
DrawRevertAdvancedCustomization(buttonSize);
DrawRevertAdvancedDyes(buttonSize);
DrawRevertAutomationButton(buttonSize);
DrawReapplyAutomationButton(buttonSize);
DrawResetSettingsButton(buttonSize);
}
private ActorIdentifier _playerIdentifier;
@ -144,33 +152,38 @@ public sealed class DesignQuickBar : Window, IDisposable
{
var design = _designCombo.Design as Design;
var available = 0;
var tooltip = string.Empty;
_tooltipBuilder.Clear();
if (design == null)
{
tooltip = "No design selected.";
_tooltipBuilder.Append("No design selected.");
}
else
{
if (_playerIdentifier.IsValid && _playerData.Valid)
{
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 (available != 0)
tooltip += '\n';
_tooltipBuilder.Append('\n');
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)
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();
if (!clicked)
return;
@ -183,7 +196,7 @@ public sealed class DesignQuickBar : Window, IDisposable
}
using var _ = design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
_stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks);
_stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks with { IsFinal = true });
}
private void DrawRevertButton(Vector2 buttonSize)
@ -192,28 +205,32 @@ public sealed class DesignQuickBar : Window, IDisposable
return;
var available = 0;
var tooltip = string.Empty;
_tooltipBuilder.Clear();
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false })
{
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 (available != 0)
tooltip += '\n';
_tooltipBuilder.Append('\n');
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)
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();
if (clicked)
_stateManager.ResetState(state!, StateSource.Manual);
_stateManager.ResetState(state!, StateSource.Manual, isFinal: true);
}
private void DrawRevertAutomationButton(Vector2 buttonSize)
@ -225,34 +242,37 @@ public sealed class DesignQuickBar : Window, IDisposable
return;
var available = 0;
var tooltip = string.Empty;
_tooltipBuilder.Clear();
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
{
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 (available != 0)
tooltip += '\n';
_tooltipBuilder.Append('\n');
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)
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();
if (!clicked)
return;
foreach (var actor in data.Objects)
{
_autoDesignApplier.ReapplyAutomation(actor, id, state!, true, out var forcedRedraw);
_stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual);
_autoDesignApplier.ReapplyAutomation(actor, id, state!, true, false, out var forcedRedraw);
_stateManager.ReapplyAutomationState(actor, forcedRedraw, true, StateSource.Manual);
}
}
@ -265,69 +285,104 @@ public sealed class DesignQuickBar : Window, IDisposable
return;
var available = 0;
var tooltip = string.Empty;
_tooltipBuilder.Clear();
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
{
available |= 1;
tooltip = "Left-Click: Reapply the player character's current automation on top of their current 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 (available != 0)
tooltip += '\n';
_tooltipBuilder.Append('\n');
available |= 2;
tooltip += $"Right-Click: Reapply {_targetIdentifier}'s current automation on top of their current state.";
_tooltipBuilder.Append("Right-Click: Reapply ")
.Append(_targetIdentifier)
.Append("'s current automation on top of their current state.");
}
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.Repeat, 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, out var forcedRedraw);
_stateManager.ReapplyState(actor, forcedRedraw, StateSource.Manual);
_autoDesignApplier.ReapplyAutomation(actor, id, state!, false, false, out var forcedRedraw);
_stateManager.ReapplyAutomationState(actor, forcedRedraw, false, StateSource.Manual);
}
}
private void DrawRevertAdvancedCustomization(Vector2 buttonSize)
{
if (!_config.UseAdvancedParameters)
return;
if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced))
if (!_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization))
return;
var available = 0;
var tooltip = string.Empty;
_tooltipBuilder.Clear();
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
{
available |= 1;
tooltip = "Left-Click: Revert the advanced customizations and dyes of the player character to their game state.";
_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)
tooltip += '\n';
_tooltipBuilder.Append('\n');
available |= 2;
tooltip += $"Right-Click: Revert the advanced customizations and dyes of {_targetIdentifier} to their game state.";
_tooltipBuilder.Append("Right-Click: Revert the advanced customizations of ")
.Append(_targetIdentifier)
.Append(" to their game state.");
}
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.Palette, buttonSize, tooltip, available);
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.PaintBrush, buttonSize, available);
ImGui.SameLine();
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)
@ -336,26 +391,28 @@ public sealed class DesignQuickBar : Window, IDisposable
return;
var available = 0;
var tooltip = string.Empty;
_tooltipBuilder.Clear();
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
{
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 (available != 0)
tooltip += '\n';
_tooltipBuilder.Append('\n');
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)
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();
if (clicked)
_stateManager.ResetCustomize(state!, StateSource.Manual);
@ -367,35 +424,80 @@ public sealed class DesignQuickBar : Window, IDisposable
return;
var available = 0;
var tooltip = string.Empty;
_tooltipBuilder.Clear();
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
{
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 (available != 0)
tooltip += '\n';
_tooltipBuilder.Append('\n');
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)
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();
if (clicked)
_stateManager.ResetEquip(state!, StateSource.Manual);
}
private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip,
int available)
private void DrawResetSettingsButton(Vector2 buttonSize)
{
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))
return (true, _playerIdentifier, _playerData, _playerState);
if ((available & 2) == 2 && ImGui.IsItemClicked(ImGuiMouseButton.Right))
@ -435,12 +537,16 @@ public sealed class DesignQuickBar : Window, IDisposable
++_numButtons;
}
if ((_config.UseAdvancedParameters || _config.UseAdvancedDyes) && _config.QdbButtons.HasFlag(QdbButtons.RevertAdvanced))
if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedCustomization))
++_numButtons;
if (_config.QdbButtons.HasFlag(QdbButtons.RevertAdvancedDyes))
++_numButtons;
if (_config.QdbButtons.HasFlag(QdbButtons.RevertCustomize))
++_numButtons;
if (_config.QdbButtons.HasFlag(QdbButtons.RevertEquip))
++_numButtons;
if (_config.UseTemporarySettings && _config.QdbButtons.HasFlag(QdbButtons.ResetSettings))
++_numButtons;
if (_config.QdbButtons.HasFlag(QdbButtons.ApplyDesign))
{
++_numButtons;

View file

@ -1,4 +1,5 @@
using Glamourer.Designs;
using Glamourer.Interop.Material;
using Glamourer.State;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -7,12 +8,13 @@ namespace Glamourer.Gui.Equipment;
public struct BonusDrawData(BonusItemFlag slot, in DesignData designData)
{
private IDesignEditor _editor;
private object _object;
public readonly BonusItemFlag Slot = slot;
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;
@ -42,6 +44,7 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData)
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)
@ -53,5 +56,6 @@ public struct BonusDrawData(BonusItemFlag slot, in DesignData designData)
DisplayApplication = false,
GameItem = state.BaseData.BonusItem(slot),
AllowRevert = true,
HasAdvancedDyes = state.Materials.CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)),
};
}

View file

@ -1,10 +1,11 @@
using Dalamud.Plugin.Services;
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
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;

View file

@ -1,4 +1,5 @@
using Glamourer.Designs;
using Glamourer.Interop.Material;
using Glamourer.State;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -7,9 +8,9 @@ namespace Glamourer.Gui.Equipment;
public struct EquipDrawData(EquipSlot slot, in DesignData designData)
{
private IDesignEditor _editor;
private object _object;
public readonly EquipSlot Slot = slot;
private IDesignEditor _editor = null!;
private object _object = null!;
public readonly EquipSlot Slot = slot;
public bool Locked;
public bool DisplayApplication;
public bool AllowRevert;
@ -26,18 +27,19 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData)
public readonly void SetStains(StainIds stains)
=> _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)
{
var manager = (DesignManager)_editor;
var design = (Design)_object;
manager.ChangeApplyItem(design, Slot, value);
manager.ChangeApplyItem((Design)_object, Slot, value);
}
public readonly void SetApplyStain(bool value)
{
var manager = (DesignManager)_editor;
var design = (Design)_object;
manager.ChangeApplyStains(design, Slot, value);
manager.ChangeApplyStains((Design)_object, Slot, value);
}
public EquipItem CurrentItem = designData.Item(slot);
@ -46,6 +48,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData)
public StainIds GameStains = default;
public bool CurrentApply;
public bool CurrentApplyStain;
public bool HasAdvancedDyes;
public readonly Gender CurrentGender = designData.Customize.Gender;
public readonly Race CurrentRace = designData.Customize.Race;
@ -58,6 +61,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData)
CurrentApply = design.DoApplyEquip(slot),
CurrentApplyStain = design.DoApplyStain(slot),
Locked = design.WriteProtected(),
HasAdvancedDyes = design.GetMaterialDataRef().CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)),
DisplayApplication = true,
};
@ -70,6 +74,7 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData)
DisplayApplication = false,
GameItem = state.BaseData.Item(slot),
GameStains = state.BaseData.Stain(slot),
HasAdvancedDyes = state.Materials.CheckExistenceSlot(MaterialValueIndex.FromSlot(slot)),
AllowRevert = true,
};
}
}

View 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;
}
}

View file

@ -5,12 +5,13 @@ using Glamourer.Events;
using Glamourer.Gui.Materials;
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
using OtterGui;
using Dalamud.Bindings.ImGui;
using OtterGui.Extensions;
using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Text.EndObjects;
using OtterGui.Widgets;
using Penumbra.GameData.Data;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -31,20 +32,24 @@ public class EquipmentDrawer
private readonly Configuration _config;
private readonly GPoseService _gPose;
private readonly AdvancedDyePopup _advancedDyes;
private readonly ItemCopyService _itemCopy;
private float _requiredComboWidthUnscaled;
private float _requiredComboWidth;
private Stain? _draggedStain;
private Stain? _draggedStain;
private EquipItemSlotCache _draggedItem;
private EquipSlot _dragTarget;
public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, TextureService textures,
Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes)
Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes, ItemCopyService itemCopy)
{
_items = items;
_textures = textures;
_config = config;
_gPose = gPose;
_advancedDyes = advancedDyes;
_itemCopy = itemCopy;
_stainData = items.Stains;
_stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites);
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray();
@ -63,6 +68,7 @@ public class EquipmentDrawer
private Vector2 _iconSize;
private float _comboLength;
private uint _advancedMaterialColor;
public void Prepare()
{
@ -74,7 +80,9 @@ public class EquipmentDrawer
.Max(i => ImGui.CalcTextSize($"{i.Item2.Name} ({i.Item2.ModelString})").X)
/ ImGuiHelpers.GlobalScale;
_requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale;
_requiredComboWidth = _requiredComboWidthUnscaled * ImGuiHelpers.GlobalScale;
_advancedMaterialColor = ColorId.AdvancedDyeActive.Value();
_dragTarget = EquipSlot.Unknown;
}
private bool VerifyRestrictedGear(EquipDrawData data)
@ -91,7 +99,7 @@ public class EquipmentDrawer
if (_config.HideApplyCheckmarks)
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 };
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
@ -106,7 +114,7 @@ public class EquipmentDrawer
if (_config.HideApplyCheckmarks)
bonusDrawData.DisplayApplication = false;
using var id = ImRaii.PushId(100 + (int)bonusDrawData.Slot);
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);
@ -118,7 +126,7 @@ public class EquipmentDrawer
public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
{
if (mainhand.CurrentItem.PrimaryId.Id == 0)
if (mainhand.CurrentItem.PrimaryId.Id == 0 && !allWeapons)
return;
if (_config.HideApplyCheckmarks)
@ -127,7 +135,7 @@ public class EquipmentDrawer
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 };
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
@ -174,12 +182,13 @@ public class EquipmentDrawer
change = true;
}
ImGuiUtil.HoverTooltip($"{_config.DeleteDesignModifier.ToString()} and Right-click to clear.");
ImUtf8.HoverTooltip($"{_config.DeleteDesignModifier.ToString()} and Right-click to clear.");
}
return change;
}
#region Small
private void DrawEquipSmall(in EquipDrawData equipDrawData)
@ -196,14 +205,13 @@ public class EquipmentDrawer
}
else if (equipDrawData.IsState)
{
_advancedDyes.DrawButton(equipDrawData.Slot);
_advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u);
}
if (VerifyRestrictedGear(equipDrawData))
label += " (Restricted)";
ImGui.SameLine();
ImGui.TextUnformatted(label);
DrawEquipLabel(equipDrawData is { IsDesign: true, HasAdvancedDyes: true }, label);
}
private void DrawBonusItemSmall(in BonusDrawData bonusDrawData)
@ -218,11 +226,10 @@ public class EquipmentDrawer
}
else if (bonusDrawData.IsState)
{
_advancedDyes.DrawButton(bonusDrawData.Slot);
_advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u);
}
ImGui.SameLine();
ImGui.TextUnformatted(label);
DrawEquipLabel(bonusDrawData is { IsDesign: true, HasAdvancedDyes: true }, label);
}
private void DrawWeaponsSmall(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
@ -239,12 +246,12 @@ public class EquipmentDrawer
}
else if (mainhand.IsState)
{
_advancedDyes.DrawButton(EquipSlot.MainHand);
_advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : 0u);
}
if (allWeapons)
mainhandLabel += $" ({mainhand.CurrentItem.Type.ToName()})";
WeaponHelpMarker(mainhandLabel);
WeaponHelpMarker(mainhand is { IsDesign: true, HasAdvancedDyes: true }, mainhandLabel);
if (offhand.CurrentItem.Type is FullEquipType.Unknown)
return;
@ -261,10 +268,10 @@ public class EquipmentDrawer
}
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
@ -285,8 +292,8 @@ public class EquipmentDrawer
DrawApply(equipDrawData);
}
ImGui.SameLine();
ImGui.TextUnformatted(label);
DrawEquipLabel(equipDrawData is { IsDesign: true, HasAdvancedDyes: true }, label);
DrawStain(equipDrawData, false);
if (equipDrawData.DisplayApplication)
{
@ -295,13 +302,13 @@ public class EquipmentDrawer
}
else if (equipDrawData.IsState)
{
_advancedDyes.DrawButton(equipDrawData.Slot);
_advancedDyes.DrawButton(equipDrawData.Slot, equipDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u);
}
if (VerifyRestrictedGear(equipDrawData))
{
ImGui.SameLine();
ImGui.TextUnformatted("(Restricted)");
ImUtf8.Text("(Restricted)"u8);
}
}
@ -319,11 +326,10 @@ public class EquipmentDrawer
}
else if (bonusDrawData.IsState)
{
_advancedDyes.DrawButton(bonusDrawData.Slot);
_advancedDyes.DrawButton(bonusDrawData.Slot, bonusDrawData.HasAdvancedDyes ? _advancedMaterialColor : 0u);
}
ImGui.SameLine();
ImGui.TextUnformatted(label);
DrawEquipLabel(bonusDrawData is { IsDesign: true, HasAdvancedDyes: true }, label);
}
private void DrawWeaponsNormal(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
@ -334,7 +340,7 @@ public class EquipmentDrawer
mainhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.MainHand);
var left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
ImGui.SameLine();
using (ImRaii.Group())
using (ImUtf8.Group())
{
DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, false, left);
if (mainhand.DisplayApplication)
@ -343,7 +349,8 @@ public class EquipmentDrawer
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);
if (mainhand.DisplayApplication)
@ -353,7 +360,7 @@ public class EquipmentDrawer
}
else if (mainhand.IsState)
{
_advancedDyes.DrawButton(EquipSlot.MainHand);
_advancedDyes.DrawButton(EquipSlot.MainHand, mainhand.HasAdvancedDyes ? _advancedMaterialColor : 0u);
}
}
@ -364,7 +371,7 @@ public class EquipmentDrawer
var right = ImGui.IsItemClicked(ImGuiMouseButton.Right);
left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
ImGui.SameLine();
using (ImRaii.Group())
using (ImUtf8.Group())
{
DrawOffhand(mainhand, offhand, out var offhandLabel, false, right, left);
if (offhand.DisplayApplication)
@ -373,7 +380,7 @@ public class EquipmentDrawer
DrawApply(offhand);
}
WeaponHelpMarker(offhandLabel);
WeaponHelpMarker(offhand is { IsDesign: true, HasAdvancedDyes: true }, offhandLabel);
DrawStain(offhand, false);
if (offhand.DisplayApplication)
@ -381,9 +388,9 @@ public class EquipmentDrawer
ImGui.SameLine();
DrawApplyStain(offhand);
}
else if (mainhand.IsState)
else if (offhand.IsState)
{
_advancedDyes.DrawButton(EquipSlot.OffHand);
_advancedDyes.DrawButton(EquipSlot.OffHand, offhand.HasAdvancedDyes ? _advancedMaterialColor : 0u);
}
}
}
@ -400,6 +407,7 @@ public class EquipmentDrawer
? _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss)
: _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, width);
_itemCopy.HandleCopyPaste(data, index);
if (!change)
DrawStainDragDrop(data, index, stain, found);
@ -424,8 +432,8 @@ public class EquipmentDrawer
using var dragSource = ImUtf8.DragDropSource();
if (dragSource.Success)
{
if (DragDropSource.SetPayload("stainDragDrop"u8))
_draggedStain = stain;
DragDropSource.SetPayload("stainDragDrop"u8);
_draggedStain = stain;
ImUtf8.Text($"Dragging {stain.Name}...");
}
}
@ -450,10 +458,12 @@ public class EquipmentDrawer
using var disabled = ImRaii.Disabled(data.Locked);
var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
_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))
@ -468,17 +478,71 @@ public class EquipmentDrawer
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,
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)
data.SetItem(combo.CurrentSelection);
else if (combo.CustomVariant.Id > 0)
data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant));
if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, EquipItem.BonusItemNothing(data.Slot), out var item))
if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, EquipItem.BonusItemNothing(data.Slot),
out var 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,
in T currentItem, in T revertItem, in T clearItem, out T? item) where T : IEquatable<T>
{
@ -502,7 +566,7 @@ public class EquipmentDrawer
(false, true, _) => ("Right-click to clear.\nControl and mouse wheel to scroll.", clearItem, true),
(false, false, _) => ("Control and mouse wheel to scroll.", default, false),
};
ImGuiUtil.HoverTooltip(tt);
ImUtf8.HoverTooltip(tt);
return clicked && valid;
}
@ -527,8 +591,13 @@ public class EquipmentDrawer
if (combo.Draw(mainhand.CurrentItem.Name, mainhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
_requiredComboWidth))
changedItem = combo.CurrentSelection;
else if (ResetOrClear(mainhand.Locked || unknown, open, mainhand.AllowRevert, false, mainhand.CurrentItem, mainhand.GameItem,
default, out var c))
else if (combo.CustomVariant.Id > 0 && (drawAll || ItemData.ConvertWeaponId(combo.CustomSetId) == mainhand.CurrentItem.Type))
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;
if (changedItem != null)
@ -544,8 +613,9 @@ public class EquipmentDrawer
}
}
if (unknown && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip("The weapon type could not be identified, thus changing it to other weapons of that type is not possible.");
if (unknown)
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)
@ -565,6 +635,10 @@ public class EquipmentDrawer
if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
_requiredComboWidth))
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);
if (ResetOrClear(locked, clear, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item))
@ -595,14 +669,14 @@ public class EquipmentDrawer
#endregion
private static void WeaponHelpMarker(string label, string? type = null)
private void WeaponHelpMarker(bool hasAdvancedDyes, string label, string? type = null)
{
ImGui.SameLine();
ImGuiComponents.HelpMarker(
"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.");
ImGui.SameLine();
ImGui.TextUnformatted(label);
DrawEquipLabel(hasAdvancedDyes, label);
if (type == null)
return;
@ -610,4 +684,17 @@ public class EquipmentDrawer
pos.Y += ImGui.GetFrameHeightWithSpacing();
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);
}
}

View file

@ -2,7 +2,7 @@
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Glamourer.Unlocks;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui.Widgets;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Structs;

View file

@ -1,10 +1,10 @@
using Dalamud.Plugin.Services;
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using Lumina.Excel.Sheets;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Extensions;
using OtterGui.Log;
using OtterGui.Raii;
using OtterGui.Text;

View file

@ -0,0 +1,73 @@
using Glamourer.Services;
using Dalamud.Bindings.ImGui;
using OtterGui.Services;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Equipment;
public class ItemCopyService(ItemManager items, DictStain stainData) : IUiService
{
public EquipItem? Item { get; private set; }
public Stain? Stain { get; private set; }
public void Copy(in EquipItem item)
=> Item = item;
public void Copy(in Stain stain)
=> Stain = stain;
public void Paste(int which, Action<int, StainId> setter)
{
if (Stain is { } stain)
setter(which, stain.RowIndex);
}
public void Paste(FullEquipType type, Action<EquipItem> setter)
{
if (Item is not { } item)
return;
if (type != item.Type)
{
if (type.IsBonus())
item = items.Identify(type.ToBonus(), item.PrimaryId, item.Variant);
else if (type.IsEquipment() || type.IsAccessory())
item = items.Identify(type.ToSlot(), item.PrimaryId, item.Variant);
else
item = items.Identify(type.ToSlot(), item.PrimaryId, item.SecondaryId, item.Variant);
}
if (item.Valid && item.Type == type)
setter(item);
}
public void HandleCopyPaste(in EquipDrawData data)
{
if (ImGui.GetIO().KeyCtrl)
{
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Middle))
Paste(data.CurrentItem.Type, data.SetItem);
}
else if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) && ImGui.IsMouseClicked(ImGuiMouseButton.Middle))
{
Copy(data.CurrentItem);
}
}
public void HandleCopyPaste(in EquipDrawData data, int which)
{
if (ImGui.GetIO().KeyCtrl)
{
if (ImGui.IsItemHovered() && ImGui.IsMouseClicked(ImGuiMouseButton.Middle))
Paste(which, data.SetStain);
}
else if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)
&& ImGui.IsMouseClicked(ImGuiMouseButton.Middle)
&& stainData.TryGetValue(data.CurrentStains[which].Id, out var stain))
{
Copy(stain);
}
}
}

View file

@ -1,8 +1,8 @@
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
using OtterGui;
using Dalamud.Bindings.ImGui;
using OtterGui.Classes;
using OtterGui.Extensions;
using OtterGui.Log;
using OtterGui.Raii;
using OtterGui.Text;
@ -19,6 +19,10 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
private ItemId _currentItem;
private float _innerWidth;
public PrimaryId CustomSetId { get; private set; }
public SecondaryId CustomWeaponId { get; private set; }
public Variant CustomVariant { get; private set; }
public WeaponCombo(ItemManager items, FullEquipType type, Logger log, FavoriteManager favorites)
: base(() => GetWeapons(favorites, items, type), MouseWheelType.Control, log)
{
@ -46,8 +50,9 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
public bool Draw(string previewName, ItemId previewIdx, float width, float innerWidth)
{
_innerWidth = innerWidth;
_currentItem = previewIdx;
_innerWidth = innerWidth;
_currentItem = previewIdx;
CustomVariant = 0;
return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
}
@ -74,6 +79,24 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
return ret;
}
protected override void OnClosePopup()
{
// If holding control while the popup closes, try to parse the input as a full tuple of set id, weapon 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 != 3
|| !ushort.TryParse(split[0], out var setId)
|| !ushort.TryParse(split[1], out var weaponId)
|| !byte.TryParse(split[2], out var variant))
return;
CustomSetId = setId;
CustomWeaponId = weaponId;
CustomVariant = variant;
}
protected override bool IsVisible(int globalIndex, LowerString filter)
=> base.IsVisible(globalIndex, filter) || Items[globalIndex].ModelString.StartsWith(filter.Lower);

View file

@ -3,7 +3,7 @@ using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
using Glamourer.Gui.Materials;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;

View file

@ -39,6 +39,12 @@ public class GlamourerChangelog
Add1_3_3_0(Changelog);
Add1_3_4_0(Changelog);
Add1_3_5_0(Changelog);
Add1_3_6_0(Changelog);
Add1_3_7_0(Changelog);
Add1_3_8_0(Changelog);
Add1_4_0_0(Changelog);
Add1_5_0_0(Changelog);
Add1_5_1_0(Changelog);
}
private (int, ChangeLogDisplayType) ConfigData()
@ -59,16 +65,127 @@ public class GlamourerChangelog
}
}
private static void Add1_5_1_0(Changelog log)
=> log.NextVersion("Version 1.5.1.0")
.RegisterHighlight("Added support for Penumbras PCP functionality to add the current state of the character as a design.")
.RegisterEntry("On import, a design for the PCP is created and, if possible, applied to the character.", 1)
.RegisterEntry("No automation is assigned.", 1)
.RegisterEntry("Finer control about this can be found in the settings.", 1)
.RegisterEntry("Fixed an issue with static visors not toggling through Glamourer (1.5.0.7).")
.RegisterEntry("The advanced dye slot combo now contains glasses (1.5.0.7).")
.RegisterEntry("Several fixes for patch-related issues (1.5.0.1 - 1.5.0.6");
private static void Add1_5_0_0(Changelog log)
=> log.NextVersion("Version 1.5.0.0")
.RegisterImportant("Updated for game version 7.30 and Dalamud API13, which uses a new GUI backend. Some things may not work as expected. Please let me know any issues you encounter.")
.RegisterHighlight("Added the new Viera Ears state to designs. Old designs will not apply the state.")
.RegisterHighlight("Added the option to make newly created designs write-protected by default to the design defaults.")
.RegisterEntry("Fixed issues with reverting state and IPC.")
.RegisterEntry("Fixed an issue when using the mousewheel to scroll through designs (1.4.0.3).")
.RegisterEntry("Fixed an issue with invalid bonus items (1.4.0.3).")
.RegisterHighlight("Added drag & drop of equipment pieces which will try to match the corresponding model IDs in other slots if possible (1.4.0.2).")
.RegisterEntry("Heavily optimized some issues when having many designs and creating new ones or updating them (1.4.0.2)")
.RegisterEntry("Fixed an issue with staining templates (1.4.0.1).")
.RegisterEntry("Fixed an issue with the QDB buttons not counting correctly (1.4.0.1).");
private static void Add1_4_0_0(Changelog log)
=> log.NextVersion("Version 1.4.0.0")
.RegisterHighlight("The design selector width is now draggable within certain restrictions that depend on the total window width.")
.RegisterEntry("The current behavior may not be final, let me know if you have any comments.", 1)
.RegisterEntry("Regular customization colors can now be dragged & dropped onto other customizations.")
.RegisterEntry(
"If no identical color is available in the target slot, the most similar color available (for certain values of similar) will be chosen instead.",
1)
.RegisterEntry("Resetting advanced dyes and customizations has been split into two buttons for the quick design bar.")
.RegisterEntry("Weapons now also support custom ID input in the combo search box.")
.RegisterEntry("Added new IPC methods GetExtendedDesignData, AddDesign, DeleteDesign, GetDesignBase64, GetDesignJObject.")
.RegisterEntry("Added the option to prevent immediate repeats for random design selection (Thanks Diorik!).")
.RegisterEntry("Optimized some multi-design changes when selecting many designs and changing them at once.")
.RegisterEntry("Fixed item combos not starting from the currently selected item when scrolling them via mouse wheel.")
.RegisterEntry("Fixed some issue with Glamourer not searching mods by name for mod associations in some cases.")
.RegisterEntry("Fixed the IPC methods SetMetaState and SetMetaStateName not working (Thanks Caraxi!).")
.RegisterEntry("Added new IPC method GetDesignListExtended. (1.3.8.6)")
.RegisterEntry(
"Improved the naming of NPCs for identifiers by using Haselnussbombers new naming functionality (Thanks Hasel!). (1.3.8.6)")
.RegisterEntry(
"Added a modifier key separate from the delete modifier key that is used for less important key-checks, specifically toggling incognito mode. (1.3.8.5)")
.RegisterEntry("Used better Penumbra IPC for some things. (1.3.8.5)")
.RegisterEntry("Fixed an issue with advanced dyes for weapons. (1.3.8.5)")
.RegisterEntry("Fixed an issue with NPC automation due to missing job detection. (1.3.8.1)");
private static void Add1_3_8_0(Changelog log)
=> log.NextVersion("Version 1.3.8.0")
.RegisterImportant("Updated Glamourer for update 7.20 and Dalamud API 12.")
.RegisterEntry(
"This is not thoroughly tested, but I decided to push to stable instead of testing because otherwise a lot of people would just go to testing just for early access again despite having no business doing so.",
1)
.RegisterEntry(
"I also do not use most of the functionality of Glamourer myself, so I am unable to even encounter most issues myself.", 1)
.RegisterEntry("If you encounter any issues, please report them quickly on the discord.", 1)
.RegisterEntry("Added a chat command to clear temporary settings applied by Glamourer to Penumbra.")
.RegisterEntry("Fixed small issues with customizations not applicable to your race still applying.");
private static void Add1_3_7_0(Changelog log)
=> log.NextVersion("Version 1.3.7.0")
.RegisterImportant(
"The option to disable advanced customizations or advanced dyes has been removed. The functionality can no longer be disabled entirely, you can just decide not to use it, and to hide it.")
.RegisterHighlight(
"You can now configure which panels (like Customization, Equipment, Advanced Customization etc.) are displayed at all, and which are expanded by default. This does not disable any functionality.")
.RegisterHighlight(
"The Unlocks tab now shows whether items are modded in the currently selected collection in Penumbra in Overview mode and shows and can filter and sort for it in Detailed mode.")
.RegisterEntry("Added an optional button to the Quick Design Bar to reset all temporary settings applied by Glamourer.")
.RegisterHighlight(
"Any existing advanced dyes will now be highlighted on the corresponding Advanced Dye buttons in the actors panel and on the corresponding equip slot name in the design panel.")
.RegisterEntry("This also affects currently inactive advanced dyes, which can now be manually removed on the inactive materials.",
1)
.RegisterHighlight(
"In the design list of an automation set, the design indices are now highlighted if a design contains advanced dyes, mod associations, or links to other designs.")
.RegisterHighlight("Some quality of life improvements:")
.RegisterEntry("Added some buttons for some application rule presets to the Application Rules panel.", 1)
.RegisterEntry("Added some buttons to enable, disable or delete all advanced dyes in a design.", 1)
.RegisterEntry("Some of those buttons are also available in multi-design selection to apply to all selected designs at once.", 1)
.RegisterEntry(
"A copied material color set from Penumbra should now be able to be imported into a advanced dye color set, as well as the other way around.")
.RegisterEntry(
"Automatically applied character updates when applying a design with mod associations and temporary settings are now skipped to prevent some issues with GPose. This should not affect anything else.")
.RegisterEntry("Glamourer now differentiates between temporary settings applied through manual or automatic application.");
private static void Add1_3_6_0(Changelog log)
=> log.NextVersion("Version 1.3.6.0")
.RegisterHighlight("Added some new multi design selection functionality to change design settings of many designs at once.")
.RegisterEntry("Also added the number of selected designs and folders to the multi design selection display.", 1)
.RegisterEntry("Glamourer will now use temporary settings when saving mod associations, if they exist in Penumbra.")
.RegisterEntry(
"Actually added the checkbox to reset all temporary settings to Automation Sets (functionality was there, just not exposed to the UI...).")
.RegisterEntry(
"Adapted the behavior for identified copies of characters that have a different state than the character itself to deal with the associated Penumbra changes.")
.RegisterEntry(
"Added '/glamour resetdesign' as a command, that re-applies automation but resets randomly chosen designs (Thanks Diorik).")
.RegisterEntry("All existing facepaints should now be accepted in designs, including NPC facepaints.")
.RegisterEntry(
"Overwriting a design with your characters current state will now discard any prior advanced dyes and only add those from the current state.")
.RegisterEntry("Fixed an issue with racial mount and accessory scaling when changing zones on a changed race.")
.RegisterEntry("Fixed issues with the detection of gear set changes in certain circumstances (Thanks Cordelia).")
.RegisterEntry("Fixed an issue with the Force to Inherit checkbox in mod associations.")
.RegisterEntry(
"Added a new IPC event that fires only when Glamourer finalizes its current changes to a character (for/from Cordelia).")
.RegisterEntry("Added new IPC to set a meta flag on actors. (for/from Cordelia).");
private static void Add1_3_5_0(Changelog log)
=> log.NextVersion("Version 1.3.5.0")
.RegisterHighlight("Added the usage of the new Temporary Mod Setting functionality from Penumbra to apply mod associations. This is on by default but can be turned back to permanent changes in the settings.")
.RegisterHighlight(
"Added the usage of the new Temporary Mod Setting functionality from Penumbra to apply mod associations. This is on by default but can be turned back to permanent changes in the settings.")
.RegisterEntry("Designs now have a setting to always reset all prior temporary settings made by Glamourer on application.", 1)
.RegisterEntry("Automation Sets also have a setting to do this, independently of the designs contained in them.", 1)
.RegisterEntry("Automation Sets also have a setting to do this, independently of the designs contained in them.", 1)
.RegisterHighlight("More NPC customization options should now be accepted as valid for designs, regardless of clan/gender.")
.RegisterHighlight("The 'Apply' chat command had the currently selected design and the current quick bar design added as choices.")
.RegisterEntry("The application buttons for designs, NPCs or actors should now stick at the top of their respective panels even when scrolling down.")
.RegisterEntry(
"The application buttons for designs, NPCs or actors should now stick at the top of their respective panels even when scrolling down.")
.RegisterHighlight("Randomly chosen designs should now stay across loading screens or redrawing. (1.3.4.3)")
.RegisterEntry("In automation, Random designs now have an option to always choose another design, including during loading screens or redrawing.", 1)
.RegisterEntry(
"In automation, Random designs now have an option to always choose another design, including during loading screens or redrawing.",
1)
.RegisterEntry("Fixed an issue where disabling auto designs did not work as expected.")
.RegisterEntry("Fixed the inversion of application flags in IPC calls.")
.RegisterEntry("Fixed an issue with the scaling of the Advanced Dye popup with increased font sizes.")
@ -93,15 +210,18 @@ public class GlamourerChangelog
=> log.NextVersion("Version 1.3.2.0")
.RegisterEntry("Fixed an issue with weapon hiding when leaving GPose or changing zones.")
.RegisterEntry("Added support for unnamed items to be previewed from Penumbra.")
.RegisterEntry("Item combos filters now check if the model string starts with the current filter, instead of checking if the primary ID contains the current filter.")
.RegisterEntry(
"Item combos filters now check if the model string starts with the current filter, instead of checking if the primary ID contains the current filter.")
.RegisterEntry("Improved the handling of bonus items (glasses) in designs.")
.RegisterEntry("Imported .chara files now import bonus items.")
.RegisterEntry("Added a Debug Data rider in the Actors tab that is visible if Debug Mode is enabled and (currently) contains some IDs.")
.RegisterEntry(
"Added a Debug Data rider in the Actors tab that is visible if Debug Mode is enabled and (currently) contains some IDs.")
.RegisterEntry("Fixed bonus items not reverting correctly in some cases.")
.RegisterEntry("Fixed an issue with the RNG in cheat codes and events skipping some possible entries.")
.RegisterEntry("Fixed the chat log context menu for glamourer Try-On.")
.RegisterEntry("Fixed some issues with cheat code sets.")
.RegisterEntry("Made the popped out Advanced Dye Window and Unlocks Window non-docking as that caused issues when docked to the main Glamourer window.")
.RegisterEntry(
"Made the popped out Advanced Dye Window and Unlocks Window non-docking as that caused issues when docked to the main Glamourer window.")
.RegisterEntry("Refreshed NPC name associations.")
.RegisterEntry("Removed a now useless cheat code.")
.RegisterEntry("Added API for Bonus Items. (1.3.1.1)");

View file

@ -12,7 +12,7 @@ using Glamourer.Gui.Tabs.NpcTab;
using Glamourer.Gui.Tabs.SettingsTab;
using Glamourer.Gui.Tabs.UnlocksTab;
using Glamourer.Interop.Penumbra;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Custom;

View file

@ -1,4 +1,5 @@
using Dalamud.Interface;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
@ -7,8 +8,7 @@ using FFXIVClientStructs.Interop;
using Glamourer.Designs;
using Glamourer.Interop.Material;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
using Dalamud.Bindings.ImGui;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Text;
@ -17,6 +17,7 @@ using Penumbra.GameData.Enums;
using Penumbra.GameData.Files.MaterialStructs;
using Penumbra.GameData.Interop;
using Penumbra.String;
using Notification = OtterGui.Classes.Notification;
namespace Glamourer.Gui.Materials;
@ -39,9 +40,6 @@ public sealed unsafe class AdvancedDyePopup(
private bool ShouldBeDrawn()
{
if (!config.UseAdvancedDyes)
return false;
if (_drawIndex is not { Valid: true })
return false;
@ -51,28 +49,29 @@ public sealed unsafe class AdvancedDyePopup(
return true;
}
public void DrawButton(EquipSlot slot)
=> DrawButton(MaterialValueIndex.FromSlot(slot));
public void DrawButton(EquipSlot slot, uint color)
=> DrawButton(MaterialValueIndex.FromSlot(slot), color);
public void DrawButton(BonusItemFlag slot)
=> DrawButton(MaterialValueIndex.FromSlot(slot));
public void DrawButton(BonusItemFlag slot, uint color)
=> DrawButton(MaterialValueIndex.FromSlot(slot), color);
private void DrawButton(MaterialValueIndex index)
private void DrawButton(MaterialValueIndex index, uint color)
{
if (!config.UseAdvancedDyes)
if (config.HideDesignPanel.HasFlag(DesignPanelFlag.AdvancedDyes))
return;
ImGui.SameLine();
using var id = ImRaii.PushId(index.SlotIndex | ((int)index.DrawObject << 8));
using var id = ImUtf8.PushId(index.SlotIndex | ((int)index.DrawObject << 8));
var isOpen = index == _drawIndex;
using (ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), isOpen)
.Push(ImGuiCol.Text, ColorId.HeaderButtons.Value(), isOpen)
.Push(ImGuiCol.Border, ColorId.HeaderButtons.Value(), isOpen))
var (textColor, buttonColor) = isOpen
? (ColorId.HeaderButtons.Value(), ImGui.GetColorU32(ImGuiCol.ButtonActive))
: (color, 0u);
using (ImRaii.PushColor(ImGuiCol.Border, textColor, isOpen))
{
using var frame = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, isOpen);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Palette.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
string.Empty, false, true))
if (ImUtf8.IconButton(FontAwesomeIcon.Palette, ""u8, default, false, textColor, buttonColor))
{
_forceFocus = true;
_selectedMaterial = byte.MaxValue;
@ -80,7 +79,7 @@ public sealed unsafe class AdvancedDyePopup(
}
}
ImGuiUtil.HoverTooltip("Open advanced dyes for this slot.");
ImUtf8.HoverTooltip("Open advanced dyes for this slot."u8);
}
private (string Path, string GamePath) ResourceName(MaterialValueIndex index)
@ -92,20 +91,21 @@ public sealed unsafe class AdvancedDyePopup(
var modelHandle = model == null ? null : model->ModelResourceHandle;
var path = materialHandle == null
? string.Empty
: ByteString.FromSpanUnsafe(materialHandle->ResourceHandle.FileName.AsSpan(), true).ToString();
: ByteString.FromSpanUnsafe(materialHandle->FileName.AsSpan(), true).ToString();
var gamePath = modelHandle == null
? string.Empty
: modelHandle->GetMaterialFileNameBySlotAsString(index.MaterialIndex);
: modelHandle->GetMaterialFileNameBySlot(index.MaterialIndex).ToString();
return (path, gamePath);
}
private void DrawTabBar(ReadOnlySpan<Pointer<Texture>> textures, ReadOnlySpan<Pointer<Material>> materials, ref bool firstAvailable)
{
using var bar = ImRaii.TabBar("tabs");
using var bar = ImUtf8.TabBar("tabs"u8);
if (!bar)
return;
var table = new ColorTable.Table();
var table = new ColorTable.Table();
var highLightColor = ColorId.AdvancedDyeActive.Value();
for (byte i = 0; i < MaterialService.MaterialsPerModel; ++i)
{
var index = _drawIndex!.Value with { MaterialIndex = i };
@ -124,17 +124,30 @@ public sealed unsafe class AdvancedDyePopup(
if (available)
firstAvailable = false;
using var tab = _label.TabItem(i, select);
var hasAdvancedDyes = _state.Materials.CheckExistenceMaterial(index);
using var c = ImRaii.PushColor(ImGuiCol.Text, highLightColor, hasAdvancedDyes);
using var tab = _label.TabItem(i, select);
c.Pop();
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
{
using var enabled = ImRaii.Enabled();
var (path, gamePath) = ResourceName(index);
using var tt = ImUtf8.Tooltip();
if (gamePath.Length == 0 || path.Length == 0)
ImGui.SetTooltip("This material does not exist.");
ImUtf8.Text("This material does not exist."u8);
else if (!available)
ImGui.SetTooltip($"This material does not have an associated color set.\n\n{gamePath}\n{path}");
ImUtf8.Text($"This material does not have an associated color set.\n\n{gamePath}\n{path}");
else
ImGui.SetTooltip($"{gamePath}\n{path}");
ImUtf8.Text($"{gamePath}\n{path}");
if (hasAdvancedDyes && !available)
{
ImUtf8.Text("\nRight-Click to remove ineffective advanced dyes."u8);
if (ImGui.IsMouseClicked(ImGuiMouseButton.Right))
for (byte row = 0; row < ColorTable.NumRows; ++row)
stateManager.ResetMaterialValue(_state, index with { RowIndex = row }, ApplySettings.Game);
}
}
if ((tab.Success || select is ImGuiTabItemFlags.SetSelected) && available)
@ -173,7 +186,7 @@ public sealed unsafe class AdvancedDyePopup(
DrawTabBar(textures, materials, ref firstAvailable);
if (firstAvailable)
ImGui.TextUnformatted("No Editable Materials available.");
ImUtf8.Text("No Editable Materials available."u8);
}
private void DrawWindow(ReadOnlySpan<Pointer<Texture>> textures, ReadOnlySpan<Pointer<Material>> materials)
@ -197,7 +210,7 @@ public sealed unsafe class AdvancedDyePopup(
var width = 7 * ImGui.GetFrameHeight() // Buttons
+ 3 * ImGui.GetStyle().ItemSpacing.X // around text
+ 7 * ImGui.GetStyle().ItemInnerSpacing.X
+ 200 * ImGuiHelpers.GlobalScale // Drags
+ 200 * ImGuiHelpers.GlobalScale // Drags
+ 7 * UiBuilder.MonoFont.GetCharAdvance(' ') * ImGuiHelpers.GlobalScale // Row
+ 2 * ImGui.GetStyle().WindowPadding.X;
var height = 19 * ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().WindowPadding.Y + 3 * ImGui.GetStyle().ItemSpacing.Y;
@ -251,32 +264,89 @@ public sealed unsafe class AdvancedDyePopup(
DrawAllRow(materialIndex, table);
}
private static void CopyToClipboard(in ColorTable.Table table)
{
try
{
fixed (ColorTable.Table* ptr = &table)
{
var data = new ReadOnlySpan<byte>(ptr, sizeof(ColorTable.Table));
var base64 = Convert.ToBase64String(data);
ImGui.SetClipboardText(base64);
}
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not copy color table to clipboard:\n{ex}");
}
}
private static bool ImportFromClipboard(out ColorTable.Table table)
{
try
{
var base64 = ImGui.GetClipboardText();
if (base64.Length > 0)
{
var data = Convert.FromBase64String(base64);
if (sizeof(ColorTable.Table) <= data.Length)
{
table = new ColorTable.Table();
fixed (ColorTable.Table* tPtr = &table)
{
fixed (byte* ptr = data)
{
new ReadOnlySpan<byte>(ptr, sizeof(ColorTable.Table)).CopyTo(new Span<byte>(tPtr, sizeof(ColorTable.Table)));
return true;
}
}
}
}
if (ColorRowClipboard.IsTableSet)
{
table = ColorRowClipboard.Table;
return true;
}
}
catch (Exception ex)
{
Glamourer.Messager.AddMessage(new Notification(ex, "Could not paste color table from clipboard.",
"Could not paste color table from clipboard.", NotificationType.Error));
}
table = default;
return false;
}
private void DrawAllRow(MaterialValueIndex materialIndex, in ColorTable.Table table)
{
using var id = ImRaii.PushId(100);
var buttonSize = new Vector2(ImGui.GetFrameHeight());
ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), buttonSize, "Highlight all affected colors on the character.",
false, true);
ImUtf8.IconButton(FontAwesomeIcon.Crosshairs, "Highlight all affected colors on the character."u8, buttonSize);
if (ImGui.IsItemHovered())
preview.OnHover(materialIndex with { RowIndex = byte.MaxValue }, _actor.Index, table);
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGui.TextUnformatted("All Color Row Pairs (1-16)");
ImUtf8.Text("All Color Row Pairs (1-16)"u8);
}
var spacing = ImGui.GetStyle().ItemInnerSpacing.X;
ImGui.SameLine(ImGui.GetWindowSize().X - 3 * buttonSize.X - 2 * spacing - ImGui.GetStyle().WindowPadding.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this table to your clipboard.", false,
true))
if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Export this table to your clipboard."u8, buttonSize))
{
ColorRowClipboard.Table = table;
CopyToClipboard(table);
}
ImGui.SameLine(0, spacing);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize,
"Import an exported table from your clipboard onto this table.", !ColorRowClipboard.IsTableSet, true))
if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported table from your clipboard onto this table."u8, buttonSize)
&& ImportFromClipboard(out var newTable))
for (var idx = 0; idx < ColorTable.NumRows; ++idx)
{
var row = ColorRowClipboard.Table[idx];
var row = newTable[idx];
var internalRow = new ColorRow(row);
var slot = materialIndex.ToEquipSlot();
var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand
@ -287,15 +357,14 @@ public sealed unsafe class AdvancedDyePopup(
}
ImGui.SameLine(0, spacing);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this table to game state.", !_anyChanged,
true))
if (ImUtf8.IconButton(FontAwesomeIcon.UndoAlt, "Reset this table to game state."u8, buttonSize, !_anyChanged))
for (byte i = 0; i < ColorTable.NumRows; ++i)
stateManager.ResetMaterialValue(_state, materialIndex with { RowIndex = i }, ApplySettings.Game);
}
private void DrawRow(ref ColorTableRow row, MaterialValueIndex index, in ColorTable.Table table)
{
using var id = ImRaii.PushId(index.RowIndex);
using var id = ImUtf8.PushId(index.RowIndex);
var changed = _state.Materials.TryGetValue(index, out var value);
if (!changed)
{
@ -305,8 +374,9 @@ public sealed unsafe class AdvancedDyePopup(
{
EquipSlot.MainHand => _state.ModelData.Weapon(EquipSlot.MainHand),
EquipSlot.OffHand => _state.ModelData.Weapon(EquipSlot.OffHand),
EquipSlot.Unknown => _state.ModelData.BonusItem((index.SlotIndex - 16u).ToBonusSlot()).Armor().ToWeapon(0), // TODO: Handle better
_ => _state.ModelData.Armor(slot).ToWeapon(0),
EquipSlot.Unknown =>
_state.ModelData.BonusItem((index.SlotIndex - 16u).ToBonusSlot()).Armor().ToWeapon(0), // TODO: Handle better
_ => _state.ModelData.Armor(slot).ToWeapon(0),
};
value = new MaterialValueState(internalRow, internalRow, weapon, StateSource.Manual);
}
@ -317,8 +387,7 @@ public sealed unsafe class AdvancedDyePopup(
}
var buttonSize = new Vector2(ImGui.GetFrameHeight());
ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Crosshairs.ToIconString(), buttonSize, "Highlight the affected colors on the character.",
false, true);
ImUtf8.IconButton(FontAwesomeIcon.Crosshairs, "Highlight the affected colors on the character."u8, buttonSize);
if (ImGui.IsItemHovered())
preview.OnHover(index, _actor.Index, table);
@ -328,28 +397,28 @@ public sealed unsafe class AdvancedDyePopup(
{
var rowIndex = index.RowIndex / 2 + 1;
var rowSuffix = (index.RowIndex & 1) == 0 ? 'A' : 'B';
ImGui.TextUnformatted($"Row {rowIndex,2}{rowSuffix}");
ImUtf8.Text($"Row {rowIndex,2}{rowSuffix}");
}
ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2);
var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse,
v => value.Model.Diffuse = v, "D");
var applied = ImUtf8.ColorPicker("##diffuse"u8, "Change the diffuse value for this row."u8, value.Model.Diffuse,
v => value.Model.Diffuse = v, "D"u8);
var spacing = ImGui.GetStyle().ItemInnerSpacing;
ImGui.SameLine(0, spacing.X);
applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular,
v => value.Model.Specular = v, "S");
applied |= ImUtf8.ColorPicker("##specular"u8, "Change the specular value for this row."u8, value.Model.Specular,
v => value.Model.Specular = v, "S"u8);
ImGui.SameLine(0, spacing.X);
applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive,
v => value.Model.Emissive = v, "E");
applied |= ImUtf8.ColorPicker("##emissive"u8, "Change the emissive value for this row."u8, value.Model.Emissive,
v => value.Model.Emissive = v, "E"u8);
ImGui.SameLine(0, spacing.X);
if (_mode is not ColorRow.Mode.Dawntrail)
{
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
applied |= DragGloss(ref value.Model.GlossStrength);
ImGuiUtil.HoverTooltip("Change the gloss strength for this row.");
ImUtf8.HoverTooltip("Change the gloss strength for this row."u8);
}
else
{
@ -361,7 +430,7 @@ public sealed unsafe class AdvancedDyePopup(
{
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
applied |= DragSpecularStrength(ref value.Model.SpecularStrength);
ImGuiUtil.HoverTooltip("Change the specular strength for this row.");
ImUtf8.HoverTooltip("Change the specular strength for this row."u8);
}
else
{
@ -369,19 +438,18 @@ public sealed unsafe class AdvancedDyePopup(
}
ImGui.SameLine(0, spacing.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), buttonSize, "Export this row to your clipboard.", false,
true))
if (ImUtf8.IconButton(FontAwesomeIcon.Clipboard, "Export this row to your clipboard."u8, buttonSize))
ColorRowClipboard.Row = value.Model;
ImGui.SameLine(0, spacing.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), buttonSize,
"Import an exported row from your clipboard onto this row.", !ColorRowClipboard.IsSet, true))
if (ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported row from your clipboard onto this row."u8, buttonSize,
!ColorRowClipboard.IsSet))
{
value.Model = ColorRowClipboard.Row;
applied = true;
}
ImGui.SameLine(0, spacing.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UndoAlt.ToIconString(), buttonSize, "Reset this row to game state.", !changed, true))
if (ImUtf8.IconButton(FontAwesomeIcon.UndoAlt, "Reset this row to game state."u8, buttonSize, !changed))
stateManager.ResetMaterialValue(_state, index, ApplySettings.Game);
if (applied)
@ -392,7 +460,8 @@ public sealed unsafe class AdvancedDyePopup(
{
var tmp = value;
var minValue = ImGui.GetIO().KeyCtrl ? 0f : (float)Half.Epsilon;
if (!ImUtf8.DragScalar("##Gloss"u8, ref tmp, "%.1f G"u8, 0.001f, minValue, Math.Max(0.01f, 0.005f * value), ImGuiSliderFlags.AlwaysClamp))
if (!ImUtf8.DragScalar("##Gloss"u8, ref tmp, "%.1f G"u8, 0.001f, minValue, Math.Max(0.01f, 0.005f * value),
ImGuiSliderFlags.AlwaysClamp))
return false;
var tmp2 = Math.Clamp(tmp, minValue, (float)Half.MaxValue);

View file

@ -3,7 +3,7 @@ using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Glamourer.Designs;
using Glamourer.Interop.Material;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Services;
using OtterGui.Text;
@ -18,7 +18,6 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config)
public const float GlossWidth = 100;
public const float SpecularStrengthWidth = 125;
private EquipSlot _newSlot = EquipSlot.Head;
private int _newMaterialIdx;
private int _newRowIdx;
private MaterialValueIndex _newKey = MaterialValueIndex.FromSlot(EquipSlot.Head);
@ -35,6 +34,10 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config)
+ (GlossWidth + SpecularStrengthWidth) * ImGuiHelpers.GlobalScale
+ 6 * _spacing
+ ImUtf8.CalcTextSize("Revert"u8).X;
DrawMultiButtons(design);
ImUtf8.Dummy(0);
ImGui.Separator();
ImUtf8.Dummy(0);
if (available > 1.95 * colorWidth)
DrawSingleRow(design);
else
@ -42,9 +45,42 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config)
DrawNew(design);
}
private void DrawMultiButtons(Design design)
{
var any = design.Materials.Count > 0;
var disabled = !_config.DeleteDesignModifier.IsActive();
var size = new Vector2(200 * ImUtf8.GlobalScale, 0);
if (ImUtf8.ButtonEx("Enable All Advanced Dyes"u8,
any
? "Enable the application of all contained advanced dyes without deleting them."u8
: "This design does not contain any advanced dyes."u8, size,
!any || disabled))
_designManager.ChangeApplyMulti(design, null, null, null, null, null, null, true, null);
;
if (disabled && any)
ImUtf8.HoverTooltip($"Hold {_config.DeleteDesignModifier} while clicking to enable.");
ImGui.SameLine();
if (ImUtf8.ButtonEx("Disable All Advanced Dyes"u8,
any
? "Disable the application of all contained advanced dyes without deleting them."u8
: "This design does not contain any advanced dyes."u8, size,
!any || disabled))
_designManager.ChangeApplyMulti(design, null, null, null, null, null, null, false, null);
if (disabled && any)
ImUtf8.HoverTooltip($"Hold {_config.DeleteDesignModifier} while clicking to disable.");
if (ImUtf8.ButtonEx("Delete All Advanced Dyes"u8, any ? ""u8 : "This design does not contain any advanced dyes."u8, size,
!any || disabled))
while (design.Materials.Count > 0)
_designManager.ChangeMaterialValue(design, MaterialValueIndex.FromKey(design.Materials[0].Item1), null);
if (disabled && any)
ImUtf8.HoverTooltip($"Hold {_config.DeleteDesignModifier} while clicking to delete.");
}
private void DrawName(MaterialValueIndex index)
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f));
using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0.05f, 0.5f));
ImUtf8.TextFramed(index.ToString(), 0, new Vector2((GlossWidth + SpecularStrengthWidth) * ImGuiHelpers.GlobalScale + _spacing, 0),
borderColor: ImGui.GetColorU32(ImGuiCol.Text));
}
@ -139,16 +175,44 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config)
"If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."u8);
}
public sealed class MaterialSlotCombo;
public sealed class MaterialSlotCombo;
private void DrawSlotCombo()
{
var width = ImUtf8.CalcTextSize(EquipSlot.OffHand.ToName()).X + ImGui.GetFrameHeightWithSpacing();
ImGui.SetNextItemWidth(width);
using var combo = ImUtf8.Combo("##slot"u8, _newKey.SlotName());
if (combo)
{
var currentSlot = _newKey.ToEquipSlot();
foreach (var tmpSlot in EquipSlotExtensions.FullSlots)
{
if (ImUtf8.Selectable(tmpSlot.ToName(), tmpSlot == currentSlot) && currentSlot != tmpSlot)
_newKey = MaterialValueIndex.FromSlot(tmpSlot) with
{
MaterialIndex = (byte)_newMaterialIdx,
RowIndex = (byte)_newRowIdx,
};
}
var currentBonus = _newKey.ToBonusSlot();
foreach (var bonusSlot in BonusExtensions.AllFlags)
{
if (ImUtf8.Selectable(bonusSlot.ToName(), bonusSlot == currentBonus) && bonusSlot != currentBonus)
_newKey = MaterialValueIndex.FromSlot(bonusSlot) with
{
MaterialIndex = (byte)_newMaterialIdx,
RowIndex = (byte)_newRowIdx,
};
}
}
ImUtf8.HoverTooltip("Choose a slot for an advanced dye row."u8);
}
public void DrawNew(Design design)
{
if (EquipSlotCombo.Draw("##slot", "Choose a slot for an advanced dye row.", ref _newSlot))
_newKey = MaterialValueIndex.FromSlot(_newSlot) with
{
MaterialIndex = (byte)_newMaterialIdx,
RowIndex = (byte)_newRowIdx,
};
DrawSlotCombo();
ImUtf8.SameLineInner();
DrawMaterialIdxDrag();
ImUtf8.SameLineInner();
@ -165,22 +229,27 @@ public class MaterialDrawer(DesignManager _designManager, Configuration _config)
{
ImGui.SetNextItemWidth(ImUtf8.CalcTextSize("Material AA"u8).X);
var format = $"Material {(char)('A' + _newMaterialIdx)}";
if (ImUtf8.DragScalar("##Material"u8, ref _newMaterialIdx, format, 0, MaterialService.MaterialsPerModel - 1, 0.01f))
if (ImUtf8.DragScalar("##Material"u8, ref _newMaterialIdx, format, 0, MaterialService.MaterialsPerModel - 1, 0.01f,
ImGuiSliderFlags.NoInput))
{
_newMaterialIdx = Math.Clamp(_newMaterialIdx, 0, MaterialService.MaterialsPerModel - 1);
_newKey = _newKey with { MaterialIndex = (byte)_newMaterialIdx };
}
ImUtf8.HoverTooltip("Drag this to the left or right to change its value."u8);
}
private void DrawRowIdxDrag()
{
ImGui.SetNextItemWidth(ImUtf8.CalcTextSize("Row 0000"u8).X);
var format = $"Row {_newRowIdx / 2 + 1}{(char)(_newRowIdx % 2 + 'A')}";
if (ImUtf8.DragScalar("##Row"u8, ref _newRowIdx, format, 0, ColorTable.NumRows - 1, 0.01f))
if (ImUtf8.DragScalar("##Row"u8, ref _newRowIdx, format, 0, ColorTable.NumRows - 1, 0.01f, ImGuiSliderFlags.NoInput))
{
_newRowIdx = Math.Clamp(_newRowIdx, 0, ColorTable.NumRows - 1);
_newKey = _newKey with { RowIndex = (byte)_newRowIdx };
}
ImUtf8.HoverTooltip("Drag this to the left or right to change its value."u8);
}
private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row, bool disabled)

View file

@ -1,39 +1,40 @@
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
using Glamourer.State;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui.Raii;
using Penumbra.Api.Enums;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui;
public sealed class PenumbraChangedItemTooltip : IDisposable
{
private readonly PenumbraService _penumbra;
private readonly StateManager _stateManager;
private readonly ItemManager _items;
private readonly ObjectManager _objects;
private readonly CustomizeService _customize;
private readonly GPoseService _gpose;
private readonly PenumbraService _penumbra;
private readonly StateManager _stateManager;
private readonly ItemManager _items;
private readonly ActorObjectManager _objects;
private readonly CustomizeService _customize;
private readonly GPoseService _gpose;
private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2];
private readonly EquipItem[] _lastItems = new EquipItem[EquipFlagExtensions.NumEquipFlags / 2 + BonusExtensions.AllFlags.Count];
public IEnumerable<KeyValuePair<EquipSlot, EquipItem>> LastItems
=> EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand).Zip(_lastItems)
.Select(p => new KeyValuePair<EquipSlot, EquipItem>(p.First, p.Second));
public IEnumerable<KeyValuePair<object, EquipItem>> LastItems
=> EquipSlotExtensions.EqdpSlots.Cast<object>().Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)
.Concat(BonusExtensions.AllFlags.Cast<object>()).Zip(_lastItems)
.Select(p => new KeyValuePair<object, EquipItem>(p.First, p.Second));
public ChangedItemType LastType { get; private set; } = ChangedItemType.None;
public uint LastId { get; private set; } = 0;
public uint LastId { get; private set; }
public DateTime LastTooltip { get; private set; } = DateTime.MinValue;
public DateTime LastClick { get; private set; } = DateTime.MinValue;
public PenumbraChangedItemTooltip(PenumbraService penumbra, StateManager stateManager, ItemManager items, ObjectManager objects,
public PenumbraChangedItemTooltip(PenumbraService penumbra, StateManager stateManager, ItemManager items, ActorObjectManager objects,
CustomizeService customize, GPoseService gpose)
{
_penumbra = penumbra;
@ -72,6 +73,21 @@ public sealed class PenumbraChangedItemTooltip : IDisposable
if (!Player())
return;
var bonusSlot = item.Type.ToBonus();
if (bonusSlot is not BonusItemFlag.Unknown)
{
// + 2 due to weapons.
var glasses = _lastItems[bonusSlot.ToSlot() + 2];
using (_ = !openTooltip ? null : ImRaii.Tooltip())
{
ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor.");
if (glasses.Valid)
ImGui.TextUnformatted($"{prefix}Control + Right-Click to re-apply {glasses.Name} to current actor.");
}
return;
}
var slot = item.Type.ToSlot();
var last = _lastItems[slot.ToIndex()];
switch (slot)
@ -109,6 +125,27 @@ public sealed class PenumbraChangedItemTooltip : IDisposable
public void ApplyItem(ActorState state, EquipItem item)
{
var bonusSlot = item.Type.ToBonus();
if (bonusSlot is not BonusItemFlag.Unknown)
{
// + 2 due to weapons.
var glasses = _lastItems[bonusSlot.ToSlot() + 2];
if (ImGui.GetIO().KeyCtrl && glasses.Valid)
{
Glamourer.Log.Debug($"Re-Applying {glasses.Name} to {bonusSlot.ToName()}.");
SetLastItem(bonusSlot, default, state);
_stateManager.ChangeBonusItem(state, bonusSlot, glasses, ApplySettings.Manual);
}
else
{
Glamourer.Log.Debug($"Applying {item.Name} to {bonusSlot.ToName()}.");
SetLastItem(bonusSlot, item, state);
_stateManager.ChangeBonusItem(state, bonusSlot, item, ApplySettings.Manual);
}
return;
}
var slot = item.Type.ToSlot();
var last = _lastItems[slot.ToIndex()];
switch (slot)
@ -265,7 +302,22 @@ public sealed class PenumbraChangedItemTooltip : IDisposable
{
var oldItem = state.ModelData.Item(slot);
if (oldItem.Id != item.Id)
_lastItems[slot.ToIndex()] = oldItem;
last = oldItem;
}
}
private void SetLastItem(BonusItemFlag slot, EquipItem item, ActorState state)
{
ref var last = ref _lastItems[slot.ToSlot() + 2];
if (!item.Valid)
{
last = default;
}
else
{
var oldItem = state.ModelData.BonusItem(slot);
if (oldItem.Id != item.Id)
last = oldItem;
}
}

View file

@ -10,9 +10,8 @@ using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Gui.Materials;
using Glamourer.Interop;
using Glamourer.Interop.Structs;
using Glamourer.State;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
@ -22,7 +21,6 @@ using Penumbra.GameData.Actors;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using ObjectManager = Glamourer.Interop.ObjectManager;
namespace Glamourer.Gui.Tabs.ActorTab;
@ -35,7 +33,7 @@ public class ActorPanel
private readonly AutoDesignApplier _autoDesignApplier;
private readonly Configuration _config;
private readonly DesignConverter _converter;
private readonly ObjectManager _objects;
private readonly ActorObjectManager _objects;
private readonly DesignManager _designManager;
private readonly ImportService _importService;
private readonly ICondition _conditions;
@ -53,13 +51,13 @@ public class ActorPanel
AutoDesignApplier autoDesignApplier,
Configuration config,
DesignConverter converter,
ObjectManager objects,
ActorObjectManager objects,
DesignManager designManager,
ImportService importService,
ICondition conditions,
DictModelChara modelChara,
CustomizeParameterDrawer parameterDrawer,
AdvancedDyePopup advancedDyes,
AdvancedDyePopup advancedDyes,
EditorHistory editorHistory)
{
_selector = selector;
@ -87,7 +85,7 @@ public class ActorPanel
_rightButtons =
[
new LockedButton(this),
new HeaderDrawer.IncognitoButton(_config.Ephemeral),
new HeaderDrawer.IncognitoButton(_config),
];
}
@ -106,7 +104,7 @@ public class ActorPanel
{
using var group = ImRaii.Group();
(_identifier, _data) = _selector.Selection;
_lockedRedraw = _identifier.Type is IdentifierType.Special
_lockedRedraw = _identifier.Type is IdentifierType.Special || _objects.IsInLobby
|| _conditions[ConditionFlag.OccupiedInCutSceneEvent];
(_actorName, _actor) = GetHeaderName();
DrawHeader();
@ -157,6 +155,7 @@ public class ActorPanel
using var table = ImUtf8.Table("##Panel", 1, ImGuiTableFlags.BordersOuter | ImGuiTableFlags.ScrollY, ImGui.GetContentRegionAvail());
if (!table || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state))
return;
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableNextColumn();
ImGui.Dummy(Vector2.Zero);
@ -191,10 +190,14 @@ public class ActorPanel
private void DrawCustomizationsHeader()
{
if (_config.HideDesignPanel.HasFlag(DesignPanelFlag.Customization))
return;
var header = _state!.ModelData.ModelId == 0
? "Customization"
: $"Customization (Model Id #{_state.ModelData.ModelId})###Customization";
using var h = ImUtf8.CollapsingHeaderId(header);
var expand = _config.AutoExpandDesignPanel.HasFlag(DesignPanelFlag.Customization);
using var h = ImUtf8.CollapsingHeaderId(header, expand ? ImGuiTreeNodeFlags.DefaultOpen : ImGuiTreeNodeFlags.None);
if (!h)
return;
@ -207,7 +210,7 @@ public class ActorPanel
private void DrawEquipmentHeader()
{
using var h = ImUtf8.CollapsingHeaderId("Equipment"u8);
using var h = DesignPanelFlag.Equipment.Header(_config);
if (!h)
return;
@ -235,14 +238,12 @@ public class ActorPanel
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
DrawEquipmentMetaToggles();
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
_equipmentDrawer.DrawDragDropTooltip();
}
private void DrawParameterHeader()
{
if (!_config.UseAdvancedParameters)
return;
using var h = ImUtf8.CollapsingHeaderId("Advanced Customizations"u8);
using var h = DesignPanelFlag.AdvancedCustomizations.Header(_config);
if (!h)
return;
@ -254,7 +255,7 @@ public class ActorPanel
if (!_config.DebugMode)
return;
using var h = ImUtf8.CollapsingHeaderId("Debug Data"u8);
using var h = DesignPanelFlag.DebugData.Header(_config);
if (!h)
return;
@ -304,6 +305,12 @@ public class ActorPanel
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.WeaponState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!));
}
ImGui.SameLine();
using (_ = ImRaii.Group())
{
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.EarState, _stateManager, _state!));
}
}
private void DrawMonsterPanel()
@ -385,7 +392,7 @@ public class ActorPanel
{
if (ImGuiUtil.DrawDisabledButton("Revert to Game", Vector2.Zero, "Revert the character to its actual state in the game.",
_state!.IsLocked))
_stateManager.ResetState(_state!, StateSource.Manual);
_stateManager.ResetState(_state!, StateSource.Manual, isFinal: true);
ImGui.SameLine();
@ -393,8 +400,8 @@ public class ActorPanel
"Reapply the current automation state for the character on top of its current state..",
!_config.EnableAutoDesigns || _state!.IsLocked))
{
_autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false, out var forcedRedraw);
_stateManager.ReapplyState(_actor, forcedRedraw, StateSource.Manual);
_autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, false, false, out var forcedRedraw);
_stateManager.ReapplyAutomationState(_actor, forcedRedraw, false, StateSource.Manual);
}
ImGui.SameLine();
@ -402,15 +409,15 @@ public class ActorPanel
"Try to revert the character to the state it would have using automated designs.",
!_config.EnableAutoDesigns || _state!.IsLocked))
{
_autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true, out var forcedRedraw);
_stateManager.ReapplyState(_actor, forcedRedraw, StateSource.Manual);
_autoDesignApplier.ReapplyAutomation(_actor, _identifier, _state!, true, false, out var forcedRedraw);
_stateManager.ReapplyAutomationState(_actor, forcedRedraw, true, StateSource.Manual);
}
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton("Reapply", Vector2.Zero,
"Try to reapply the configured state if something went wrong. Should generally not be necessary.",
_state!.IsLocked))
_stateManager.ReapplyState(_actor, false, StateSource.Manual);
_stateManager.ReapplyState(_actor, false, StateSource.Manual, true);
}
private void DrawApplyToSelf()
@ -423,7 +430,7 @@ public class ActorPanel
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
_stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)),
ApplySettings.Manual);
ApplySettings.Manual with { IsFinal = true });
}
private void DrawApplyToTarget()
@ -440,7 +447,7 @@ public class ActorPanel
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
_stateManager.ApplyDesign(state, _converter.Convert(_state!, ApplicationRules.FromModifiers(_state!)),
ApplySettings.Manual);
ApplySettings.Manual with { IsFinal = true });
}
@ -467,7 +474,7 @@ public class ActorPanel
var text = ImGui.GetClipboardText();
var design = panel._converter.FromBase64(text, applyCustomize, applyGear, out _)
?? throw new Exception("The clipboard did not contain valid data.");
panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks);
panel._stateManager.ApplyDesign(panel._state!, design, ApplySettings.ManualWithLinks with { IsFinal = true });
}
catch (Exception ex)
{

View file

@ -1,19 +1,17 @@
using System.Security.AccessControl;
using Dalamud.Interface;
using Glamourer.Interop;
using Glamourer.Interop.Structs;
using ImGuiNET;
using Dalamud.Interface;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.ActorTab;
public class ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config)
public class ActorSelector(ActorObjectManager objects, ActorManager actors, EphemeralConfig config)
{
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
@ -89,11 +87,11 @@ public class ActorSelector(ObjectManager objects, ActorManager actors, Ephemeral
if (!child)
return;
objects.Update();
_world = new WorldId(objects.Player.Valid ? objects.Player.HomeWorld : (ushort)0);
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing);
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
var remainder = ImGuiClip.FilteredClippedDraw(objects.Identifiers, skips, CheckFilter, DrawSelectable);
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing);
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
var remainder = ImGuiClip.FilteredClippedDraw(objects.Where(p => p.Value.Objects.Any(a => a.Model)), skips, CheckFilter,
DrawSelectable);
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
}

View file

@ -1,5 +1,5 @@
using Dalamud.Interface.Utility;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs.ActorTab;

View file

@ -1,5 +1,5 @@
using Dalamud.Interface.Utility;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs.AutomationTab;

View file

@ -1,11 +1,12 @@
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Utility;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Custom;
using OtterGui.Extensions;
using OtterGui.Log;
using OtterGui.Widgets;
using Penumbra.GameData.DataContainers;
using OtterGui.Custom;
namespace Glamourer.Gui.Tabs.AutomationTab;

View file

@ -1,5 +1,5 @@
using Dalamud.Game.ClientState.Objects.Enums;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using Penumbra.GameData.Actors;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Gui;

View file

@ -4,7 +4,7 @@ using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Designs.Special;
using Glamourer.Events;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
@ -278,7 +278,7 @@ public sealed class RandomRestrictionDrawer : IService, IDisposable
private void LookupTooltip(IEnumerable<Design> designs)
{
using var _ = ImRaii.Tooltip();
var tt = string.Join('\n', designs.Select(d => _designFileSystem.FindLeaf(d, out var l) ? l.FullName() : d.Name.Text).OrderBy(t => t));
var tt = string.Join('\n', designs.Select(d => _designFileSystem.TryGetValue(d, out var l) ? l.FullName() : d.Name.Text).OrderBy(t => t));
ImGui.TextUnformatted(tt.Length == 0
? "Matches no currently existing designs."
: "Matches the following designs:");

View file

@ -6,8 +6,9 @@ using Glamourer.Designs.Special;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Extensions;
using OtterGui.Log;
using OtterGui.Raii;
using OtterGui.Text;
@ -30,10 +31,10 @@ public class SetPanel(
Configuration _config,
RandomRestrictionDrawer _randomDrawer)
{
private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log);
private readonly HeaderDrawer.Button[] _rightButtons = [new HeaderDrawer.IncognitoButton(_config.Ephemeral)];
private string? _tempName;
private int _dragIndex = -1;
private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log);
private readonly HeaderDrawer.Button[] _rightButtons = [new HeaderDrawer.IncognitoButton(_config)];
private string? _tempName;
private int _dragIndex = -1;
private Action? _endAction;
@ -52,44 +53,59 @@ public class SetPanel(
private void DrawPanel()
{
using var child = ImRaii.Child("##Panel", -Vector2.One, true);
using var child = ImUtf8.Child("##Panel"u8, -Vector2.One, true);
if (!child || !_selector.HasSelection)
return;
var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y };
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
using (ImUtf8.Group())
{
var enabled = Selection.Enabled;
if (ImGui.Checkbox("##Enabled", ref enabled))
_manager.SetState(_selector.SelectionIndex, enabled);
ImGuiUtil.LabeledHelpMarker("Enabled",
"Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time.");
}
ImGui.SameLine();
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
{
var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game;
if (ImGui.Checkbox("##gameState", ref useGame))
_manager.ChangeBaseState(_selector.SelectionIndex, useGame ? AutoDesignSet.Base.Game : AutoDesignSet.Base.Current);
ImGuiUtil.LabeledHelpMarker("Use Game State as Base",
"When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. "
+ "Otherwise, they will be applied on top of the characters actual current look using Glamourer.");
}
ImGui.SameLine();
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
{
var editing = _config.ShowAutomationSetEditing;
if (ImGui.Checkbox("##Show Editing", ref editing))
using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
{
_config.ShowAutomationSetEditing = editing;
_config.Save();
var enabled = Selection.Enabled;
if (ImUtf8.Checkbox("##Enabled"u8, ref enabled))
_manager.SetState(_selector.SelectionIndex, enabled);
ImUtf8.LabeledHelpMarker("Enabled"u8,
"Whether the designs in this set should be applied at all. Only one set can be enabled for a character at the same time."u8);
}
ImGuiUtil.LabeledHelpMarker("Show Editing",
"Show options to change the name or the associated character or NPC of this design set.");
using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
{
var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game;
if (ImUtf8.Checkbox("##gameState"u8, ref useGame))
_manager.ChangeBaseState(_selector.SelectionIndex, useGame ? AutoDesignSet.Base.Game : AutoDesignSet.Base.Current);
ImUtf8.LabeledHelpMarker("Use Game State as Base"u8,
"When this is enabled, the designs matching conditions will be applied successively on top of what your character is supposed to look like for the game. "u8
+ "Otherwise, they will be applied on top of the characters actual current look using Glamourer."u8);
}
}
ImGui.SameLine();
using (ImUtf8.Group())
{
using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
{
var editing = _config.ShowAutomationSetEditing;
if (ImUtf8.Checkbox("##Show Editing"u8, ref editing))
{
_config.ShowAutomationSetEditing = editing;
_config.Save();
}
ImUtf8.LabeledHelpMarker("Show Editing"u8,
"Show options to change the name or the associated character or NPC of this design set."u8);
}
using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
{
var resetSettings = _selector.Selection!.ResetTemporarySettings;
if (ImGui.Checkbox("##resetSettings", ref resetSettings))
_manager.ChangeResetSettings(_selector.SelectionIndex, resetSettings);
ImUtf8.LabeledHelpMarker("Reset Temporary Settings"u8,
"Always reset all temporary settings applied by Glamourer when this automation set is applied, regardless of active designs."u8);
}
}
if (_config.ShowAutomationSetEditing)
@ -145,42 +161,43 @@ public class SetPanel(
(false, false) => 4,
};
using var table = ImRaii.Table("SetTable", numRows, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY);
using var table = ImUtf8.Table("SetTable"u8, numRows, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollX | ImGuiTableFlags.ScrollY);
if (!table)
return;
ImGui.TableSetupColumn("##del", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
ImGui.TableSetupColumn("##Index", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale);
ImUtf8.TableSetupColumn("##del"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
ImUtf8.TableSetupColumn("##Index"u8, ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale);
if (singleRow)
{
ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale);
ImUtf8.TableSetupColumn("Design"u8, ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale);
if (_config.ShowAllAutomatedApplicationRules)
ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed,
ImUtf8.TableSetupColumn("Application"u8, ImGuiTableColumnFlags.WidthFixed,
6 * ImGui.GetFrameHeight() + 10 * ImGuiHelpers.GlobalScale);
else
ImGui.TableSetupColumn("Use", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X);
ImUtf8.TableSetupColumn("Use"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X);
}
else
{
ImGui.TableSetupColumn("Design / Job Restrictions", ImGuiTableColumnFlags.WidthFixed, 250 * ImGuiHelpers.GlobalScale);
ImUtf8.TableSetupColumn("Design / Job Restrictions"u8, ImGuiTableColumnFlags.WidthFixed,
250 * ImGuiHelpers.GlobalScale - (ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ScrollbarSize : 0));
if (_config.ShowAllAutomatedApplicationRules)
ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed,
ImUtf8.TableSetupColumn("Application"u8, ImGuiTableColumnFlags.WidthFixed,
3 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale);
else
ImGui.TableSetupColumn("Use", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X);
ImUtf8.TableSetupColumn("Use"u8, ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X);
}
if (singleRow)
ImGui.TableSetupColumn("Job Restrictions", ImGuiTableColumnFlags.WidthStretch);
ImUtf8.TableSetupColumn("Job Restrictions"u8, ImGuiTableColumnFlags.WidthStretch);
if (_config.ShowUnlockedItemWarnings)
ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, 2 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale);
ImUtf8.TableSetupColumn(""u8, ImGuiTableColumnFlags.WidthFixed, 2 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale);
ImGui.TableHeadersRow();
foreach (var (design, idx) in Selection.Designs.WithIndex())
{
using var id = ImRaii.PushId(idx);
using var id = ImUtf8.PushId(idx);
ImGui.TableNextColumn();
var keyValid = _config.DeleteDesignModifier.IsActive();
var tt = keyValid
@ -190,8 +207,8 @@ public class SetPanel(
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), new Vector2(ImGui.GetFrameHeight()), tt, !keyValid, true))
_endAction = () => _manager.DeleteDesign(Selection, idx);
ImGui.TableNextColumn();
ImGui.Selectable($"#{idx + 1:D2}");
DrawDragDrop(Selection, idx);
DrawSelectable(idx, design.Design);
ImGui.TableNextColumn();
DrawRandomEditing(Selection, design, idx);
_designCombo.Draw(Selection, design, idx);
@ -219,8 +236,7 @@ public class SetPanel(
ImGui.TableNextColumn();
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("New");
ImUtf8.TextFrameAligned("New"u8);
ImGui.TableNextColumn();
_designCombo.Draw(Selection, null, -1);
ImGui.TableNextRow();
@ -229,26 +245,64 @@ public class SetPanel(
_endAction = null;
}
private void DrawSelectable(int idx, IDesignStandIn design)
{
var highlight = 0u;
var sb = new StringBuilder();
if (design is Design d)
{
var count = design.AllLinks(true).Count();
if (count > 1)
{
sb.AppendLine($"This design contains {count - 1} links to other designs.");
highlight = ColorId.HeaderButtons.Value();
}
count = d.AssociatedMods.Count;
if (count > 0)
{
sb.AppendLine($"This design contains {count} mod associations.");
highlight = ColorId.ModdedItemMarker.Value();
}
count = design.GetMaterialData().Count(p => p.Item2.Enabled);
if (count > 0)
{
sb.AppendLine($"This design contains {count} enabled advanced dyes.");
highlight = ColorId.AdvancedDyeActive.Value();
}
}
using (ImRaii.PushColor(ImGuiCol.Text, highlight, highlight != 0))
{
ImUtf8.Selectable($"#{idx + 1:D2}");
}
ImUtf8.HoverTooltip($"{sb}");
DrawDragDrop(Selection, idx);
}
private int _tmpGearset = int.MaxValue;
private int _whichIndex = -1;
private void DrawConditions(AutoDesign design, int idx)
{
var usingGearset = design.GearsetIndex >= 0;
if (ImGui.Button($"{(usingGearset ? "Gearset:" : "Jobs:")}##usingGearset"))
if (ImUtf8.Button($"{(usingGearset ? "Gearset:" : "Jobs:")}##usingGearset"))
{
usingGearset = !usingGearset;
_manager.ChangeGearsetCondition(Selection, idx, (short)(usingGearset ? 0 : -1));
}
ImGuiUtil.HoverTooltip("Click to switch between Job and Gearset restrictions.");
ImUtf8.HoverTooltip("Click to switch between Job and Gearset restrictions."u8);
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
if (usingGearset)
{
var set = 1 + (_tmpGearset == int.MaxValue || _whichIndex != idx ? design.GearsetIndex : _tmpGearset);
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
if (ImGui.InputInt("##whichGearset", ref set, 0, 0))
if (ImUtf8.InputScalar("##whichGearset"u8, ref set))
{
_whichIndex = idx;
_tmpGearset = Math.Clamp(set, 1, 100);
@ -346,12 +400,12 @@ public class SetPanel(
ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color);
}
ImGuiUtil.HoverTooltip(sb.ToString());
ImUtf8.HoverTooltip($"{sb}");
}
else
{
ImGuiUtil.DrawTextButton(string.Empty, size, 0);
ImGuiUtil.HoverTooltip(good);
ImUtf8.HoverTooltip(good);
}
}
}
@ -359,7 +413,7 @@ public class SetPanel(
private void DrawDragDrop(AutoDesignSet set, int index)
{
const string dragDropLabel = "DesignDragDrop";
using (var target = ImRaii.DragDropTarget())
using (var target = ImUtf8.DragDropTarget())
{
if (target.Success && ImGuiUtil.IsDropping(dragDropLabel))
{
@ -373,15 +427,15 @@ public class SetPanel(
}
}
using (var source = ImRaii.DragDropSource())
using (var source = ImUtf8.DragDropSource())
{
if (source)
{
ImGui.TextUnformatted($"Moving design #{index + 1:D2}...");
if (ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0))
ImUtf8.Text($"Moving design #{index + 1:D2}...");
if (ImGui.SetDragDropPayload(dragDropLabel, null, 0))
{
_dragIndex = index;
_selector._dragDesignIndex = index;
_selector.DragDesignIndex = index;
}
}
}
@ -400,16 +454,16 @@ public class SetPanel(
}
style.Pop();
ImGuiUtil.HoverTooltip("Toggle all application modes at once.");
ImUtf8.HoverTooltip("Toggle all application modes at once."u8);
if (_config.ShowAllAutomatedApplicationRules)
{
void Box(int idx)
{
var (type, description) = ApplicationTypeExtensions.Types[idx];
var value = design.Type.HasFlag(type);
if (ImGui.Checkbox($"##{(byte)type}", ref value))
if (ImUtf8.Checkbox($"##{(byte)type}", ref value))
newType = value ? newType | type : newType & ~type;
ImGuiUtil.HoverTooltip(description);
ImUtf8.HoverTooltip(description);
}
ImGui.SameLine();

View file

@ -2,12 +2,12 @@
using Dalamud.Interface.Utility;
using Glamourer.Automation;
using Glamourer.Events;
using Glamourer.Interop;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Extensions;
using OtterGui.Raii;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Interop;
using Penumbra.String;
using ImGuiClip = OtterGui.ImGuiClip;
@ -18,8 +18,7 @@ public class SetSelector : IDisposable
private readonly Configuration _config;
private readonly AutoDesignManager _manager;
private readonly AutomationChanged _event;
private readonly ActorManager _actors;
private readonly ObjectManager _objects;
private readonly ActorObjectManager _objects;
private readonly List<(AutoDesignSet, int)> _list = [];
public AutoDesignSet? Selection { get; private set; }
@ -38,14 +37,13 @@ public class SetSelector : IDisposable
private int _dragIndex = -1;
private Action? _endAction;
internal int _dragDesignIndex = -1;
internal int DragDesignIndex = -1;
public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorManager actors, ObjectManager objects)
public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorObjectManager objects)
{
_manager = manager;
_event = @event;
_config = config;
_actors = actors;
_objects = objects;
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.SetSelector);
}
@ -94,7 +92,7 @@ public class SetSelector : IDisposable
}
private LowerString _filter = LowerString.Empty;
private uint _enabledFilter = 0;
private uint _enabledFilter;
private float _width;
private Vector2 _defaultItemSpacing;
private Vector2 _selectableSize;
@ -146,7 +144,7 @@ public class SetSelector : IDisposable
ImGui.SameLine();
var f = _enabledFilter;
if (ImGui.CheckboxFlags("##enabledFilter", ref f, 3))
if (ImGui.CheckboxFlags("##enabledFilter", ref f, 3u))
{
_enabledFilter = _enabledFilter switch
{
@ -177,7 +175,6 @@ public class SetSelector : IDisposable
UpdateList();
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing);
_selectableSize = new Vector2(0, 2 * ImGui.GetTextLineHeight() + ImGui.GetStyle().ItemSpacing.Y);
_objects.Update();
ImGuiClip.ClippedDraw(_list, DrawSetSelectable, _selectableSize.Y + 2 * ImGui.GetStyle().ItemSpacing.Y);
_endAction?.Invoke();
_endAction = null;
@ -186,7 +183,7 @@ public class SetSelector : IDisposable
private void DrawSetSelectable((AutoDesignSet Set, int Index) pair)
{
using var id = ImRaii.PushId(pair.Index);
using (var color = ImRaii.PushColor(ImGuiCol.Text, pair.Set.Enabled ? ColorId.EnabledAutoSet.Value() : ColorId.DisabledAutoSet.Value()))
using (ImRaii.PushColor(ImGuiCol.Text, pair.Set.Enabled ? ColorId.EnabledAutoSet.Value() : ColorId.DisabledAutoSet.Value()))
{
if (ImGui.Selectable(GetSetName(pair.Set, pair.Index), pair.Set == Selection, ImGuiSelectableFlags.None, _selectableSize))
{
@ -285,9 +282,9 @@ public class SetSelector : IDisposable
private void NewSetButton(Vector2 size)
{
var id = _actors.GetCurrentPlayer();
var id = _objects.Actors.GetCurrentPlayer();
if (!id.IsValid)
id = _actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue);
id = _objects.Actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size,
$"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true))
_manager.AddDesignSet("New Automation Set", id);
@ -332,15 +329,15 @@ public class SetSelector : IDisposable
}
else if (ImGuiUtil.IsDropping("DesignDragDrop"))
{
if (_dragDesignIndex >= 0)
if (DragDesignIndex >= 0)
{
var idx = _dragDesignIndex;
var idx = DragDesignIndex;
var setTo = set;
var setFrom = Selection!;
_endAction = () => _manager.MoveDesignToSet(setFrom, idx, setTo);
}
_dragDesignIndex = -1;
DragDesignIndex = -1;
}
}
}
@ -350,7 +347,7 @@ public class SetSelector : IDisposable
if (source)
{
ImGui.TextUnformatted($"Moving design set {GetSetName(set, index)} from position {index + 1}...");
if (ImGui.SetDragDropPayload(dragDropLabel, nint.Zero, 0))
if (ImGui.SetDragDropPayload(dragDropLabel, null, 0))
_dragIndex = index;
}
}

View file

@ -1,18 +1,17 @@
using Dalamud.Interface;
using Glamourer.GameData;
using Glamourer.Designs;
using Glamourer.Interop;
using Glamourer.Interop.Structs;
using Glamourer.State;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
using Penumbra.GameData.Interop;
namespace Glamourer.Gui.Tabs.DebugTab;
public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer
public class ActiveStatePanel(StateManager _stateManager, ActorObjectManager _objectManager) : IGameDataDrawer
{
public string Label
=> $"Active Actors ({_stateManager.Count})###Active Actors";
@ -22,8 +21,7 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM
public void Draw()
{
_objectManager.Update();
foreach (var (identifier, actors) in _objectManager.Identifiers)
foreach (var (identifier, actors) in _objectManager)
{
if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Trash.ToIconString()}##{actors.Label}", new Vector2(ImGui.GetFrameHeight()),
string.Empty, !_stateManager.ContainsKey(identifier), true))
@ -66,13 +64,15 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM
static string ItemString(in DesignData data, EquipSlot slot)
{
var item = data.Item(slot);
return $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})";
return
$"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})";
}
static string BonusItemString(in DesignData data, BonusItemFlag slot)
{
var item = data.BonusItem(slot);
return $"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})";
return
$"{item.Name} ({item.Id.ToDiscriminatingString()} {item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})";
}
PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state.Sources[MetaIndex.ModelId]);
@ -87,6 +87,9 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM
PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(),
state.Sources[MetaIndex.VisorState]);
ImGui.TableNextRow();
PrintRow("Viera Ears Visible", state.BaseData.AreEarsVisible(), state.ModelData.AreEarsVisible(),
state.Sources[MetaIndex.EarState]);
ImGui.TableNextRow();
PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(),
state.Sources[MetaIndex.WeaponState]);
ImGui.TableNextRow();

View file

@ -1,13 +1,13 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using Glamourer.Interop;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.GameData.Gui.Debug;
using Penumbra.GameData.Interop;
namespace Glamourer.Gui.Tabs.DebugTab;
public unsafe class AdvancedCustomizationDrawer(ObjectManager objects) : IGameDataDrawer
public unsafe class AdvancedCustomizationDrawer(ActorObjectManager objects) : IGameDataDrawer
{
public string Label
=> "Advanced Customizations";
@ -31,8 +31,8 @@ public unsafe class AdvancedCustomizationDrawer(ObjectManager objects) : IGameDa
return;
}
DrawCBuffer("Customize"u8, model.AsHuman->CustomizeParameterCBuffer, 0);
DrawCBuffer("Decal"u8, model.AsHuman->DecalColorCBuffer, 1);
DrawCBuffer("Customize"u8, model.AsHuman->CustomizeParameterCBuffer, 0);
DrawCBuffer("Decal"u8, model.AsHuman->DecalColorCBuffer, 1);
DrawCBuffer("Unk1"u8, *(ConstantBuffer**)((byte*)model.AsHuman + 0xBA0), 2);
DrawCBuffer("Unk2"u8, *(ConstantBuffer**)((byte*)model.AsHuman + 0xBA8), 3);
}

View file

@ -1,6 +1,7 @@
using Glamourer.Automation;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Extensions;
using OtterGui.Raii;
using Penumbra.GameData.Gui.Debug;

View file

@ -1,7 +1,7 @@
using Dalamud.Interface;
using Glamourer.GameData;
using Glamourer.Services;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Text;
@ -28,9 +28,35 @@ public class CustomizationServicePanel(CustomizeService customize) : IGameDataDr
DrawNpcCustomizationInfo(set);
}
DrawFacepaintInfo();
DrawColorInfo();
}
private void DrawFacepaintInfo()
{
using var tree = ImUtf8.TreeNode("NPC Facepaints"u8);
if (!tree)
return;
using var table = ImUtf8.Table("data"u8, 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
if (!table)
return;
ImGui.TableNextColumn();
ImUtf8.TableHeader("Id"u8);
ImGui.TableNextColumn();
ImUtf8.TableHeader("Facepaint"u8);
for (var i = 0; i < 128; ++i)
{
var index = new CustomizeValue((byte)i);
ImUtf8.DrawTableColumn($"{i:D3}");
using var font = ImRaii.PushFont(UiBuilder.IconFont);
ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.FacePaint, index)
? FontAwesomeIcon.Check.ToIconString()
: FontAwesomeIcon.Times.ToIconString());
}
}
private void DrawColorInfo()
{
using var tree = ImUtf8.TreeNode("NPC Colors"u8);
@ -57,16 +83,16 @@ public class CustomizationServicePanel(CustomizeService customize) : IGameDataDr
var index = new CustomizeValue((byte)i);
ImUtf8.DrawTableColumn($"{i:D3}");
using var font = ImRaii.PushFont(UiBuilder.IconFont);
ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.HairColor, index)
ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.HairColor, index)
? FontAwesomeIcon.Check.ToIconString()
: FontAwesomeIcon.Times.ToIconString());
ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.EyeColorLeft, index)
ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.EyeColorLeft, index)
? FontAwesomeIcon.Check.ToIconString()
: FontAwesomeIcon.Times.ToIconString());
ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.FacePaintColor, index)
ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.FacePaintColor, index)
? FontAwesomeIcon.Check.ToIconString()
: FontAwesomeIcon.Times.ToIconString());
ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckColor(CustomizeIndex.TattooColor, index)
ImUtf8.DrawTableColumn(customize.NpcCustomizeSet.CheckValue(CustomizeIndex.TattooColor, index)
? FontAwesomeIcon.Check.ToIconString()
: FontAwesomeIcon.Times.ToIconString());
}

View file

@ -1,5 +1,5 @@
using Glamourer.Unlocks;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;

View file

@ -1,5 +1,5 @@
using Glamourer.Interop;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using Penumbra.GameData.Files;
using Penumbra.GameData.Gui.Debug;

View file

@ -1,4 +1,4 @@
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Widgets;

View file

@ -1,5 +1,4 @@
using Glamourer.Gui.Tabs.DebugTab.IpcTester;
using ImGuiNET;
using Microsoft.Extensions.DependencyInjection;
using OtterGui.Raii;
using Penumbra.GameData.Gui.Debug;
@ -36,6 +35,7 @@ public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees)
provider.GetRequiredService<ModelEvaluationPanel>(),
provider.GetRequiredService<ObjectManagerPanel>(),
provider.GetRequiredService<PenumbraPanel>(),
provider.GetRequiredService<DynamisPanel>(),
provider.GetRequiredService<IpcTesterPanel>(),
provider.GetRequiredService<DatFilePanel>(),
provider.GetRequiredService<GlamourPlatePanel>(),

View file

@ -1,7 +1,7 @@
using Dalamud.Interface;
using Glamourer.Designs;
using Glamourer.Utility;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Raii;

View file

@ -1,8 +1,11 @@
using Dalamud.Interface;
using Glamourer.Designs;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Extensions;
using OtterGui.Filesystem;
using OtterGui.Raii;
using OtterGui.Text;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
@ -18,6 +21,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
public void Draw()
{
DrawButtons();
foreach (var (design, idx) in _designManager.Designs.WithIndex())
{
using var t = ImRaii.TreeNode($"{design.Name}##{idx}");
@ -25,7 +29,8 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
continue;
DrawDesign(design, _designFileSystem);
var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize, design.Application.Meta,
var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize,
design.Application.Meta,
design.WriteProtected());
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
ImGuiUtil.TextWrapped(base64);
@ -34,6 +39,26 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
}
}
private void DrawButtons()
{
if (ImUtf8.Button("Generate 500 Test Designs"u8))
for (var i = 0; i < 500; ++i)
{
var design = _designManager.CreateEmpty($"Test Designs/Test Design {i}", true);
_designManager.AddTag(design, "_DebugTest");
}
ImUtf8.SameLineInner();
if (ImUtf8.Button("Remove All Test Designs"u8))
{
var designs = _designManager.Designs.Where(d => d.Tags.Contains("_DebugTest")).ToArray();
foreach (var design in designs)
_designManager.Delete(design);
if (_designFileSystem.Find("Test Designs", out var path) && path is DesignFileSystem.Folder { TotalChildren: 0 })
_designFileSystem.Delete(path);
}
}
public static void DrawDesign(DesignBase design, DesignFileSystem? fileSystem)
{
using var table = ImRaii.Table("##equip", 8, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit);
@ -52,7 +77,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
ImGui.TableNextRow();
ImGuiUtil.DrawTableColumn("Design File System Path");
if (fileSystem != null)
ImGuiUtil.DrawTableColumn(fileSystem.FindLeaf(d, out var leaf) ? leaf.FullName() : "No Path Known");
ImGuiUtil.DrawTableColumn(fileSystem.TryGetValue(d, out var leaf) ? leaf.FullName() : "No Path Known");
ImGui.TableNextRow();
ImGuiUtil.DrawTableColumn("Creation");
@ -89,6 +114,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
ImGuiUtil.DrawTableColumn(index.ToName());
ImGuiUtil.DrawTableColumn(design.DesignData.GetMeta(index).ToString());
ImGuiUtil.DrawTableColumn(design.DoApplyMeta(index) ? "Apply" : "Keep");
ImGui.TableNextRow();
}
ImGuiUtil.DrawTableColumn("Model ID");

View file

@ -1,8 +1,9 @@
using Dalamud.Interface;
using Glamourer.Designs;
using Glamourer.Services;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Extensions;
using OtterGui.Raii;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;

View file

@ -0,0 +1,16 @@
using OtterGui.Services;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab;
public class DynamisPanel(DynamisIpc dynamis) : IGameDataDrawer
{
public string Label
=> "Dynamis Interop";
public void Draw()
=> dynamis.DrawDebugInfo();
public bool Disabled
=> false;
}

View file

@ -1,5 +1,5 @@
using Glamourer.State;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab;

View file

@ -3,24 +3,26 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game;
using Glamourer.Designs;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.State;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Extensions;
using OtterGui.Text;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab;
public unsafe class GlamourPlatePanel : IGameDataDrawer
{
private readonly DesignManager _design;
private readonly ItemManager _items;
private readonly StateManager _state;
private readonly ObjectManager _objects;
private readonly DesignManager _design;
private readonly ItemManager _items;
private readonly StateManager _state;
private readonly ActorObjectManager _objects;
public string Label
=> "Glamour Plates";
@ -28,7 +30,8 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer
public bool Disabled
=> false;
public GlamourPlatePanel(IGameInteropProvider interop, ItemManager items, DesignManager design, StateManager state, ObjectManager objects)
public GlamourPlatePanel(IGameInteropProvider interop, ItemManager items, DesignManager design, StateManager state,
ActorObjectManager objects)
{
_items = items;
_design = design;
@ -42,24 +45,24 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer
var manager = MirageManager.Instance();
using (ImRaii.Group())
{
ImGui.TextUnformatted("Address:");
ImGui.TextUnformatted("Number of Glamour Plates:");
ImGui.TextUnformatted("Glamour Plates Requested:");
ImGui.TextUnformatted("Glamour Plates Loaded:");
ImGui.TextUnformatted("Is Applying Glamour Plates:");
ImUtf8.Text("Address:"u8);
ImUtf8.Text("Number of Glamour Plates:"u8);
ImUtf8.Text("Glamour Plates Requested:"u8);
ImUtf8.Text("Glamour Plates Loaded:"u8);
ImUtf8.Text("Is Applying Glamour Plates:"u8);
}
ImGui.SameLine();
using (ImRaii.Group())
{
ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)manager:X}");
ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlates.Length.ToString());
ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesRequested.ToString());
ImUtf8.CopyOnClickSelectable($"0x{(ulong)manager:X}");
ImUtf8.Text(manager == null ? "-" : manager->GlamourPlates.Length.ToString());
ImUtf8.Text(manager == null ? "-" : manager->GlamourPlatesRequested.ToString());
ImGui.SameLine();
if (ImGui.SmallButton("Request Update"))
if (ImUtf8.SmallButton("Request Update"u8))
RequestGlamour();
ImGui.TextUnformatted(manager == null ? "-" : manager->GlamourPlatesLoaded.ToString());
ImGui.TextUnformatted(manager == null ? "-" : manager->IsApplyingGlamourPlate.ToString());
ImUtf8.Text(manager == null ? "-" : manager->GlamourPlatesLoaded.ToString());
ImUtf8.Text(manager == null ? "-" : manager->IsApplyingGlamourPlate.ToString());
}
if (manager == null)
@ -71,28 +74,28 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer
for (var i = 0; i < manager->GlamourPlates.Length; ++i)
{
using var tree = ImRaii.TreeNode($"Plate #{i + 1:D2}");
using var tree = ImUtf8.TreeNode($"Plate #{i + 1:D2}");
if (!tree)
continue;
ref var plate = ref manager->GlamourPlates[i];
if (ImGuiUtil.DrawDisabledButton("Apply to Player", Vector2.Zero, string.Empty, !enabled))
if (ImUtf8.ButtonEx("Apply to Player"u8, ""u8, Vector2.Zero, !enabled))
{
var design = CreateDesign(plate);
_state.ApplyDesign(state!, design, ApplySettings.Manual);
_state.ApplyDesign(state!, design, ApplySettings.Manual with { IsFinal = true });
}
using (ImRaii.Group())
{
foreach (var slot in EquipSlotExtensions.FullSlots)
ImGui.TextUnformatted(slot.ToName());
ImUtf8.Text(slot.ToName());
}
ImGui.SameLine();
using (ImRaii.Group())
{
foreach (var (_, index) in EquipSlotExtensions.FullSlots.WithIndex())
ImGui.TextUnformatted($"{plate.ItemIds[index]:D6}, {StainIds.FromGlamourPlate(plate, index)}");
ImUtf8.Text($"{plate.ItemIds[index]:D6}, {StainIds.FromGlamourPlate(plate, index)}");
}
}
}

View file

@ -1,5 +1,5 @@
using FFXIVClientStructs.FFXIV.Client.Game;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Gui.Debug;
@ -23,7 +23,7 @@ public unsafe class InventoryPanel : IGameDataDrawer
ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)inventory:X}");
var equip = inventory->GetInventoryContainer(InventoryType.EquippedItems);
if (equip == null || equip->Loaded == 0)
if (equip == null || equip->IsLoaded)
return;
ImGuiUtil.CopyOnClickSelectable($"0x{(ulong)equip:X}");

View file

@ -3,10 +3,11 @@ using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using Glamourer.Api.Enums;
using Glamourer.Api.IpcSubscribers;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Text;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
@ -15,6 +16,7 @@ public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiServi
private Dictionary<Guid, string> _designs = [];
private int _gameObjectIndex;
private string _gameObjectName = string.Empty;
private string _designName = string.Empty;
private uint _key;
private ApplyFlag _flags = ApplyFlagEx.DesignDefault;
private Guid? _design;
@ -30,6 +32,7 @@ public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiServi
IpcTesterHelpers.IndexInput(ref _gameObjectIndex);
IpcTesterHelpers.KeyInput(ref _key);
IpcTesterHelpers.NameInput(ref _gameObjectName);
ImUtf8.InputText("##designName"u8, ref _designName, "Design Name..."u8);
ImGuiUtil.GuidInput("##identifier", "Design Identifier...", string.Empty, ref _design, ref _designText,
ImGui.GetContentRegionAvail().X);
IpcTesterHelpers.DrawFlagInput(ref _flags);
@ -54,6 +57,48 @@ public class DesignIpcTester(IDalamudPluginInterface pluginInterface) : IUiServi
IpcTesterHelpers.DrawIntro(ApplyDesignName.Label);
if (ImGuiUtil.DrawDisabledButton("Apply##Name", Vector2.Zero, string.Empty, !_design.HasValue))
_lastError = new ApplyDesignName(pluginInterface).Invoke(_design!.Value, _gameObjectName, _key, _flags);
IpcTesterHelpers.DrawIntro(GetExtendedDesignData.Label);
if (_design.HasValue)
{
var (display, path, color, draw) = new GetExtendedDesignData(pluginInterface).Invoke(_design.Value);
if (path.Length > 0)
ImUtf8.Text($"{display} ({path}){(draw ? " in QDB"u8 : ""u8)}", color);
else
ImUtf8.Text("No Data"u8);
}
else
{
ImUtf8.Text("No Data"u8);
}
IpcTesterHelpers.DrawIntro(GetDesignBase64.Label);
if (ImUtf8.Button("To Clipboard##Base64"u8) && _design.HasValue)
{
var data = new GetDesignBase64(pluginInterface).Invoke(_design.Value);
ImUtf8.SetClipboardText(data);
}
IpcTesterHelpers.DrawIntro(AddDesign.Label);
if (ImUtf8.Button("Add from Clipboard"u8))
try
{
var data = ImUtf8.GetClipboardText();
_lastError = new AddDesign(pluginInterface).Invoke(data, _designName, out var newDesign);
if (_lastError is GlamourerApiEc.Success)
{
_design = newDesign;
_designText = newDesign.ToString();
}
}
catch
{
_lastError = GlamourerApiEc.UnknownError;
}
IpcTesterHelpers.DrawIntro(DeleteDesign.Label);
if (ImUtf8.Button("Delete##Design"u8) && _design.HasValue)
_lastError = new DeleteDesign(pluginInterface).Invoke(_design.Value);
}
private void DrawDesignsPopup()

View file

@ -1,6 +1,6 @@
using Glamourer.Api.Enums;
using Glamourer.Designs;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using static Penumbra.GameData.Files.ShpkFile;

View file

@ -1,7 +1,7 @@
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Glamourer.Api.IpcSubscribers;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab.IpcTester;
@ -51,6 +51,7 @@ public class IpcTesterPanel(
Glamourer.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester.");
state.GPoseChanged.Enable();
state.StateChanged.Enable();
state.StateFinalized.Enable();
framework.Update += CheckUnsubscribe;
_subscribed = true;
}
@ -73,5 +74,6 @@ public class IpcTesterPanel(
_subscribed = false;
state.GPoseChanged.Disable();
state.StateChanged.Disable();
state.StateFinalized.Disable();
}
}

View file

@ -1,7 +1,7 @@
using Dalamud.Plugin;
using Glamourer.Api.Enums;
using Glamourer.Api.IpcSubscribers;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;

View file

@ -5,12 +5,13 @@ using Glamourer.Api.Enums;
using Glamourer.Api.Helpers;
using Glamourer.Api.IpcSubscribers;
using Glamourer.Designs;
using ImGuiNET;
using Dalamud.Bindings.ImGui;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Text;
using Penumbra.GameData.Interop;
using Penumbra.String;
@ -31,9 +32,16 @@ public class StateIpcTester : IUiService, IDisposable
private string? _getStateString;
public readonly EventSubscriber<nint, StateChangeType> StateChanged;
private nint _lastStateChangeActor;
private ByteString _lastStateChangeName = ByteString.Empty;
private DateTime _lastStateChangeTime;
private nint _lastStateChangeActor;
private ByteString _lastStateChangeName = ByteString.Empty;
private DateTime _lastStateChangeTime;
private StateChangeType _lastStateChangeType;
public readonly EventSubscriber<nint, StateFinalizationType> StateFinalized;
private nint _lastStateFinalizeActor;
private ByteString _lastStateFinalizeName = ByteString.Empty;
private DateTime _lastStateFinalizeTime;
private StateFinalizationType _lastStateFinalizeType;
public readonly EventSubscriber<bool> GPoseChanged;
private bool _lastGPoseChangeValue;
@ -44,15 +52,18 @@ public class StateIpcTester : IUiService, IDisposable
public StateIpcTester(IDalamudPluginInterface pluginInterface)
{
_pluginInterface = pluginInterface;
StateChanged = Api.IpcSubscribers.StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged);
StateChanged = StateChangedWithType.Subscriber(_pluginInterface, OnStateChanged);
StateFinalized = Api.IpcSubscribers.StateFinalized.Subscriber(_pluginInterface, OnStateFinalized);
GPoseChanged = Api.IpcSubscribers.GPoseChanged.Subscriber(_pluginInterface, OnGPoseChange);
StateChanged.Disable();
StateFinalized.Disable();
GPoseChanged.Disable();
}
public void Dispose()
{
StateChanged.Dispose();
StateFinalized.Dispose();
GPoseChanged.Dispose();
}
@ -73,86 +84,88 @@ public class StateIpcTester : IUiService, IDisposable
IpcTesterHelpers.DrawIntro("Last Error");
ImGui.TextUnformatted(_lastError.ToString());
IpcTesterHelpers.DrawIntro("Last State Change");
PrintName();
PrintChangeName();
IpcTesterHelpers.DrawIntro("Last State Finalization");
PrintFinalizeName();
IpcTesterHelpers.DrawIntro("Last GPose Change");
ImGui.TextUnformatted($"{_lastGPoseChangeValue} at {_lastGPoseChangeTime.ToLocalTime().TimeOfDay}");
IpcTesterHelpers.DrawIntro(GetState.Label);
DrawStatePopup();
if (ImGui.Button("Get##Idx"))
if (ImUtf8.Button("Get##Idx"u8))
{
(_lastError, _state) = new GetState(_pluginInterface).Invoke(_gameObjectIndex, _key);
_stateString = _state?.ToString(Formatting.Indented) ?? "No State Available";
ImGui.OpenPopup("State");
ImUtf8.OpenPopup("State"u8);
}
IpcTesterHelpers.DrawIntro(GetStateName.Label);
if (ImGui.Button("Get##Name"))
if (ImUtf8.Button("Get##Name"u8))
{
(_lastError, _state) = new GetStateName(_pluginInterface).Invoke(_gameObjectName, _key);
_stateString = _state?.ToString(Formatting.Indented) ?? "No State Available";
ImGui.OpenPopup("State");
ImUtf8.OpenPopup("State"u8);
}
IpcTesterHelpers.DrawIntro(GetStateBase64.Label);
if (ImGui.Button("Get##Base64Idx"))
if (ImUtf8.Button("Get##Base64Idx"u8))
{
(_lastError, _getStateString) = new GetStateBase64(_pluginInterface).Invoke(_gameObjectIndex, _key);
_stateString = _getStateString ?? "No State Available";
ImGui.OpenPopup("State");
ImUtf8.OpenPopup("State"u8);
}
IpcTesterHelpers.DrawIntro(GetStateBase64Name.Label);
if (ImGui.Button("Get##Base64Idx"))
if (ImUtf8.Button("Get##Base64Idx"u8))
{
(_lastError, _getStateString) = new GetStateBase64Name(_pluginInterface).Invoke(_gameObjectName, _key);
_stateString = _getStateString ?? "No State Available";
ImGui.OpenPopup("State");
ImUtf8.OpenPopup("State"u8);
}
IpcTesterHelpers.DrawIntro(ApplyState.Label);
if (ImGuiUtil.DrawDisabledButton("Apply Last##Idx", Vector2.Zero, string.Empty, _state == null))
_lastError = new ApplyState(_pluginInterface).Invoke(_state!, _gameObjectIndex, _key, _flags);
ImGui.SameLine();
if (ImGui.Button("Apply Base64##Idx"))
if (ImUtf8.Button("Apply Base64##Idx"u8))
_lastError = new ApplyState(_pluginInterface).Invoke(_base64State, _gameObjectIndex, _key, _flags);
IpcTesterHelpers.DrawIntro(ApplyStateName.Label);
if (ImGuiUtil.DrawDisabledButton("Apply Last##Name", Vector2.Zero, string.Empty, _state == null))
_lastError = new ApplyStateName(_pluginInterface).Invoke(_state!, _gameObjectName, _key, _flags);
ImGui.SameLine();
if (ImGui.Button("Apply Base64##Name"))
if (ImUtf8.Button("Apply Base64##Name"u8))
_lastError = new ApplyStateName(_pluginInterface).Invoke(_base64State, _gameObjectName, _key, _flags);
IpcTesterHelpers.DrawIntro(RevertState.Label);
if (ImGui.Button("Revert##Idx"))
if (ImUtf8.Button("Revert##Idx"u8))
_lastError = new RevertState(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags);
IpcTesterHelpers.DrawIntro(RevertStateName.Label);
if (ImGui.Button("Revert##Name"))
if (ImUtf8.Button("Revert##Name"u8))
_lastError = new RevertStateName(_pluginInterface).Invoke(_gameObjectName, _key, _flags);
IpcTesterHelpers.DrawIntro(UnlockState.Label);
if (ImGui.Button("Unlock##Idx"))
if (ImUtf8.Button("Unlock##Idx"u8))
_lastError = new UnlockState(_pluginInterface).Invoke(_gameObjectIndex, _key);
IpcTesterHelpers.DrawIntro(UnlockStateName.Label);
if (ImGui.Button("Unlock##Name"))
if (ImUtf8.Button("Unlock##Name"u8))
_lastError = new UnlockStateName(_pluginInterface).Invoke(_gameObjectName, _key);
IpcTesterHelpers.DrawIntro(UnlockAll.Label);
if (ImGui.Button("Unlock##All"))
if (ImUtf8.Button("Unlock##All"u8))
_numUnlocked = new UnlockAll(_pluginInterface).Invoke(_key);
ImGui.SameLine();
ImGui.TextUnformatted($"Unlocked {_numUnlocked}");
IpcTesterHelpers.DrawIntro(RevertToAutomation.Label);
if (ImGui.Button("Revert##AutomationIdx"))
if (ImUtf8.Button("Revert##AutomationIdx"u8))
_lastError = new RevertToAutomation(_pluginInterface).Invoke(_gameObjectIndex, _key, _flags);
IpcTesterHelpers.DrawIntro(RevertToAutomationName.Label);
if (ImGui.Button("Revert##AutomationName"))
if (ImUtf8.Button("Revert##AutomationName"u8))
_lastError = new RevertToAutomationName(_pluginInterface).Invoke(_gameObjectName, _key, _flags);
}
@ -162,44 +175,70 @@ public class StateIpcTester : IUiService, IDisposable
if (_stateString == null)
return;
using var p = ImRaii.Popup("State");
using var p = ImUtf8.Popup("State"u8);
if (!p)
return;
if (ImGui.Button("Copy to Clipboard"))
ImGui.SetClipboardText(_stateString);
if (ImUtf8.Button("Copy to Clipboard"u8))
ImUtf8.SetClipboardText(_stateString);
if (_stateString[0] is '{')
{
ImGui.SameLine();
if (ImGui.Button("Copy as Base64") && _state != null)
ImGui.SetClipboardText(DesignConverter.ToBase64(_state));
if (ImUtf8.Button("Copy as Base64"u8) && _state != null)
ImUtf8.SetClipboardText(DesignConverter.ToBase64(_state));
}
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
ImGuiUtil.TextWrapped(_stateString ?? string.Empty);
ImUtf8.TextWrapped(_stateString ?? string.Empty);
if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
if (ImUtf8.Button("Close"u8, -Vector2.UnitX) || !ImGui.IsWindowFocused())
ImGui.CloseCurrentPopup();
}
private unsafe void PrintName()
private unsafe void PrintChangeName()
{
ImGuiNative.igTextUnformatted(_lastStateChangeName.Path, _lastStateChangeName.Path + _lastStateChangeName.Length);
ImUtf8.Text(_lastStateChangeName.Span);
ImGui.SameLine(0, 0);
ImUtf8.Text($" ({_lastStateChangeType})");
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGuiUtil.CopyOnClickSelectable($"0x{_lastStateChangeActor:X}");
ImUtf8.CopyOnClickSelectable($"0x{_lastStateChangeActor:X}");
}
ImGui.SameLine();
ImGui.TextUnformatted($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}");
ImUtf8.Text($"at {_lastStateChangeTime.ToLocalTime().TimeOfDay}");
}
private void OnStateChanged(nint actor, StateChangeType _)
private unsafe void PrintFinalizeName()
{
ImUtf8.Text(_lastStateFinalizeName.Span);
ImGui.SameLine(0, 0);
ImUtf8.Text($" ({_lastStateFinalizeType})");
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImUtf8.CopyOnClickSelectable($"0x{_lastStateFinalizeActor:X}");
}
ImGui.SameLine();
ImUtf8.Text($"at {_lastStateFinalizeTime.ToLocalTime().TimeOfDay}");
}
private void OnStateChanged(nint actor, StateChangeType type)
{
_lastStateChangeActor = actor;
_lastStateChangeTime = DateTime.UtcNow;
_lastStateChangeName = actor != nint.Zero ? ((Actor)actor).Utf8Name.Clone() : ByteString.Empty;
_lastStateChangeType = type;
}
private void OnStateFinalized(nint actor, StateFinalizationType type)
{
_lastStateFinalizeActor = actor;
_lastStateFinalizeTime = DateTime.UtcNow;
_lastStateFinalizeName = actor != nint.Zero ? ((Actor)actor).Utf8Name.Clone() : ByteString.Empty;
_lastStateFinalizeType = type;
}
private void OnGPoseChange(bool value)

Some files were not shown because too many files have changed in this diff Show more